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