1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 #include <getopt.h>
4 #include <stdio.h>
5 #include <stdlib.h>
6 
7 #include "alloc-util.h"
8 #include "log.h"
9 #include "main-func.h"
10 #include "path-util.h"
11 #include "pretty-print.h"
12 #include "string-util.h"
13 #include "strv.h"
14 #include "unit-name.h"
15 
16 static enum {
17         ACTION_ESCAPE,
18         ACTION_UNESCAPE,
19         ACTION_MANGLE
20 } arg_action = ACTION_ESCAPE;
21 static const char *arg_suffix = NULL;
22 static const char *arg_template = NULL;
23 static bool arg_path = false;
24 static bool arg_instance = false;
25 
help(void)26 static int help(void) {
27         _cleanup_free_ char *link = NULL;
28         int r;
29 
30         r = terminal_urlify_man("systemd-escape", "1", &link);
31         if (r < 0)
32                 return log_oom();
33 
34         printf("%s [OPTIONS...] [NAME...]\n\n"
35                "Escape strings for usage in systemd unit names.\n\n"
36                "  -h --help               Show this help\n"
37                "     --version            Show package version\n"
38                "     --suffix=SUFFIX      Unit suffix to append to escaped strings\n"
39                "     --template=TEMPLATE  Insert strings as instance into template\n"
40                "     --instance           With --unescape, show just the instance part\n"
41                "  -u --unescape           Unescape strings\n"
42                "  -m --mangle             Mangle strings\n"
43                "  -p --path               When escaping/unescaping assume the string is a path\n"
44                "\nSee the %s for details.\n",
45                program_invocation_short_name,
46                link);
47 
48         return 0;
49 }
50 
parse_argv(int argc,char * argv[])51 static int parse_argv(int argc, char *argv[]) {
52 
53         enum {
54                 ARG_VERSION = 0x100,
55                 ARG_SUFFIX,
56                 ARG_TEMPLATE
57         };
58 
59         static const struct option options[] = {
60                 { "help",      no_argument,       NULL, 'h'           },
61                 { "version",   no_argument,       NULL, ARG_VERSION   },
62                 { "suffix",    required_argument, NULL, ARG_SUFFIX    },
63                 { "template",  required_argument, NULL, ARG_TEMPLATE  },
64                 { "unescape",  no_argument,       NULL, 'u'           },
65                 { "mangle",    no_argument,       NULL, 'm'           },
66                 { "path",      no_argument,       NULL, 'p'           },
67                 { "instance",  no_argument,       NULL, 'i'           },
68                 {}
69         };
70 
71         int c;
72 
73         assert(argc >= 0);
74         assert(argv);
75 
76         while ((c = getopt_long(argc, argv, "hump", options, NULL)) >= 0)
77 
78                 switch (c) {
79 
80                 case 'h':
81                         return help();
82 
83                 case ARG_VERSION:
84                         return version();
85 
86                 case ARG_SUFFIX: {
87                         UnitType t = unit_type_from_string(optarg);
88                         if (t < 0)
89                                 return log_error_errno(t, "Invalid unit suffix type \"%s\".", optarg);
90 
91                         arg_suffix = optarg;
92                         break;
93                 }
94 
95                 case ARG_TEMPLATE:
96                         if (!unit_name_is_valid(optarg, UNIT_NAME_TEMPLATE))
97                                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
98                                                        "Template name %s is not valid.", optarg);
99 
100                         arg_template = optarg;
101                         break;
102 
103                 case 'u':
104                         arg_action = ACTION_UNESCAPE;
105                         break;
106 
107                 case 'm':
108                         arg_action = ACTION_MANGLE;
109                         break;
110 
111                 case 'p':
112                         arg_path = true;
113                         break;
114 
115                 case 'i':
116                         arg_instance = true;
117                         break;
118 
119                 case '?':
120                         return -EINVAL;
121 
122                 default:
123                         assert_not_reached();
124                 }
125 
126         if (optind >= argc)
127                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
128                                        "Not enough arguments.");
129 
130         if (arg_template && arg_suffix)
131                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
132                                        "--suffix= and --template= may not be combined.");
133 
134         if ((arg_template || arg_suffix) && arg_action == ACTION_MANGLE)
135                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
136                                        "--suffix= and --template= are not compatible with --mangle.");
137 
138         if (arg_suffix && arg_action == ACTION_UNESCAPE)
139                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
140                                        "--suffix is not compatible with --unescape.");
141 
142         if (arg_path && !IN_SET(arg_action, ACTION_ESCAPE, ACTION_UNESCAPE))
143                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
144                                        "--path may not be combined with --mangle.");
145 
146         if (arg_instance && arg_action != ACTION_UNESCAPE)
147                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
148                                        "--instance must be used in conjunction with --unescape.");
149 
150         if (arg_instance && arg_template)
151                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
152                                        "--instance may not be combined with --template.");
153 
154         return 1;
155 }
156 
run(int argc,char * argv[])157 static int run(int argc, char *argv[]) {
158         int r;
159 
160         log_setup();
161 
162         r = parse_argv(argc, argv);
163         if (r <= 0)
164                 return r;
165 
166         STRV_FOREACH(i, argv + optind) {
167                 _cleanup_free_ char *e = NULL;
168 
169                 switch (arg_action) {
170 
171                 case ACTION_ESCAPE:
172                         if (arg_path) {
173                                 r = unit_name_path_escape(*i, &e);
174                                 if (r < 0) {
175                                         if (r == -EINVAL) {
176                                                 /* If escaping failed because the string was invalid, let's print a
177                                                  * friendly message about it. Catch these specific error cases
178                                                  * explicitly. */
179 
180                                                 if (!path_is_valid(*i))
181                                                         return log_error_errno(r, "Input '%s' is not a valid file system path, failed to escape.", *i);
182                                                 if (!path_is_absolute(*i))
183                                                         return log_error_errno(r, "Input '%s' is not an absolute file system path, failed to escape.", *i);
184                                                 if (!path_is_normalized(*i))
185                                                         return log_error_errno(r, "Input '%s' is not a normalized file system path, failed to escape.", *i);
186                                         }
187 
188                                         /* All other error cases. */
189                                         return log_error_errno(r, "Failed to escape string: %m");
190                                 }
191 
192                                 /* If the escaping worked, then still warn if the path is not like we'd like
193                                  * it. Because that means escaping is not necessarily reversible. */
194 
195                                 if (!path_is_valid(*i))
196                                         log_warning("Input '%s' is not a valid file system path, escaping is likely not going be reversible.", *i);
197                                 else if (!path_is_absolute(*i))
198                                         log_warning("Input '%s' is not an absolute file system path, escaping is likely not going to be reversible.", *i);
199 
200                                 /* Note that we don't complain about paths not being normalized here, because
201                                  * some forms of non-normalization is actually OK, such as a series // and
202                                  * unit_name_path_escape() will clean those up silently, and the reversal is
203                                  * "close enough" to be OK. */
204                         } else {
205                                 e = unit_name_escape(*i);
206                                 if (!e)
207                                         return log_oom();
208                         }
209 
210                         if (arg_template) {
211                                 char *x;
212 
213                                 r = unit_name_replace_instance(arg_template, e, &x);
214                                 if (r < 0)
215                                         return log_error_errno(r, "Failed to replace instance: %m");
216 
217                                 free_and_replace(e, x);
218                         } else if (arg_suffix) {
219                                 if (!strextend(&e, ".", arg_suffix))
220                                         return log_oom();
221                         }
222 
223                         break;
224 
225                 case ACTION_UNESCAPE: {
226                         _cleanup_free_ char *name = NULL;
227 
228                         if (arg_template || arg_instance) {
229                                 _cleanup_free_ char *template = NULL;
230 
231                                 r = unit_name_to_instance(*i, &name);
232                                 if (r < 0)
233                                         return log_error_errno(r, "Failed to extract instance: %m");
234                                 if (isempty(name))
235                                         return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
236                                                                "Unit %s is missing the instance name.", *i);
237 
238                                 r = unit_name_template(*i, &template);
239                                 if (r < 0)
240                                         return log_error_errno(r, "Failed to extract template: %m");
241                                 if (arg_template && !streq(arg_template, template))
242                                         return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
243                                                                "Unit %s template %s does not match specified template %s.",
244                                                                *i, template, arg_template);
245                         } else {
246                                 name = strdup(*i);
247                                 if (!name)
248                                         return log_oom();
249                         }
250 
251                         if (arg_path)
252                                 r = unit_name_path_unescape(name, &e);
253                         else
254                                 r = unit_name_unescape(name, &e);
255                         if (r < 0)
256                                 return log_error_errno(r, "Failed to unescape string: %m");
257 
258                         break;
259                 }
260 
261                 case ACTION_MANGLE:
262                         r = unit_name_mangle(*i, 0, &e);
263                         if (r < 0)
264                                 return log_error_errno(r, "Failed to mangle name: %m");
265 
266                         break;
267                 }
268 
269                 if (i != argv + optind)
270                         fputc(' ', stdout);
271 
272                 fputs(e, stdout);
273         }
274 
275         fputc('\n', stdout);
276 
277         return 0;
278 }
279 
280 DEFINE_MAIN_FUNCTION(run);
281