• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

systemd / systemd / 25084703852

28 Apr 2026 09:34PM UTC coverage: 71.849% (-0.02%) from 71.865%
25084703852

push

github

daandemeyer
ci: Reduce noise from claude-review workflow

322528 of 448894 relevant lines covered (71.85%)

1177215.84 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

27.64
/src/test/test-loop-util.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <fcntl.h>
4
#include <linux/fs.h>
5
#include <linux/loop.h>
6
#include <pthread.h>
7
#include <sys/file.h>
8
#include <sys/ioctl.h>
9
#include <unistd.h>
10

11
#include "alloc-util.h"
12
#include "argv-util.h"
13
#include "capability-util.h"
14
#include "dissect-image.h"
15
#include "fd-util.h"
16
#include "gpt.h"
17
#include "loop-util.h"
18
#include "mkfs-util.h"
19
#include "mount-util.h"
20
#include "namespace-util.h"
21
#include "parse-util.h"
22
#include "path-util.h"
23
#include "string-util.h"
24
#include "strv.h"
25
#include "tests.h"
26
#include "time-util.h"
27
#include "tmpfile-util.h"
28
#include "virt.h"
29

30
static unsigned arg_n_threads = 5;
31
static unsigned arg_n_iterations = 3;
32
static usec_t arg_timeout = 0;
33

34
#if HAVE_BLKID
35
static usec_t end = 0;
36

37
static void verify_dissected_image(DissectedImage *dissected) {
1✔
38
        ASSERT_TRUE(dissected->partitions[PARTITION_ESP].found);
1✔
39
        ASSERT_NOT_NULL(dissected->partitions[PARTITION_ESP].node);
1✔
40
        ASSERT_TRUE(dissected->partitions[PARTITION_XBOOTLDR].found);
1✔
41
        ASSERT_NOT_NULL(dissected->partitions[PARTITION_XBOOTLDR].node);
1✔
42
        ASSERT_TRUE(dissected->partitions[PARTITION_ROOT].found);
1✔
43
        ASSERT_NOT_NULL(dissected->partitions[PARTITION_ROOT].node);
1✔
44
        ASSERT_TRUE(dissected->partitions[PARTITION_HOME].found);
1✔
45
        ASSERT_NOT_NULL(dissected->partitions[PARTITION_HOME].node);
1✔
46
}
1✔
47

48
static void verify_dissected_image_harder(DissectedImage *dissected) {
×
49
        verify_dissected_image(dissected);
×
50

51
        ASSERT_STREQ(dissected->partitions[PARTITION_ESP].fstype, "vfat");
×
52
        ASSERT_STREQ(dissected->partitions[PARTITION_XBOOTLDR].fstype, "vfat");
×
53
        ASSERT_STREQ(dissected->partitions[PARTITION_ROOT].fstype, "ext4");
×
54
        ASSERT_STREQ(dissected->partitions[PARTITION_HOME].fstype, "ext4");
×
55
}
×
56

57
static void* thread_func(void *ptr) {
×
58
        int fd = PTR_TO_FD(ptr);
×
59

60
        for (unsigned i = 0; i < arg_n_iterations; i++) {
×
61
                _cleanup_(loop_device_unrefp) LoopDevice *loop = NULL;
×
62
                _cleanup_(umount_and_rmdir_and_freep) char *mounted = NULL;
×
63
                _cleanup_(dissected_image_unrefp) DissectedImage *dissected = NULL;
×
64

65
                if (now(CLOCK_MONOTONIC) >= end) {
×
66
                        log_notice("Time's up, exiting thread's loop");
×
67
                        break;
×
68
                }
69

70
                log_notice("> Thread iteration #%u.", i);
×
71

72
                ASSERT_OK(mkdtemp_malloc(NULL, &mounted));
×
73

74
                ASSERT_OK(loop_device_make(fd, O_RDONLY, 0, UINT64_MAX, 0, LO_FLAGS_PARTSCAN, LOCK_SH, &loop));
×
75
                ASSERT_NOT_NULL(loop->dev);
×
76
                ASSERT_NOT_NULL(loop->backing_file);
×
77

78
                log_notice("Acquired loop device %s, will mount on %s", loop->node, mounted);
×
79

80
                ASSERT_OK(dissect_loop_device(
×
81
                                loop,
82
                                /* verity= */ NULL,
83
                                /* mount_options= */ NULL,
84
                                /* image_policy= */ NULL,
85
                                /* image_filter= */ NULL,
86
                                DISSECT_IMAGE_READ_ONLY|DISSECT_IMAGE_ADD_PARTITION_DEVICES|DISSECT_IMAGE_PIN_PARTITION_DEVICES,
87
                                &dissected));
88

89
                log_info("Dissected loop device %s", loop->node);
×
90

91
                for (PartitionDesignator d = 0; d < _PARTITION_DESIGNATOR_MAX; d++) {
×
92
                        if (!dissected->partitions[d].found)
×
93
                                continue;
×
94

95
                        log_notice("Found node %s fstype %s designator %s",
×
96
                                   dissected->partitions[d].node,
97
                                   dissected->partitions[d].fstype,
98
                                   partition_designator_to_string(d));
99
                }
100

101
                verify_dissected_image(dissected);
×
102

103
                ASSERT_OK(dissected_image_mount(
×
104
                                dissected,
105
                                mounted,
106
                                /* uid_shift= */ UID_INVALID,
107
                                /* uid_range= */ UID_INVALID,
108
                                /* userns_fd= */ -EBADF,
109
                                DISSECT_IMAGE_READ_ONLY));
110

111
                /* Now the block device is mounted, we don't need no manual lock anymore, the devices are now
112
                 * pinned by the mounts. */
113
                ASSERT_OK(loop_device_flock(loop, LOCK_UN));
×
114

115
                log_notice("Unmounting %s", mounted);
×
116
                mounted = umount_and_rmdir_and_free(mounted);
×
117

118
                log_notice("Unmounted.");
×
119

120
                dissected = dissected_image_unref(dissected);
×
121

122
                log_notice("Detaching loop device %s", loop->node);
×
123
                loop = loop_device_unref(loop);
×
124
                log_notice("Detached loop device.");
×
125
        }
126

127
        log_notice("Leaving thread");
×
128

129
        return NULL;
×
130
}
131
#endif
132

133
static bool have_root_gpt_type(void) {
1✔
134
#ifdef SD_GPT_ROOT_NATIVE
135
        return true;
1✔
136
#else
137
        return false;
138
#endif
139
}
140

141
static int intro(void) {
1✔
142
        int r;
1✔
143

144
        log_show_tid(true);
1✔
145
        log_show_time(true);
1✔
146
        log_show_color(true);
1✔
147

148
        if (saved_argc >= 2) {
1✔
149
                r = safe_atou(saved_argv[1], &arg_n_threads);
×
150
                if (r < 0)
×
151
                        return log_error_errno(r, "Failed to parse first argument (number of threads): %s", saved_argv[1]);
×
152
                if (arg_n_threads <= 0)
×
153
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Number of threads must be at least 1, refusing.");
×
154
        }
155

156
        if (saved_argc >= 3) {
1✔
157
                r = safe_atou(saved_argv[2], &arg_n_iterations);
×
158
                if (r < 0)
×
159
                        return log_error_errno(r, "Failed to parse second argument (number of iterations): %s", saved_argv[2]);
×
160
                if (arg_n_iterations <= 0)
×
161
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Number of iterations must be at least 1, refusing.");
×
162
        }
163

164
        if (saved_argc >= 4) {
1✔
165
                r = parse_sec(saved_argv[3], &arg_timeout);
×
166
                if (r < 0)
×
167
                        return log_error_errno(r, "Failed to parse third argument (timeout): %s", saved_argv[3]);
×
168
        }
169

170
        if (saved_argc >= 5)
1✔
171
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too many arguments (expected 3 at max).");
×
172

173
        if (!have_root_gpt_type())
1✔
174
                return log_tests_skipped("No root partition GPT defined for this architecture");
×
175

176
        r = find_executable("sfdisk", NULL);
1✔
177
        if (r < 0)
1✔
178
                return log_tests_skipped_errno(r, "Could not find sfdisk command");
×
179

180
        return EXIT_SUCCESS;
181
}
182

183
TEST(loop_block) {
1✔
184
#if HAVE_BLKID
185
        _cleanup_(dissected_image_unrefp) DissectedImage *dissected = NULL;
1✔
186
        _cleanup_(umount_and_rmdir_and_freep) char *mounted = NULL;
×
187
        pthread_t threads[arg_n_threads];
1✔
188
        sd_id128_t id;
1✔
189
#endif
190
        _cleanup_free_ char *p = NULL, *cmd = NULL;
1✔
191
        _cleanup_pclose_ FILE *sfdisk = NULL;
1✔
192
        _cleanup_(loop_device_unrefp) LoopDevice *loop = NULL;
×
193
        _cleanup_close_ int fd = -EBADF;
1✔
194

195
        ASSERT_OK(tempfn_random_child("/var/tmp", "sfdisk", &p));
1✔
196
        fd = ASSERT_OK_ERRNO(open(p, O_CREAT|O_EXCL|O_RDWR|O_CLOEXEC|O_NOFOLLOW, 0666));
1✔
197
        ASSERT_OK_ERRNO(ftruncate(fd, 256*1024*1024));
1✔
198

199
        cmd = ASSERT_NOT_NULL(strjoin("sfdisk ", p));
1✔
200
        sfdisk = ASSERT_NOT_NULL(popen(cmd, "we"));
1✔
201

202
        /* A reasonably complex partition table that fits on a 64K disk */
203
        fputs("label: gpt\n"
1✔
204
              "size=32M, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B\n"
205
              "size=32M, type=BC13C2FF-59E6-4262-A352-B275FD6F7172\n"
206
              "size=32M, type=0657FD6D-A4AB-43C4-84E5-0933C84B4F4F\n"
207
              "size=32M, type=", sfdisk);
208

209
#ifdef SD_GPT_ROOT_NATIVE
210
        fprintf(sfdisk, SD_ID128_UUID_FORMAT_STR, SD_ID128_FORMAT_VAL(SD_GPT_ROOT_NATIVE));
1✔
211
#else
212
        fprintf(sfdisk, SD_ID128_UUID_FORMAT_STR, SD_ID128_FORMAT_VAL(SD_GPT_ROOT_X86_64));
213
#endif
214

215
        fputs("\n"
1✔
216
              "size=32M, type=933AC7E1-2EB4-4F13-B844-0E14E2AEF915\n", sfdisk);
217

218
        ASSERT_EQ(pclose(sfdisk), 0);
1✔
219
        sfdisk = NULL;
1✔
220

221
#if HAVE_BLKID
222
        ASSERT_OK(dissect_image_file(
1✔
223
                                  p,
224
                                  /* verity= */ NULL,
225
                                  /* mount_options= */ NULL,
226
                                  /* image_policy= */ NULL,
227
                                  /* image_filter= */ NULL,
228
                                  /* flags= */ 0,
229
                                  &dissected));
230
        verify_dissected_image(dissected);
1✔
231
        dissected = dissected_image_unref(dissected);
1✔
232
#endif
233

234
        if (have_effective_cap(CAP_SYS_ADMIN) <= 0) {
1✔
235
                log_tests_skipped("not running privileged");
×
236
                return;
×
237
        }
238

239
        if (detect_container() != 0 || running_in_chroot() != 0) {
1✔
240
                log_tests_skipped("Test not supported in a container/chroot, requires udev/uevent notifications");
1✔
241
                return;
1✔
242
        }
243

244
        ASSERT_OK(loop_device_make(fd, O_RDWR, 0, UINT64_MAX, 0, LO_FLAGS_PARTSCAN, LOCK_EX, &loop));
×
245

246
#if HAVE_BLKID
247
        ASSERT_OK(dissect_loop_device(
×
248
                                  loop,
249
                                  /* verity= */ NULL,
250
                                  /* mount_options= */ NULL,
251
                                  /* image_policy= */ NULL,
252
                                  /* image_filter= */ NULL,
253
                                  DISSECT_IMAGE_ADD_PARTITION_DEVICES|DISSECT_IMAGE_PIN_PARTITION_DEVICES,
254
                                  &dissected));
255
        verify_dissected_image(dissected);
×
256

257
        FOREACH_STRING(fs, "vfat", "ext4") {
×
258
                if (ASSERT_OK(mkfs_exists(fs)) == 0) {
×
259
                        log_tests_skipped("mkfs.{vfat|ext4} not installed");
×
260
                        return;
×
261
                }
262
        }
263

264
        ASSERT_OK(sd_id128_randomize(&id));
×
265
        ASSERT_OK(make_filesystem(dissected->partitions[PARTITION_ESP].node, "vfat", "EFI", NULL, id, MKFS_DISCARD, 0, NULL, NULL, NULL));
×
266

267
        ASSERT_OK(sd_id128_randomize(&id));
×
268
        ASSERT_OK(make_filesystem(dissected->partitions[PARTITION_XBOOTLDR].node, "vfat", "xbootldr", NULL, id, MKFS_DISCARD, 0, NULL, NULL, NULL));
×
269

270
        ASSERT_OK(sd_id128_randomize(&id));
×
271
        ASSERT_OK(make_filesystem(dissected->partitions[PARTITION_ROOT].node, "ext4", "root", NULL, id, MKFS_DISCARD, 0, NULL, NULL, NULL));
×
272

273
        ASSERT_OK(sd_id128_randomize(&id));
×
274
        ASSERT_OK(make_filesystem(dissected->partitions[PARTITION_HOME].node, "ext4", "home", NULL, id, MKFS_DISCARD, 0, NULL, NULL, NULL));
×
275

276
        dissected = dissected_image_unref(dissected);
×
277

278
        /* We created the file systems now via the per-partition block devices. But the dissection code might
279
         * probe them via the whole block device. These block devices have separate buffer caches though,
280
         * hence what was written via the partition device might not appear on the whole block device
281
         * yet. Let's hence explicitly flush the whole block device, so that the read-back definitely
282
         * works. */
283
        ASSERT_OK_ERRNO(ioctl(loop->fd, BLKFLSBUF, 0));
×
284

285
        /* Try to read once, without pinning or adding partitions, i.e. by only accessing the whole block
286
         * device. */
287
        ASSERT_OK(dissect_loop_device(
×
288
                                  loop,
289
                                  /* verity= */ NULL,
290
                                  /* mount_options= */ NULL,
291
                                  /* image_policy= */ NULL,
292
                                  /* image_filter= */ NULL,
293
                                  /* flags= */ 0,
294
                                  &dissected));
295
        verify_dissected_image_harder(dissected);
×
296
        dissected = dissected_image_unref(dissected);
×
297

298
        /* Now go via the loopback device after all, but this time add/pin, because now we want to mount it. */
299
        ASSERT_OK(dissect_loop_device(
×
300
                                  loop,
301
                                  /* verity= */ NULL,
302
                                  /* mount_options= */ NULL,
303
                                  /* image_policy= */ NULL,
304
                                  /* image_filter= */ NULL,
305
                                  DISSECT_IMAGE_ADD_PARTITION_DEVICES|DISSECT_IMAGE_PIN_PARTITION_DEVICES,
306
                                  &dissected));
307
        verify_dissected_image_harder(dissected);
×
308

309
        ASSERT_OK(mkdtemp_malloc(NULL, &mounted));
×
310

311
        /* We are particularly correct here, and now downgrade LOCK → LOCK_SH. That's because we are done
312
         * with formatting the file systems, so we don't need the exclusive lock anymore. From now on a
313
         * shared one is fine. This way udev can now probe the device if it wants, but still won't call
314
         * BLKRRPART on it, and that's good, because that would destroy our partition table while we are at
315
         * it. */
316
        ASSERT_OK(loop_device_flock(loop, LOCK_SH));
×
317

318
        /* This is a test for the loopback block device setup code and it's use by the image dissection
319
         * logic: since the kernel APIs are hard use and prone to races, let's test this in a heavy duty
320
         * test: we open a bunch of threads and repeatedly allocate and deallocate loopback block devices in
321
         * them in parallel, with an image file with a number of partitions. */
322
        ASSERT_OK(detach_mount_namespace());
×
323

324
        /* This first (writable) mount will initialize the mount point dirs, so that the subsequent read-only ones can work */
325
        ASSERT_OK(dissected_image_mount(
×
326
                                  dissected,
327
                                  mounted,
328
                                  /* uid_shift= */ UID_INVALID,
329
                                  /* uid_range= */ UID_INVALID,
330
                                  /* userns_fd= */ -EBADF,
331
                                  0));
332

333
        /* Now we mounted everything, the partitions are pinned. Now it's fine to release the lock
334
         * fully. This means udev could now issue BLKRRPART again, but that's OK given this will fail because
335
         * we now mounted the device. */
336
        ASSERT_OK(loop_device_flock(loop, LOCK_UN));
×
337

338
        ASSERT_OK(umount_recursive(mounted, 0));
×
339
        loop = loop_device_unref(loop);
×
340

341
        log_notice("Threads are being started now");
×
342

343
        /* zero timeout means pick default: let's make sure we run for 10s on slow systems at max */
344
        if (arg_timeout == 0)
×
345
                arg_timeout = slow_tests_enabled() ? 5 * USEC_PER_SEC : 1 * USEC_PER_SEC;
×
346

347
        end = usec_add(now(CLOCK_MONOTONIC), arg_timeout);
×
348

349
        if (arg_n_threads > 1)
×
350
                for (unsigned i = 0; i < arg_n_threads; i++)
×
351
                        ASSERT_EQ(pthread_create(threads + i, NULL, thread_func, FD_TO_PTR(fd)), 0);
×
352

353
        log_notice("All threads started now.");
×
354

355
        if (arg_n_threads == 1)
×
356
                ASSERT_NULL(thread_func(FD_TO_PTR(fd)));
×
357
        else
358
                for (unsigned i = 0; i < arg_n_threads; i++) {
×
359
                        log_notice("Joining thread #%u.", i);
×
360

361
                        void *k;
×
362
                        ASSERT_EQ(pthread_join(threads[i], &k), 0);
×
363
                        ASSERT_NULL(k);
×
364

365
                        log_notice("Joined thread #%u.", i);
×
366
                }
367

368
        log_notice("Threads are all terminated now.");
×
369
#else
370
        log_notice("Cutting test short, since we do not have libblkid.");
371
#endif
372
}
373

374
static int make_test_image(int *ret_fd) {
×
375
        _cleanup_free_ char *p = NULL, *cmd = NULL;
×
376
        _cleanup_pclose_ FILE *sfdisk = NULL;
×
377

378
        ASSERT_OK(tempfn_random_child("/var/tmp", "sfdisk", &p));
×
379
        int fd = ASSERT_OK_ERRNO(open(p, O_CREAT|O_EXCL|O_RDWR|O_CLOEXEC|O_NOFOLLOW, 0666));
×
380
        ASSERT_OK_ERRNO(ftruncate(fd, 256*1024*1024));
×
381

382
        cmd = ASSERT_NOT_NULL(strjoin("sfdisk ", p));
×
383
        sfdisk = ASSERT_NOT_NULL(popen(cmd, "we"));
×
384

385
        fputs("label: gpt\n"
×
386
              "size=32M, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B\n", sfdisk);
387

388
        ASSERT_EQ(pclose(sfdisk), 0);
×
389
        sfdisk = NULL;
×
390

391
        (void) unlink(p);
×
392

393
        *ret_fd = fd;
×
394
        return 0;
×
395
}
396

397
TEST(sector_size_regular_file) {
1✔
398
        _cleanup_(loop_device_unrefp) LoopDevice *loop = NULL;
×
399
        _cleanup_close_ int fd = -EBADF;
1✔
400

401
        if (have_effective_cap(CAP_SYS_ADMIN) <= 0) {
1✔
402
                log_tests_skipped("not running privileged");
×
403
                return;
×
404
        }
405

406
        if (detect_container() != 0 || running_in_chroot() != 0) {
1✔
407
                log_tests_skipped("Test not supported in a container/chroot");
1✔
408
                return;
1✔
409
        }
410

411
        ASSERT_OK(make_test_image(&fd));
×
412

413
        /* sector_size=0 on regular file: should default to 512 */
414
        ASSERT_OK(loop_device_make(fd, O_RDWR, 0, UINT64_MAX, 0, 0, LOCK_EX, &loop));
×
415
        ASSERT_EQ(loop->sector_size, 512u);
×
416
        loop = loop_device_unref(loop);
×
417

418
        /* sector_size=UINT32_MAX on regular file with GPT: should probe and find 512 */
419
        ASSERT_OK(loop_device_make(fd, O_RDWR, 0, UINT64_MAX, UINT32_MAX, 0, LOCK_EX, &loop));
×
420
        ASSERT_EQ(loop->sector_size, 512u);
×
421
        loop = loop_device_unref(loop);
×
422

423
        /* Explicit sector_size=512 on regular file */
424
        ASSERT_OK(loop_device_make(fd, O_RDWR, 0, UINT64_MAX, 512, 0, LOCK_EX, &loop));
×
425
        ASSERT_EQ(loop->sector_size, 512u);
×
426
        loop = loop_device_unref(loop);
×
427

428
        /* Explicit sector_size=4096 on regular file */
429
        ASSERT_OK(loop_device_make(fd, O_RDWR, 0, UINT64_MAX, 4096, 0, LOCK_EX, &loop));
×
430
        ASSERT_EQ(loop->sector_size, 4096u);
×
431
        loop = loop_device_unref(loop);
×
432
}
433

434
TEST(sector_size_block_device) {
1✔
435
        _cleanup_(loop_device_unrefp) LoopDevice *block_loop = NULL, *loop = NULL;
1✔
436
        _cleanup_close_ int fd = -EBADF;
1✔
437

438
        if (have_effective_cap(CAP_SYS_ADMIN) <= 0) {
1✔
439
                log_tests_skipped("not running privileged");
×
440
                return;
×
441
        }
442

443
        if (detect_container() != 0 || running_in_chroot() != 0) {
1✔
444
                log_tests_skipped("Test not supported in a container/chroot, requires udev/uevent notifications");
1✔
445
                return;
1✔
446
        }
447

448
        ASSERT_OK(make_test_image(&fd));
×
449

450
        /* Create a loop device to use as our block device */
451
        ASSERT_OK(loop_device_make(fd, O_RDWR, 0, UINT64_MAX, 0, LO_FLAGS_PARTSCAN, LOCK_EX, &block_loop));
×
452
        ASSERT_FALSE(LOOP_DEVICE_IS_FOREIGN(block_loop));
×
453
        ASSERT_OK(loop_device_flock(block_loop, LOCK_SH));
×
454

455
        uint32_t device_ssz = block_loop->sector_size;
×
456

457
        /* sector_size=0 on block device: should use device directly */
458
        ASSERT_OK(loop_device_make(block_loop->fd, O_RDWR, 0, UINT64_MAX, 0, 0, LOCK_SH, &loop));
×
459
        ASSERT_FALSE(loop->created);
×
460
        ASSERT_EQ(loop->sector_size, device_ssz);
×
461
        loop = loop_device_unref(loop);
×
462

463
        /* sector_size=UINT32_MAX on block device: should probe, match device, use directly */
464
        ASSERT_OK(loop_device_make(block_loop->fd, O_RDWR, 0, UINT64_MAX, UINT32_MAX, 0, LOCK_SH, &loop));
×
465
        ASSERT_FALSE(loop->created);
×
466
        ASSERT_EQ(loop->sector_size, device_ssz);
×
467
        loop = loop_device_unref(loop);
×
468

469
        /* sector_size=UINT32_MAX with LO_FLAGS_PARTSCAN: should probe, match, use directly */
470
        ASSERT_OK(loop_device_make(block_loop->fd, O_RDWR, 0, UINT64_MAX, UINT32_MAX, LO_FLAGS_PARTSCAN, LOCK_SH, &loop));
×
471
        ASSERT_FALSE(loop->created);
×
472
        ASSERT_EQ(loop->sector_size, device_ssz);
×
473
        loop = loop_device_unref(loop);
×
474

475
        /* Explicit sector_size matching device: should use device directly */
476
        ASSERT_OK(loop_device_make(block_loop->fd, O_RDWR, 0, UINT64_MAX, device_ssz, 0, LOCK_SH, &loop));
×
477
        ASSERT_FALSE(loop->created);
×
478
        ASSERT_EQ(loop->sector_size, device_ssz);
×
479
        loop = loop_device_unref(loop);
×
480

481
        /* Explicit sector_size=4096 (differs from device 512): should create a real loop device */
482
        if (device_ssz != 4096) {
×
483
                ASSERT_OK(loop_device_make(block_loop->fd, O_RDWR, 0, UINT64_MAX, 4096, 0, LOCK_SH, &loop));
×
484
                ASSERT_TRUE(loop->created);
×
485
                ASSERT_EQ(loop->sector_size, 4096u);
×
486
                loop = loop_device_unref(loop);
×
487
        }
488
}
489

490
TEST(sector_size_mismatch) {
1✔
491
        _cleanup_(loop_device_unrefp) LoopDevice *block_loop = NULL, *loop = NULL;
1✔
492
        _cleanup_close_ int fd = -EBADF;
1✔
493

494
        if (have_effective_cap(CAP_SYS_ADMIN) <= 0) {
1✔
495
                log_tests_skipped("not running privileged");
×
496
                return;
×
497
        }
498

499
        if (detect_container() != 0 || running_in_chroot() != 0) {
1✔
500
                log_tests_skipped("Test not supported in a container/chroot");
1✔
501
                return;
1✔
502
        }
503

504
        /* Create an image with a GPT written at 512-byte sectors, then create a loop device with
505
         * 4096-byte sectors on top. This simulates the CD-ROM scenario where the device has large
506
         * blocks but the GPT uses 512-byte sectors. */
507
        ASSERT_OK(make_test_image(&fd));
×
508

509
        /* Create a loop device with 4096-byte sector size — GPT was written at 512 */
510
        ASSERT_OK(loop_device_make(fd, O_RDWR, 0, UINT64_MAX, 4096, 0, LOCK_EX, &block_loop));
×
511
        ASSERT_TRUE(block_loop->created);
×
512
        ASSERT_EQ(block_loop->sector_size, 4096u);
×
513
        ASSERT_OK(loop_device_flock(block_loop, LOCK_SH));
×
514

515
        /* sector_size=0: no preference, should use block device directly despite GPT mismatch */
516
        ASSERT_OK(loop_device_make(block_loop->fd, O_RDWR, 0, UINT64_MAX, 0, 0, LOCK_SH, &loop));
×
517
        ASSERT_FALSE(loop->created);
×
518
        ASSERT_EQ(loop->sector_size, 4096u);
×
519
        loop = loop_device_unref(loop);
×
520

521
        /* sector_size=UINT32_MAX: should probe GPT at 512, detect mismatch with device 4096,
522
         * and create a new loop device with 512-byte sectors */
523
        ASSERT_OK(loop_device_make(block_loop->fd, O_RDWR, 0, UINT64_MAX, UINT32_MAX, 0, LOCK_SH, &loop));
×
524
        ASSERT_TRUE(loop->created);
×
525
        ASSERT_EQ(loop->sector_size, 512u);
×
526
        loop = loop_device_unref(loop);
×
527

528
        /* Explicit sector_size=512: differs from device 4096, should create a new loop device */
529
        ASSERT_OK(loop_device_make(block_loop->fd, O_RDWR, 0, UINT64_MAX, 512, 0, LOCK_SH, &loop));
×
530
        ASSERT_TRUE(loop->created);
×
531
        ASSERT_EQ(loop->sector_size, 512u);
×
532
        loop = loop_device_unref(loop);
×
533

534
        /* Explicit sector_size=4096: matches device, should use directly */
535
        ASSERT_OK(loop_device_make(block_loop->fd, O_RDWR, 0, UINT64_MAX, 4096, 0, LOCK_SH, &loop));
×
536
        ASSERT_FALSE(loop->created);
×
537
        ASSERT_EQ(loop->sector_size, 4096u);
×
538
        loop = loop_device_unref(loop);
×
539
}
540

541
TEST(partscan_required) {
1✔
542
        _cleanup_(loop_device_unrefp) LoopDevice *block_loop = NULL, *loop = NULL;
1✔
543
        _cleanup_close_ int fd = -EBADF;
1✔
544

545
        if (have_effective_cap(CAP_SYS_ADMIN) <= 0) {
1✔
546
                log_tests_skipped("not running privileged");
×
547
                return;
×
548
        }
549

550
        if (detect_container() != 0 || running_in_chroot() != 0) {
1✔
551
                log_tests_skipped("Test not supported in a container/chroot, requires udev/uevent notifications");
1✔
552
                return;
1✔
553
        }
554

555
        ASSERT_OK(make_test_image(&fd));
×
556

557
        /* Set up a backing loop device without LO_FLAGS_PARTSCAN. */
558
        ASSERT_OK(loop_device_make(fd, O_RDWR, 0, UINT64_MAX, 0, 0, LOCK_EX, &block_loop));
×
559
        ASSERT_TRUE(block_loop->created);
×
560
        ASSERT_OK(loop_device_flock(block_loop, LOCK_SH));
×
561

562
        /* Without LO_FLAGS_PARTSCAN: shortcut should be taken (reuse existing loop). */
563
        ASSERT_OK(loop_device_make(block_loop->fd, O_RDWR, 0, UINT64_MAX, 0, 0, LOCK_SH, &loop));
×
564
        ASSERT_FALSE(loop->created);
×
565
        loop = loop_device_unref(loop);
×
566

567
        /* With LO_FLAGS_PARTSCAN: backing loop has partscan disabled, so a new loop device with
568
         * partscan must be created. */
569
        ASSERT_OK(loop_device_make(block_loop->fd, O_RDWR, 0, UINT64_MAX, 0, LO_FLAGS_PARTSCAN, LOCK_SH, &loop));
×
570
        ASSERT_TRUE(loop->created);
×
571
        loop = loop_device_unref(loop);
×
572
}
573

574
DEFINE_TEST_MAIN_WITH_INTRO(LOG_DEBUG, intro);
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc