1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
4 #include <linux/loop.h>
10 #include "alloc-util.h"
11 #include "capability-util.h"
12 #include "dissect-image.h"
17 #include "main-func.h"
18 #include "missing_loop.h"
19 #include "mkfs-util.h"
20 #include "mount-util.h"
21 #include "namespace-util.h"
22 #include "parse-util.h"
23 #include "path-util.h"
24 #include "string-util.h"
27 #include "tmpfile-util.h"
28 #include "user-util.h"
31 static unsigned arg_n_threads
= 5;
32 static unsigned arg_n_iterations
= 3;
33 static usec_t arg_timeout
= 0;
36 static usec_t end
= 0;
38 static void verify_dissected_image(DissectedImage
*dissected
) {
39 assert_se(dissected
->partitions
[PARTITION_ESP
].found
);
40 assert_se(dissected
->partitions
[PARTITION_ESP
].node
);
41 assert_se(dissected
->partitions
[PARTITION_XBOOTLDR
].found
);
42 assert_se(dissected
->partitions
[PARTITION_XBOOTLDR
].node
);
43 assert_se(dissected
->partitions
[PARTITION_ROOT
].found
);
44 assert_se(dissected
->partitions
[PARTITION_ROOT
].node
);
45 assert_se(dissected
->partitions
[PARTITION_HOME
].found
);
46 assert_se(dissected
->partitions
[PARTITION_HOME
].node
);
49 static void verify_dissected_image_harder(DissectedImage
*dissected
) {
50 verify_dissected_image(dissected
);
52 assert_se(streq(dissected
->partitions
[PARTITION_ESP
].fstype
, "vfat"));
53 assert_se(streq(dissected
->partitions
[PARTITION_XBOOTLDR
].fstype
, "vfat"));
54 assert_se(streq(dissected
->partitions
[PARTITION_ROOT
].fstype
, "ext4"));
55 assert_se(streq(dissected
->partitions
[PARTITION_HOME
].fstype
, "ext4"));
58 static void* thread_func(void *ptr
) {
59 int fd
= PTR_TO_FD(ptr
);
62 for (unsigned i
= 0; i
< arg_n_iterations
; i
++) {
63 _cleanup_(loop_device_unrefp
) LoopDevice
*loop
= NULL
;
64 _cleanup_(umount_and_rmdir_and_freep
) char *mounted
= NULL
;
65 _cleanup_(dissected_image_unrefp
) DissectedImage
*dissected
= NULL
;
67 if (now(CLOCK_MONOTONIC
) >= end
) {
68 log_notice("Time's up, exiting thread's loop");
72 log_notice("> Thread iteration #%u.", i
);
74 assert_se(mkdtemp_malloc(NULL
, &mounted
) >= 0);
76 r
= loop_device_make(fd
, O_RDONLY
, 0, UINT64_MAX
, 0, LO_FLAGS_PARTSCAN
, LOCK_SH
, &loop
);
78 log_error_errno(r
, "Failed to allocate loopback device: %m");
81 assert_se(loop
->backing_file
);
83 log_notice("Acquired loop device %s, will mount on %s", loop
->node
, mounted
);
85 r
= dissect_loop_device(loop
, NULL
, NULL
, NULL
, DISSECT_IMAGE_READ_ONLY
|DISSECT_IMAGE_ADD_PARTITION_DEVICES
|DISSECT_IMAGE_PIN_PARTITION_DEVICES
, &dissected
);
87 log_error_errno(r
, "Failed dissect loopback device %s: %m", loop
->node
);
90 log_info("Dissected loop device %s", loop
->node
);
92 for (PartitionDesignator d
= 0; d
< _PARTITION_DESIGNATOR_MAX
; d
++) {
93 if (!dissected
->partitions
[d
].found
)
96 log_notice("Found node %s fstype %s designator %s",
97 dissected
->partitions
[d
].node
,
98 dissected
->partitions
[d
].fstype
,
99 partition_designator_to_string(d
));
102 verify_dissected_image(dissected
);
104 r
= dissected_image_mount(dissected
, mounted
, UID_INVALID
, UID_INVALID
, DISSECT_IMAGE_READ_ONLY
);
105 log_notice_errno(r
, "Mounted %s → %s: %m", loop
->node
, mounted
);
108 /* Now the block device is mounted, we don't need no manual lock anymore, the devices are now
109 * pinned by the mounts. */
110 assert_se(loop_device_flock(loop
, LOCK_UN
) >= 0);
112 log_notice("Unmounting %s", mounted
);
113 mounted
= umount_and_rmdir_and_free(mounted
);
115 log_notice("Unmounted.");
117 dissected
= dissected_image_unref(dissected
);
119 log_notice("Detaching loop device %s", loop
->node
);
120 loop
= loop_device_unref(loop
);
121 log_notice("Detached loop device.");
124 log_notice("Leaving thread");
130 static bool have_root_gpt_type(void) {
131 #ifdef SD_GPT_ROOT_NATIVE
138 static int run(int argc
, char *argv
[]) {
140 _cleanup_(dissected_image_unrefp
) DissectedImage
*dissected
= NULL
;
141 _cleanup_(umount_and_rmdir_and_freep
) char *mounted
= NULL
;
142 pthread_t threads
[arg_n_threads
];
145 _cleanup_free_
char *p
= NULL
, *cmd
= NULL
;
146 _cleanup_(pclosep
) FILE *sfdisk
= NULL
;
147 _cleanup_(loop_device_unrefp
) LoopDevice
*loop
= NULL
;
148 _cleanup_close_
int fd
= -EBADF
;
151 test_setup_logging(LOG_DEBUG
);
154 log_show_color(true);
157 r
= safe_atou(argv
[1], &arg_n_threads
);
159 return log_error_errno(r
, "Failed to parse first argument (number of threads): %s", argv
[1]);
160 if (arg_n_threads
<= 0)
161 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Number of threads must be at least 1, refusing.");
165 r
= safe_atou(argv
[2], &arg_n_iterations
);
167 return log_error_errno(r
, "Failed to parse second argument (number of iterations): %s", argv
[2]);
168 if (arg_n_iterations
<= 0)
169 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Number of iterations must be at least 1, refusing.");
173 r
= parse_sec(argv
[3], &arg_timeout
);
175 return log_error_errno(r
, "Failed to parse third argument (timeout): %s", argv
[3]);
179 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Too many arguments (expected 3 at max).");
181 if (!have_root_gpt_type())
182 return log_tests_skipped("No root partition GPT defined for this architecture");
184 r
= find_executable("sfdisk", NULL
);
186 return log_tests_skipped_errno(r
, "Could not find sfdisk command");
188 assert_se(tempfn_random_child("/var/tmp", "sfdisk", &p
) >= 0);
189 fd
= open(p
, O_CREAT
|O_EXCL
|O_RDWR
|O_CLOEXEC
|O_NOFOLLOW
, 0666);
191 assert_se(ftruncate(fd
, 256*1024*1024) >= 0);
193 assert_se(cmd
= strjoin("sfdisk ", p
));
194 assert_se(sfdisk
= popen(cmd
, "we"));
196 /* A reasonably complex partition table that fits on a 64K disk */
198 "size=32M, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B\n"
199 "size=32M, type=BC13C2FF-59E6-4262-A352-B275FD6F7172\n"
200 "size=32M, type=0657FD6D-A4AB-43C4-84E5-0933C84B4F4F\n"
201 "size=32M, type=", sfdisk
);
203 #ifdef SD_GPT_ROOT_NATIVE
204 fprintf(sfdisk
, SD_ID128_UUID_FORMAT_STR
, SD_ID128_FORMAT_VAL(SD_GPT_ROOT_NATIVE
));
206 fprintf(sfdisk
, SD_ID128_UUID_FORMAT_STR
, SD_ID128_FORMAT_VAL(SD_GPT_ROOT_X86_64
));
210 "size=32M, type=933AC7E1-2EB4-4F13-B844-0E14E2AEF915\n", sfdisk
);
212 assert_se(pclose(sfdisk
) == 0);
216 assert_se(dissect_image_file(p
, NULL
, NULL
, NULL
, 0, &dissected
) >= 0);
217 verify_dissected_image(dissected
);
218 dissected
= dissected_image_unref(dissected
);
221 if (geteuid() != 0 || have_effective_cap(CAP_SYS_ADMIN
) <= 0) {
222 log_tests_skipped("not running privileged");
226 if (detect_container() > 0) {
227 log_tests_skipped("Test not supported in a container, requires udev/uevent notifications");
231 assert_se(loop_device_make(fd
, O_RDWR
, 0, UINT64_MAX
, 0, LO_FLAGS_PARTSCAN
, LOCK_EX
, &loop
) >= 0);
234 assert_se(dissect_loop_device(loop
, NULL
, NULL
, NULL
, DISSECT_IMAGE_ADD_PARTITION_DEVICES
|DISSECT_IMAGE_PIN_PARTITION_DEVICES
, &dissected
) >= 0);
235 verify_dissected_image(dissected
);
237 FOREACH_STRING(fs
, "vfat", "ext4") {
241 log_tests_skipped("mkfs.{vfat|ext4} not installed");
247 assert_se(sd_id128_randomize(&id
) >= 0);
248 assert_se(make_filesystem(dissected
->partitions
[PARTITION_ESP
].node
, "vfat", "EFI", NULL
, id
, true, 0, NULL
) >= 0);
250 assert_se(sd_id128_randomize(&id
) >= 0);
251 assert_se(make_filesystem(dissected
->partitions
[PARTITION_XBOOTLDR
].node
, "vfat", "xbootldr", NULL
, id
, true, 0, NULL
) >= 0);
253 assert_se(sd_id128_randomize(&id
) >= 0);
254 assert_se(make_filesystem(dissected
->partitions
[PARTITION_ROOT
].node
, "ext4", "root", NULL
, id
, true, 0, NULL
) >= 0);
256 assert_se(sd_id128_randomize(&id
) >= 0);
257 assert_se(make_filesystem(dissected
->partitions
[PARTITION_HOME
].node
, "ext4", "home", NULL
, id
, true, 0, NULL
) >= 0);
259 dissected
= dissected_image_unref(dissected
);
261 /* We created the file systems now via the per-partition block devices. But the dissection code might
262 * probe them via the whole block device. These block devices have separate buffer caches though,
263 * hence what was written via the partition device might not appear on the whole block device
264 * yet. Let's hence explicitly flush the whole block device, so that the read-back definitely
266 assert_se(ioctl(loop
->fd
, BLKFLSBUF
, 0) >= 0);
268 /* Try to read once, without pinning or adding partitions, i.e. by only accessing the whole block
270 assert_se(dissect_loop_device(loop
, NULL
, NULL
, NULL
, 0, &dissected
) >= 0);
271 verify_dissected_image_harder(dissected
);
272 dissected
= dissected_image_unref(dissected
);
274 /* Now go via the loopback device after all, but this time add/pin, because now we want to mount it. */
275 assert_se(dissect_loop_device(loop
, NULL
, NULL
, NULL
, DISSECT_IMAGE_ADD_PARTITION_DEVICES
|DISSECT_IMAGE_PIN_PARTITION_DEVICES
, &dissected
) >= 0);
276 verify_dissected_image_harder(dissected
);
278 assert_se(mkdtemp_malloc(NULL
, &mounted
) >= 0);
280 /* We are particularly correct here, and now downgrade LOCK → LOCK_SH. That's because we are done
281 * with formatting the file systems, so we don't need the exclusive lock anymore. From now on a
282 * shared one is fine. This way udev can now probe the device if it wants, but still won't call
283 * BLKRRPART on it, and that's good, because that would destroy our partition table while we are at
285 assert_se(loop_device_flock(loop
, LOCK_SH
) >= 0);
287 /* This is a test for the loopback block device setup code and it's use by the image dissection
288 * logic: since the kernel APIs are hard use and prone to races, let's test this in a heavy duty
289 * test: we open a bunch of threads and repeatedly allocate and deallocate loopback block devices in
290 * them in parallel, with an image file with a number of partitions. */
291 assert_se(detach_mount_namespace() >= 0);
293 /* This first (writable) mount will initialize the mount point dirs, so that the subsequent read-only ones can work */
294 assert_se(dissected_image_mount(dissected
, mounted
, UID_INVALID
, UID_INVALID
, 0) >= 0);
296 /* Now we mounted everything, the partitions are pinned. Now it's fine to release the lock
297 * fully. This means udev could now issue BLKRRPART again, but that's OK given this will fail because
298 * we now mounted the device. */
299 assert_se(loop_device_flock(loop
, LOCK_UN
) >= 0);
301 assert_se(umount_recursive(mounted
, 0) >= 0);
302 loop
= loop_device_unref(loop
);
304 log_notice("Threads are being started now");
306 /* zero timeout means pick default: let's make sure we run for 10s on slow systems at max */
307 if (arg_timeout
== 0)
308 arg_timeout
= slow_tests_enabled() ? 5 * USEC_PER_SEC
: 1 * USEC_PER_SEC
;
310 end
= usec_add(now(CLOCK_MONOTONIC
), arg_timeout
);
312 if (arg_n_threads
> 1)
313 for (unsigned i
= 0; i
< arg_n_threads
; i
++)
314 assert_se(pthread_create(threads
+ i
, NULL
, thread_func
, FD_TO_PTR(fd
)) == 0);
316 log_notice("All threads started now.");
318 if (arg_n_threads
== 1)
319 assert_se(thread_func(FD_TO_PTR(fd
)) == NULL
);
321 for (unsigned i
= 0; i
< arg_n_threads
; i
++) {
322 log_notice("Joining thread #%u.", i
);
325 assert_se(pthread_join(threads
[i
], &k
) == 0);
328 log_notice("Joined thread #%u.", i
);
331 log_notice("Threads are all terminated now.");
333 log_notice("Cutting test short, since we do not have libblkid.");
338 DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run
);