1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 #include <errno.h>
4 #include <getopt.h>
5 #include <limits.h>
6 #include <stdbool.h>
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <sys/stat.h>
10 #include <sys/types.h>
11 
12 #include "alloc-util.h"
13 #include "binfmt-util.h"
14 #include "conf-files.h"
15 #include "def.h"
16 #include "fd-util.h"
17 #include "fileio.h"
18 #include "log.h"
19 #include "main-func.h"
20 #include "pager.h"
21 #include "path-util.h"
22 #include "pretty-print.h"
23 #include "string-util.h"
24 #include "strv.h"
25 
26 static bool arg_cat_config = false;
27 static PagerFlags arg_pager_flags = 0;
28 static bool arg_unregister = false;
29 
delete_rule(const char * rulename)30 static int delete_rule(const char *rulename) {
31         const char *fn = strjoina("/proc/sys/fs/binfmt_misc/", rulename);
32         return write_string_file(fn, "-1", WRITE_STRING_FILE_DISABLE_BUFFER);
33 }
34 
apply_rule(const char * filename,unsigned line,const char * rule)35 static int apply_rule(const char *filename, unsigned line, const char *rule) {
36         assert(filename);
37         assert(line > 0);
38         assert(rule);
39         assert(rule[0]);
40 
41         _cleanup_free_ char *rulename = NULL;
42         const char *e;
43         int r;
44 
45         e = strchrnul(rule + 1, rule[0]);
46         rulename = strndup(rule + 1, e - rule - 1);
47         if (!rulename)
48                 return log_oom();
49 
50         if (!filename_is_valid(rulename) ||
51             STR_IN_SET(rulename, "register", "status"))
52                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
53                                        "%s:%u: Rule name '%s' is not valid, refusing.",
54                                        filename, line, rulename);
55         r = delete_rule(rulename);
56         if (r < 0 && r != -ENOENT)
57                 log_warning_errno(r, "%s:%u: Failed to delete rule '%s', ignoring: %m",
58                                   filename, line, rulename);
59         if (r >= 0)
60                 log_debug("%s:%u: Rule '%s' deleted.", filename, line, rulename);
61 
62         r = write_string_file("/proc/sys/fs/binfmt_misc/register", rule, WRITE_STRING_FILE_DISABLE_BUFFER);
63         if (r < 0)
64                 return log_error_errno(r, "%s:%u: Failed to add binary format '%s': %m",
65                                        filename, line, rulename);
66 
67         log_debug("%s:%u: Binary format '%s' registered.", filename, line, rulename);
68         return 0;
69 }
70 
apply_file(const char * filename,bool ignore_enoent)71 static int apply_file(const char *filename, bool ignore_enoent) {
72         _cleanup_fclose_ FILE *f = NULL;
73         _cleanup_free_ char *pp = NULL;
74         int r;
75 
76         assert(filename);
77 
78         r = search_and_fopen(filename, "re", NULL, (const char**) CONF_PATHS_STRV("binfmt.d"), &f, &pp);
79         if (r < 0) {
80                 if (ignore_enoent && r == -ENOENT)
81                         return 0;
82 
83                 return log_error_errno(r, "Failed to open file '%s': %m", filename);
84         }
85 
86         log_debug("Applying %s…", pp);
87         for (unsigned line = 1;; line++) {
88                 _cleanup_free_ char *text = NULL;
89                 char *p;
90                 int k;
91 
92                 k = read_line(f, LONG_LINE_MAX, &text);
93                 if (k < 0)
94                         return log_error_errno(k, "Failed to read file '%s': %m", pp);
95                 if (k == 0)
96                         break;
97 
98                 p = strstrip(text);
99                 if (isempty(p))
100                         continue;
101                 if (strchr(COMMENTS, p[0]))
102                         continue;
103 
104                 k = apply_rule(filename, line, p);
105                 if (k < 0 && r >= 0)
106                         r = k;
107         }
108 
109         return r;
110 }
111 
help(void)112 static int help(void) {
113         _cleanup_free_ char *link = NULL;
114         int r;
115 
116         r = terminal_urlify_man("systemd-binfmt.service", "8", &link);
117         if (r < 0)
118                 return log_oom();
119 
120         printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
121                "Registers binary formats with the kernel.\n\n"
122                "  -h --help             Show this help\n"
123                "     --version          Show package version\n"
124                "     --cat-config       Show configuration files\n"
125                "     --no-pager         Do not pipe output into a pager\n"
126                "     --unregister       Unregister all existing entries\n"
127                "\nSee the %s for details.\n",
128                program_invocation_short_name,
129                link);
130 
131         return 0;
132 }
133 
parse_argv(int argc,char * argv[])134 static int parse_argv(int argc, char *argv[]) {
135         enum {
136                 ARG_VERSION = 0x100,
137                 ARG_CAT_CONFIG,
138                 ARG_NO_PAGER,
139                 ARG_UNREGISTER,
140         };
141 
142         static const struct option options[] = {
143                 { "help",       no_argument, NULL, 'h'            },
144                 { "version",    no_argument, NULL, ARG_VERSION    },
145                 { "cat-config", no_argument, NULL, ARG_CAT_CONFIG },
146                 { "no-pager",   no_argument, NULL, ARG_NO_PAGER   },
147                 { "unregister", no_argument, NULL, ARG_UNREGISTER },
148                 {}
149         };
150 
151         int c;
152 
153         assert(argc >= 0);
154         assert(argv);
155 
156         while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
157 
158                 switch (c) {
159 
160                 case 'h':
161                         return help();
162 
163                 case ARG_VERSION:
164                         return version();
165 
166                 case ARG_CAT_CONFIG:
167                         arg_cat_config = true;
168                         break;
169 
170                 case ARG_NO_PAGER:
171                         arg_pager_flags |= PAGER_DISABLE;
172                         break;
173 
174                 case ARG_UNREGISTER:
175                         arg_unregister = true;
176                         break;
177 
178                 case '?':
179                         return -EINVAL;
180 
181                 default:
182                         assert_not_reached();
183                 }
184 
185         if ((arg_unregister || arg_cat_config) && argc > optind)
186                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
187                                        "Positional arguments are not allowed with --cat-config or --unregister");
188 
189         return 1;
190 }
191 
run(int argc,char * argv[])192 static int run(int argc, char *argv[]) {
193         int r, k;
194 
195         r = parse_argv(argc, argv);
196         if (r <= 0)
197                 return r;
198 
199         log_setup();
200 
201         umask(0022);
202 
203         r = 0;
204 
205         if (arg_unregister)
206                 return disable_binfmt();
207 
208         if (argc > optind)
209                 for (int i = optind; i < argc; i++) {
210                         k = apply_file(argv[i], false);
211                         if (k < 0 && r >= 0)
212                                 r = k;
213                 }
214         else {
215                 _cleanup_strv_free_ char **files = NULL;
216 
217                 r = conf_files_list_strv(&files, ".conf", NULL, 0, (const char**) CONF_PATHS_STRV("binfmt.d"));
218                 if (r < 0)
219                         return log_error_errno(r, "Failed to enumerate binfmt.d files: %m");
220 
221                 if (arg_cat_config) {
222                         pager_open(arg_pager_flags);
223 
224                         return cat_files(NULL, files, 0);
225                 }
226 
227                 /* Flush out all rules */
228                 r = write_string_file("/proc/sys/fs/binfmt_misc/status", "-1", WRITE_STRING_FILE_DISABLE_BUFFER);
229                 if (r < 0)
230                         log_warning_errno(r, "Failed to flush binfmt_misc rules, ignoring: %m");
231                 else
232                         log_debug("Flushed all binfmt_misc rules.");
233 
234                 STRV_FOREACH(f, files) {
235                         k = apply_file(*f, true);
236                         if (k < 0 && r >= 0)
237                                 r = k;
238                 }
239         }
240 
241         return r;
242 }
243 
244 DEFINE_MAIN_FUNCTION(run);
245