2
* Given a block device and a partition table type,
3
* try to parse the partition table, and list the
4
* contents. Optionally add or remove partitions.
2
* partx: tell the kernel about your disk's partitions
6
3
* [This is not an fdisk - adding and removing partitions
7
4
* is not a change of the disk, but just telling the kernel
8
5
* about presence and numbering of on-disk partitions.]
11
* partx [-{l|a|d}] [--type TYPE] [--nr M-N] [partition] wholedisk
12
* where TYPE is {dos|bsd|solaris|unixware|gpt}.
14
* Read wholedisk and add all partitions:
17
* Subdivide a partition into slices (and delete or shrink the partition):
18
* [Not easy: one needs the partition number of partition -
19
* that is the last 4 or 6 bits of the minor; it can also be found
20
* in /proc/partitions; but there is no good direct way.]
21
* partx -a partition wholedisk
23
* Delete all partitions from wholedisk:
26
* Delete partitions M-N from wholedisk:
27
* partx -d --nr M-N wholedisk
29
7
* aeb, 2000-03-21 -- sah is 42 now
9
* Copyright (C) 2010 Davidlohr Bueso <dave@gnu.org>
10
* Rewritten to use libblkid for util-linux
11
* based on ideas from Karel Zak <kzak@redhat.com>
35
18
#include <stdlib.h>
36
19
#include <string.h>
37
21
#include <getopt.h>
22
#include <sys/types.h>
38
24
#include <unistd.h>
39
26
#include <sys/ioctl.h>
40
#include <linux/hdreg.h> /* HDIO_GETGEO */
41
#ifdef HAVE_LINUX_COMPILER_H
42
#include <linux/compiler.h>
44
27
#include <linux/blkpg.h>
33
#include "pathnames.h"
47
36
#include "blkdev.h"
51
static void errmerge(int err, int m, char *msg1, char *msg2);
56
struct slice slices[MAXSLICES];
58
enum action { LIST, ADD, DELETE };
67
addpts(char *t, ptreader f)
69
if (ptct >= MAXTYPES) {
70
fprintf(stderr, "addpts: too many types\n");
81
addpts("gpt", read_gpt_pt);
82
addpts("dos", read_dos_pt);
83
addpts("bsd", read_bsd_pt);
84
addpts("solaris", read_solaris_pt);
85
addpts("unixware", read_unixware_pt);
88
static char short_opts[] = "ladgvn:t:";
89
static const struct option long_opts[] = {
90
{ "gpt", no_argument, NULL, 'g' },
91
{ "type", required_argument, NULL, 't' },
92
{ "nr", required_argument, NULL, 'n' },
100
main(int argc, char **argv){
101
int fd, fd2, c, i, j, k, n;
102
unsigned long long size;
103
struct hd_geometry g;
105
struct blkpg_ioctl_arg a;
106
struct blkpg_partition pt;
108
enum action what = LIST;
109
char *p, *type, *diskdevice, *device;
118
type = device = diskdevice = NULL;
120
while ((c = getopt_long (argc, argv, short_opts, long_opts, NULL))
127
what = DELETE; break;
129
force_gpt = 1; break;
147
fprintf(stderr, "unknown option\n");
151
if (optind == argc-2) {
152
device = argv[optind];
153
diskdevice = argv[optind+1];
154
} else if (optind == argc-1) {
155
diskdevice = device = argv[optind];
157
fprintf(stderr, "call: partx -opts [device] wholedisk\n");
161
fd = open(diskdevice, O_RDONLY);
167
/* remove the indicated partitions from the kernel partition tables */
168
if (what == DELETE) {
169
if (device != diskdevice) {
171
"call: partx -d [--nr M-N] wholedisk\n");
41
/* this is the default upper limit, could be modified by --nr */
42
#define SLICES_MAX 256
44
/* all the columns (-o option) */
72
const char *name; /* header */
73
double whint; /* width hint (N < 1 is in percent of termwidth) */
74
int flags; /* TT_FL_* */
78
/* columns descriptions */
79
struct colinfo infos[__NCOLUMNS] = {
80
[COL_PARTNO] = { "NR", 0.25, TT_FL_RIGHT, N_("partition number") },
81
[COL_START] = { "START", 0.30, TT_FL_RIGHT, N_("start of the partition in sectors") },
82
[COL_END] = { "END", 0.30, TT_FL_RIGHT, N_("end of the partition in sectors") },
83
[COL_SECTORS] = { "SECTORS", 0.30, TT_FL_RIGHT, N_("number of sectors") },
84
[COL_SIZE] = { "SIZE", 0.30, TT_FL_RIGHT, N_("human readable size") },
85
[COL_NAME] = { "NAME", 0.30, TT_FL_TRUNC, N_("partition name") },
86
[COL_UUID] = { "UUID", 36, 0, N_("partition UUID")},
87
[COL_SCHEME] = { "SCHEME", 0.1, TT_FL_TRUNC, N_("partition table type (dos, gpt, ...)")},
88
[COL_FLAGS] = { "FLAGS", 0.1, TT_FL_TRUNC, N_("partition flags")},
89
[COL_TYPE] = { "TYPE", 1, TT_FL_RIGHT, N_("partition type hex or uuid")},
91
/* array with IDs of enabled columns */
92
static int columns[__NCOLUMNS], ncolumns;
95
static int partx_flags;
98
static inline int get_column_id(int num)
100
assert(ARRAY_SIZE(columns) == __NCOLUMNS);
101
assert(num < ncolumns);
102
assert(columns[num] < __NCOLUMNS);
106
static inline struct colinfo *get_column_info(int num)
108
return &infos[ get_column_id(num) ];
111
static int column_name_to_id(const char *name, size_t namesz)
117
for (i = 0; i < __NCOLUMNS; i++) {
118
const char *cn = infos[i].name;
120
if (!strncasecmp(name, cn, namesz) && !*(cn + namesz))
123
warnx(_("unknown column: %s"), name);
128
* Given a partition return the corresponding partition number.
130
* Note that this function tries to use sysfs, otherwise it assumes that the
131
* last characters are always numeric (sda1, sdc20, etc).
133
* Returns -1 on error.
135
static int get_partno_from_device(char *partition, dev_t devno)
139
char *p, *end = NULL;
144
/* the device exits, read the partition number from /sys
145
* TODO: move this to stuff to lib/sysfs.c */
149
snprintf(path, sizeof(path),
150
_PATH_SYS_DEVBLOCK "/%d:%d/partition",
151
major(devno), minor(devno));
152
f = fopen(path, "r");
154
if (fscanf(f, "%d", &partno) != 1)
178
while (upper == 0 || lower <= upper) {
186
a.op = BLKPG_DEL_PARTITION;
188
a.datalen = sizeof(pt);
190
if (ioctl(fd, BLKPG, &a) == -1)
195
"error deleting partition %d: ",
196
"error deleting partitions %d-%d: ");
198
EBUSY: mounted or in use as swap
199
ENXIO: no such nonempty partition
200
EINVAL: not wholedisk, or bad pno
201
EACCES/EPERM: permission denied
203
if (err && err != EBUSY && err != ENXIO) {
162
sz = strlen(partition);
163
p = partition + sz - 1;
165
if (!isdigit((unsigned int) *p))
168
while (isdigit((unsigned int) *(p - 1))) p--;
171
partno = strtol(p, &end, 10);
172
if (errno || !end || *end || p == end)
177
errx(EXIT_FAILURE, _("%s: failed to get partition number"), partition);
180
static int get_max_partno(const char *disk, dev_t devno)
182
char path[PATH_MAX], *parent;
188
if (!devno && !stat(disk, &st))
192
parent = strrchr(disk, '/');
197
snprintf(path, sizeof(path), _PATH_SYS_DEVBLOCK "/%d:%d/",
198
major(devno), minor(devno));
204
while ((d = readdir(dir))) {
207
if (!strcmp(d->d_name, ".") ||
208
!strcmp(d->d_name, ".."))
210
#ifdef _DIRENT_HAVE_D_TYPE
211
if (d->d_type != DT_DIR)
214
if (strncmp(parent, d->d_name, strlen(parent)))
216
snprintf(path, sizeof(path), "%s/partition", d->d_name);
218
fd = openat(dirfd(dir), path, O_RDONLY);
221
FILE *f = fdopen(fd, "r");
223
if (fscanf(f, "%d", &x) == 1 && x > partno)
207
if (err == 0 && verbose)
208
printf("deleted partition %d\n", lower);
212
"error deleting partition %d: ",
213
"error deleting partitions %d-%d: ");
217
if (device != diskdevice) {
218
fd2 = open(device, O_RDONLY);
227
if (ioctl(fd, HDIO_GETGEO, &g)) {
228
perror("HDIO_GETGEO");
232
fprintf(stderr, "last arg is not the whole disk\n");
233
fprintf(stderr, "call: partx -opts device wholedisk\n");
237
if (ioctl(fd2, HDIO_GETGEO, &g)) {
238
perror("HDIO_GETGEO");
243
if (blkdev_get_sectors(fd2, &size) != 0) {
247
all.size = (unsigned int) size;
250
printf("device %s: start %d size %d\n",
251
device, all.start, all.size);
254
fprintf(stderr, "That disk slice has size 0\n");
258
all.size = 0; /* probably extended partition */
260
/* add the indicated partitions to the kernel partition tables */
236
static void del_parts_warnx(const char *device, int first, int last)
239
warnx(_("%s: error deleting partition %d"), device, first);
241
warnx(_("%s: error deleting partitions %d-%d"),
242
device, first, last);
245
static int del_parts(int fd, const char *device, dev_t devno,
246
int lower, int upper)
248
int rc = 0, i, errfirst = 0, errlast = 0;
263
for (i = 0; i < ptct; i++) {
265
if (!type || !strcmp(type, ptp->type)) {
266
n = ptp->fn(fd, all, slices, ARRAY_SIZE(slices));
267
if (n >= 0 && verbose)
268
printf("%s: %d slices\n", ptp->type, n);
269
if (n > 0 && (verbose || what == LIST)) {
271
printf("#%2d: %9d-%9d (%9d sectors, %6d MB)\n",
274
slices[j].start+slices[j].size-1,
276
(int)((512 * (long long) slices[j].size)
279
if (n > 0 && what == ADD) {
280
/* test for overlap, as in the case of an
281
extended partition, and reduce size */
282
for (j=0; j<n; j++) {
283
for (k=j+1; k<n; k++) {
284
if (slices[k].start > slices[j].start &&
285
slices[k].start < slices[j].start +
287
slices[j].size = slices[k].start -
290
printf("reduced size of "
291
"partition #%d to %d\n",
297
for (j=0; j<n; j++) {
299
pt.start = 512 * (long long) slices[j].start;
300
pt.length = 512 * (long long) slices[j].size;
303
a.op = BLKPG_ADD_PARTITION;
305
a.datalen = sizeof(pt);
307
if (ioctl(fd, BLKPG, &a) == -1) {
310
"error adding partition %d\n",
313
printf("added partition %d\n", lower+j);
323
xmalloc (size_t size) {
330
fprintf(stderr, "Out of memory\n");
337
sseek(int fd, unsigned int secnr) {
339
in = ((long long) secnr << 9);
342
if ((out = lseek(fd, in, SEEK_SET)) != in)
344
fprintf(stderr, "lseek error\n");
353
unsigned char *block;
358
getblock(int fd, unsigned int secnr) {
361
for (bp = blockhead; bp; bp = bp->next)
362
if (bp->secnr == secnr)
364
if (sseek(fd, secnr))
366
bp = xmalloc(sizeof(struct block));
368
bp->next = blockhead;
370
bp->block = (unsigned char *) xmalloc(1024);
371
if (read(fd, bp->block, 1024) != 1024) {
372
fprintf(stderr, "read error, sector %d\n", secnr);
378
/* call with errno and integer m and error message */
379
/* merge to interval m-n */
381
errmerge(int err, int m, char *msg1, char *msg2) {
382
static int preverr, firstm, prevm;
384
if (err != preverr) {
387
fprintf(stderr, msg1, firstm);
389
fprintf(stderr, msg2, firstm, prevm);
255
if (!upper || lower < 0 || upper < 0) {
256
int n = get_max_partno(device, devno);
260
upper = n + upper + 1;
262
lower = n + lower + 1;
265
warnx(_("defined range <%d:%d> "
266
"does not make sense"), lower, upper);
270
for (i = lower; i <= upper; i++) {
271
if (partx_del_partition(fd, i) == 0) {
273
printf(_("%s: partition #%d removed\n"), device, i);
278
warn(_("%s: delete partition #%d failed"), device, i);
280
errlast = errfirst = i;
281
else if (errlast + 1 == i)
284
del_parts_warnx(device, errfirst, errlast);
285
errlast = errfirst = i;
290
del_parts_warnx(device, errfirst, errlast);
294
static void add_parts_warnx(const char *device, int first, int last)
297
warnx(_("%s: error adding partition %d"), device, first);
299
warnx(_("%s: error adding partitions %d-%d"),
300
device, first, last);
303
static int add_parts(int fd, const char *device,
304
blkid_partlist ls, int lower, int upper)
306
int i, nparts, rc = 0, errfirst = 0, errlast = 0;
312
nparts = blkid_partlist_numof_partitions(ls);
314
for (i = 0; i < nparts; i++) {
315
blkid_partition par = blkid_partlist_get_partition(ls, i);
316
int n = blkid_partition_get_partno(par);
317
uintmax_t start, size;
319
if (lower && n < lower)
321
if (upper && n > upper)
324
start = blkid_partition_get_start(par);
325
size = blkid_partition_get_size(par);
327
if (blkid_partition_is_extended(par))
329
* Let's follow Linux kernel and reduce
330
* DOS extended partition to 1 or 2 sectors
332
size = min(size, (uintmax_t) 2);
334
if (partx_add_partition(fd, n, start, size) == 0) {
336
printf(_("%s: partition #%d added\n"), device, n);
341
warn(_("%s: add partition #%d failed"), device, n);
343
errlast = errfirst = n;
344
else if (errlast + 1 == n)
347
add_parts_warnx(device, errfirst, errlast);
348
errlast = errfirst = n;
353
add_parts_warnx(device, errfirst, errlast);
357
static int list_parts(blkid_partlist ls, int lower, int upper)
363
nparts = blkid_partlist_numof_partitions(ls);
365
for (i = 0; i < nparts; i++) {
366
blkid_partition par = blkid_partlist_get_partition(ls, i);
367
int n = blkid_partition_get_partno(par);
368
uintmax_t start, size;
370
if (lower && n < lower)
372
if (upper && n > upper)
375
start = blkid_partition_get_start(par);
376
size = blkid_partition_get_size(par);
378
printf("#%2d: %9ju-%9ju (%9ju sectors, %6ju MB)\n",
379
n, start, start + size -1,
380
size, (size << 9) / 1000000);
385
static void add_tt_line(struct tt *tt, blkid_partition par)
387
struct tt_line *line;
393
line = tt_add_line(tt, NULL);
395
warn(_("failed to add line to output"));
399
for (i = 0; i < ncolumns; i++) {
403
switch (get_column_id(i)) {
405
rc = asprintf(&str, "%d",
406
blkid_partition_get_partno(par));
409
rc = asprintf(&str, "%ju",
410
blkid_partition_get_start(par));
413
rc = asprintf(&str, "%ju",
414
blkid_partition_get_start(par) +
415
blkid_partition_get_size(par) - 1);
418
rc = asprintf(&str, "%ju",
419
blkid_partition_get_size(par));
422
if (partx_flags & FL_BYTES)
423
rc = asprintf(&str, "%ju", (uintmax_t)
424
blkid_partition_get_size(par) << 9);
426
str = size_to_human_string(
427
blkid_partition_get_size(par) << 9);
430
str = (char *) blkid_partition_get_name(par);
435
str = (char *) blkid_partition_get_uuid(par);
440
str = (char *) blkid_partition_get_type_string(par);
444
rc = asprintf(&str, "0x%x",
445
blkid_partition_get_type(par));
448
rc = asprintf(&str, "0x%llx", blkid_partition_get_flags(par));
452
blkid_parttable tab = blkid_partition_get_table(par);
454
str = (char *) blkid_parttable_get_type(tab);
465
tt_line_set_data(line, i, str);
469
static int show_parts(blkid_partlist ls, int tt_flags, int lower, int upper)
477
nparts = blkid_partlist_numof_partitions(ls);
481
tt = tt_new_table(tt_flags);
483
warn(_("failed to initialize output table"));
487
for (i = 0; i < ncolumns; i++) {
488
struct colinfo *col = get_column_info(i);
490
if (!tt_define_column(tt, col->name, col->whint, col->flags)) {
491
warnx(_("failed to initialize output column"));
496
for (i = 0; i < nparts; i++) {
497
blkid_partition par = blkid_partlist_get_partition(ls, i);
498
int n = blkid_partition_get_partno(par);
500
if (lower && n < lower)
502
if (upper && n > upper)
505
add_tt_line(tt, par);
515
static int parse_range(const char *str, int *lower, int *upper)
525
if (*str == ':') { /* <:N> */
527
*upper = strtol(str, &end, 10);
528
if (errno || !end || *end || end == str)
531
*upper = *lower = strtol(str, &end, 10);
532
if (errno || !end || end == str)
535
if (*end == ':' && !*(end + 1)) /* <M:> */
537
else if (*end == '-' || *end == ':') { /* <M:N> <M-N> */
541
*upper = strtol(str, &end, 10);
543
if (errno || !end || *end || end == str)
550
static blkid_partlist get_partlist(blkid_probe pr,
551
const char *device, char *type)
560
char *name[] = { type, NULL };
562
if (blkid_probe_filter_partitions_type(pr,
563
BLKID_FLTR_ONLYIN, name)) {
564
warnx(_("failed to initialize blkid "
565
"filter for '%s'"), type);
570
ls = blkid_probe_get_partitions(pr);
572
warnx(_("%s: failed to read partition table"), device);
576
tab = blkid_partlist_get_table(ls);
578
printf(_("%s: partition table '%s' detected\n"),
580
blkid_parttable_get_type(tab));
582
if (!blkid_partlist_numof_partitions(ls)) {
583
warnx(_("%s: %s partition table does not contains "
584
"usable partitions"), device,
585
blkid_parttable_get_type(tab));
591
static void __attribute__((__noreturn__)) usage(FILE *out)
597
" %s [-a|-d|-s] [--nr <N:M> | <device>] <wholedisk>\n"),
598
program_invocation_short_name);
602
" -a, --add add specified partitions or all of them\n"
603
" -d, --delete delete specified partitions or all of them\n"
604
" -l, --list list partitions (DEPRECATED)\n"
605
" -s, --show list partitions\n\n"
607
" -b, --bytes print SIZE in bytes rather than in human readable format\n"
608
" -g, --noheadings don't print headings for --show\n"
609
" -r, --raw use raw format output\n"
610
" -t, --type <TYPE> specify the partition type (dos, bsd, solaris, etc.)\n"
611
" -n, --nr <M:N> specify the range of partitions (--nr 2:4)\n"
612
" -o, --output <LIST> output column\n"
613
" -h, --help print this help\n\n"));
615
fprintf(out, _("\nAvailable columns (for --show):\n"));
617
for (i = 0; i < __NCOLUMNS; i++)
618
fprintf(out, " %10s %s\n", infos[i].name, _(infos[i].help));
620
fprintf(out, _("\nFor more information see partx(8).\n"));
622
exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
625
static void __attribute__((__noreturn__))
626
errx_mutually_exclusive(const char *opts)
628
errx(EXIT_FAILURE, "%s %s", opts, _("options are mutually exclusive"));
631
int main(int argc, char **argv)
633
int fd, c, what = 0, lower = 0, upper = 0, rc = 0;
636
char *device = NULL; /* pointer to atgv[], ie: /dev/sda1 */
637
char *wholedisk = NULL; /* allocated, ie: /dev/sda */
638
dev_t disk_devno = 0, part_devno = 0;
640
static const struct option long_opts[] = {
641
{ "bytes", no_argument, NULL, 'b' },
642
{ "noheadings", no_argument, NULL, 'g' },
643
{ "raw", no_argument, NULL, 'r' },
644
{ "list", no_argument, NULL, 'l' },
645
{ "show", no_argument, NULL, 's' },
646
{ "add", no_argument, NULL, 'a' },
647
{ "delete", no_argument, NULL, 'd' },
648
{ "type", required_argument, NULL, 't' },
649
{ "nr", required_argument, NULL, 'n' },
650
{ "output", required_argument, NULL, 'o' },
651
{ "help", no_argument, NULL, 'h' },
655
while ((c = getopt_long(argc, argv, "abdglrsvn:t:o:h", long_opts, NULL)) != -1) {
659
errx_mutually_exclusive("--{add,delete,show,list,raw}");
663
partx_flags |= FL_BYTES;
667
errx_mutually_exclusive("--{add,delete,show,list,raw}");
671
tt_flags |= TT_FL_NOHEADINGS;
675
errx_mutually_exclusive("--{add,delete,show,list,raw}");
679
if (parse_range(optarg, &lower, &upper))
680
errx(EXIT_FAILURE, _("failed to parse --nr <M-N> range"));
684
if (tt_parse_columns_list(optarg, columns, &ncolumns,
689
tt_flags |= TT_FL_RAW;
691
errx_mutually_exclusive("--{add,delete,show,list,raw}");
697
errx_mutually_exclusive("--{add,delete,show,list,raw}");
714
/* -o <list> enables --show mode by default */
715
if (ncolumns && !what)
718
/* backwardly compatible default */
722
/* --show default, could by modified by -o */
723
if (what == ACT_SHOW && !ncolumns) {
724
columns[ncolumns++] = COL_PARTNO;
725
columns[ncolumns++] = COL_START;
726
columns[ncolumns++] = COL_END;
727
columns[ncolumns++] = COL_SECTORS;
728
columns[ncolumns++] = COL_SIZE;
729
columns[ncolumns++] = COL_NAME;
730
columns[ncolumns++] = COL_UUID;
734
* Note that 'partx /dev/sda1' == 'partx /dev/sda1 /dev/sda'
735
* so assume that the device and/or disk are always the last
736
* arguments to be passed to partx.
738
if (optind == argc - 2) {
739
/* passed 2 arguments:
740
* /dev/sda1 /dev/sda : partition + whole-disk
741
* -- /dev/sda1 : partition that should be used as a whole-disk
743
device = argv[optind];
745
if (strcmp(device, "-") == 0) {
747
wholedisk = xstrdup(argv[optind + 1]);
749
device = argv[optind];
750
wholedisk = xstrdup(argv[optind + 1]);
752
} else if (optind == argc - 1) {
753
/* passed only one arg (ie: /dev/sda3 or /dev/sda) */
756
device = argv[optind];
758
if (stat(device, &sb))
759
err(EXIT_FAILURE, _("%s: stat failed"), device);
761
part_devno = sb.st_rdev;
763
if (blkid_devno_to_wholedisk(part_devno,
764
NULL, 0, &disk_devno) == 0 &&
765
part_devno != disk_devno)
766
wholedisk = blkid_devno_to_devname(disk_devno);
769
wholedisk = xstrdup(device);
770
disk_devno = part_devno;
777
if (device && (upper || lower))
778
errx(EXIT_FAILURE, _("--nr and <partition> are mutually exclusive}"));
783
/* use partno from given partition instead of --nr range, e.g:
786
* partx -d --nr 3 /dev/sda
790
if (!part_devno && !stat(device, &sb))
791
part_devno = sb.st_rdev;
793
lower = upper = get_partno_from_device(device, part_devno);
797
printf("device: %s, whole-disk: %s, lower: %d, upper: %d\n",
798
device, wholedisk, lower, upper);
800
if (what == ACT_ADD || what == ACT_DELETE) {
803
if (stat(wholedisk, &x) || !S_ISBLK(x.st_mode))
804
errx(EXIT_FAILURE, _("%s: not a block device"), wholedisk);
806
if ((fd = open(wholedisk, O_RDONLY)) == -1)
807
err(EXIT_FAILURE, _("%s: open failed"), wholedisk);
809
if (what == ACT_DELETE)
810
rc = del_parts(fd, wholedisk, disk_devno, lower, upper);
812
blkid_probe pr = blkid_new_probe();
813
blkid_partlist ls = NULL;
815
if (!pr || blkid_probe_set_device(pr, fd, 0, 0))
816
warnx(_("%s: failed to initialize blkid prober"),
819
ls = get_partlist(pr, wholedisk, type);
822
int n = blkid_partlist_numof_partitions(ls);
825
lower = n + lower + 1;
827
upper = n + upper + 1;
829
warnx(_("defined range <%d:%d> "
830
"does not make sense"), lower, upper);
836
rc = show_parts(ls, tt_flags, lower, upper);
839
rc = list_parts(ls, lower, upper);
842
rc = add_parts(fd, wholedisk, ls, lower, upper);
846
blkid_free_probe(pr);
850
return rc ? EXIT_FAILURE : EXIT_SUCCESS;