]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
d58ad743 LP |
2 | |
3 | #include "alloc-util.h" | |
f4351959 | 4 | #include "chase-symlinks.h" |
9a4b883b | 5 | #include "dirent-util.h" |
686d13b9 | 6 | #include "env-file.h" |
7eda2d7f | 7 | #include "env-util.h" |
d58ad743 | 8 | #include "fd-util.h" |
4fa744a3 | 9 | #include "fileio.h" |
d58ad743 LP |
10 | #include "fs-util.h" |
11 | #include "macro.h" | |
12 | #include "os-util.h" | |
9a4b883b | 13 | #include "parse-util.h" |
6ef06723 | 14 | #include "path-util.h" |
9a4b883b | 15 | #include "stat-util.h" |
d58ad743 | 16 | #include "string-util.h" |
686d13b9 | 17 | #include "strv.h" |
6ef06723 | 18 | #include "utf8.h" |
9a4b883b | 19 | #include "xattr-util.h" |
6ef06723 ZJS |
20 | |
21 | bool image_name_is_valid(const char *s) { | |
22 | if (!filename_is_valid(s)) | |
23 | return false; | |
24 | ||
25 | if (string_has_cc(s, NULL)) | |
26 | return false; | |
27 | ||
28 | if (!utf8_is_valid(s)) | |
29 | return false; | |
30 | ||
31 | /* Temporary files for atomically creating new files */ | |
32 | if (startswith(s, ".#")) | |
33 | return false; | |
34 | ||
35 | return true; | |
36 | } | |
d58ad743 | 37 | |
1d079673 | 38 | int path_is_extension_tree(const char *path, const char *extension) { |
d58ad743 LP |
39 | int r; |
40 | ||
41 | assert(path); | |
42 | ||
43 | /* Does the path exist at all? If not, generate an error immediately. This is useful so that a missing root dir | |
5238e957 | 44 | * always results in -ENOENT, and we can properly distinguish the case where the whole root doesn't exist from |
d58ad743 LP |
45 | * the case where just the os-release file is missing. */ |
46 | if (laccess(path, F_OK) < 0) | |
47 | return -errno; | |
48 | ||
9a4b883b LB |
49 | /* We use /usr/lib/extension-release.d/extension-release[.NAME] as flag for something being a system extension, |
50 | * and {/etc|/usr/lib}/os-release as a flag for something being an OS (when not an extension). */ | |
1d079673 | 51 | r = open_extension_release(path, extension, NULL, NULL); |
d58ad743 LP |
52 | if (r == -ENOENT) /* We got nothing */ |
53 | return 0; | |
54 | if (r < 0) | |
55 | return r; | |
56 | ||
57 | return 1; | |
58 | } | |
59 | ||
6ddd0511 | 60 | int open_extension_release(const char *root, const char *extension, char **ret_path, int *ret_fd) { |
d58ad743 | 61 | _cleanup_free_ char *q = NULL; |
a5648b80 | 62 | int r, fd; |
d58ad743 | 63 | |
6ddd0511 LB |
64 | if (extension) { |
65 | const char *extension_full_path; | |
66 | ||
67 | if (!image_name_is_valid(extension)) | |
68 | return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), | |
69 | "The extension name %s is invalid.", extension); | |
70 | ||
71 | extension_full_path = strjoina("/usr/lib/extension-release.d/extension-release.", extension); | |
72 | r = chase_symlinks(extension_full_path, root, CHASE_PREFIX_ROOT, | |
9e8a392a ZJS |
73 | ret_path ? &q : NULL, |
74 | ret_fd ? &fd : NULL); | |
ed15f8bc ZJS |
75 | log_full_errno_zerook(LOG_DEBUG, MIN(r, 0), "Checking for %s: %m", extension_full_path); |
76 | ||
9a4b883b LB |
77 | /* Cannot find the expected extension-release file? The image filename might have been |
78 | * mangled on deployment, so fallback to checking for any file in the extension-release.d | |
79 | * directory, and return the first one with a user.extension-release xattr instead. | |
80 | * The user.extension-release.strict xattr is checked to ensure the author of the image | |
81 | * considers it OK if names do not match. */ | |
82 | if (r == -ENOENT) { | |
83 | _cleanup_free_ char *extension_release_dir_path = NULL; | |
84 | _cleanup_closedir_ DIR *extension_release_dir = NULL; | |
85 | ||
86 | r = chase_symlinks_and_opendir("/usr/lib/extension-release.d/", root, CHASE_PREFIX_ROOT, | |
87 | &extension_release_dir_path, &extension_release_dir); | |
88 | if (r < 0) | |
ed15f8bc | 89 | return log_debug_errno(r, "Cannot open %s/usr/lib/extension-release.d/, ignoring: %m", root); |
9a4b883b LB |
90 | |
91 | r = -ENOENT; | |
9a4b883b LB |
92 | FOREACH_DIRENT(de, extension_release_dir, return -errno) { |
93 | int k; | |
94 | ||
95 | if (!IN_SET(de->d_type, DT_REG, DT_UNKNOWN)) | |
96 | continue; | |
97 | ||
98 | const char *image_name = startswith(de->d_name, "extension-release."); | |
99 | if (!image_name) | |
100 | continue; | |
101 | ||
ed15f8bc ZJS |
102 | if (!image_name_is_valid(image_name)) { |
103 | log_debug("%s/%s is not a valid extension-release file name, ignoring.", | |
104 | extension_release_dir_path, de->d_name); | |
9a4b883b | 105 | continue; |
ed15f8bc | 106 | } |
9a4b883b LB |
107 | |
108 | /* We already chased the directory, and checked that | |
109 | * this is a real file, so we shouldn't fail to open it. */ | |
110 | _cleanup_close_ int extension_release_fd = openat(dirfd(extension_release_dir), | |
111 | de->d_name, | |
112 | O_PATH|O_CLOEXEC|O_NOFOLLOW); | |
113 | if (extension_release_fd < 0) | |
114 | return log_debug_errno(errno, | |
115 | "Failed to open extension-release file %s/%s: %m", | |
116 | extension_release_dir_path, | |
117 | de->d_name); | |
118 | ||
119 | /* Really ensure it is a regular file after we open it. */ | |
ed15f8bc ZJS |
120 | if (fd_verify_regular(extension_release_fd) < 0) { |
121 | log_debug("%s/%s is not a regular file, ignoring.", extension_release_dir_path, de->d_name); | |
9a4b883b | 122 | continue; |
ed15f8bc | 123 | } |
9a4b883b LB |
124 | |
125 | /* No xattr or cannot parse it? Then skip this. */ | |
126 | _cleanup_free_ char *extension_release_xattr = NULL; | |
c53e07e2 | 127 | k = fgetxattr_malloc(extension_release_fd, "user.extension-release.strict", &extension_release_xattr); |
9a4b883b LB |
128 | if (k < 0 && !ERRNO_IS_NOT_SUPPORTED(k) && k != -ENODATA) |
129 | log_debug_errno(k, | |
ed15f8bc ZJS |
130 | "%s/%s: Failed to read 'user.extension-release.strict' extended attribute from file: %m", |
131 | extension_release_dir_path, de->d_name); | |
132 | if (k < 0) { | |
133 | log_debug("%s/%s does not have user.extension-release.strict xattr, ignoring.", extension_release_dir_path, de->d_name); | |
9a4b883b | 134 | continue; |
ed15f8bc | 135 | } |
9a4b883b LB |
136 | |
137 | /* Explicitly set to request strict matching? Skip it. */ | |
138 | k = parse_boolean(extension_release_xattr); | |
139 | if (k < 0) | |
140 | log_debug_errno(k, | |
ed15f8bc ZJS |
141 | "%s/%s: Failed to parse 'user.extension-release.strict' extended attribute from file: %m", |
142 | extension_release_dir_path, de->d_name); | |
143 | else if (k > 0) | |
144 | log_debug("%s/%s: 'user.extension-release.strict' attribute is true, ignoring file.", | |
145 | extension_release_dir_path, de->d_name); | |
146 | if (k != 0) | |
9a4b883b LB |
147 | continue; |
148 | ||
ed15f8bc ZJS |
149 | log_debug("%s/%s: 'user.extension-release.strict' attribute is false…", |
150 | extension_release_dir_path, de->d_name); | |
151 | ||
9a4b883b LB |
152 | /* We already found what we were looking for, but there's another candidate? |
153 | * We treat this as an error, as we want to enforce that there are no ambiguities | |
154 | * in case we are in the fallback path.*/ | |
155 | if (r == 0) { | |
156 | r = -ENOTUNIQ; | |
157 | break; | |
158 | } | |
159 | ||
160 | r = 0; /* Found it! */ | |
161 | ||
162 | if (ret_fd) | |
163 | fd = TAKE_FD(extension_release_fd); | |
164 | ||
165 | if (ret_path) { | |
166 | q = path_join(extension_release_dir_path, de->d_name); | |
167 | if (!q) | |
168 | return -ENOMEM; | |
169 | } | |
170 | } | |
171 | } | |
6ddd0511 LB |
172 | } else { |
173 | const char *p; | |
174 | ||
175 | FOREACH_STRING(p, "/etc/os-release", "/usr/lib/os-release") { | |
176 | r = chase_symlinks(p, root, CHASE_PREFIX_ROOT, | |
9e8a392a ZJS |
177 | ret_path ? &q : NULL, |
178 | ret_fd ? &fd : NULL); | |
6ddd0511 LB |
179 | if (r != -ENOENT) |
180 | break; | |
181 | } | |
d58ad743 | 182 | } |
a5648b80 ZJS |
183 | if (r < 0) |
184 | return r; | |
d58ad743 LP |
185 | |
186 | if (ret_fd) { | |
187 | int real_fd; | |
188 | ||
189 | /* Convert the O_PATH fd into a proper, readable one */ | |
a5648b80 ZJS |
190 | real_fd = fd_reopen(fd, O_RDONLY|O_CLOEXEC|O_NOCTTY); |
191 | safe_close(fd); | |
d58ad743 LP |
192 | if (real_fd < 0) |
193 | return real_fd; | |
194 | ||
195 | *ret_fd = real_fd; | |
196 | } | |
197 | ||
198 | if (ret_path) | |
199 | *ret_path = TAKE_PTR(q); | |
200 | ||
201 | return 0; | |
202 | } | |
203 | ||
6ddd0511 | 204 | int fopen_extension_release(const char *root, const char *extension, char **ret_path, FILE **ret_file) { |
d58ad743 LP |
205 | _cleanup_free_ char *p = NULL; |
206 | _cleanup_close_ int fd = -1; | |
207 | FILE *f; | |
208 | int r; | |
209 | ||
210 | if (!ret_file) | |
6ddd0511 | 211 | return open_extension_release(root, extension, ret_path, NULL); |
d58ad743 | 212 | |
6ddd0511 | 213 | r = open_extension_release(root, extension, ret_path ? &p : NULL, &fd); |
d58ad743 LP |
214 | if (r < 0) |
215 | return r; | |
216 | ||
4fa744a3 | 217 | f = take_fdopen(&fd, "r"); |
d58ad743 LP |
218 | if (!f) |
219 | return -errno; | |
d58ad743 | 220 | |
d58ad743 LP |
221 | if (ret_path) |
222 | *ret_path = TAKE_PTR(p); | |
9e8a392a | 223 | *ret_file = f; |
d58ad743 LP |
224 | |
225 | return 0; | |
226 | } | |
227 | ||
6ddd0511 | 228 | static int parse_release_internal(const char *root, const char *extension, va_list ap) { |
d58ad743 LP |
229 | _cleanup_fclose_ FILE *f = NULL; |
230 | _cleanup_free_ char *p = NULL; | |
d58ad743 LP |
231 | int r; |
232 | ||
6ddd0511 | 233 | r = fopen_extension_release(root, extension, &p, &f); |
d58ad743 LP |
234 | if (r < 0) |
235 | return r; | |
236 | ||
6ddd0511 LB |
237 | return parse_env_filev(f, p, ap); |
238 | } | |
239 | ||
209c1470 | 240 | int _parse_extension_release(const char *root, const char *extension, ...) { |
6ddd0511 LB |
241 | va_list ap; |
242 | int r; | |
243 | ||
244 | va_start(ap, extension); | |
245 | r = parse_release_internal(root, extension, ap); | |
246 | va_end(ap); | |
247 | ||
248 | return r; | |
249 | } | |
250 | ||
209c1470 | 251 | int _parse_os_release(const char *root, ...) { |
6ddd0511 LB |
252 | va_list ap; |
253 | int r; | |
254 | ||
d58ad743 | 255 | va_start(ap, root); |
6ddd0511 | 256 | r = parse_release_internal(root, NULL, ap); |
d58ad743 LP |
257 | va_end(ap); |
258 | ||
259 | return r; | |
260 | } | |
261 | ||
262 | int load_os_release_pairs(const char *root, char ***ret) { | |
263 | _cleanup_fclose_ FILE *f = NULL; | |
264 | _cleanup_free_ char *p = NULL; | |
265 | int r; | |
266 | ||
267 | r = fopen_os_release(root, &p, &f); | |
268 | if (r < 0) | |
269 | return r; | |
270 | ||
aa8fbc74 | 271 | return load_env_file_pairs(f, p, ret); |
d58ad743 | 272 | } |
e1bb4b0d LB |
273 | |
274 | int load_os_release_pairs_with_prefix(const char *root, const char *prefix, char ***ret) { | |
275 | _cleanup_strv_free_ char **os_release_pairs = NULL, **os_release_pairs_prefixed = NULL; | |
e1bb4b0d LB |
276 | int r; |
277 | ||
278 | r = load_os_release_pairs(root, &os_release_pairs); | |
279 | if (r < 0) | |
280 | return r; | |
281 | ||
282 | STRV_FOREACH_PAIR(p, q, os_release_pairs) { | |
283 | char *line; | |
284 | ||
2094cd49 | 285 | /* We strictly return only the four main ID fields and ignore the rest */ |
e1bb4b0d LB |
286 | if (!STR_IN_SET(*p, "ID", "VERSION_ID", "BUILD_ID", "VARIANT_ID")) |
287 | continue; | |
288 | ||
289 | ascii_strlower(*p); | |
290 | line = strjoin(prefix, *p, "=", *q); | |
291 | if (!line) | |
292 | return -ENOMEM; | |
293 | r = strv_consume(&os_release_pairs_prefixed, line); | |
294 | if (r < 0) | |
295 | return r; | |
296 | } | |
297 | ||
298 | *ret = TAKE_PTR(os_release_pairs_prefixed); | |
299 | ||
300 | return 0; | |
301 | } | |
eb590035 LB |
302 | |
303 | int load_extension_release_pairs(const char *root, const char *extension, char ***ret) { | |
304 | _cleanup_fclose_ FILE *f = NULL; | |
305 | _cleanup_free_ char *p = NULL; | |
306 | int r; | |
307 | ||
308 | r = fopen_extension_release(root, extension, &p, &f); | |
309 | if (r < 0) | |
310 | return r; | |
311 | ||
312 | return load_env_file_pairs(f, p, ret); | |
313 | } |