]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/sysupdate/sysupdate-partition.c
tree-wide: "<n>bit" → "<n>-bit"
[thirdparty/systemd.git] / src / sysupdate / sysupdate-partition.c
CommitLineData
43cc7a3e
LP
1/* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3#include <sys/file.h>
4
5#include "alloc-util.h"
6#include "extract-word.h"
7#include "gpt.h"
8#include "id128-util.h"
9#include "parse-util.h"
10#include "stdio-util.h"
11#include "string-util.h"
12#include "sysupdate-partition.h"
43cc7a3e
LP
13
14void partition_info_destroy(PartitionInfo *p) {
15 assert(p);
16
17 p->label = mfree(p->label);
18 p->device = mfree(p->device);
19}
20
21static int fdisk_partition_get_attrs_as_uint64(
22 struct fdisk_partition *pa,
23 uint64_t *ret) {
24
25 uint64_t flags = 0;
26 const char *a;
27 int r;
28
29 assert(pa);
30 assert(ret);
31
32 /* Retrieve current flags as uint64_t mask */
33
34 a = fdisk_partition_get_attrs(pa);
35 if (!a) {
36 *ret = 0;
37 return 0;
38 }
39
40 for (;;) {
41 _cleanup_free_ char *word = NULL;
42
43 r = extract_first_word(&a, &word, ",", EXTRACT_DONT_COALESCE_SEPARATORS);
44 if (r < 0)
45 return r;
46 if (r == 0)
47 break;
48
49 if (streq(word, "RequiredPartition"))
92e72028 50 flags |= SD_GPT_FLAG_REQUIRED_PARTITION;
43cc7a3e 51 else if (streq(word, "NoBlockIOProtocol"))
92e72028 52 flags |= SD_GPT_FLAG_NO_BLOCK_IO_PROTOCOL;
43cc7a3e 53 else if (streq(word, "LegacyBIOSBootable"))
92e72028 54 flags |= SD_GPT_FLAG_LEGACY_BIOS_BOOTABLE;
43cc7a3e
LP
55 else {
56 const char *e;
57 unsigned u;
58
59 /* Drop "GUID" prefix if specified */
60 e = startswith(word, "GUID:") ?: word;
61
62 if (safe_atou(e, &u) < 0) {
63 log_debug("Unknown partition flag '%s', ignoring.", word);
64 continue;
65 }
66
da890466 67 if (u >= sizeof(flags)*8) { /* partition flags on GPT are 64-bit. Let's ignore any further
43cc7a3e
LP
68 bits should libfdisk report them */
69 log_debug("Partition flag above bit 63 (%s), ignoring.", word);
70 continue;
71 }
72
73 flags |= UINT64_C(1) << u;
74 }
75 }
76
77 *ret = flags;
78 return 0;
79}
80
81static int fdisk_partition_set_attrs_as_uint64(
82 struct fdisk_partition *pa,
83 uint64_t flags) {
84
85 _cleanup_free_ char *attrs = NULL;
86 int r;
87
88 assert(pa);
89
90 for (unsigned i = 0; i < sizeof(flags) * 8; i++) {
91 if (!FLAGS_SET(flags, UINT64_C(1) << i))
92 continue;
93
94 r = strextendf_with_separator(&attrs, ",", "%u", i);
95 if (r < 0)
96 return r;
97 }
98
99 return fdisk_partition_set_attrs(pa, strempty(attrs));
100}
101
102int read_partition_info(
103 struct fdisk_context *c,
104 struct fdisk_table *t,
105 size_t i,
106 PartitionInfo *ret) {
107
108 _cleanup_free_ char *label_copy = NULL, *device = NULL;
63b96eb9 109 const char *label;
43cc7a3e 110 struct fdisk_partition *p;
43cc7a3e
LP
111 uint64_t start, size, flags;
112 sd_id128_t ptid, id;
22e932f4 113 GptPartitionType type;
43cc7a3e
LP
114 size_t partno;
115 int r;
116
117 assert(c);
118 assert(t);
119 assert(ret);
120
121 p = fdisk_table_get_partition(t, i);
122 if (!p)
123 return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read partition metadata: %m");
124
125 if (fdisk_partition_is_used(p) <= 0) {
126 *ret = (PartitionInfo) PARTITION_INFO_NULL;
127 return 0; /* not found! */
128 }
129
130 if (fdisk_partition_has_partno(p) <= 0 ||
131 fdisk_partition_has_start(p) <= 0 ||
132 fdisk_partition_has_size(p) <= 0)
133 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Found a partition without a number, position or size.");
134
135 partno = fdisk_partition_get_partno(p);
136
137 start = fdisk_partition_get_start(p);
138 assert(start <= UINT64_MAX / 512U);
139 start *= 512U;
140
141 size = fdisk_partition_get_size(p);
142 assert(size <= UINT64_MAX / 512U);
143 size *= 512U;
144
145 label = fdisk_partition_get_name(p);
146 if (!label)
147 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Found a partition without a label.");
148
63b96eb9 149 r = fdisk_partition_get_type_as_id128(p, &ptid);
43cc7a3e 150 if (r < 0)
63b96eb9 151 return log_error_errno(r, "Failed to read partition type UUID: %m");
43cc7a3e 152
02e32aa6 153 r = fdisk_partition_get_uuid_as_id128(p, &id);
43cc7a3e 154 if (r < 0)
02e32aa6 155 return log_error_errno(r, "Failed to read partition UUID: %m");
43cc7a3e
LP
156
157 r = fdisk_partition_get_attrs_as_uint64(p, &flags);
158 if (r < 0)
159 return log_error_errno(r, "Failed to get partition flags: %m");
160
161 r = fdisk_partition_to_string(p, c, FDISK_FIELD_DEVICE, &device);
162 if (r != 0)
163 return log_error_errno(r, "Failed to get partition device name: %m");
164
165 label_copy = strdup(label);
166 if (!label_copy)
167 return log_oom();
168
22e932f4
DDM
169 type = gpt_partition_type_from_uuid(ptid);
170
43cc7a3e
LP
171 *ret = (PartitionInfo) {
172 .partno = partno,
173 .start = start,
174 .size = size,
175 .flags = flags,
176 .type = ptid,
177 .uuid = id,
178 .label = TAKE_PTR(label_copy),
179 .device = TAKE_PTR(device),
22e932f4
DDM
180 .no_auto = FLAGS_SET(flags, SD_GPT_FLAG_NO_AUTO) && gpt_partition_type_knows_no_auto(type),
181 .read_only = FLAGS_SET(flags, SD_GPT_FLAG_READ_ONLY) && gpt_partition_type_knows_read_only(type),
182 .growfs = FLAGS_SET(flags, SD_GPT_FLAG_GROWFS) && gpt_partition_type_knows_growfs(type),
43cc7a3e
LP
183 };
184
185 return 1; /* found! */
186}
187
188int find_suitable_partition(
189 const char *device,
190 uint64_t space,
191 sd_id128_t *partition_type,
192 PartitionInfo *ret) {
193
194 _cleanup_(partition_info_destroy) PartitionInfo smallest = PARTITION_INFO_NULL;
195 _cleanup_(fdisk_unref_contextp) struct fdisk_context *c = NULL;
196 _cleanup_(fdisk_unref_tablep) struct fdisk_table *t = NULL;
197 size_t n_partitions;
198 int r;
199
200 assert(device);
201 assert(ret);
202
203 c = fdisk_new_context();
204 if (!c)
205 return log_oom();
206
207 r = fdisk_assign_device(c, device, /* readonly= */ true);
208 if (r < 0)
209 return log_error_errno(r, "Failed to open device '%s': %m", device);
210
211 if (!fdisk_is_labeltype(c, FDISK_DISKLABEL_GPT))
212 return log_error_errno(SYNTHETIC_ERRNO(EHWPOISON), "Disk %s has no GPT disk label, not suitable.", device);
213
214 r = fdisk_get_partitions(c, &t);
215 if (r < 0)
216 return log_error_errno(r, "Failed to acquire partition table: %m");
217
218 n_partitions = fdisk_table_get_nents(t);
219 for (size_t i = 0; i < n_partitions; i++) {
220 _cleanup_(partition_info_destroy) PartitionInfo pinfo = PARTITION_INFO_NULL;
221
222 r = read_partition_info(c, t, i, &pinfo);
223 if (r < 0)
224 return r;
225 if (r == 0) /* not assigned */
226 continue;
227
228 /* Filter out non-matching partition types */
229 if (partition_type && !sd_id128_equal(pinfo.type, *partition_type))
230 continue;
231
232 if (!streq_ptr(pinfo.label, "_empty")) /* used */
233 continue;
234
235 if (space != UINT64_MAX && pinfo.size < space) /* too small */
236 continue;
237
238 if (smallest.partno != SIZE_MAX && smallest.size <= pinfo.size) /* already found smaller */
239 continue;
240
241 smallest = pinfo;
242 pinfo = (PartitionInfo) PARTITION_INFO_NULL;
243 }
244
245 if (smallest.partno == SIZE_MAX)
246 return log_error_errno(SYNTHETIC_ERRNO(ENOSPC), "No available partition of a suitable size found.");
247
248 *ret = smallest;
249 smallest = (PartitionInfo) PARTITION_INFO_NULL;
250
251 return 0;
252}
253
254int patch_partition(
255 const char *device,
256 const PartitionInfo *info,
257 PartitionChange change) {
258
259 _cleanup_(fdisk_unref_partitionp) struct fdisk_partition *pa = NULL;
260 _cleanup_(fdisk_unref_contextp) struct fdisk_context *c = NULL;
261 bool tweak_no_auto, tweak_read_only, tweak_growfs;
22e932f4 262 GptPartitionType type;
43cc7a3e
LP
263 int r, fd;
264
265 assert(device);
266 assert(info);
267 assert(change <= _PARTITION_CHANGE_MAX);
268
269 if (change == 0) /* Nothing to do */
270 return 0;
271
272 c = fdisk_new_context();
273 if (!c)
274 return log_oom();
275
276 r = fdisk_assign_device(c, device, /* readonly= */ false);
277 if (r < 0)
278 return log_error_errno(r, "Failed to open device '%s': %m", device);
279
280 assert_se((fd = fdisk_get_devfd(c)) >= 0);
281
282 /* Make sure udev doesn't read the device while we make changes (this lock is released automatically
283 * by the kernel when the fd is closed, i.e. when the fdisk context is freed, hence no explicit
284 * unlock by us here anywhere.) */
285 if (flock(fd, LOCK_EX) < 0)
286 return log_error_errno(errno, "Failed to lock block device '%s': %m", device);
287
288 if (!fdisk_is_labeltype(c, FDISK_DISKLABEL_GPT))
289 return log_error_errno(SYNTHETIC_ERRNO(EHWPOISON), "Disk %s has no GPT disk label, not suitable.", device);
290
291 r = fdisk_get_partition(c, info->partno, &pa);
292 if (r < 0)
293 return log_error_errno(r, "Failed to read partition %zu of GPT label of '%s': %m", info->partno, device);
294
295 if (change & PARTITION_LABEL) {
296 r = fdisk_partition_set_name(pa, info->label);
297 if (r < 0)
298 return log_error_errno(r, "Failed to update partition label: %m");
299 }
300
301 if (change & PARTITION_UUID) {
302 r = fdisk_partition_set_uuid(pa, SD_ID128_TO_UUID_STRING(info->uuid));
303 if (r < 0)
304 return log_error_errno(r, "Failed to update partition UUID: %m");
305 }
306
22e932f4
DDM
307 type = gpt_partition_type_from_uuid(info->type);
308
43cc7a3e
LP
309 /* Tweak the read-only flag, but only if supported by the partition type */
310 tweak_no_auto =
311 FLAGS_SET(change, PARTITION_NO_AUTO) &&
22e932f4 312 gpt_partition_type_knows_no_auto(type);
43cc7a3e
LP
313 tweak_read_only =
314 FLAGS_SET(change, PARTITION_READ_ONLY) &&
22e932f4 315 gpt_partition_type_knows_read_only(type);
43cc7a3e
LP
316 tweak_growfs =
317 FLAGS_SET(change, PARTITION_GROWFS) &&
22e932f4 318 gpt_partition_type_knows_growfs(type);
43cc7a3e
LP
319
320 if (change & PARTITION_FLAGS) {
321 uint64_t flags;
322
323 /* Update the full flags parameter, and import the read-only flag into it */
324
325 flags = info->flags;
326 if (tweak_no_auto)
92e72028 327 SET_FLAG(flags, SD_GPT_FLAG_NO_AUTO, info->no_auto);
43cc7a3e 328 if (tweak_read_only)
92e72028 329 SET_FLAG(flags, SD_GPT_FLAG_READ_ONLY, info->read_only);
43cc7a3e 330 if (tweak_growfs)
92e72028 331 SET_FLAG(flags, SD_GPT_FLAG_GROWFS, info->growfs);
43cc7a3e
LP
332
333 r = fdisk_partition_set_attrs_as_uint64(pa, flags);
334 if (r < 0)
335 return log_error_errno(r, "Failed to update partition flags: %m");
336
337 } else if (tweak_no_auto || tweak_read_only || tweak_growfs) {
338 uint64_t old_flags, new_flags;
339
340 /* So we aren't supposed to update the full flags parameter, but we are supposed to update
341 * the RO flag of it. */
342
343 r = fdisk_partition_get_attrs_as_uint64(pa, &old_flags);
344 if (r < 0)
345 return log_error_errno(r, "Failed to get old partition flags: %m");
346
347 new_flags = old_flags;
348 if (tweak_no_auto)
92e72028 349 SET_FLAG(new_flags, SD_GPT_FLAG_NO_AUTO, info->no_auto);
43cc7a3e 350 if (tweak_read_only)
92e72028 351 SET_FLAG(new_flags, SD_GPT_FLAG_READ_ONLY, info->read_only);
43cc7a3e 352 if (tweak_growfs)
92e72028 353 SET_FLAG(new_flags, SD_GPT_FLAG_GROWFS, info->growfs);
43cc7a3e
LP
354
355 if (new_flags != old_flags) {
356 r = fdisk_partition_set_attrs_as_uint64(pa, new_flags);
357 if (r < 0)
358 return log_error_errno(r, "Failed to update partition flags: %m");
359 }
360 }
361
362 r = fdisk_set_partition(c, info->partno, pa);
363 if (r < 0)
364 return log_error_errno(r, "Failed to update partition: %m");
365
366 r = fdisk_write_disklabel(c);
367 if (r < 0)
368 return log_error_errno(r, "Failed to write updated partition table: %m");
369
370 return 0;
371}