1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
4 #include <linux/loop.h>
7 #include "alloc-util.h"
8 #include "dissect-image.h"
13 #include "missing_loop.h"
14 #include "mkfs-util.h"
15 #include "mount-util.h"
16 #include "namespace-util.h"
17 #include "string-util.h"
20 #include "tmpfile-util.h"
21 #include "user-util.h"
25 #define N_ITERATIONS 3
27 static usec_t end
= 0;
29 static void* thread_func(void *ptr
) {
30 int fd
= PTR_TO_FD(ptr
);
33 for (unsigned i
= 0; i
< N_ITERATIONS
; i
++) {
34 _cleanup_(loop_device_unrefp
) LoopDevice
*loop
= NULL
;
35 _cleanup_(umount_and_rmdir_and_freep
) char *mounted
= NULL
;
36 _cleanup_(dissected_image_unrefp
) DissectedImage
*dissected
= NULL
;
38 if (now(CLOCK_MONOTONIC
) >= end
) {
39 log_notice("Time's up, exiting thread's loop");
43 log_notice("> Thread iteration #%u.", i
);
45 assert_se(mkdtemp_malloc(NULL
, &mounted
) >= 0);
47 r
= loop_device_make(fd
, O_RDONLY
, 0, UINT64_MAX
, LO_FLAGS_PARTSCAN
, &loop
);
49 log_error_errno(r
, "Failed to allocate loopback device: %m");
52 log_notice("Acquired loop device %s, will mount on %s", loop
->node
, mounted
);
54 r
= dissect_image(loop
->fd
, NULL
, NULL
, loop
->uevent_seqnum_not_before
, loop
->timestamp_not_before
, DISSECT_IMAGE_READ_ONLY
, &dissected
);
56 log_error_errno(r
, "Failed dissect loopback device %s: %m", loop
->node
);
59 log_info("Dissected loop device %s", loop
->node
);
61 for (PartitionDesignator d
= 0; d
< _PARTITION_DESIGNATOR_MAX
; d
++) {
62 if (!dissected
->partitions
[d
].found
)
65 log_notice("Found node %s fstype %s designator %s",
66 dissected
->partitions
[d
].node
,
67 dissected
->partitions
[d
].fstype
,
68 partition_designator_to_string(d
));
71 assert_se(dissected
->partitions
[PARTITION_ESP
].found
);
72 assert_se(dissected
->partitions
[PARTITION_ESP
].node
);
73 assert_se(dissected
->partitions
[PARTITION_XBOOTLDR
].found
);
74 assert_se(dissected
->partitions
[PARTITION_XBOOTLDR
].node
);
75 assert_se(dissected
->partitions
[PARTITION_ROOT
].found
);
76 assert_se(dissected
->partitions
[PARTITION_ROOT
].node
);
77 assert_se(dissected
->partitions
[PARTITION_HOME
].found
);
78 assert_se(dissected
->partitions
[PARTITION_HOME
].node
);
80 r
= dissected_image_mount(dissected
, mounted
, UID_INVALID
, DISSECT_IMAGE_READ_ONLY
);
81 log_notice_errno(r
, "Mounted %s → %s: %m", loop
->node
, mounted
);
84 log_notice("Unmounting %s", mounted
);
85 mounted
= umount_and_rmdir_and_free(mounted
);
87 log_notice("Unmounted.");
89 dissected
= dissected_image_unref(dissected
);
91 log_notice("Detaching loop device %s", loop
->node
);
92 loop
= loop_device_unref(loop
);
93 log_notice("Detached loop device.");
96 log_notice("Leaving thread");
101 static bool have_root_gpt_type(void) {
102 #ifdef GPT_ROOT_NATIVE
109 int main(int argc
, char *argv
[]) {
110 _cleanup_free_
char *p
= NULL
, *cmd
= NULL
;
111 _cleanup_(pclosep
) FILE *sfdisk
= NULL
;
112 _cleanup_(loop_device_unrefp
) LoopDevice
*loop
= NULL
;
113 _cleanup_close_
int fd
= -1;
114 _cleanup_(dissected_image_unrefp
) DissectedImage
*dissected
= NULL
;
115 _cleanup_(umount_and_rmdir_and_freep
) char *mounted
= NULL
;
116 pthread_t threads
[N_THREADS
];
121 test_setup_logging(LOG_DEBUG
);
125 if (!have_root_gpt_type()) {
126 log_tests_skipped("No root partition GPT defined for this architecture, exiting.");
127 return EXIT_TEST_SKIP
;
130 if (detect_container() > 0) {
131 log_tests_skipped("Test not supported in a container, requires udev/uevent notifications.");
132 return EXIT_TEST_SKIP
;
135 if (strstr_ptr(ci_environment(), "autopkgtest") || strstr_ptr(ci_environment(), "github-actions")) {
136 // FIXME: we should reenable this one day
137 log_tests_skipped("Skipping test on Ubuntu autopkgtest CI/GH Actions, test too slow and installed udev too flakey.");
138 return EXIT_TEST_SKIP
;
141 /* This is a test for the loopback block device setup code and it's use by the image dissection
142 * logic: since the kernel APIs are hard use and prone to races, let's test this in a heavy duty
143 * test: we open a bunch of threads and repeatedly allocate and deallocate loopback block devices in
144 * them in parallel, with an image file with a number of partitions. */
146 r
= detach_mount_namespace();
147 if (ERRNO_IS_PRIVILEGE(r
)) {
148 log_tests_skipped("Lacking privileges");
149 return EXIT_TEST_SKIP
;
152 FOREACH_STRING(fs
, "vfat", "ext4") {
156 log_tests_skipped("mkfs.{vfat|ext4} not installed");
157 return EXIT_TEST_SKIP
;
163 assert_se(tempfn_random_child("/var/tmp", "sfdisk", &p
) >= 0);
164 fd
= open(p
, O_CREAT
|O_EXCL
|O_RDWR
|O_CLOEXEC
|O_NOFOLLOW
, 0666);
166 assert_se(ftruncate(fd
, 256*1024*1024) >= 0);
168 assert_se(cmd
= strjoin("sfdisk ", p
));
169 assert_se(sfdisk
= popen(cmd
, "we"));
171 /* A reasonably complex partition table that fits on a 64K disk */
173 "size=32M, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B\n"
174 "size=32M, type=BC13C2FF-59E6-4262-A352-B275FD6F7172\n"
175 "size=32M, type=0657FD6D-A4AB-43C4-84E5-0933C84B4F4F\n"
176 "size=32M, type=", sfdisk
);
178 #ifdef GPT_ROOT_NATIVE
179 fprintf(sfdisk
, SD_ID128_UUID_FORMAT_STR
, SD_ID128_FORMAT_VAL(GPT_ROOT_NATIVE
));
181 fprintf(sfdisk
, SD_ID128_UUID_FORMAT_STR
, SD_ID128_FORMAT_VAL(GPT_ROOT_X86_64
));
185 "size=32M, type=933AC7E1-2EB4-4F13-B844-0E14E2AEF915\n", sfdisk
);
187 assert_se(pclose(sfdisk
) == 0);
190 assert_se(loop_device_make(fd
, O_RDWR
, 0, UINT64_MAX
, LO_FLAGS_PARTSCAN
, &loop
) >= 0);
191 assert_se(dissect_image(loop
->fd
, NULL
, NULL
, loop
->uevent_seqnum_not_before
, loop
->timestamp_not_before
, 0, &dissected
) >= 0);
193 assert_se(dissected
->partitions
[PARTITION_ESP
].found
);
194 assert_se(dissected
->partitions
[PARTITION_ESP
].node
);
195 assert_se(dissected
->partitions
[PARTITION_XBOOTLDR
].found
);
196 assert_se(dissected
->partitions
[PARTITION_XBOOTLDR
].node
);
197 assert_se(dissected
->partitions
[PARTITION_ROOT
].found
);
198 assert_se(dissected
->partitions
[PARTITION_ROOT
].node
);
199 assert_se(dissected
->partitions
[PARTITION_HOME
].found
);
200 assert_se(dissected
->partitions
[PARTITION_HOME
].node
);
202 assert_se(sd_id128_randomize(&id
) >= 0);
203 assert_se(make_filesystem(dissected
->partitions
[PARTITION_ESP
].node
, "vfat", "EFI", id
, true) >= 0);
205 assert_se(sd_id128_randomize(&id
) >= 0);
206 assert_se(make_filesystem(dissected
->partitions
[PARTITION_XBOOTLDR
].node
, "vfat", "xbootldr", id
, true) >= 0);
208 assert_se(sd_id128_randomize(&id
) >= 0);
209 assert_se(make_filesystem(dissected
->partitions
[PARTITION_ROOT
].node
, "ext4", "root", id
, true) >= 0);
211 assert_se(sd_id128_randomize(&id
) >= 0);
212 assert_se(make_filesystem(dissected
->partitions
[PARTITION_HOME
].node
, "ext4", "home", id
, true) >= 0);
214 dissected
= dissected_image_unref(dissected
);
215 assert_se(dissect_image(loop
->fd
, NULL
, NULL
, loop
->uevent_seqnum_not_before
, loop
->timestamp_not_before
, 0, &dissected
) >= 0);
217 assert_se(mkdtemp_malloc(NULL
, &mounted
) >= 0);
219 /* This first (writable) mount will initialize the mount point dirs, so that the subsequent read-only ones can work */
220 assert_se(dissected_image_mount(dissected
, mounted
, UID_INVALID
, 0) >= 0);
222 assert_se(umount_recursive(mounted
, 0) >= 0);
223 loop
= loop_device_unref(loop
);
225 log_notice("Threads are being started now");
227 /* Let's make sure we run for 10s on slow systems at max */
228 end
= usec_add(now(CLOCK_MONOTONIC
),
229 slow_tests_enabled() ? 5 * USEC_PER_SEC
:
232 for (unsigned i
= 0; i
< N_THREADS
; i
++)
233 assert_se(pthread_create(threads
+ i
, NULL
, thread_func
, FD_TO_PTR(fd
)) == 0);
235 log_notice("All threads started now.");
237 for (unsigned i
= 0; i
< N_THREADS
; i
++) {
238 log_notice("Joining thread #%u.", i
);
241 assert_se(pthread_join(threads
[i
], &k
) == 0);
242 assert_se(k
== NULL
);
244 log_notice("Joined thread #%u.", i
);
247 log_notice("Threads are all terminated now.");