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

systemd / systemd / 20733218376

05 Jan 2026 11:49PM UTC coverage: 72.444% (-0.3%) from 72.733%
20733218376

push

github

yuwata
man/kernel-install: /proc/cmdline is not used as a fallback in container

309102 of 426678 relevant lines covered (72.44%)

1164706.41 hits per line

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

81.23
/src/shared/edit-util.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <stdio.h>
4
#include <stdlib.h>
5
#include <sys/stat.h>
6
#include <unistd.h>
7

8
#include "alloc-util.h"
9
#include "copy.h"
10
#include "edit-util.h"
11
#include "errno-util.h"
12
#include "fd-util.h"
13
#include "fileio.h"
14
#include "fs-util.h"
15
#include "log.h"
16
#include "mkdir-label.h"
17
#include "path-util.h"
18
#include "process-util.h"
19
#include "string-util.h"
20
#include "strv.h"
21
#include "tmpfile-util-label.h"
22

23
typedef struct EditFile {
24
        EditFileContext *context;
25
        char *path;
26
        char *original_path;
27
        char **comment_paths;
28
        char *temp;
29
        unsigned line;
30
} EditFile;
31

32
void edit_file_context_done(EditFileContext *context) {
31✔
33
        int r;
31✔
34

35
        assert(context);
31✔
36

37
        FOREACH_ARRAY(i, context->files, context->n_files) {
56✔
38
                unlink_and_free(i->temp);
25✔
39

40
                if (context->remove_parent) {
25✔
41
                        _cleanup_free_ char *parent = NULL;
16✔
42

43
                        r = path_extract_directory(i->path, &parent);
16✔
44
                        if (r < 0)
16✔
45
                                log_debug_errno(r, "Failed to extract directory from '%s', ignoring: %m", i->path);
×
46
                        else if (rmdir(parent) < 0 && !IN_SET(errno, ENOENT, ENOTEMPTY))
16✔
47
                                log_debug_errno(errno, "Failed to remove parent directory '%s', ignoring: %m", parent);
16✔
48
                }
49

50
                free(i->path);
25✔
51
                free(i->original_path);
25✔
52
                strv_free(i->comment_paths);
25✔
53
        }
54

55
        context->files = mfree(context->files);
31✔
56
        context->n_files = 0;
31✔
57
}
31✔
58

59
bool edit_files_contains(const EditFileContext *context, const char *path) {
25✔
60
        assert(context);
25✔
61
        assert(path);
25✔
62

63
        FOREACH_ARRAY(i, context->files, context->n_files)
26✔
64
                if (path_equal(i->path, path))
1✔
65
                        return true;
66

67
        return false;
68
}
69

70
int edit_files_add(
25✔
71
                EditFileContext *context,
72
                const char *path,
73
                const char *original_path,
74
                char * const *comment_paths) {
75

76
        _cleanup_free_ char *new_path = NULL, *new_original_path = NULL;
25✔
77
        _cleanup_strv_free_ char **new_comment_paths = NULL;
25✔
78

79
        assert(context);
25✔
80
        assert(path);
25✔
81

82
        if (edit_files_contains(context, path))
25✔
83
                return 0;
84

85
        if (!GREEDY_REALLOC(context->files, context->n_files + 1))
25✔
86
                return log_oom();
×
87

88
        new_path = strdup(path);
25✔
89
        if (!new_path)
25✔
90
                return log_oom();
×
91

92
        if (original_path) {
25✔
93
                new_original_path = strdup(original_path);
4✔
94
                if (!new_original_path)
4✔
95
                        return log_oom();
×
96
        }
97

98
        if (comment_paths) {
25✔
99
                new_comment_paths = strv_copy(comment_paths);
15✔
100
                if (!new_comment_paths)
15✔
101
                        return log_oom();
×
102
        }
103

104
        context->files[context->n_files] = (EditFile) {
25✔
105
                .context = context,
106
                .path = TAKE_PTR(new_path),
25✔
107
                .original_path = TAKE_PTR(new_original_path),
25✔
108
                .comment_paths = TAKE_PTR(new_comment_paths),
25✔
109
                .line = 1,
110
        };
111
        context->n_files++;
25✔
112

113
        return 1;
25✔
114
}
115

116
static int populate_edit_temp_file(EditFile *e, FILE *f, const char *filename) {
11✔
117
        assert(e);
11✔
118
        assert(e->context);
11✔
119
        assert(!e->context->read_from_stdin);
11✔
120
        assert(e->path);
11✔
121
        assert(f);
11✔
122
        assert(filename);
11✔
123

124
        bool has_original = e->original_path && access(e->original_path, F_OK) >= 0;
11✔
125
        bool has_target = access(e->path, F_OK) >= 0;
11✔
126
        const char *source;
11✔
127
        int r;
11✔
128

129
        if (has_original && (!has_target || e->context->overwrite_with_origin))
11✔
130
                /* We are asked to overwrite target with original_path or target doesn't exist. */
131
                source = e->original_path;
4✔
132
        else if (has_target)
7✔
133
                /* Target exists and shouldn't be overwritten. */
134
                source = e->path;
2✔
135
        else
136
                source = NULL;
137

138
        if (e->comment_paths) {
11✔
139
                _cleanup_free_ char *source_contents = NULL;
6✔
140

141
                if (source) {
6✔
142
                        r = read_full_file(source, &source_contents, NULL);
1✔
143
                        if (r < 0)
1✔
144
                                return log_error_errno(r, "Failed to read source file '%s': %m", source);
×
145
                }
146

147
                fprintf(f,
11✔
148
                        "### Editing %s\n"
149
                        "%s\n"
150
                        "\n"
151
                        "%s%s"
152
                        "\n"
153
                        "%s\n",
154
                        e->path,
155
                        e->context->marker_start,
156
                        strempty(source_contents),
157
                        source_contents && endswith(source_contents, "\n") ? "" : "\n",
1✔
158
                        e->context->marker_end);
6✔
159

160
                e->line = 4; /* Start editing at the contents area */
6✔
161

162
                STRV_FOREACH(path, e->comment_paths) {
20✔
163
                        _cleanup_free_ char *comment = NULL;
14✔
164

165
                        /* Skip the file which is being edited and the source file (can be the same) */
166
                        if (PATH_IN_SET(*path, e->path, source))
14✔
167
                                continue;
1✔
168

169
                        r = read_full_file(*path, &comment, NULL);
13✔
170
                        if (r < 0)
13✔
171
                                return log_error_errno(r, "Failed to read comment file '%s': %m", *path);
×
172

173
                        fprintf(f, "\n\n### %s", *path);
13✔
174

175
                        if (!isempty(comment)) {
26✔
176
                                _cleanup_free_ char *c = NULL;
13✔
177

178
                                c = strreplace(strstrip(comment), "\n", "\n# ");
13✔
179
                                if (!c)
13✔
180
                                        return log_oom();
×
181

182
                                fprintf(f, "\n# %s", c);
13✔
183
                        }
184
                }
185
        } else if (source) {
5✔
186
                r = copy_file_fd(source, fileno(f), COPY_REFLINK);
5✔
187
                if (r < 0) {
5✔
188
                        assert(r != -ENOENT);
×
189
                        return log_error_errno(r, "Failed to copy file '%s' to temporary file '%s': %m",
×
190
                                               source, filename);
191
                }
192
        }
193

194
        return 0;
195
}
196

197
static int create_edit_temp_file(EditFile *e, const char *contents, size_t contents_size) {
24✔
198
        _cleanup_(unlink_and_freep) char *temp = NULL;
×
199
        _cleanup_fclose_ FILE *f = NULL;
24✔
200
        int r;
24✔
201

202
        assert(e);
24✔
203
        assert(e->context);
24✔
204
        assert(e->path);
24✔
205
        assert(!e->comment_paths || (e->context->marker_start && e->context->marker_end));
24✔
206
        assert(contents || contents_size == 0);
24✔
207
        assert(e->context->read_from_stdin == !!contents);
24✔
208

209
        if (e->temp)
24✔
210
                return 0;
211

212
        r = mkdir_parents_label(e->path, 0755);
24✔
213
        if (r < 0)
24✔
214
                return log_error_errno(r, "Failed to create parent directories for '%s': %m", e->path);
×
215

216
        r = fopen_temporary_label(e->path, e->path, &f, &temp);
24✔
217
        if (r < 0)
24✔
218
                return log_error_errno(r, "Failed to create temporary file for '%s': %m", e->path);
×
219

220
        if (fchmod(fileno(f), 0644) < 0)
24✔
221
                return log_error_errno(errno, "Failed to change mode of temporary file '%s': %m", temp);
×
222

223
        if (e->context->read_from_stdin) {
24✔
224
                if (fwrite(contents, 1, contents_size, f) != contents_size)
13✔
225
                        return log_error_errno(SYNTHETIC_ERRNO(EIO),
×
226
                                               "Failed to write stdin data to temporary file '%s'.", temp);
227
        } else {
228
                r = populate_edit_temp_file(e, f, temp);
11✔
229
                if (r < 0)
11✔
230
                        return r;
231
        }
232

233
        r = fflush_and_check(f);
24✔
234
        if (r < 0)
24✔
235
                return log_error_errno(r, "Failed to write to temporary file '%s': %m", temp);
×
236

237
        e->temp = TAKE_PTR(temp);
24✔
238

239
        return 0;
24✔
240
}
241

242
static int run_editor_child(const EditFileContext *context) {
11✔
243
        _cleanup_strv_free_ char **args = NULL, **editor = NULL;
×
244
        int r;
11✔
245

246
        assert(context);
11✔
247
        assert(context->n_files >= 1);
11✔
248

249
        /* SYSTEMD_EDITOR takes precedence over EDITOR which takes precedence over VISUAL.
250
         * If neither SYSTEMD_EDITOR nor EDITOR nor VISUAL are present, we try to execute
251
         * well known editors. */
252
        FOREACH_STRING(e, "SYSTEMD_EDITOR", "EDITOR", "VISUAL") {
22✔
253
                const char *m = empty_to_null(getenv(e));
22✔
254
                if (m) {
11✔
255
                        editor = strv_split(m, WHITESPACE);
11✔
256
                        if (!editor)
11✔
257
                                return log_oom();
×
258

259
                        break;
260
                }
261
        }
262

263
        if (context->n_files == 1 && context->files[0].line > 1) {
11✔
264
                /* If editing a single file only, use the +LINE syntax to put cursor on the right line */
265
                r = strv_extendf(&args, "+%u", context->files[0].line);
6✔
266
                if (r < 0)
6✔
267
                        return log_oom();
×
268
        }
269

270
        FOREACH_ARRAY(i, context->files, context->n_files) {
22✔
271
                r = strv_extend(&args, i->temp);
11✔
272
                if (r < 0)
11✔
273
                        return log_oom();
×
274
        }
275

276
        size_t editor_n = strv_length(editor);
11✔
277
        if (editor_n > 0) {
11✔
278
                /* Strings are owned by 'editor' and 'args' */
279
                _cleanup_free_ char **cmdline = new(char*, editor_n + strv_length(args) + 1);
11✔
280
                if (!cmdline)
11✔
281
                        return log_oom();
×
282

283
                *mempcpy_typesafe(mempcpy_typesafe(cmdline, editor, editor_n), args, strv_length(args)) = NULL;
11✔
284

285
                execvp(cmdline[0], cmdline);
×
286
                log_warning_errno(errno, "Specified editor '%s' not available, trying fallbacks: %m", editor[0]);
×
287
        }
288

289
        bool prepended = false;
×
290
        FOREACH_STRING(name, "editor", "nano", "vim", "vi") {
×
291
                if (!prepended) {
×
292
                        r = strv_prepend(&args, name);
×
293
                        prepended = true;
294
                } else
295
                        r = free_and_strdup(&args[0], name);
×
296
                if (r < 0)
×
297
                        return log_oom();
×
298

299
                execvp(args[0], (char* const*) args);
×
300

301
                if (errno == ENOTDIR) {
×
302
                        log_debug_errno(errno,
×
303
                                        "Failed to execute '%s': a path component is not a directory, skipping...",
304
                                        name);
305
                        continue;
×
306
                }
307
                /* We do not fail if the editor doesn't exist because we want to try each one of them
308
                 * before failing. */
309
                if (errno != ENOENT)
×
310
                        return log_error_errno(errno, "Failed to execute '%s': %m", name);
×
311
        }
312

313
        return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
×
314
                               "Cannot edit files, no editor available. Please set either $SYSTEMD_EDITOR, $EDITOR or $VISUAL.");
315
}
316

317
static int run_editor(const EditFileContext *context) {
11✔
318
        int r;
11✔
319

320
        assert(context);
11✔
321

322
        r = pidref_safe_fork(
11✔
323
                        "(editor)",
324
                        FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_RLIMIT_NOFILE_SAFE|FORK_CLOSE_ALL_FDS|FORK_REOPEN_LOG|FORK_LOG|FORK_WAIT,
325
                        /* ret= */ NULL);
326
        if (r < 0)
22✔
327
                return r;
328
        if (r == 0) { /* Child */
22✔
329
                r = run_editor_child(context);
11✔
330
                _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
×
331
        }
332

333
        return 0;
334
}
335

336
static int strip_edit_temp_file(EditFile *e) {
24✔
337
        _cleanup_free_ char *old_contents = NULL, *tmp = NULL, *new_contents = NULL;
24✔
338
        const char *stripped;
24✔
339
        bool with_marker;
24✔
340
        int r;
24✔
341

342
        assert(e);
24✔
343
        assert(e->context);
24✔
344
        assert(!e->context->marker_start == !e->context->marker_end);
24✔
345
        assert(e->temp);
24✔
346

347
        r = read_full_file(e->temp, &old_contents, NULL);
24✔
348
        if (r < 0)
24✔
349
                return log_error_errno(r, "Failed to read temporary file '%s': %m", e->temp);
×
350

351
        tmp = strdup(old_contents);
24✔
352
        if (!tmp)
24✔
353
                return log_oom();
×
354

355
        with_marker = e->context->marker_start && !e->context->read_from_stdin;
24✔
356

357
        if (with_marker) {
24✔
358
                /* Trim out the lines between the two markers */
359
                char *contents_start, *contents_end;
9✔
360

361
                contents_start = strstrafter(tmp, e->context->marker_start) ?: tmp;
9✔
362

363
                contents_end = strstr(contents_start, e->context->marker_end);
9✔
364
                if (contents_end)
9✔
365
                        *contents_end = '\0';
2✔
366

367
                stripped = strstrip(contents_start);
9✔
368
        } else
369
                stripped = strstrip(tmp);
15✔
370

371
        if (isempty(stripped)) {
24✔
372
                /* People keep coming back to #24208 due to edits outside of markers. Let's detect this
373
                 * and point them in the right direction. */
374
                if (with_marker)
2✔
375
                        for (const char *p = old_contents;;) {
2✔
376
                                p = skip_leading_chars(p, WHITESPACE);
84✔
377
                                if (*p == '\0')
84✔
378
                                        break;
379
                                if (*p != '#') {
84✔
380
                                        log_warning("Found modifications outside of the staging area, which would be discarded.");
×
381
                                        break;
382
                                }
383

384
                                /* Skip the whole line if commented out */
385
                                p = strchr(p, '\n');
84✔
386
                                if (!p)
84✔
387
                                        break;
388
                                p++;
82✔
389
                        }
390

391
                return 0; /* File is empty (has no real changes) */
2✔
392
        }
393

394
        /* Trim prefix and suffix, but ensure suffixed by single newline */
395
        new_contents = strjoin(stripped, "\n");
22✔
396
        if (!new_contents)
22✔
397
                return log_oom();
×
398

399
        if (streq(old_contents, new_contents)) /* Don't touch the file if the above didn't change a thing */
22✔
400
                return 1; /* Contents have real changes */
401

402
        r = write_string_file(e->temp, new_contents,
3✔
403
                              WRITE_STRING_FILE_TRUNCATE | WRITE_STRING_FILE_AVOID_NEWLINE);
404
        if (r < 0)
3✔
405
                return log_error_errno(r, "Failed to strip temporary file '%s': %m", e->temp);
×
406

407
        return 1; /* Contents have real changes */
408
}
409

410
static int edit_file_install_one(EditFile *e) {
24✔
411
        int r;
24✔
412

413
        assert(e);
24✔
414
        assert(e->path);
24✔
415
        assert(e->temp);
24✔
416

417
        r = strip_edit_temp_file(e);
24✔
418
        if (r <= 0)
24✔
419
                return r;
420

421
        r = RET_NERRNO(rename(e->temp, e->path));
22✔
422
        if (r < 0)
×
423
                return log_error_errno(r,
×
424
                                       "Failed to rename temporary file '%s' to target file '%s': %m",
425
                                       e->temp, e->path);
426
        e->temp = mfree(e->temp);
22✔
427

428
        return 1;
22✔
429
}
430

431
static int edit_file_install_one_stdin(EditFile *e, const char *contents, size_t contents_size, int *fd) {
14✔
432
        int r;
14✔
433

434
        assert(e);
14✔
435
        assert(e->path);
14✔
436
        assert(contents || contents_size == 0);
14✔
437
        assert(fd);
14✔
438

439
        if (contents_size == 0)
14✔
440
                return 0;
14✔
441

442
        if (*fd >= 0) {
14✔
443
                r = mkdir_parents_label(e->path, 0755);
1✔
444
                if (r < 0)
1✔
445
                        return log_error_errno(r, "Failed to create parent directories for '%s': %m", e->path);
×
446

447
                r = copy_file_atomic_at(*fd, NULL, AT_FDCWD, e->path, 0644, COPY_REFLINK|COPY_REPLACE|COPY_MAC_CREATE);
1✔
448
                if (r < 0)
1✔
449
                        return log_error_errno(r, "Failed to copy stdin contents to '%s': %m", e->path);
×
450

451
                return 1;
452
        }
453

454
        r = create_edit_temp_file(e, contents, contents_size);
13✔
455
        if (r < 0)
13✔
456
                return r;
457

458
        _cleanup_close_ int tfd = open(e->temp, O_PATH|O_CLOEXEC);
27✔
459
        if (tfd < 0)
13✔
460
                return log_error_errno(errno, "Failed to pin temporary file '%s': %m", e->temp);
×
461

462
        r = edit_file_install_one(e);
13✔
463
        if (r <= 0)
13✔
464
                return r;
465

466
        *fd = TAKE_FD(tfd);
13✔
467

468
        return 1;
13✔
469
}
470

471
int do_edit_files_and_install(EditFileContext *context) {
24✔
472
        _cleanup_free_ char *stdin_data = NULL;
24✔
473
        size_t stdin_size = 0;
24✔
474
        int r;
24✔
475

476
        assert(context);
24✔
477

478
        if (context->n_files == 0)
24✔
479
                return log_debug_errno(SYNTHETIC_ERRNO(ENOENT), "Got no files to edit.");
×
480

481
        if (context->read_from_stdin) {
24✔
482
                r = read_full_stream(stdin, &stdin_data, &stdin_size);
13✔
483
                if (r < 0)
13✔
484
                        return log_error_errno(r, "Failed to read stdin: %m");
×
485
        } else {
486
                FOREACH_ARRAY(editfile, context->files, context->n_files) {
22✔
487
                        r = create_edit_temp_file(editfile, /* contents= */ NULL, /* contents_size= */ 0);
11✔
488
                        if (r < 0)
11✔
489
                                return r;
490
                }
491

492
                r = run_editor(context);
11✔
493
                if (r < 0)
11✔
494
                        return r;
495
        }
496

497
        _cleanup_close_ int stdin_data_fd = -EBADF;
24✔
498

499
        FOREACH_ARRAY(editfile, context->files, context->n_files) {
49✔
500
                if (context->read_from_stdin) {
25✔
501
                        r = edit_file_install_one_stdin(editfile, stdin_data, stdin_size, &stdin_data_fd);
14✔
502
                        if (r == 0) {
14✔
503
                                log_notice("Stripped stdin content is empty, not writing file.");
×
504
                                return 0;
×
505
                        }
506
                } else {
507
                        r = edit_file_install_one(editfile);
11✔
508
                        if (r == 0) {
11✔
509
                                log_notice("%s: after editing, new contents are empty, not writing file.",
2✔
510
                                           editfile->path);
511
                                continue;
2✔
512
                        }
513
                }
514
                if (r < 0)
23✔
515
                        return r;
516

517
                log_info("Successfully installed edited file '%s'.", editfile->path);
23✔
518
        }
519

520
        return 0;
521
}
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

© 2026 Coveralls, Inc