]>
Commit | Line | Data |
---|---|---|
1 | /* | |
2 | * SPDX-License-Identifier: GPL-2.0-or-later | |
3 | * | |
4 | * This program is free software; you can redistribute it and/or modify | |
5 | * it under the terms of the GNU General Public License as published by | |
6 | * the Free Software Foundation; either version 2 of the License, or | |
7 | * (at your option) any later version. | |
8 | * | |
9 | * Original implementation from Ted Ts'o; losetup was part of mount. | |
10 | * | |
11 | * Copyright (C) 2011-2023 Karel Zak <kzak@redhat.com> | |
12 | * | |
13 | * losetup.c - setup and control loop devices | |
14 | */ | |
15 | #include <assert.h> | |
16 | #include <stdio.h> | |
17 | #include <string.h> | |
18 | #include <errno.h> | |
19 | #include <stdlib.h> | |
20 | #include <unistd.h> | |
21 | #include <sys/ioctl.h> | |
22 | #include <sys/stat.h> | |
23 | #include <inttypes.h> | |
24 | #include <getopt.h> | |
25 | ||
26 | #include <libsmartcols.h> | |
27 | ||
28 | #include "c.h" | |
29 | #include "cctype.h" | |
30 | #include "nls.h" | |
31 | #include "strutils.h" | |
32 | #include "loopdev.h" | |
33 | #include "closestream.h" | |
34 | #include "optutils.h" | |
35 | #include "xalloc.h" | |
36 | #include "canonicalize.h" | |
37 | #include "pathnames.h" | |
38 | ||
39 | enum { | |
40 | A_CREATE = 1, /* setup a new device */ | |
41 | A_DELETE, /* delete given device(s) */ | |
42 | A_DELETE_ALL, /* delete all devices */ | |
43 | A_SHOW, /* list devices */ | |
44 | A_SHOW_ONE, /* print info about one device */ | |
45 | A_FIND_FREE, /* find first unused */ | |
46 | A_SET_CAPACITY, /* set device capacity */ | |
47 | A_SET_DIRECT_IO, /* set accessing backing file by direct io */ | |
48 | A_SET_BLOCKSIZE, /* set logical block size of the loop device */ | |
49 | }; | |
50 | ||
51 | enum { | |
52 | COL_NAME = 0, | |
53 | COL_AUTOCLR, | |
54 | COL_BACK_FILE, | |
55 | COL_BACK_INO, | |
56 | COL_BACK_MAJMIN, | |
57 | COL_BACK_MAJ, | |
58 | COL_BACK_MIN, | |
59 | COL_MAJMIN, | |
60 | COL_MAJ, | |
61 | COL_MIN, | |
62 | COL_OFFSET, | |
63 | COL_PARTSCAN, | |
64 | COL_REF, | |
65 | COL_RO, | |
66 | COL_SIZELIMIT, | |
67 | COL_DIO, | |
68 | COL_LOGSEC, | |
69 | }; | |
70 | ||
71 | /* basic output flags */ | |
72 | static int no_headings; | |
73 | static int raw; | |
74 | static int json; | |
75 | ||
76 | struct colinfo { | |
77 | const char * const name; | |
78 | double whint; | |
79 | int flags; | |
80 | const char *help; | |
81 | ||
82 | int json_type; /* default is string */ | |
83 | }; | |
84 | ||
85 | static const struct colinfo infos[] = { | |
86 | [COL_AUTOCLR] = { "AUTOCLEAR", 1, SCOLS_FL_RIGHT, N_("autoclear flag set"), SCOLS_JSON_BOOLEAN}, | |
87 | [COL_BACK_FILE] = { "BACK-FILE", 0.3, SCOLS_FL_NOEXTREMES, N_("device backing file")}, | |
88 | [COL_BACK_INO] = { "BACK-INO", 4, SCOLS_FL_RIGHT, N_("backing file inode number"), SCOLS_JSON_NUMBER}, | |
89 | [COL_BACK_MAJMIN] = { "BACK-MAJ:MIN", 6, 0, N_("backing file major:minor device number")}, | |
90 | [COL_BACK_MAJ] = { "BACK-MAJ", 6, 0, N_("backing file major device number")}, | |
91 | [COL_BACK_MIN] = { "BACK-MIN", 6, 0, N_("backing file minor device number")}, | |
92 | [COL_NAME] = { "NAME", 0.25, 0, N_("loop device name")}, | |
93 | [COL_OFFSET] = { "OFFSET", 5, SCOLS_FL_RIGHT, N_("offset from the beginning"), SCOLS_JSON_NUMBER}, | |
94 | [COL_PARTSCAN] = { "PARTSCAN", 1, SCOLS_FL_RIGHT, N_("partscan flag set"), SCOLS_JSON_BOOLEAN}, | |
95 | [COL_REF] = { "REF", 0.1, 0, N_("loop device reference string")}, | |
96 | [COL_RO] = { "RO", 1, SCOLS_FL_RIGHT, N_("read-only device"), SCOLS_JSON_BOOLEAN}, | |
97 | [COL_SIZELIMIT] = { "SIZELIMIT", 5, SCOLS_FL_RIGHT, N_("size limit of the file in bytes"), SCOLS_JSON_NUMBER}, | |
98 | [COL_MAJMIN] = { "MAJ:MIN", 3, 0, N_("loop device major:minor number")}, | |
99 | [COL_MAJ] = { "MAJ", 1, SCOLS_FL_RIGHT, N_("loop device major number"), SCOLS_JSON_NUMBER}, | |
100 | [COL_MIN] = { "MIN", 1, SCOLS_FL_RIGHT, N_("loop device minor number"), SCOLS_JSON_NUMBER}, | |
101 | [COL_DIO] = { "DIO", 1, SCOLS_FL_RIGHT, N_("access backing file with direct-io"), SCOLS_JSON_BOOLEAN}, | |
102 | [COL_LOGSEC] = { "LOG-SEC", 4, SCOLS_FL_RIGHT, N_("logical sector size in bytes"), SCOLS_JSON_NUMBER}, | |
103 | }; | |
104 | ||
105 | static int columns[ARRAY_SIZE(infos) * 2] = {-1}; | |
106 | static size_t ncolumns; | |
107 | ||
108 | static int get_column_id(int num) | |
109 | { | |
110 | assert(num >= 0); | |
111 | assert((size_t) num < ncolumns); | |
112 | assert(columns[num] < (int) ARRAY_SIZE(infos)); | |
113 | return columns[num]; | |
114 | } | |
115 | ||
116 | static const struct colinfo *get_column_info(int num) | |
117 | { | |
118 | return &infos[ get_column_id(num) ]; | |
119 | } | |
120 | ||
121 | static int column_name_to_id(const char *name, size_t namesz) | |
122 | { | |
123 | size_t i; | |
124 | ||
125 | for (i = 0; i < ARRAY_SIZE(infos); i++) { | |
126 | const char *cn = infos[i].name; | |
127 | ||
128 | if (!c_strncasecmp(name, cn, namesz) && !*(cn + namesz)) | |
129 | return i; | |
130 | } | |
131 | warnx(_("unknown column: %s"), name); | |
132 | return -1; | |
133 | } | |
134 | ||
135 | static int printf_loopdev(struct loopdev_cxt *lc) | |
136 | { | |
137 | uint64_t x; | |
138 | dev_t dev = 0; | |
139 | ino_t ino = 0; | |
140 | char *fname; | |
141 | uint32_t type; | |
142 | ||
143 | fname = loopcxt_get_backing_file(lc); | |
144 | if (!fname) | |
145 | return -EINVAL; | |
146 | ||
147 | if (loopcxt_get_backing_devno(lc, &dev) == 0) | |
148 | loopcxt_get_backing_inode(lc, &ino); | |
149 | ||
150 | if (!dev && !ino) { | |
151 | /* | |
152 | * Probably non-root user (no permissions to | |
153 | * call LOOP_GET_STATUS ioctls). | |
154 | */ | |
155 | printf("%s%s: []: (%s)", | |
156 | loopcxt_get_device(lc), | |
157 | loopcxt_is_lost(lc) ? " (lost)" : "", | |
158 | fname); | |
159 | ||
160 | if (loopcxt_get_offset(lc, &x) == 0 && x) | |
161 | printf(_(", offset %ju"), x); | |
162 | if (loopcxt_get_sizelimit(lc, &x) == 0 && x) | |
163 | printf(_(", sizelimit %ju"), x); | |
164 | ||
165 | goto done; | |
166 | } | |
167 | ||
168 | printf("%s%s: [%04jd]:%ju (%s)", | |
169 | loopcxt_get_device(lc), | |
170 | loopcxt_is_lost(lc) ? " (lost)" : "", | |
171 | (intmax_t) dev, (uintmax_t) ino, fname); | |
172 | ||
173 | if (loopcxt_get_offset(lc, &x) == 0 && x) | |
174 | printf(_(", offset %ju"), x); | |
175 | if (loopcxt_get_sizelimit(lc, &x) == 0 && x) | |
176 | printf(_(", sizelimit %ju"), x); | |
177 | ||
178 | if (loopcxt_get_encrypt_type(lc, &type) == 0) { | |
179 | const char *e = loopcxt_get_crypt_name(lc); | |
180 | ||
181 | if ((!e || !*e) && type == 1) | |
182 | e = "XOR"; | |
183 | if (e && *e) | |
184 | printf(_(", encryption %s (type %u)"), e, type); | |
185 | } | |
186 | ||
187 | done: | |
188 | free(fname); | |
189 | printf("\n"); | |
190 | return 0; | |
191 | } | |
192 | ||
193 | static int show_all_loops(struct loopdev_cxt *lc, const char *file, | |
194 | uint64_t offset, int flags) | |
195 | { | |
196 | struct stat sbuf, *st = &sbuf; | |
197 | char *cn_file = NULL; | |
198 | ||
199 | if (loopcxt_init_iterator(lc, LOOPITER_FL_USED)) | |
200 | return -1; | |
201 | ||
202 | if (!file || stat(file, st)) | |
203 | st = NULL; | |
204 | ||
205 | while (loopcxt_next(lc) == 0) { | |
206 | if (file) { | |
207 | int used; | |
208 | const char *bf = cn_file ? cn_file : file; | |
209 | ||
210 | used = loopcxt_is_used(lc, st, bf, offset, 0, flags); | |
211 | if (!used && !cn_file) { | |
212 | bf = cn_file = canonicalize_path(file); | |
213 | used = loopcxt_is_used(lc, st, bf, offset, 0, flags); | |
214 | } | |
215 | if (!used) | |
216 | continue; | |
217 | } | |
218 | printf_loopdev(lc); | |
219 | } | |
220 | loopcxt_deinit_iterator(lc); | |
221 | free(cn_file); | |
222 | return 0; | |
223 | } | |
224 | ||
225 | static void warn_lost(struct loopdev_cxt *lc) | |
226 | { | |
227 | dev_t devno = loopcxt_get_devno(lc); | |
228 | ||
229 | if (devno <= 0) | |
230 | return; | |
231 | ||
232 | warnx(("device node %s (%u:%u) is lost. You may use mknod(1) to recover it."), | |
233 | loopcxt_get_device(lc), major(devno), minor(devno)); | |
234 | } | |
235 | ||
236 | static int delete_loop(struct loopdev_cxt *lc) | |
237 | { | |
238 | if (loopcxt_delete_device(lc)) { | |
239 | warn(_("%s: detach failed"), loopcxt_get_device(lc)); | |
240 | if (loopcxt_is_lost(lc)) | |
241 | warn_lost(lc); | |
242 | } else | |
243 | return 0; | |
244 | ||
245 | return -1; | |
246 | } | |
247 | ||
248 | static int delete_all_loops(struct loopdev_cxt *lc) | |
249 | { | |
250 | int res = 0; | |
251 | ||
252 | if (loopcxt_init_iterator(lc, LOOPITER_FL_USED)) | |
253 | return -1; | |
254 | ||
255 | while (loopcxt_next(lc) == 0) | |
256 | res += delete_loop(lc); | |
257 | ||
258 | loopcxt_deinit_iterator(lc); | |
259 | return res; | |
260 | } | |
261 | ||
262 | static int set_scols_data(struct loopdev_cxt *lc, struct libscols_line *ln) | |
263 | { | |
264 | size_t i; | |
265 | ||
266 | for (i = 0; i < ncolumns; i++) { | |
267 | const char *p = NULL; /* external data */ | |
268 | char *np = NULL; /* allocated here */ | |
269 | uint64_t x = 0; | |
270 | int rc = 0; | |
271 | ||
272 | switch(get_column_id(i)) { | |
273 | case COL_NAME: | |
274 | p = loopcxt_get_device(lc); | |
275 | if (loopcxt_is_lost(lc)) { | |
276 | xasprintf(&np, "%s (lost)", p); | |
277 | p = NULL; | |
278 | } | |
279 | break; | |
280 | case COL_BACK_FILE: | |
281 | np = loopcxt_get_backing_file(lc); | |
282 | break; | |
283 | case COL_OFFSET: | |
284 | if (loopcxt_get_offset(lc, &x) == 0) | |
285 | xasprintf(&np, "%jd", x); | |
286 | break; | |
287 | case COL_SIZELIMIT: | |
288 | if (loopcxt_get_sizelimit(lc, &x) == 0) | |
289 | xasprintf(&np, "%jd", x); | |
290 | break; | |
291 | case COL_BACK_MAJMIN: | |
292 | { | |
293 | dev_t dev = 0; | |
294 | if (loopcxt_get_backing_devno(lc, &dev) == 0 && dev) | |
295 | xasprintf(&np, raw || json ? "%u:%u" : "%8u:%-3u", | |
296 | major(dev), minor(dev)); | |
297 | break; | |
298 | } | |
299 | case COL_BACK_MAJ: | |
300 | { | |
301 | dev_t dev = 0; | |
302 | if (loopcxt_get_backing_devno(lc, &dev) == 0 && dev) | |
303 | xasprintf(&np, "%u", major(dev)); | |
304 | break; | |
305 | } | |
306 | case COL_BACK_MIN: | |
307 | { | |
308 | dev_t dev = 0; | |
309 | if (loopcxt_get_backing_devno(lc, &dev) == 0 && dev) | |
310 | xasprintf(&np, "%u", minor(dev)); | |
311 | break; | |
312 | } | |
313 | case COL_MAJMIN: | |
314 | { | |
315 | dev_t dev = loopcxt_get_devno(lc); | |
316 | if (dev) | |
317 | xasprintf(&np, raw || json ? "%u:%u" :"%3u:%-3u", | |
318 | major(dev), minor(dev)); | |
319 | break; | |
320 | } | |
321 | case COL_MAJ: { | |
322 | dev_t dev = loopcxt_get_devno(lc); | |
323 | if (dev) | |
324 | xasprintf(&np, "%u", major(dev)); | |
325 | break; | |
326 | } | |
327 | case COL_MIN: { | |
328 | dev_t dev = loopcxt_get_devno(lc); | |
329 | if (dev) | |
330 | xasprintf(&np, "%u", minor(dev)); | |
331 | break; | |
332 | } | |
333 | case COL_BACK_INO: | |
334 | { | |
335 | ino_t ino = 0; | |
336 | if (loopcxt_get_backing_inode(lc, &ino) == 0 && ino) | |
337 | xasprintf(&np, "%ju", ino); | |
338 | break; | |
339 | } | |
340 | case COL_AUTOCLR: | |
341 | p = loopcxt_is_autoclear(lc) ? "1" : "0"; | |
342 | break; | |
343 | case COL_RO: | |
344 | p = loopcxt_is_readonly(lc) ? "1" : "0"; | |
345 | break; | |
346 | case COL_DIO: | |
347 | p = loopcxt_is_dio(lc) ? "1" : "0"; | |
348 | break; | |
349 | case COL_PARTSCAN: | |
350 | p = loopcxt_is_partscan(lc) ? "1" : "0"; | |
351 | break; | |
352 | case COL_LOGSEC: | |
353 | if (loopcxt_get_blocksize(lc, &x) == 0) | |
354 | xasprintf(&np, "%jd", x); | |
355 | break; | |
356 | case COL_REF: | |
357 | np = loopcxt_get_refname(lc); | |
358 | break; | |
359 | default: | |
360 | return -EINVAL; | |
361 | } | |
362 | ||
363 | ||
364 | if (p) | |
365 | rc = scols_line_set_data(ln, i, p); /* calls strdup() */ | |
366 | else if (np) | |
367 | rc = scols_line_refer_data(ln, i, np); /* only refers */ | |
368 | ||
369 | if (rc) | |
370 | err(EXIT_FAILURE, _("failed to add output data")); | |
371 | } | |
372 | ||
373 | return 0; | |
374 | } | |
375 | ||
376 | static int show_table(struct loopdev_cxt *lc, | |
377 | const char *file, | |
378 | uint64_t offset, | |
379 | int flags) | |
380 | { | |
381 | struct stat sbuf, *st = &sbuf; | |
382 | struct libscols_table *tb; | |
383 | struct libscols_line *ln; | |
384 | int rc = 0; | |
385 | size_t i; | |
386 | ||
387 | scols_init_debug(0); | |
388 | ||
389 | if (!(tb = scols_new_table())) | |
390 | err(EXIT_FAILURE, _("failed to allocate output table")); | |
391 | scols_table_enable_raw(tb, raw); | |
392 | scols_table_enable_json(tb, json); | |
393 | scols_table_enable_noheadings(tb, no_headings); | |
394 | ||
395 | if (json) | |
396 | scols_table_set_name(tb, "loopdevices"); | |
397 | ||
398 | for (i = 0; i < ncolumns; i++) { | |
399 | const struct colinfo *ci = get_column_info(i); | |
400 | struct libscols_column *cl; | |
401 | ||
402 | cl = scols_table_new_column(tb, ci->name, ci->whint, ci->flags); | |
403 | if (!cl) | |
404 | err(EXIT_FAILURE, _("failed to allocate output column")); | |
405 | if (json) | |
406 | scols_column_set_json_type(cl, ci->json_type); | |
407 | } | |
408 | ||
409 | /* only one loopdev requested (already assigned to loopdev_cxt) */ | |
410 | if (loopcxt_get_device(lc)) { | |
411 | ln = scols_table_new_line(tb, NULL); | |
412 | if (!ln) | |
413 | err(EXIT_FAILURE, _("failed to allocate output line")); | |
414 | rc = set_scols_data(lc, ln); | |
415 | ||
416 | /* list all loopdevs */ | |
417 | } else { | |
418 | char *cn_file = NULL; | |
419 | ||
420 | rc = loopcxt_init_iterator(lc, LOOPITER_FL_USED); | |
421 | if (rc) | |
422 | goto done; | |
423 | if (!file || stat(file, st)) | |
424 | st = NULL; | |
425 | ||
426 | while (loopcxt_next(lc) == 0) { | |
427 | if (file) { | |
428 | int used; | |
429 | const char *bf = cn_file ? cn_file : file; | |
430 | ||
431 | used = loopcxt_is_used(lc, st, bf, offset, 0, flags); | |
432 | if (!used && !cn_file) { | |
433 | bf = cn_file = canonicalize_path(file); | |
434 | used = loopcxt_is_used(lc, st, bf, offset, 0, flags); | |
435 | } | |
436 | if (!used) | |
437 | continue; | |
438 | } | |
439 | ||
440 | ln = scols_table_new_line(tb, NULL); | |
441 | if (!ln) | |
442 | err(EXIT_FAILURE, _("failed to allocate output line")); | |
443 | rc = set_scols_data(lc, ln); | |
444 | if (rc) | |
445 | break; | |
446 | } | |
447 | ||
448 | loopcxt_deinit_iterator(lc); | |
449 | free(cn_file); | |
450 | } | |
451 | done: | |
452 | if (rc == 0) | |
453 | rc = scols_print_table(tb); | |
454 | scols_unref_table(tb); | |
455 | return rc; | |
456 | } | |
457 | ||
458 | static void __attribute__((__noreturn__)) usage(void) | |
459 | { | |
460 | FILE *out = stdout; | |
461 | size_t i; | |
462 | ||
463 | fputs(USAGE_HEADER, out); | |
464 | ||
465 | fprintf(out, | |
466 | _(" %1$s [options] [<loopdev>]\n" | |
467 | " %1$s [options] -f | <loopdev> <file>\n"), | |
468 | program_invocation_short_name); | |
469 | ||
470 | fputs(USAGE_SEPARATOR, out); | |
471 | fputs(_("Set up and control loop devices.\n"), out); | |
472 | ||
473 | /* commands */ | |
474 | fputs(USAGE_OPTIONS, out); | |
475 | fputs(_(" -a, --all list all used devices\n"), out); | |
476 | fputs(_(" -d, --detach <loopdev>... detach one or more devices\n"), out); | |
477 | fputs(_(" -D, --detach-all detach all used devices\n"), out); | |
478 | fputs(_(" -f, --find find first unused device\n"), out); | |
479 | fputs(_(" -c, --set-capacity <loopdev> resize the device\n"), out); | |
480 | fputs(_(" -j, --associated <file> list all devices associated with <file>\n"), out); | |
481 | fputs(_(" -L, --nooverlap avoid possible conflict between devices\n"), out); | |
482 | ||
483 | /* commands options */ | |
484 | fputs(USAGE_SEPARATOR, out); | |
485 | fputs(_(" -o, --offset <num> start at offset <num> into file\n"), out); | |
486 | fputs(_(" --sizelimit <num> device is limited to <num> bytes of the file\n"), out); | |
487 | fputs(_(" -b, --sector-size <num> set the logical sector size to <num>\n"), out); | |
488 | fputs(_(" -P, --partscan create a partitioned loop device\n"), out); | |
489 | fputs(_(" -r, --read-only set up a read-only loop device\n"), out); | |
490 | fputs(_(" --direct-io[=<on|off>] open backing file with O_DIRECT\n"), out); | |
491 | fputs(_(" --loop-ref <string> loop device reference\n"), out); | |
492 | fputs(_(" --show print device name after setup (with -f)\n"), out); | |
493 | fputs(_(" -v, --verbose verbose mode\n"), out); | |
494 | ||
495 | /* output options */ | |
496 | fputs(USAGE_SEPARATOR, out); | |
497 | fputs(_(" -J, --json use JSON --list output format\n"), out); | |
498 | fputs(_(" -l, --list list info about all or specified (default)\n"), out); | |
499 | fputs(_(" -n, --noheadings don't print headings for --list output\n"), out); | |
500 | fputs(_(" -O, --output <cols> specify columns to output for --list\n"), out); | |
501 | fputs(_(" --output-all output all columns\n"), out); | |
502 | fputs(_(" --raw use raw --list output format\n"), out); | |
503 | ||
504 | fputs(USAGE_SEPARATOR, out); | |
505 | fprintf(out, USAGE_HELP_OPTIONS(31)); | |
506 | ||
507 | fputs(USAGE_COLUMNS, out); | |
508 | for (i = 0; i < ARRAY_SIZE(infos); i++) | |
509 | fprintf(out, " %12s %s\n", infos[i].name, _(infos[i].help)); | |
510 | ||
511 | fprintf(out, USAGE_MAN_TAIL("losetup(8)")); | |
512 | ||
513 | exit(EXIT_SUCCESS); | |
514 | } | |
515 | ||
516 | static void warn_size(const char *filename, uint64_t size, uint64_t offset, int flags) | |
517 | { | |
518 | struct stat st; | |
519 | ||
520 | if (!size) { | |
521 | if (stat(filename, &st) || S_ISBLK(st.st_mode)) | |
522 | return; | |
523 | size = st.st_size; | |
524 | ||
525 | if (flags & LOOPDEV_FL_OFFSET) | |
526 | size -= offset; | |
527 | } | |
528 | ||
529 | if (size < 512) | |
530 | warnx(_("%s: Warning: file is smaller than 512 bytes; the loop device " | |
531 | "may be useless or invisible for system tools."), | |
532 | filename); | |
533 | else if (size % 512) | |
534 | warnx(_("%s: Warning: file does not end on a 512-byte sector boundary; " | |
535 | "the remaining end of the file will be ignored."), | |
536 | filename); | |
537 | } | |
538 | ||
539 | static int find_unused(struct loopdev_cxt *lc) | |
540 | { | |
541 | int rc; | |
542 | ||
543 | rc = loopcxt_find_unused(lc); | |
544 | if (!rc) | |
545 | return 0; | |
546 | ||
547 | if (access(_PATH_DEV_LOOPCTL, F_OK) == 0 && | |
548 | access(_PATH_DEV_LOOPCTL, W_OK) != 0) | |
549 | ; | |
550 | else | |
551 | errno = -rc; | |
552 | ||
553 | warn(_("cannot find an unused loop device")); | |
554 | ||
555 | return rc; | |
556 | } | |
557 | ||
558 | static int create_loop(struct loopdev_cxt *lc, | |
559 | int nooverlap, int lo_flags, int flags, | |
560 | const char *file, const char *refname, | |
561 | uint64_t offset, uint64_t sizelimit, | |
562 | uint64_t blocksize) | |
563 | { | |
564 | int hasdev = loopcxt_has_device(lc); | |
565 | int rc = 0, ntries = 0; | |
566 | ||
567 | /* losetup --find --noverlap file.img */ | |
568 | if (!hasdev && nooverlap) { | |
569 | rc = loopcxt_find_overlap(lc, file, offset, sizelimit); | |
570 | switch (rc) { | |
571 | case 0: /* not found */ | |
572 | break; | |
573 | ||
574 | case 1: /* overlap */ | |
575 | loopcxt_deinit(lc); | |
576 | errx(EXIT_FAILURE, _("%s: overlapping loop device exists"), file); | |
577 | ||
578 | case 2: /* overlap -- full size and offset match (reuse) */ | |
579 | { | |
580 | uint32_t lc_encrypt_type; | |
581 | ||
582 | /* Once a loop is initialized RO, there is no | |
583 | * way to change its parameters. */ | |
584 | if (loopcxt_is_readonly(lc) | |
585 | && !(lo_flags & LO_FLAGS_READ_ONLY)) { | |
586 | loopcxt_deinit(lc); | |
587 | errx(EXIT_FAILURE, _("%s: overlapping read-only loop device exists"), file); | |
588 | } | |
589 | ||
590 | /* This is no more supported, but check to be safe. */ | |
591 | if (loopcxt_get_encrypt_type(lc, &lc_encrypt_type) == 0 | |
592 | && lc_encrypt_type != LO_CRYPT_NONE) { | |
593 | loopcxt_deinit(lc); | |
594 | errx(EXIT_FAILURE, _("%s: overlapping encrypted loop device exists"), file); | |
595 | } | |
596 | ||
597 | lc->config.info.lo_flags &= ~LO_FLAGS_AUTOCLEAR; | |
598 | if (loopcxt_ioctl_status(lc)) { | |
599 | loopcxt_deinit(lc); | |
600 | errx(EXIT_FAILURE, _("%s: failed to re-use loop device"), file); | |
601 | } | |
602 | return 0; /* success, re-use */ | |
603 | } | |
604 | default: /* error */ | |
605 | loopcxt_deinit(lc); | |
606 | errx(EXIT_FAILURE, _("failed to inspect loop devices")); | |
607 | return -errno; | |
608 | } | |
609 | } | |
610 | ||
611 | if (hasdev) | |
612 | loopcxt_add_device(lc); | |
613 | ||
614 | /* losetup --noverlap /dev/loopN file.img */ | |
615 | if (hasdev && nooverlap) { | |
616 | struct loopdev_cxt lc2; | |
617 | ||
618 | if (loopcxt_init(&lc2, 0)) { | |
619 | loopcxt_deinit(lc); | |
620 | err(EXIT_FAILURE, _("failed to initialize loopcxt")); | |
621 | } | |
622 | rc = loopcxt_find_overlap(&lc2, file, offset, sizelimit); | |
623 | loopcxt_deinit(&lc2); | |
624 | ||
625 | if (rc) { | |
626 | loopcxt_deinit(lc); | |
627 | if (rc > 0) | |
628 | errx(EXIT_FAILURE, _("%s: overlapping loop device exists"), file); | |
629 | err(EXIT_FAILURE, _("%s: failed to check for conflicting loop devices"), file); | |
630 | } | |
631 | } | |
632 | ||
633 | /* Create a new device */ | |
634 | do { | |
635 | const char *errpre; | |
636 | ||
637 | /* Note that loopcxt_{find_unused,set_device}() resets | |
638 | * loopcxt struct. | |
639 | */ | |
640 | if (!hasdev && (rc = find_unused(lc))) | |
641 | break; | |
642 | if (flags & LOOPDEV_FL_OFFSET) | |
643 | loopcxt_set_offset(lc, offset); | |
644 | if (flags & LOOPDEV_FL_SIZELIMIT) | |
645 | loopcxt_set_sizelimit(lc, sizelimit); | |
646 | if (lo_flags) | |
647 | loopcxt_set_flags(lc, lo_flags); | |
648 | if (blocksize > 0) | |
649 | loopcxt_set_blocksize(lc, blocksize); | |
650 | if (refname && (rc = loopcxt_set_refname(lc, refname))) { | |
651 | warnx(_("cannot set loop reference string")); | |
652 | break; | |
653 | } | |
654 | if ((rc = loopcxt_set_backing_file(lc, file))) { | |
655 | warn(_("%s: failed to use backing file"), file); | |
656 | break; | |
657 | } | |
658 | errno = 0; | |
659 | rc = loopcxt_setup_device(lc); | |
660 | if (rc == 0) | |
661 | break; /* success */ | |
662 | ||
663 | if ((errno == EBUSY || errno == EAGAIN) && !hasdev && ntries < 64) { | |
664 | xusleep(200000); | |
665 | ntries++; | |
666 | continue; | |
667 | } | |
668 | ||
669 | /* errors */ | |
670 | errpre = hasdev && lc->fd < 0 ? | |
671 | loopcxt_get_device(lc) : file; | |
672 | warn(_("%s: failed to set up loop device"), errpre); | |
673 | break; | |
674 | } while (hasdev == 0); | |
675 | ||
676 | return rc; | |
677 | } | |
678 | ||
679 | int main(int argc, char **argv) | |
680 | { | |
681 | struct loopdev_cxt lc; | |
682 | int act = 0, flags = 0, no_overlap = 0, c; | |
683 | char *file = NULL, *refname = NULL; | |
684 | uint64_t offset = 0, sizelimit = 0, blocksize = 0; | |
685 | int res = 0, showdev = 0, lo_flags = 0; | |
686 | char *outarg = NULL; | |
687 | int list = 0; | |
688 | unsigned long use_dio = 0, set_dio = 0, set_blocksize = 0; | |
689 | ||
690 | enum { | |
691 | OPT_SIZELIMIT = CHAR_MAX + 1, | |
692 | OPT_SHOW, | |
693 | OPT_RAW, | |
694 | OPT_REF, | |
695 | OPT_DIO, | |
696 | OPT_OUTPUT_ALL | |
697 | }; | |
698 | static const struct option longopts[] = { | |
699 | { "all", no_argument, NULL, 'a' }, | |
700 | { "set-capacity", required_argument, NULL, 'c' }, | |
701 | { "detach", required_argument, NULL, 'd' }, | |
702 | { "detach-all", no_argument, NULL, 'D' }, | |
703 | { "find", no_argument, NULL, 'f' }, | |
704 | { "nooverlap", no_argument, NULL, 'L' }, | |
705 | { "help", no_argument, NULL, 'h' }, | |
706 | { "associated", required_argument, NULL, 'j' }, | |
707 | { "json", no_argument, NULL, 'J' }, | |
708 | { "list", no_argument, NULL, 'l' }, | |
709 | { "sector-size", required_argument, NULL, 'b' }, | |
710 | { "noheadings", no_argument, NULL, 'n' }, | |
711 | { "offset", required_argument, NULL, 'o' }, | |
712 | { "output", required_argument, NULL, 'O' }, | |
713 | { "output-all", no_argument, NULL, OPT_OUTPUT_ALL }, | |
714 | { "sizelimit", required_argument, NULL, OPT_SIZELIMIT }, | |
715 | { "partscan", no_argument, NULL, 'P' }, | |
716 | { "read-only", no_argument, NULL, 'r' }, | |
717 | { "direct-io", optional_argument, NULL, OPT_DIO }, | |
718 | { "raw", no_argument, NULL, OPT_RAW }, | |
719 | { "loop-ref", required_argument, NULL, OPT_REF, }, | |
720 | { "show", no_argument, NULL, OPT_SHOW }, | |
721 | { "verbose", no_argument, NULL, 'v' }, | |
722 | { "version", no_argument, NULL, 'V' }, | |
723 | { NULL, 0, NULL, 0 } | |
724 | }; | |
725 | ||
726 | static const ul_excl_t excl[] = { /* rows and cols in ASCII order */ | |
727 | { 'D','a','c','d','f','j' }, | |
728 | { 'D','c','d','f','l' }, | |
729 | { 'D','c','d','f','O' }, | |
730 | { 'J',OPT_RAW }, | |
731 | { 0 } | |
732 | }; | |
733 | int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; | |
734 | ||
735 | setlocale(LC_ALL, ""); | |
736 | bindtextdomain(PACKAGE, LOCALEDIR); | |
737 | textdomain(PACKAGE); | |
738 | close_stdout_atexit(); | |
739 | ||
740 | if (loopcxt_init(&lc, 0)) | |
741 | err(EXIT_FAILURE, _("failed to initialize loopcxt")); | |
742 | ||
743 | while ((c = getopt_long(argc, argv, "ab:c:d:Dfhj:JlLno:O:PrvV", | |
744 | longopts, NULL)) != -1) { | |
745 | ||
746 | err_exclusive_options(c, longopts, excl, excl_st); | |
747 | ||
748 | switch (c) { | |
749 | case 'a': | |
750 | act = A_SHOW; | |
751 | break; | |
752 | case 'b': | |
753 | set_blocksize = 1; | |
754 | blocksize = strtosize_or_err(optarg, _("failed to parse logical block size")); | |
755 | break; | |
756 | case 'c': | |
757 | act = A_SET_CAPACITY; | |
758 | if (loopcxt_set_device(&lc, optarg)) | |
759 | err(EXIT_FAILURE, _("%s: failed to use device"), | |
760 | optarg); | |
761 | break; | |
762 | case 'r': | |
763 | lo_flags |= LO_FLAGS_READ_ONLY; | |
764 | break; | |
765 | case OPT_REF: | |
766 | refname = optarg; | |
767 | break; | |
768 | case 'd': | |
769 | act = A_DELETE; | |
770 | if (loopcxt_set_device(&lc, optarg)) | |
771 | err(EXIT_FAILURE, _("%s: failed to use device"), | |
772 | optarg); | |
773 | break; | |
774 | case 'D': | |
775 | act = A_DELETE_ALL; | |
776 | break; | |
777 | case 'f': | |
778 | act = A_FIND_FREE; | |
779 | break; | |
780 | case 'J': | |
781 | json = 1; | |
782 | break; | |
783 | case 'j': | |
784 | act = A_SHOW; | |
785 | file = optarg; | |
786 | break; | |
787 | case 'l': | |
788 | list = 1; | |
789 | break; | |
790 | case 'L': | |
791 | no_overlap = 1; | |
792 | break; | |
793 | case 'n': | |
794 | no_headings = 1; | |
795 | break; | |
796 | case OPT_RAW: | |
797 | raw = 1; | |
798 | break; | |
799 | case 'o': | |
800 | offset = strtosize_or_err(optarg, _("failed to parse offset")); | |
801 | flags |= LOOPDEV_FL_OFFSET; | |
802 | break; | |
803 | case 'O': | |
804 | outarg = optarg; | |
805 | list = 1; | |
806 | break; | |
807 | case OPT_OUTPUT_ALL: | |
808 | list = 1; | |
809 | for (ncolumns = 0; ncolumns < ARRAY_SIZE(infos); ncolumns++) | |
810 | columns[ncolumns] = ncolumns; | |
811 | break; | |
812 | case 'P': | |
813 | lo_flags |= LO_FLAGS_PARTSCAN; | |
814 | break; | |
815 | case OPT_SHOW: | |
816 | showdev = 1; | |
817 | break; | |
818 | case OPT_DIO: | |
819 | use_dio = set_dio = 1; | |
820 | if (optarg) | |
821 | use_dio = ul_parse_switch(optarg, "on", "off", NULL); | |
822 | if (use_dio) | |
823 | lo_flags |= LO_FLAGS_DIRECT_IO; | |
824 | break; | |
825 | case 'v': | |
826 | break; | |
827 | case OPT_SIZELIMIT: /* --sizelimit */ | |
828 | sizelimit = strtosize_or_err(optarg, _("failed to parse size")); | |
829 | flags |= LOOPDEV_FL_SIZELIMIT; | |
830 | break; | |
831 | ||
832 | case 'h': | |
833 | usage(); | |
834 | case 'V': | |
835 | print_version(EXIT_SUCCESS); | |
836 | default: | |
837 | errtryhelp(EXIT_FAILURE); | |
838 | } | |
839 | } | |
840 | ||
841 | ul_path_init_debug(); | |
842 | ul_sysfs_init_debug(); | |
843 | ||
844 | /* default is --list --all */ | |
845 | if (argc == 1) { | |
846 | act = A_SHOW; | |
847 | list = 1; | |
848 | } | |
849 | ||
850 | if (!act && argc == 2 && (raw || json)) { | |
851 | act = A_SHOW; | |
852 | list = 1; | |
853 | } | |
854 | ||
855 | /* default --list output columns */ | |
856 | if (list && !ncolumns) { | |
857 | columns[ncolumns++] = COL_NAME; | |
858 | columns[ncolumns++] = COL_SIZELIMIT; | |
859 | columns[ncolumns++] = COL_OFFSET; | |
860 | columns[ncolumns++] = COL_AUTOCLR; | |
861 | columns[ncolumns++] = COL_RO; | |
862 | columns[ncolumns++] = COL_BACK_FILE; | |
863 | columns[ncolumns++] = COL_DIO; | |
864 | columns[ncolumns++] = COL_LOGSEC; | |
865 | } | |
866 | ||
867 | if (act == A_FIND_FREE && optind < argc) { | |
868 | /* | |
869 | * losetup -f <backing_file> | |
870 | */ | |
871 | act = A_CREATE; | |
872 | file = argv[optind++]; | |
873 | ||
874 | if (optind < argc) | |
875 | errx(EXIT_FAILURE, _("unexpected arguments")); | |
876 | } | |
877 | ||
878 | if (list && !act && optind == argc) | |
879 | /* | |
880 | * losetup --list defaults to --all | |
881 | */ | |
882 | act = A_SHOW; | |
883 | ||
884 | if (!act && optind + 1 == argc) { | |
885 | /* | |
886 | * losetup [--list] <device> | |
887 | * OR | |
888 | * losetup {--direct-io[=off]|--logical-blocksize=size}... <device> | |
889 | */ | |
890 | if (set_dio) { | |
891 | act = A_SET_DIRECT_IO; | |
892 | lo_flags &= ~LO_FLAGS_DIRECT_IO; | |
893 | } else if (set_blocksize) | |
894 | act = A_SET_BLOCKSIZE; | |
895 | else | |
896 | act = A_SHOW_ONE; | |
897 | ||
898 | if (loopcxt_set_device(&lc, argv[optind])) | |
899 | err(EXIT_FAILURE, _("%s: failed to use device"), | |
900 | argv[optind]); | |
901 | optind++; | |
902 | } | |
903 | if (!act) { | |
904 | /* | |
905 | * losetup <loopdev> <backing_file> | |
906 | */ | |
907 | act = A_CREATE; | |
908 | ||
909 | if (optind >= argc) | |
910 | errx(EXIT_FAILURE, _("no loop device specified")); | |
911 | /* don't use is_loopdev() here, the device does not have exist yet */ | |
912 | if (loopcxt_set_device(&lc, argv[optind])) | |
913 | err(EXIT_FAILURE, _("%s: failed to use device"), | |
914 | argv[optind]); | |
915 | optind++; | |
916 | ||
917 | if (optind >= argc) | |
918 | errx(EXIT_FAILURE, _("no file specified")); | |
919 | file = argv[optind++]; | |
920 | } | |
921 | ||
922 | if (act != A_CREATE && | |
923 | (sizelimit || lo_flags || showdev)) | |
924 | errx(EXIT_FAILURE, | |
925 | _("the options %s are allowed during loop device setup only"), | |
926 | "--{sizelimit,partscan,read-only,show}"); | |
927 | ||
928 | if ((flags & LOOPDEV_FL_OFFSET) && | |
929 | act != A_CREATE && (act != A_SHOW || !file)) | |
930 | errx(EXIT_FAILURE, _("the option --offset is not allowed in this context")); | |
931 | ||
932 | if (outarg && string_add_to_idarray(outarg, columns, ARRAY_SIZE(columns), | |
933 | &ncolumns, column_name_to_id) < 0) | |
934 | return EXIT_FAILURE; | |
935 | ||
936 | switch (act) { | |
937 | case A_CREATE: | |
938 | res = create_loop(&lc, no_overlap, lo_flags, flags, file, refname, | |
939 | offset, sizelimit, blocksize); | |
940 | if (res == 0) { | |
941 | if (showdev) | |
942 | printf("%s\n", loopcxt_get_device(&lc)); | |
943 | warn_size(file, sizelimit, offset, flags); | |
944 | } | |
945 | break; | |
946 | case A_DELETE: | |
947 | res = delete_loop(&lc); | |
948 | while (optind < argc) { | |
949 | if (loopcxt_set_device(&lc, argv[optind])) | |
950 | warn(_("%s: failed to use device"), | |
951 | argv[optind]); | |
952 | optind++; | |
953 | res += delete_loop(&lc); | |
954 | } | |
955 | break; | |
956 | case A_DELETE_ALL: | |
957 | res = delete_all_loops(&lc); | |
958 | break; | |
959 | case A_FIND_FREE: | |
960 | res = find_unused(&lc); | |
961 | if (!res) | |
962 | printf("%s%s\n", loopcxt_get_device(&lc), | |
963 | loopcxt_is_lost(&lc) ? " (lost)" : ""); | |
964 | break; | |
965 | case A_SHOW: | |
966 | if (list) | |
967 | res = show_table(&lc, file, offset, flags); | |
968 | else | |
969 | res = show_all_loops(&lc, file, offset, flags); | |
970 | break; | |
971 | case A_SHOW_ONE: | |
972 | if (list) | |
973 | res = show_table(&lc, NULL, 0, 0); | |
974 | else | |
975 | res = printf_loopdev(&lc); | |
976 | if (res) | |
977 | warn("%s", loopcxt_get_device(&lc)); | |
978 | break; | |
979 | case A_SET_CAPACITY: | |
980 | res = loopcxt_ioctl_capacity(&lc); | |
981 | if (res) | |
982 | warn(_("%s: set capacity failed"), | |
983 | loopcxt_get_device(&lc)); | |
984 | break; | |
985 | case A_SET_DIRECT_IO: | |
986 | res = loopcxt_ioctl_dio(&lc, use_dio); | |
987 | if (res) | |
988 | warn(_("%s: set direct io failed"), | |
989 | loopcxt_get_device(&lc)); | |
990 | break; | |
991 | case A_SET_BLOCKSIZE: | |
992 | res = loopcxt_ioctl_blocksize(&lc, blocksize); | |
993 | if (res) | |
994 | warn(_("%s: set logical block size failed"), | |
995 | loopcxt_get_device(&lc)); | |
996 | break; | |
997 | default: | |
998 | warnx(_("bad usage")); | |
999 | errtryhelp(EXIT_FAILURE); | |
1000 | break; | |
1001 | } | |
1002 | ||
1003 | if (res && (act == A_SET_CAPACITY | |
1004 | || act == A_CREATE | |
1005 | || act == A_SET_DIRECT_IO | |
1006 | || act == A_SET_BLOCKSIZE) | |
1007 | && loopcxt_is_lost(&lc)) | |
1008 | warn_lost(&lc); | |
1009 | ||
1010 | loopcxt_deinit(&lc); | |
1011 | return res ? EXIT_FAILURE : EXIT_SUCCESS; | |
1012 | } |