]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/import-util.c
9c9a9d7e301efd2e9b687ea0ea17010e0b08f462
[thirdparty/systemd.git] / src / shared / import-util.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include "alloc-util.h"
4 #include "btrfs-util.h"
5 #include "chattr-util.h"
6 #include "errno-util.h"
7 #include "import-util.h"
8 #include "log.h"
9 #include "nulstr-util.h"
10 #include "string-table.h"
11 #include "string-util.h"
12
13 static const char *skip_protocol_and_hostname(const char *url) {
14 const char *d;
15 size_t n;
16
17 /* A very very lenient implementation of RFC3986 Section 3.2 */
18
19 /* Find colon separating protocol and hostname */
20 d = strchr(url, ':');
21 if (!d || url == d)
22 return NULL;
23 d++;
24
25 /* Skip slashes after colon */
26 d += strspn(d, "/");
27
28 /* Skip everything till next slash or end */
29 n = strcspn(d, "/?#");
30 if (n == 0)
31 return NULL;
32
33 return d + n;
34 }
35
36 int import_url_last_component(
37 const char *url,
38 char **ret) {
39
40 const char *e, *p, *h;
41
42 /* This extracts the last path component of the specified URI, i.e. the last non-empty substrings
43 * between two "/" characters. This ignores "Query" and "Fragment" suffixes (as per RFC3986). */
44
45 h = skip_protocol_and_hostname(url);
46 if (!h)
47 return -EINVAL;
48
49 e = h + strcspn(h, "?#"); /* Cut off "Query" and "Fragment" */
50
51 while (e > h && e[-1] == '/') /* Eat trailing slashes */
52 e--;
53
54 p = e;
55 while (p > h && p[-1] != '/') /* Find component before that */
56 p--;
57
58 if (e <= p) /* Empty component? */
59 return -EADDRNOTAVAIL;
60
61 if (ret) {
62 char *s;
63
64 s = strndup(p, e - p);
65 if (!s)
66 return -ENOMEM;
67
68 *ret = s;
69 }
70
71 return 0;
72 }
73
74 int import_url_change_suffix(
75 const char *url,
76 size_t n_drop_components,
77 const char *suffix,
78 char **ret) {
79
80 const char *e, *h;
81 char *s;
82
83 assert(url);
84 assert(ret);
85
86 /* This drops the specified number of path components of the specified URI, i.e. the specified number
87 * of non-empty substring between two "/" characters from the end of the string, and then append the
88 * specified suffix instead. Before doing all this it chops off the "Query" and "Fragment" suffixes
89 * (they are *not* re-added to the final URL). Note that n_drop_components may be 0 (in which case the
90 * component are simply added to the end). The suffix may be specified as NULL or empty string in
91 * which case nothing is appended, only the specified number of components chopped off. Note that the
92 * function may be called with n_drop_components == 0 and suffix == NULL, in which case the "Query"
93 * and "Fragment" is chopped off, and ensured the URL ends in a single "/", and that's it. */
94
95 h = skip_protocol_and_hostname(url);
96 if (!h)
97 return -EINVAL;
98
99 e = h + strcspn(h, "?#"); /* Cut off "Query" and "Fragment" */
100
101 while (e > h && e[-1] == '/') /* Eat trailing slashes */
102 e--;
103
104 /* Drop the specified number of components from the end. Note that this is pretty lenient: if there
105 * are less component we silently drop those and then append the suffix to the top. */
106 while (n_drop_components > 0) {
107 while (e > h && e[-1] != '/') /* Eat last word (we don't mind if empty) */
108 e--;
109
110 while (e > h && e[-1] == '/') /* Eat slashes before the last word */
111 e--;
112
113 n_drop_components--;
114 }
115
116 s = new(char, (e - url) + 1 + strlen_ptr(suffix) + 1);
117 if (!s)
118 return -ENOMEM;
119
120 strcpy(stpcpy(mempcpy(s, url, e - url), "/"), strempty(suffix));
121 *ret = s;
122 return 0;
123 }
124
125 static const char* const import_type_table[_IMPORT_TYPE_MAX] = {
126 [IMPORT_RAW] = "raw",
127 [IMPORT_TAR] = "tar",
128 };
129
130 DEFINE_STRING_TABLE_LOOKUP(import_type, ImportType);
131
132 static const char* const import_verify_table[_IMPORT_VERIFY_MAX] = {
133 [IMPORT_VERIFY_NO] = "no",
134 [IMPORT_VERIFY_CHECKSUM] = "checksum",
135 [IMPORT_VERIFY_SIGNATURE] = "signature",
136 };
137
138 DEFINE_STRING_TABLE_LOOKUP(import_verify, ImportVerify);
139
140 int tar_strip_suffixes(const char *name, char **ret) {
141 const char *e;
142 char *s;
143
144 e = endswith(name, ".tar");
145 if (!e)
146 e = endswith(name, ".tar.xz");
147 if (!e)
148 e = endswith(name, ".tar.gz");
149 if (!e)
150 e = endswith(name, ".tar.bz2");
151 if (!e)
152 e = endswith(name, ".tar.zst");
153 if (!e)
154 e = endswith(name, ".tgz");
155 if (!e)
156 e = strchr(name, 0);
157
158 if (e <= name)
159 return -EINVAL;
160
161 s = strndup(name, e - name);
162 if (!s)
163 return -ENOMEM;
164
165 *ret = s;
166 return 0;
167 }
168
169 int raw_strip_suffixes(const char *p, char **ret) {
170
171 static const char suffixes[] =
172 ".xz\0"
173 ".gz\0"
174 ".bz2\0"
175 ".zst\0"
176 ".sysext.raw\0"
177 ".confext.raw\0"
178 ".raw\0"
179 ".qcow2\0"
180 ".img\0"
181 ".bin\0";
182
183 _cleanup_free_ char *q = NULL;
184
185 q = strdup(p);
186 if (!q)
187 return -ENOMEM;
188
189 for (;;) {
190 bool changed = false;
191
192 NULSTR_FOREACH(sfx, suffixes) {
193 char *e;
194
195 e = endswith(q, sfx);
196 if (e) {
197 *e = 0;
198 changed = true;
199 }
200 }
201
202 if (!changed)
203 break;
204 }
205
206 *ret = TAKE_PTR(q);
207
208 return 0;
209 }
210
211 int import_assign_pool_quota_and_warn(const char *path) {
212 int r;
213
214 assert(path);
215
216 r = btrfs_subvol_auto_qgroup(path, 0, true);
217 if (r == -ENOTTY) {
218 log_debug_errno(r, "Failed to set up quota hierarchy for %s, as directory is not on btrfs or not a subvolume. Ignoring.", path);
219 return 0;
220 }
221 if (r < 0)
222 return log_error_errno(r, "Failed to set up default quota hierarchy for %s: %m", path);
223 if (r > 0)
224 log_debug("Set up default quota hierarchy for %s.", path);
225
226 return 0;
227 }
228
229 int import_set_nocow_and_log(int fd, const char *path) {
230 int r;
231
232 r = chattr_fd(fd, FS_NOCOW_FL, FS_NOCOW_FL);
233 if (r < 0)
234 return log_full_errno(
235 ERRNO_IS_IOCTL_NOT_SUPPORTED(r) ? LOG_DEBUG : LOG_WARNING,
236 r, "Failed to set file attributes on %s: %m", path);
237
238 return 0;
239 }