1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
4 #include <linux/loop.h>
8 #include "alloc-util.h"
9 #include "dissect-image.h"
14 #include "main-func.h"
15 #include "missing_loop.h"
16 #include "mkfs-util.h"
17 #include "mount-util.h"
18 #include "namespace-util.h"
19 #include "parse-util.h"
20 #include "string-util.h"
23 #include "tmpfile-util.h"
24 #include "user-util.h"
27 static unsigned arg_n_threads
= 5;
28 static unsigned arg_n_iterations
= 3;
29 static usec_t arg_timeout
= 0;
32 static usec_t end
= 0;
34 static void* thread_func(void *ptr
) {
35 int fd
= PTR_TO_FD(ptr
);
38 for (unsigned i
= 0; i
< arg_n_iterations
; i
++) {
39 _cleanup_(loop_device_unrefp
) LoopDevice
*loop
= NULL
;
40 _cleanup_(umount_and_rmdir_and_freep
) char *mounted
= NULL
;
41 _cleanup_(dissected_image_unrefp
) DissectedImage
*dissected
= NULL
;
43 if (now(CLOCK_MONOTONIC
) >= end
) {
44 log_notice("Time's up, exiting thread's loop");
48 log_notice("> Thread iteration #%u.", i
);
50 assert_se(mkdtemp_malloc(NULL
, &mounted
) >= 0);
52 r
= loop_device_make(fd
, O_RDONLY
, 0, UINT64_MAX
, LO_FLAGS_PARTSCAN
, LOCK_SH
, &loop
);
54 log_error_errno(r
, "Failed to allocate loopback device: %m");
57 assert_se(loop
->backing_file
);
59 log_notice("Acquired loop device %s, will mount on %s", loop
->node
, mounted
);
61 r
= dissect_loop_device(loop
, NULL
, NULL
, DISSECT_IMAGE_READ_ONLY
, &dissected
);
63 log_error_errno(r
, "Failed dissect loopback device %s: %m", loop
->node
);
66 log_info("Dissected loop device %s", loop
->node
);
68 for (PartitionDesignator d
= 0; d
< _PARTITION_DESIGNATOR_MAX
; d
++) {
69 if (!dissected
->partitions
[d
].found
)
72 log_notice("Found node %s fstype %s designator %s",
73 dissected
->partitions
[d
].node
,
74 dissected
->partitions
[d
].fstype
,
75 partition_designator_to_string(d
));
78 assert_se(dissected
->partitions
[PARTITION_ESP
].found
);
79 assert_se(dissected
->partitions
[PARTITION_ESP
].node
);
80 assert_se(dissected
->partitions
[PARTITION_XBOOTLDR
].found
);
81 assert_se(dissected
->partitions
[PARTITION_XBOOTLDR
].node
);
82 assert_se(dissected
->partitions
[PARTITION_ROOT
].found
);
83 assert_se(dissected
->partitions
[PARTITION_ROOT
].node
);
84 assert_se(dissected
->partitions
[PARTITION_HOME
].found
);
85 assert_se(dissected
->partitions
[PARTITION_HOME
].node
);
87 r
= dissected_image_mount(dissected
, mounted
, UID_INVALID
, UID_INVALID
, DISSECT_IMAGE_READ_ONLY
);
88 log_notice_errno(r
, "Mounted %s → %s: %m", loop
->node
, mounted
);
91 /* Now the block device is mounted, we don't need no manual lock anymore, the devices are now
92 * pinned by the mounts. */
93 assert_se(loop_device_flock(loop
, LOCK_UN
) >= 0);
95 log_notice("Unmounting %s", mounted
);
96 mounted
= umount_and_rmdir_and_free(mounted
);
98 log_notice("Unmounted.");
100 dissected
= dissected_image_unref(dissected
);
102 log_notice("Detaching loop device %s", loop
->node
);
103 loop
= loop_device_unref(loop
);
104 log_notice("Detached loop device.");
107 log_notice("Leaving thread");
113 static bool have_root_gpt_type(void) {
114 #ifdef SD_GPT_ROOT_NATIVE
121 static int run(int argc
, char *argv
[]) {
122 _cleanup_free_
char *p
= NULL
, *cmd
= NULL
;
123 _cleanup_(pclosep
) FILE *sfdisk
= NULL
;
124 _cleanup_(loop_device_unrefp
) LoopDevice
*loop
= NULL
;
125 _cleanup_close_
int fd
= -1;
128 test_setup_logging(LOG_DEBUG
);
131 log_show_color(true);
134 r
= safe_atou(argv
[1], &arg_n_threads
);
136 return log_error_errno(r
, "Failed to parse first argument (number of threads): %s", argv
[1]);
137 if (arg_n_threads
<= 0)
138 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Number of threads must be at least 1, refusing.");
142 r
= safe_atou(argv
[2], &arg_n_iterations
);
144 return log_error_errno(r
, "Failed to parse second argument (number of iterations): %s", argv
[2]);
145 if (arg_n_iterations
<= 0)
146 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Number of iterations must be at least 1, refusing.");
150 r
= parse_sec(argv
[3], &arg_timeout
);
152 return log_error_errno(r
, "Failed to parse third argument (timeout): %s", argv
[3]);
156 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Too many arguments (expected 3 at max).");
158 if (!have_root_gpt_type()) {
159 log_tests_skipped("No root partition GPT defined for this architecture, exiting.");
160 return EXIT_TEST_SKIP
;
163 if (detect_container() > 0) {
164 log_tests_skipped("Test not supported in a container, requires udev/uevent notifications.");
165 return EXIT_TEST_SKIP
;
168 /* This is a test for the loopback block device setup code and it's use by the image dissection
169 * logic: since the kernel APIs are hard use and prone to races, let's test this in a heavy duty
170 * test: we open a bunch of threads and repeatedly allocate and deallocate loopback block devices in
171 * them in parallel, with an image file with a number of partitions. */
173 r
= detach_mount_namespace();
174 if (ERRNO_IS_PRIVILEGE(r
)) {
175 log_tests_skipped("Lacking privileges");
176 return EXIT_TEST_SKIP
;
179 FOREACH_STRING(fs
, "vfat", "ext4") {
183 log_tests_skipped("mkfs.{vfat|ext4} not installed");
184 return EXIT_TEST_SKIP
;
190 assert_se(tempfn_random_child("/var/tmp", "sfdisk", &p
) >= 0);
191 fd
= open(p
, O_CREAT
|O_EXCL
|O_RDWR
|O_CLOEXEC
|O_NOFOLLOW
, 0666);
193 assert_se(ftruncate(fd
, 256*1024*1024) >= 0);
195 assert_se(cmd
= strjoin("sfdisk ", p
));
196 assert_se(sfdisk
= popen(cmd
, "we"));
198 /* A reasonably complex partition table that fits on a 64K disk */
200 "size=32M, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B\n"
201 "size=32M, type=BC13C2FF-59E6-4262-A352-B275FD6F7172\n"
202 "size=32M, type=0657FD6D-A4AB-43C4-84E5-0933C84B4F4F\n"
203 "size=32M, type=", sfdisk
);
205 #ifdef SD_GPT_ROOT_NATIVE
206 fprintf(sfdisk
, SD_ID128_UUID_FORMAT_STR
, SD_ID128_FORMAT_VAL(SD_GPT_ROOT_NATIVE
));
208 fprintf(sfdisk
, SD_ID128_UUID_FORMAT_STR
, SD_ID128_FORMAT_VAL(SD_GPT_ROOT_X86_64
));
212 "size=32M, type=933AC7E1-2EB4-4F13-B844-0E14E2AEF915\n", sfdisk
);
214 assert_se(pclose(sfdisk
) == 0);
217 assert_se(loop_device_make(fd
, O_RDWR
, 0, UINT64_MAX
, LO_FLAGS_PARTSCAN
, LOCK_EX
, &loop
) >= 0);
220 _cleanup_(dissected_image_unrefp
) DissectedImage
*dissected
= NULL
;
221 _cleanup_(umount_and_rmdir_and_freep
) char *mounted
= NULL
;
222 pthread_t threads
[arg_n_threads
];
225 assert_se(dissect_loop_device(loop
, NULL
, NULL
, 0, &dissected
) >= 0);
227 assert_se(dissected
->partitions
[PARTITION_ESP
].found
);
228 assert_se(dissected
->partitions
[PARTITION_ESP
].node
);
229 assert_se(dissected
->partitions
[PARTITION_XBOOTLDR
].found
);
230 assert_se(dissected
->partitions
[PARTITION_XBOOTLDR
].node
);
231 assert_se(dissected
->partitions
[PARTITION_ROOT
].found
);
232 assert_se(dissected
->partitions
[PARTITION_ROOT
].node
);
233 assert_se(dissected
->partitions
[PARTITION_HOME
].found
);
234 assert_se(dissected
->partitions
[PARTITION_HOME
].node
);
236 assert_se(sd_id128_randomize(&id
) >= 0);
237 assert_se(make_filesystem(dissected
->partitions
[PARTITION_ESP
].node
, "vfat", "EFI", id
, true) >= 0);
239 assert_se(sd_id128_randomize(&id
) >= 0);
240 assert_se(make_filesystem(dissected
->partitions
[PARTITION_XBOOTLDR
].node
, "vfat", "xbootldr", id
, true) >= 0);
242 assert_se(sd_id128_randomize(&id
) >= 0);
243 assert_se(make_filesystem(dissected
->partitions
[PARTITION_ROOT
].node
, "ext4", "root", id
, true) >= 0);
245 assert_se(sd_id128_randomize(&id
) >= 0);
246 assert_se(make_filesystem(dissected
->partitions
[PARTITION_HOME
].node
, "ext4", "home", id
, true) >= 0);
248 dissected
= dissected_image_unref(dissected
);
249 assert_se(dissect_loop_device(loop
, NULL
, NULL
, 0, &dissected
) >= 0);
251 assert_se(mkdtemp_malloc(NULL
, &mounted
) >= 0);
253 /* We are particularly correct here, and now downgrade LOCK → LOCK_SH. That's because we are done
254 * with formatting the file systems, so we don't need the exclusive lock anymore. From now on a
255 * shared one is fine. This way udev can now probe the device if it wants, but still won't call
256 * BLKRRPART on it, and that's good, because that would destroy our partition table while we are at
258 assert_se(loop_device_flock(loop
, LOCK_SH
) >= 0);
260 /* This first (writable) mount will initialize the mount point dirs, so that the subsequent read-only ones can work */
261 assert_se(dissected_image_mount(dissected
, mounted
, UID_INVALID
, UID_INVALID
, 0) >= 0);
263 /* Now we mounted everything, the partitions are pinned. Now it's fine to release the lock
264 * fully. This means udev could now issue BLKRRPART again, but that's OK given this will fail because
265 * we now mounted the device. */
266 assert_se(loop_device_flock(loop
, LOCK_UN
) >= 0);
268 assert_se(umount_recursive(mounted
, 0) >= 0);
269 loop
= loop_device_unref(loop
);
271 log_notice("Threads are being started now");
273 /* zero timeout means pick default: let's make sure we run for 10s on slow systems at max */
274 if (arg_timeout
== 0)
275 arg_timeout
= slow_tests_enabled() ? 5 * USEC_PER_SEC
: 1 * USEC_PER_SEC
;
277 end
= usec_add(now(CLOCK_MONOTONIC
), arg_timeout
);
279 if (arg_n_threads
> 1)
280 for (unsigned i
= 0; i
< arg_n_threads
; i
++)
281 assert_se(pthread_create(threads
+ i
, NULL
, thread_func
, FD_TO_PTR(fd
)) == 0);
283 log_notice("All threads started now.");
285 if (arg_n_threads
== 1)
286 assert_se(thread_func(FD_TO_PTR(fd
)) == NULL
);
288 for (unsigned i
= 0; i
< arg_n_threads
; i
++) {
289 log_notice("Joining thread #%u.", i
);
292 assert_se(pthread_join(threads
[i
], &k
) == 0);
293 assert_se(k
== NULL
);
295 log_notice("Joined thread #%u.", i
);
298 log_notice("Threads are all terminated now.");
300 log_notice("Cutting test short, since we do not have libblkid.");
306 DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run
);