• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mendersoftware / mender / 1022567176

02 Oct 2023 07:50AM UTC coverage: 80.127% (+2.5%) from 77.645%
1022567176

push

gitlab-ci

kacf
chore: Centralize selection of `std::filesystem` library.

Signed-off-by: Kristian Amlie <kristian.amlie@northern.tech>

6447 of 8046 relevant lines covered (80.13%)

9912.21 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

80.18
/artifact/v3/scripts/executor.cpp
1
// Copyright 2023 Northern.tech AS
2
//
3
//    Licensed under the Apache License, Version 2.0 (the "License");
4
//    you may not use this file except in compliance with the License.
5
//    You may obtain a copy of the License at
6
//
7
//        http://www.apache.org/licenses/LICENSE-2.0
8
//
9
//    Unless required by applicable law or agreed to in writing, software
10
//    distributed under the License is distributed on an "AS IS" BASIS,
11
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
//    See the License for the specific language governing permissions and
13
//    limitations under the License.
14

15
#include <artifact/v3/scripts/executor.hpp>
16

17
#include <algorithm>
18
#include <chrono>
19
#include <regex>
20
#include <string>
21

22
#include <common/common.hpp>
23
#include <common/expected.hpp>
24
#include <common/path.hpp>
25

26

27
namespace mender {
28
namespace artifact {
29
namespace scripts {
30
namespace executor {
31

32
namespace expected = mender::common::expected;
33

34

35
using expected::ExpectedBool;
36

37
namespace common = mender::common;
38
namespace error = mender::common::error;
39
namespace path = mender::common::path;
40

41

42
const string expected_state_script_version {"3"};
43
const int state_script_retry_exit_code {21};
44

45
unordered_map<const State, string> state_map {
46
        {State::Idle, "Idle"},
47
        {State::Sync, "Sync"},
48
        {State::Download, "Download"},
49
        {State::ArtifactInstall, "ArtifactInstall"},
50
        {State::ArtifactReboot, "ArtifactReboot"},
51
        {State::ArtifactCommit, "ArtifactCommit"},
52
        {State::ArtifactRollback, "ArtifactRollback"},
53
        {State::ArtifactRollbackReboot, "ArtifactRollbackReboot"},
54
        {State::ArtifactFailure, "ArtifactFailure"},
55
};
56

57
unordered_map<const Action, string> action_map {
58
        {Action::Enter, "Enter"},
59
        {Action::Leave, "Leave"},
60
        {Action::Error, "Error"},
61
};
62

63
error::Error CorrectVersionFile(const string &path) {
618✔
64
        // Missing file is OK
65
        // This is because previous versions of the client wrote no
66
        // version file, so no-file=v3
67
        if (!path::FileExists(path)) {
618✔
68
                return error::NoError;
161✔
69
        }
70

71
        ifstream vf {path};
914✔
72

73
        if (!vf) {
457✔
74
                auto errnum {errno};
×
75
                return error::Error(
76
                        generic_category().default_error_condition(errnum), "Failed to open the version file");
×
77
        }
78

79
        string version;
80
        vf >> version;
457✔
81
        if (!vf) {
457✔
82
                auto errnum {errno};
×
83
                return error::Error(
84
                        generic_category().default_error_condition(errnum),
×
85
                        "Error reading the version number from the version file");
×
86
        }
87

88
        if (version != expected_state_script_version) {
457✔
89
                return executor::MakeError(
90
                        executor::VersionFileError, "Unexpected Artifact script version found: " + version);
2✔
91
        }
92
        return error::NoError;
456✔
93
}
94

95
bool isValidStateScript(const string &file, State state, Action action) {
27,256✔
96
        string expression {
97
                "(" + state_map.at(state) + ")" + "_(" + action_map.at(action) + ")_[0-9][0-9](_\\S+)?"};
54,512✔
98
        log::Trace(
27,256✔
99
                "verifying the State script format of the file: " + file
27,256✔
100
                + " using the regular expression: " + expression);
54,512✔
101
        const regex artifact_script_regexp {expression, std::regex_constants::ECMAScript};
27,256✔
102
        return regex_match(path::BaseName(file), artifact_script_regexp);
81,768✔
103
}
104

105
function<bool(const string &)> Matcher(State state, Action action) {
×
106
        return [state, action](const string &file) {
53,828✔
107
                const bool is_valid {isValidStateScript(file, state, action)};
27,256✔
108
                if (!is_valid) {
27,256✔
109
                        log::Trace(file + " is not a valid State Script for the state: " + Name(state, action));
53,144✔
110
                        return false;
26,572✔
111
                }
112
                auto exp_executable = path::IsExecutable(file, true);
684✔
113
                if (!exp_executable) {
684✔
114
                        log::Debug("Issue figuring the executable bits of: " + exp_executable.error().String());
×
115
                        return false;
×
116
                }
117
                return is_valid and exp_executable.value();
684✔
118
        };
×
119
}
120

121
bool IsArtifactScript(State state) {
×
122
        switch (state) {
×
123
        case State::Idle:
124
        case State::Sync:
125
        case State::Download:
126
                return false;
127
        case State::ArtifactInstall:
×
128
        case State::ArtifactReboot:
129
        case State::ArtifactCommit:
130
        case State::ArtifactRollback:
131
        case State::ArtifactRollbackReboot:
132
        case State::ArtifactFailure:
133
                return true;
×
134
        }
135
        assert(false);
136
        return false;
137
}
138

139
string ScriptRunner::ScriptPath(State state) {
1,274✔
140
        if (IsArtifactScript(state)) {
141
                return this->artifact_script_path_;
617✔
142
        }
143
        return this->rootfs_script_path_;
657✔
144
}
145

146
string Name(const State state, const Action action) {
27,598✔
147
        return state_map.at(state) + action_map.at(action);
27,598✔
148
}
149

150
ScriptRunner::ScriptRunner(
3,005✔
151
        events::EventLoop &loop,
152
        chrono::seconds state_script_timeout,
153
        const string &artifact_script_path,
154
        const string &rootfs_script_path,
155
        mender::common::processes::OutputCallback stdout_callback,
156
        mender::common::processes::OutputCallback stderr_callback) :
3,005✔
157
        loop_ {loop},
158
        state_script_timeout_ {state_script_timeout},
159
        artifact_script_path_ {artifact_script_path},
160
        rootfs_script_path_ {rootfs_script_path},
161
        stdout_callback_ {stdout_callback},
162
        stderr_callback_ {stderr_callback},
163
        error_script_error_ {error::NoError} {};
3,005✔
164

165
void ScriptRunner::LogErrAndExecuteNext(
10✔
166
        Error err,
167
        vector<string>::iterator current_script,
168
        vector<string>::iterator end,
169
        bool ignore_error,
170
        HandlerFunction handler) {
171
        // Collect the error and carry on
172
        if (err.code
10✔
173
                == common::processes::MakeError(common::processes::NonZeroExitStatusError, "").code) {
10✔
174
                this->error_script_error_ = this->error_script_error_.FollowedBy(executor::MakeError(
10✔
175
                        executor::NonZeroExitStatusError,
176
                        "Got non zero exit code from script: " + *current_script));
20✔
177
        } else {
178
                this->error_script_error_ = this->error_script_error_.FollowedBy(err);
×
179
        }
180

181
        // Schedule the next script execution
182
        auto local_err = Execute(std::next(current_script), end, ignore_error, handler);
10✔
183
        if (local_err != error::NoError) {
10✔
184
                return handler(local_err);
×
185
        }
186
}
187

188
void ScriptRunner::HandleScriptError(Error err, HandlerFunction handler) {
31✔
189
        if (err.code
31✔
190
                == common::processes::MakeError(common::processes::NonZeroExitStatusError, "").code) {
31✔
191
                if (this->script_.get()->GetExitStatus() == state_script_retry_exit_code) {
31✔
192
                        return handler(executor::MakeError(
×
193
                                executor::RetryExitCodeError,
194
                                "Received exit code: " + to_string(state_script_retry_exit_code)));
×
195
                }
196
                return handler(executor::MakeError(
62✔
197
                        executor::NonZeroExitStatusError,
198
                        "Received error code: " + to_string(this->script_.get()->GetExitStatus())));
62✔
199
        }
200
        return handler(err);
×
201
}
202

203
Error ScriptRunner::Execute(
1,874✔
204
        vector<string>::iterator current_script,
205
        vector<string>::iterator end,
206
        bool ignore_error,
207
        HandlerFunction handler) {
208
        // No more scripts to execute
209
        if (current_script == end) {
1,874✔
210
                handler(this->error_script_error_); // Success
1,191✔
211
                return error::NoError;
1,191✔
212
        }
213

214
        log::Info("Running State Script: " + *current_script);
683✔
215

216
        this->script_.reset(new mender::common::processes::Process({*current_script}));
2,049✔
217
        auto err {this->script_->Start(stdout_callback_, stderr_callback_)};
1,366✔
218
        if (err != error::NoError) {
683✔
219
                return err;
×
220
        }
221

222
        return this->script_.get()->AsyncWait(
223
                this->loop_,
224
                [this, current_script, end, ignore_error, handler](Error err) {
4,098✔
225
                        if (err != error::NoError) {
683✔
226
                                if (ignore_error) {
41✔
227
                                        return LogErrAndExecuteNext(err, current_script, end, ignore_error, handler);
51✔
228
                                }
229
                                return HandleScriptError(err, handler);
62✔
230
                        }
231
                        // Schedule the next script execution
232
                        auto local_err = Execute(std::next(current_script), end, ignore_error, handler);
642✔
233
                        if (local_err != error::NoError) {
642✔
234
                                return handler(local_err);
×
235
                        }
236
                },
237
                this->state_script_timeout_);
1,366✔
238
}
239

240
Error ScriptRunner::AsyncRunScripts(
1,275✔
241
        State state, Action action, HandlerFunction handler, RunError on_error) {
242
        if (IsArtifactScript(state)) {
243
                // Verify the version in the version file (OK if no version file present)
244
                auto version_file_error {
245
                        CorrectVersionFile(path::Join(this->artifact_script_path_, "version"))};
618✔
246
                if (version_file_error != error::NoError) {
618✔
247
                        return version_file_error;
1✔
248
                }
249
        }
250

251
        // Collect
252
        const auto script_path {ScriptPath(state)};
1,274✔
253
        auto exp_scripts {path::ListFiles(script_path, Matcher(state, action))};
2,548✔
254
        if (!exp_scripts) {
1,274✔
255
                // Missing directory is OK
256
                if (exp_scripts.error().IsErrno(ENOENT)) {
52✔
257
                        log::Warning("Found no state script directory (" + script_path + "). Continuing on");
104✔
258
                        handler(error::NoError);
52✔
259
                        return error::NoError;
52✔
260
                }
261
                return executor::MakeError(
262
                        executor::Code::CollectionError,
263
                        "Failed to get the scripts, error: " + exp_scripts.error().String());
×
264
        }
265

266
        // Sort
267
        {
268
                auto &unsorted_scripts {exp_scripts.value()};
1,222✔
269

270
                vector<string> sorted_scripts(unsorted_scripts.begin(), unsorted_scripts.end());
2,444✔
271

272
                sort(sorted_scripts.begin(), sorted_scripts.end());
1,222✔
273
                this->collected_scripts_ = std::move(sorted_scripts);
1,222✔
274
        }
275

276
        bool ignore_error = on_error == RunError::Ignore || action == Action::Error;
1,222✔
277

278
        // Execute
279
        auto scripts_iterator {this->collected_scripts_.begin()};
280
        auto scripts_iterator_end {this->collected_scripts_.end()};
281
        return Execute(scripts_iterator, scripts_iterator_end, ignore_error, handler);
2,444✔
282
}
283

284

285
Error ScriptRunner::RunScripts(State state, Action action, RunError on_error) {
241✔
286
        auto run_err {error::NoError};
241✔
287
        auto err = AsyncRunScripts(
288
                state,
289
                action,
290
                [this, &run_err](Error error) {
482✔
291
                        run_err = error;
241✔
292
                        this->loop_.Stop();
241✔
293
                },
241✔
294
                on_error);
241✔
295
        if (err != error::NoError) {
241✔
296
                return err;
×
297
        }
298
        this->loop_.Run();
241✔
299
        return run_err;
241✔
300
}
301

302
} // namespace executor
303
} // namespace scripts
304
} // namespace artifact
305
} // namespace mender
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc