diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 02:42:50 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 02:42:50 +0000 |
commit | 8cb83eee5a58b1fad74c34094ce3afb9e430b5a4 (patch) | |
tree | a9b2e7baeca1be40eb734371e3c8b11b02294497 /disk-utils | |
parent | Initial commit. (diff) | |
download | util-linux-8cb83eee5a58b1fad74c34094ce3afb9e430b5a4.tar.xz util-linux-8cb83eee5a58b1fad74c34094ce3afb9e430b5a4.zip |
Adding upstream version 2.33.1.upstream/2.33.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'disk-utils')
48 files changed, 20737 insertions, 0 deletions
diff --git a/disk-utils/Makemodule.am b/disk-utils/Makemodule.am new file mode 100644 index 0000000..bea0ed6 --- /dev/null +++ b/disk-utils/Makemodule.am @@ -0,0 +1,228 @@ + +if BUILD_MINIX +sbin_PROGRAMS += fsck.minix +dist_man_MANS += disk-utils/fsck.minix.8 +fsck_minix_SOURCES = \ + disk-utils/fsck.minix.c \ + disk-utils/minix_programs.h +fsck_minix_LDADD = $(LDADD) libcommon.la + +sbin_PROGRAMS += mkfs.minix +dist_man_MANS += disk-utils/mkfs.minix.8 +mkfs_minix_SOURCES = \ + disk-utils/minix_programs.h \ + disk-utils/mkfs.minix.c +mkfs_minix_LDADD = $(LDADD) libcommon.la + +check_PROGRAMS += test_mkfs_minix +test_mkfs_minix_SOURCES = $(mkfs_minix_SOURCES) +test_mkfs_minix_LDADD = $(mkfs_minix_LDADD) +test_mkfs_minix_CFLAGS = $(AM_CFLAGS) -DTEST_SCRIPT +endif + + +if BUILD_MKFS +sbin_PROGRAMS += mkfs +dist_man_MANS += disk-utils/mkfs.8 +mkfs_SOURCES = disk-utils/mkfs.c +endif + + +if BUILD_ISOSIZE +usrbin_exec_PROGRAMS += isosize +dist_man_MANS += disk-utils/isosize.8 +isosize_SOURCES = disk-utils/isosize.c +isosize_LDADD = $(LDADD) libcommon.la +endif + + +if BUILD_BFS +sbin_PROGRAMS += mkfs.bfs +dist_man_MANS += disk-utils/mkfs.bfs.8 +mkfs_bfs_SOURCES = \ + disk-utils/mkfs.bfs.c +mkfs_bfs_LDADD = $(LDADD) libcommon.la +endif + + +if BUILD_MKSWAP +sbin_PROGRAMS += mkswap +dist_man_MANS += disk-utils/mkswap.8 +mkswap_SOURCES = \ + disk-utils/mkswap.c +mkswap_LDADD = $(LDADD) libcommon.la + +mkswap_CFLAGS = $(AM_CFLAGS) +if BUILD_LIBUUID +mkswap_CFLAGS += -I$(ul_libuuid_incdir) +mkswap_LDADD += libuuid.la +endif +if BUILD_LIBBLKID +mkswap_CFLAGS += -I$(ul_libblkid_incdir) +mkswap_LDADD += libblkid.la +endif +if HAVE_SELINUX +mkswap_LDADD += -lselinux +endif +endif # BUILD_MKSWAP + + +if BUILD_SWAPLABEL +sbin_PROGRAMS += swaplabel +dist_man_MANS += disk-utils/swaplabel.8 +swaplabel_SOURCES = \ + disk-utils/swaplabel.c \ + lib/swapprober.c \ + include/swapprober.h + +swaplabel_CFLAGS = $(AM_CFLAGS) -I$(ul_libblkid_incdir) +swaplabel_LDADD = $(LDADD) libblkid.la libcommon.la + +if BUILD_LIBUUID +swaplabel_LDADD += libuuid.la +swaplabel_CFLAGS += -I$(ul_libuuid_incdir) +endif +endif #BUILD_SWAPLABEL + + +if BUILD_FSCK +sbin_PROGRAMS += fsck +dist_man_MANS += disk-utils/fsck.8 +fsck_SOURCES = disk-utils/fsck.c lib/monotonic.c +fsck_LDADD = $(LDADD) libmount.la libblkid.la libcommon.la $(REALTIME_LIBS) +fsck_CFLAGS = $(AM_CFLAGS) -I$(ul_libmount_incdir) -I$(ul_libblkid_incdir) +endif + + +if BUILD_RAW +sbin_PROGRAMS += raw +dist_man_MANS += disk-utils/raw.8 +raw_SOURCES = disk-utils/raw.c +endif + + +if BUILD_CRAMFS +cramfs_common_sources = disk-utils/cramfs.h disk-utils/cramfs_common.c +sbin_PROGRAMS += fsck.cramfs +fsck_cramfs_SOURCES = disk-utils/fsck.cramfs.c $(cramfs_common_sources) +fsck_cramfs_LDADD = $(LDADD) -lz libcommon.la +dist_man_MANS += disk-utils/fsck.cramfs.8 + +sbin_PROGRAMS += mkfs.cramfs +mkfs_cramfs_SOURCES = disk-utils/mkfs.cramfs.c $(cramfs_common_sources) +mkfs_cramfs_LDADD = $(LDADD) -lz libcommon.la +dist_man_MANS += disk-utils/mkfs.cramfs.8 +endif + +if BUILD_FDFORMAT +usrsbin_exec_PROGRAMS += fdformat +dist_man_MANS += disk-utils/fdformat.8 +fdformat_SOURCES = disk-utils/fdformat.c +fdformat_LDADD = $(LDADD) libcommon.la +endif + +if BUILD_BLOCKDEV +sbin_PROGRAMS += blockdev +dist_man_MANS += disk-utils/blockdev.8 +blockdev_SOURCES = disk-utils/blockdev.c +blockdev_LDADD = $(LDADD) libcommon.la +endif + + +if BUILD_FDISK +sbin_PROGRAMS += fdisk +dist_man_MANS += disk-utils/fdisk.8 +fdisk_SOURCES = \ + disk-utils/fdisk.c \ + disk-utils/fdisk.h \ + disk-utils/fdisk-menu.c \ + disk-utils/fdisk-list.c \ + disk-utils/fdisk-list.h + +fdisk_LDADD = $(LDADD) libcommon.la libfdisk.la \ + libsmartcols.la libtcolors.la $(READLINE_LIBS) +fdisk_CFLAGS = $(AM_CFLAGS) -I$(ul_libfdisk_incdir) -I$(ul_libsmartcols_incdir) + +if HAVE_STATIC_FDISK +sbin_PROGRAMS += fdisk.static +fdisk_static_SOURCES = $(fdisk_SOURCES) +fdisk_static_LDFLAGS = -all-static +fdisk_static_CFLAGS = $(fdisk_CFLAGS) +fdisk_static_LDADD = $(fdisk_LDADD) $(READLINE_LIBS_STATIC) +endif +endif # BUILD_FDISK + + +if BUILD_SFDISK +sbin_PROGRAMS += sfdisk +dist_man_MANS += disk-utils/sfdisk.8 +sfdisk_SOURCES = \ + disk-utils/sfdisk.c \ + disk-utils/fdisk-list.c \ + disk-utils/fdisk-list.h + +sfdisk_LDADD = $(LDADD) libcommon.la libfdisk.la \ + libsmartcols.la libtcolors.la $(READLINE_LIBS) +sfdisk_CFLAGS = $(AM_CFLAGS) -I$(ul_libfdisk_incdir) -I$(ul_libsmartcols_incdir) + +if HAVE_STATIC_SFDISK +sbin_PROGRAMS += sfdisk.static +sfdisk_static_SOURCES = $(sfdisk_SOURCES) +sfdisk_static_LDFLAGS = -all-static +sfdisk_static_CFLAGS = $(sfdisk_CFLAGS) +sfdisk_static_LDADD = $(sfdisk_LDADD) $(READLINE_LIBS_STATIC) +endif +endif # BUILD_SFDISK + + +if BUILD_CFDISK +sbin_PROGRAMS += cfdisk +dist_man_MANS += disk-utils/cfdisk.8 +cfdisk_SOURCES = disk-utils/cfdisk.c +cfdisk_LDADD = \ + $(LDADD) \ + libsmartcols.la \ + libcommon.la \ + libfdisk.la \ + libtcolors.la +cfdisk_CFLAGS = \ + $(AM_CFLAGS) \ + -I$(ul_libfdisk_incdir) \ + -I$(ul_libsmartcols_incdir) + +if BUILD_LIBMOUNT +cfdisk_CFLAGS += -I$(ul_libmount_incdir) +cfdisk_LDADD += libmount.la +endif + +if HAVE_SLANG +cfdisk_LDADD += -lslang +else +cfdisk_CFLAGS += $(NCURSES_CFLAGS) +cfdisk_LDADD += $(NCURSES_LIBS) +endif +endif # BUILD_CFDISK + + +if BUILD_PARTX +usrsbin_exec_PROGRAMS += partx addpart delpart resizepart +dist_man_MANS += \ + disk-utils/addpart.8 \ + disk-utils/delpart.8 \ + disk-utils/resizepart.8 \ + disk-utils/partx.8 + +addpart_SOURCES = disk-utils/addpart.c +addpart_LDADD = $(LDADD) libcommon.la + +delpart_SOURCES = disk-utils/delpart.c +delpart_LDADD = $(LDADD) libcommon.la + +resizepart_SOURCES = disk-utils/resizepart.c +resizepart_LDADD = $(LDADD) libcommon.la + +partx_SOURCES = disk-utils/partx.c +partx_CFLAGS = $(AM_CFLAGS) -I$(ul_libblkid_incdir) -I$(ul_libsmartcols_incdir) +partx_LDADD = $(LDADD) libblkid.la libcommon.la libsmartcols.la + +endif # BUILD_PARTX diff --git a/disk-utils/addpart.8 b/disk-utils/addpart.8 new file mode 100644 index 0000000..97d11ad --- /dev/null +++ b/disk-utils/addpart.8 @@ -0,0 +1,40 @@ +.\" addpart.8 -- man page for addpart +.\" Copyright 2007 Karel Zak <kzak@redhat.com> +.\" Copyright 2007 Red Hat, Inc. +.\" May be distributed under the GNU General Public License +.TH ADDPART 8 "January 2015" "util-linux" "System Administration" +.SH NAME +addpart \- tell the kernel about the existence of a partition +.SH SYNOPSIS +.B addpart +.I device partition start length +.SH DESCRIPTION +.B addpart +tells the Linux kernel about the existence of the specified partition. +The command is a simple wrapper around the "add partition" ioctl. + +This command doesn't manipulate partitions on a block device. + +.SH PARAMETERS +.TP +.I device +The disk device. +.TP +.I partition +The partition number. +.TP +.I start +The beginning of the partition (in 512-byte sectors). +.TP +.I length +The length of the partition (in 512-byte sectors). + +.SH SEE ALSO +.BR delpart (8), +.BR fdisk (8), +.BR parted (8), +.BR partprobe (8), +.BR partx (8) +.SH AVAILABILITY +The addpart command is part of the util-linux package and is available from +https://www.kernel.org/pub/linux/utils/util-linux/. diff --git a/disk-utils/addpart.c b/disk-utils/addpart.c new file mode 100644 index 0000000..018c995 --- /dev/null +++ b/disk-utils/addpart.c @@ -0,0 +1,67 @@ +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <fcntl.h> + +#include "c.h" +#include "nls.h" +#include "partx.h" +#include "strutils.h" + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *out = stdout; + fputs(USAGE_HEADER, out); + fprintf(out, _(" %s <disk device> <partition number> <start> <length>\n"), + program_invocation_short_name); + + fputs(USAGE_SEPARATOR, out); + fputs(_("Tell the kernel about the existence of a specified partition.\n"), out); + + fputs(USAGE_OPTIONS, out); + printf(USAGE_HELP_OPTIONS(16)); + printf(USAGE_MAN_TAIL("addpart(8)")); + exit(EXIT_SUCCESS); +} + +int main(int argc, char **argv) +{ + int c, fd; + + static const struct option longopts[] = { + {"help", no_argument, NULL, 'h'}, + {"version", no_argument, NULL, 'V'}, + {NULL, 0, NULL, 0}, + }; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + + while ((c = getopt_long(argc, argv, "Vh", longopts, NULL)) != -1) + switch (c) { + case 'V': + printf(UTIL_LINUX_VERSION); + return EXIT_SUCCESS; + case 'h': + usage(); + default: + errtryhelp(EXIT_FAILURE); + } + + if (argc != 5) { + warnx(_("not enough arguments")); + errtryhelp(EXIT_FAILURE); + } + + if ((fd = open(argv[1], O_RDONLY)) < 0) + err(EXIT_FAILURE, _("cannot open %s"), argv[1]); + + if (partx_add_partition(fd, + strtou32_or_err(argv[2], _("invalid partition number argument")), + strtou64_or_err(argv[3], _("invalid start argument")), + strtou64_or_err(argv[4], _("invalid length argument")))) + err(EXIT_FAILURE, _("failed to add partition")); + + return EXIT_SUCCESS; +} diff --git a/disk-utils/blockdev.8 b/disk-utils/blockdev.8 new file mode 100644 index 0000000..fb00606 --- /dev/null +++ b/disk-utils/blockdev.8 @@ -0,0 +1,91 @@ +.\" Copyright 1998 Andries E. Brouwer (aeb@cwi.nl) +.\" Copyright 2007 Karel Zak <kzak@redhat.com> +.\" +.\" May be distributed under the GNU General Public License +.TH BLOCKDEV 8 "August 2010" "util-linux" "System Administration" +.SH NAME +blockdev \- call block device ioctls from the command line +.SH SYNOPSIS +.B blockdev +.RB [ \-q ] +.RB [ \-v ] +.I command +.RI [ command \&...\&] +.I device +.RI [ device \&...\&] +.br +.B blockdev +.B \-\-report +.RI [ device \&...\&] +.br +.B blockdev +.RB \-h | \-V +.SH DESCRIPTION +The utility +.B blockdev +allows one to call block device ioctls from the command line. +.SH OPTIONS +.IP "\fB\-q\fP" +Be quiet. +.IP "\fB\-v\fP" +Be verbose. +.IP "\fB\-\-report\fP" +Print a report for the specified device. It is possible to give multiple +devices. If none is given, all devices which appear in /proc/partitions are +shown. Note that the partition StartSec is in 512-byte sectors. +.IP "\fB\-h\fR, \fB\-\-help\fR" +Display help text and exit. +.IP "\fB\-V\fR, \fB\-\-version\fR" +Print version and exit. +.SH COMMANDS +It is possible to give multiple devices and multiple commands. +.IP "\fB\-\-flushbufs\fP" +Flush buffers. +.IP "\fB\-\-getalignoff\fP" +Get alignment offset. +.IP "\fB\-\-getbsz\fP" +Print blocksize in bytes. +.IP "\fB\-\-getdiscardzeroes\fP" +Get discard zeroes support status. +.IP "\fB\-\-getfra\fP" +Get filesystem readahead in 512-byte sectors. +.IP "\fB\-\-getiomin\fP" +Get minimum I/O size. +.IP "\fB\-\-getioopt\fP" +Get optimal I/O size. +.IP "\fB\-\-getmaxsect\fP" +Get max sectors per request +.IP "\fB\-\-getpbsz\fP" +Get physical block (sector) size. +.IP "\fB\-\-getra\fP" +Print readahead (in 512-byte sectors). +.IP "\fB\-\-getro\fP" +Get read-only. Print 1 if the device is read-only, 0 otherwise. +.IP "\fB\-\-getsize64\fP" +Print device size in bytes. +.IP "\fB\-\-getsize\fP" +Print device size (32-bit!) in sectors. Deprecated in favor of the \-\-getsz option. +.IP "\fB\-\-getss\fP" +Print logical sector size in bytes \(en usually 512. +.IP "\fB\-\-getsz\fP" +Get size in 512-byte sectors. +.IP "\fB\-\-rereadpt\fP" +Reread partition table +.IP "\fB\-\-setbsz\fP \fIbytes\fP" +Set blocksize. Note that the block size is specific to the current file +descriptor opening the block device, so the change of block size only persists +for as long as blockdev has the device open, and is lost once blockdev exits. +.IP "\fB\-\-setfra\fP \fIsectors\fP" +Set filesystem readahead (same like \-\-setra on 2.6 kernels). +.IP "\fB\-\-setra\fP \fIsectors\fP" +Set readahead (in 512-byte sectors). +.IP "\fB\-\-setro\fP" +Set read-only. The currently active access to the device may not be affected by the change. For example +filesystem already mounted in read-write mode will not be affected. The change applies after remount. +.IP "\fB\-\-setrw\fP" +Set read-write. +.SH AUTHOR +blockdev was written by Andries E.\& Brouwer and rewritten by Karel Zak. +.SH AVAILABILITY +The blockdev command is part of the util-linux package and is available from +https://www.kernel.org/pub/linux/utils/util-linux/. diff --git a/disk-utils/blockdev.c b/disk-utils/blockdev.c new file mode 100644 index 0000000..5122460 --- /dev/null +++ b/disk-utils/blockdev.c @@ -0,0 +1,505 @@ +/* + * blockdev.c --- Do various simple block device ioctls from the command line + * aeb, 991028 + */ + +#include <stdio.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <errno.h> + +#include "c.h" +#include "nls.h" +#include "blkdev.h" +#include "pathnames.h" +#include "closestream.h" +#include "sysfs.h" + +struct bdc { + long ioc; /* ioctl code */ + const char *iocname; /* ioctl name (e.g. BLKROSET) */ + long argval; /* default argument */ + + const char *name; /* --setfoo */ + const char *argname; /* argument name or NULL */ + + const char *help; + + int argtype; + int flags; +}; + +/* command flags */ +enum { + FL_NOPTR = (1 << 1), /* does not assume pointer (ARG_INT only)*/ + FL_NORESULT = (1 << 2) /* does not return any data */ +}; + +/* ioctl argument types */ +enum { + ARG_NONE, + ARG_USHRT, + ARG_INT, + ARG_UINT, + ARG_LONG, + ARG_ULONG, + ARG_LLONG, + ARG_ULLONG +}; + +#define IOCTL_ENTRY( io ) .ioc = io, .iocname = # io + +static const struct bdc bdcms[] = +{ + { + IOCTL_ENTRY(BLKROSET), + .name = "--setro", + .argtype = ARG_INT, + .argval = 1, + .flags = FL_NORESULT, + .help = N_("set read-only") + },{ + IOCTL_ENTRY(BLKROSET), + .name = "--setrw", + .argtype = ARG_INT, + .argval = 0, + .flags = FL_NORESULT, + .help = N_("set read-write") + },{ + IOCTL_ENTRY(BLKROGET), + .name = "--getro", + .argtype = ARG_INT, + .argval = -1, + .help = N_("get read-only") + },{ + IOCTL_ENTRY(BLKDISCARDZEROES), + .name = "--getdiscardzeroes", + .argtype = ARG_UINT, + .argval = -1, + .help = N_("get discard zeroes support status") + },{ + IOCTL_ENTRY(BLKSSZGET), + .name = "--getss", + .argtype = ARG_INT, + .argval = -1, + .help = N_("get logical block (sector) size") + },{ + IOCTL_ENTRY(BLKPBSZGET), + .name = "--getpbsz", + .argtype = ARG_UINT, + .argval = -1, + .help = N_("get physical block (sector) size") + },{ + IOCTL_ENTRY(BLKIOMIN), + .name = "--getiomin", + .argtype = ARG_UINT, + .argval = -1, + .help = N_("get minimum I/O size") + },{ + IOCTL_ENTRY(BLKIOOPT), + .name = "--getioopt", + .argtype = ARG_UINT, + .argval = -1, + .help = N_("get optimal I/O size") + },{ + IOCTL_ENTRY(BLKALIGNOFF), + .name = "--getalignoff", + .argtype = ARG_INT, + .argval = -1, + .help = N_("get alignment offset in bytes") + },{ + IOCTL_ENTRY(BLKSECTGET), + .name = "--getmaxsect", + .argtype = ARG_USHRT, + .argval = -1, + .help = N_("get max sectors per request") + },{ + IOCTL_ENTRY(BLKBSZGET), + .name = "--getbsz", + .argtype = ARG_INT, + .argval = -1, + .help = N_("get blocksize") + },{ + IOCTL_ENTRY(BLKBSZSET), + .name = "--setbsz", + .argname = "<bytes>", + .argtype = ARG_INT, + .flags = FL_NORESULT, + .help = N_("set blocksize on file descriptor opening the block device") + },{ + IOCTL_ENTRY(BLKGETSIZE), + .name = "--getsize", + .argtype = ARG_ULONG, + .argval = -1, + .help = N_("get 32-bit sector count (deprecated, use --getsz)") + },{ + IOCTL_ENTRY(BLKGETSIZE64), + .name = "--getsize64", + .argtype = ARG_ULLONG, + .argval = -1, + .help = N_("get size in bytes") + },{ + IOCTL_ENTRY(BLKRASET), + .name = "--setra", + .argname = "<sectors>", + .argtype = ARG_INT, + .flags = FL_NOPTR | FL_NORESULT, + .help = N_("set readahead") + },{ + IOCTL_ENTRY(BLKRAGET), + .name = "--getra", + .argtype = ARG_LONG, + .argval = -1, + .help = N_("get readahead") + },{ + IOCTL_ENTRY(BLKFRASET), + .name = "--setfra", + .argname = "<sectors>", + .argtype = ARG_INT, + .flags = FL_NOPTR | FL_NORESULT, + .help = N_("set filesystem readahead") + },{ + IOCTL_ENTRY(BLKFRAGET), + .name = "--getfra", + .argtype = ARG_LONG, + .argval = -1, + .help = N_("get filesystem readahead") + },{ + IOCTL_ENTRY(BLKFLSBUF), + .name = "--flushbufs", + .help = N_("flush buffers") + },{ + IOCTL_ENTRY(BLKRRPART), + .name = "--rereadpt", + .help = N_("reread partition table") + } +}; + +static void __attribute__((__noreturn__)) usage(void) +{ + size_t i; + + fputs(USAGE_HEADER, stdout); + printf(_( + " %1$s [-v|-q] commands devices\n" + " %1$s --report [devices]\n" + " %1$s -h|-V\n" + ), program_invocation_short_name); + + fputs(USAGE_SEPARATOR, stdout); + puts( _("Call block device ioctls from the command line.")); + + fputs(USAGE_OPTIONS, stdout); + puts( _(" -q quiet mode")); + puts( _(" -v verbose mode")); + puts( _(" --report print report for specified (or all) devices")); + fputs(USAGE_SEPARATOR, stdout); + printf(USAGE_HELP_OPTIONS(16)); + + fputs(USAGE_SEPARATOR, stdout); + puts( _("Available commands:")); + printf(_(" %-25s get size in 512-byte sectors\n"), "--getsz"); + for (i = 0; i < ARRAY_SIZE(bdcms); i++) { + if (bdcms[i].argname) + printf(" %s %-*s %s\n", bdcms[i].name, + (int)(24 - strlen(bdcms[i].name)), + bdcms[i].argname, _(bdcms[i].help)); + else + printf(" %-25s %s\n", bdcms[i].name, + _(bdcms[i].help)); + } + + printf(USAGE_MAN_TAIL("blockdev(8)")); + exit(EXIT_SUCCESS); +} + +static int find_cmd(char *s) +{ + size_t j; + + for (j = 0; j < ARRAY_SIZE(bdcms); j++) + if (!strcmp(s, bdcms[j].name)) + return j; + return -1; +} + +static void do_commands(int fd, char **argv, int d); +static void report_header(void); +static void report_device(char *device, int quiet); +static void report_all_devices(void); + +int main(int argc, char **argv) +{ + int fd, d, j, k; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + if (argc < 2) { + warnx(_("not enough arguments")); + errtryhelp(EXIT_FAILURE); + } + + /* -V not together with commands */ + if (!strcmp(argv[1], "-V") || !strcmp(argv[1], "--version")) { + printf(UTIL_LINUX_VERSION); + return EXIT_SUCCESS; + } + if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) + usage(); + + /* --report not together with other commands */ + if (!strcmp(argv[1], "--report")) { + report_header(); + if (argc > 2) { + for (d = 2; d < argc; d++) + report_device(argv[d], 0); + } else { + report_all_devices(); + } + return EXIT_SUCCESS; + } + + /* do each of the commands on each of the devices */ + /* devices start after last command */ + for (d = 1; d < argc; d++) { + j = find_cmd(argv[d]); + if (j >= 0) { + if (bdcms[j].argname) + d++; + continue; + } + if (!strcmp(argv[d], "--getsz")) + continue; + if (!strcmp(argv[d], "--")) { + d++; + break; + } + if (argv[d][0] != '-') + break; + } + + if (d >= argc) { + warnx(_("no device specified")); + errtryhelp(EXIT_FAILURE); + } + + for (k = d; k < argc; k++) { + fd = open(argv[k], O_RDONLY, 0); + if (fd < 0) + err(EXIT_FAILURE, _("cannot open %s"), argv[k]); + do_commands(fd, argv, d); + close(fd); + } + return EXIT_SUCCESS; +} + +static void do_commands(int fd, char **argv, int d) +{ + int res, i, j; + int iarg = 0; + unsigned int uarg = 0; + unsigned short huarg = 0; + long larg = 0; + long long llarg = 0; + unsigned long lu = 0; + unsigned long long llu = 0; + int verbose = 0; + + for (i = 1; i < d; i++) { + if (!strcmp(argv[i], "-v")) { + verbose = 1; + continue; + } + if (!strcmp(argv[i], "-q")) { + verbose = 0; + continue; + } + + if (!strcmp(argv[i], "--getsz")) { + res = blkdev_get_sectors(fd, &llu); + if (res == 0) + printf("%lld\n", llu); + else + errx(EXIT_FAILURE, + _("could not get device size")); + continue; + } + + j = find_cmd(argv[i]); + if (j == -1) { + warnx(_("Unknown command: %s"), argv[i]); + errtryhelp(EXIT_FAILURE); + } + + switch (bdcms[j].argtype) { + default: + case ARG_NONE: + res = ioctl(fd, bdcms[j].ioc, 0); + break; + case ARG_USHRT: + huarg = bdcms[j].argval; + res = ioctl(fd, bdcms[j].ioc, &huarg); + break; + case ARG_INT: + if (bdcms[j].argname) { + if (i == d - 1) { + warnx(_("%s requires an argument"), + bdcms[j].name); + errtryhelp(EXIT_FAILURE); + } + iarg = atoi(argv[++i]); + } else + iarg = bdcms[j].argval; + + res = bdcms[j].flags & FL_NOPTR ? + ioctl(fd, bdcms[j].ioc, iarg) : + ioctl(fd, bdcms[j].ioc, &iarg); + break; + case ARG_UINT: + uarg = bdcms[j].argval; + res = ioctl(fd, bdcms[j].ioc, &uarg); + break; + case ARG_LONG: + larg = bdcms[j].argval; + res = ioctl(fd, bdcms[j].ioc, &larg); + break; + case ARG_LLONG: + llarg = bdcms[j].argval; + res = ioctl(fd, bdcms[j].ioc, &llarg); + break; + case ARG_ULONG: + lu = bdcms[j].argval; + res = ioctl(fd, bdcms[j].ioc, &lu); + break; + case ARG_ULLONG: + llu = bdcms[j].argval; + res = ioctl(fd, bdcms[j].ioc, &llu); + break; + } + + if (res == -1) { + warn(_("ioctl error on %s"), bdcms[j].iocname); + if (verbose) + printf(_("%s failed.\n"), _(bdcms[j].help)); + exit(EXIT_FAILURE); + } + + if (bdcms[j].argtype == ARG_NONE || + (bdcms[j].flags & FL_NORESULT)) { + if (verbose) + printf(_("%s succeeded.\n"), _(bdcms[j].help)); + continue; + } + + if (verbose) + printf("%s: ", _(bdcms[j].help)); + + switch (bdcms[j].argtype) { + case ARG_USHRT: + printf("%hu\n", huarg); + break; + case ARG_INT: + printf("%d\n", iarg); + break; + case ARG_UINT: + printf("%u\n", uarg); + break; + case ARG_LONG: + printf("%ld\n", larg); + break; + case ARG_LLONG: + printf("%lld\n", llarg); + break; + case ARG_ULONG: + printf("%lu\n", lu); + break; + case ARG_ULLONG: + printf("%llu\n", llu); + break; + } + } +} + +static void report_all_devices(void) +{ + FILE *procpt; + char line[200]; + char ptname[200 + 1]; + char device[210]; + int ma, mi, sz; + + procpt = fopen(_PATH_PROC_PARTITIONS, "r"); + if (!procpt) + err(EXIT_FAILURE, _("cannot open %s"), _PATH_PROC_PARTITIONS); + + while (fgets(line, sizeof(line), procpt)) { + if (sscanf(line, " %d %d %d %200[^\n ]", + &ma, &mi, &sz, ptname) != 4) + continue; + + sprintf(device, "/dev/%s", ptname); + report_device(device, 1); + } + + fclose(procpt); +} + +static void report_device(char *device, int quiet) +{ + int fd; + int ro, ssz, bsz; + long ra; + unsigned long long bytes; + uint64_t start = 0; + struct stat st; + + fd = open(device, O_RDONLY | O_NONBLOCK); + if (fd < 0) { + if (!quiet) + warn(_("cannot open %s"), device); + return; + } + + ro = ssz = bsz = 0; + ra = 0; + if (fstat(fd, &st) == 0) { + dev_t disk; + struct path_cxt *pc; + + pc = ul_new_sysfs_path(st.st_rdev, NULL, NULL); + if (pc && + sysfs_blkdev_get_wholedisk(pc, NULL, 0, &disk) == 0 && + disk != st.st_rdev) { + + if (ul_path_read_u64(pc, &start, "start") != 0) + err(EXIT_FAILURE, + _("%s: failed to read partition start from sysfs"), + device); + } + ul_unref_path(pc); + } + if (ioctl(fd, BLKROGET, &ro) == 0 && + ioctl(fd, BLKRAGET, &ra) == 0 && + ioctl(fd, BLKSSZGET, &ssz) == 0 && + ioctl(fd, BLKBSZGET, &bsz) == 0 && + blkdev_get_size(fd, &bytes) == 0) { + printf("%s %5ld %5d %5d %10ju %15lld %s\n", + ro ? "ro" : "rw", ra, ssz, bsz, start, bytes, device); + } else { + if (!quiet) + warnx(_("ioctl error on %s"), device); + } + + close(fd); +} + +static void report_header(void) +{ + printf(_("RO RA SSZ BSZ StartSec Size Device\n")); +} diff --git a/disk-utils/cfdisk.8 b/disk-utils/cfdisk.8 new file mode 100644 index 0000000..ff548d8 --- /dev/null +++ b/disk-utils/cfdisk.8 @@ -0,0 +1,204 @@ +.\" cfdisk.8 -- man page for cfdisk +.\" Copyright 1994 Kevin E. Martin (martin@cs.unc.edu) +.\" Copyright (C) 2014 Karel Zak <kzak@redhat.com> +.\" +.\" Permission is granted to make and distribute verbatim copies of this +.\" manual provided the copyright notice and this permission notice are +.\" preserved on all copies. +.\" +.\" Permission is granted to copy and distribute modified versions of this +.\" manual under the conditions for verbatim copying, provided that the +.\" entire resulting derived work is distributed under the terms of a +.\" permission notice identical to this one. +.\" +.TH CFDISK 8 "March 2014" "util-linux" "System Administration" +.SH NAME +cfdisk \- display or manipulate a disk partition table +.SH SYNOPSIS +.B cfdisk +[options] +.RI [ device ] +.SH DESCRIPTION +.B cfdisk +is a curses-based program for partitioning any block device. +The default device is +.IR /dev/sda . + +Note that +.B cfdisk +provides basic partitioning functionality with a user-friendly interface. +If you need advanced features, use +.BR fdisk (8) +instead. + +Since version 2.25 +.B cfdisk +supports MBR (DOS), GPT, SUN and SGI disk labels, but no longer provides any +functionality for CHS (Cylinder-Head-Sector) addressing. CHS has +never been important for Linux, and this addressing concept does not make any +sense for new devices. + +Since version 2.25 +.B cfdisk +also does not provide a 'print' command any more. +This functionality is provided by the utilities +.BR partx (8) +and +.BR lsblk (8) +in a very comfortable and rich way. + +If you want to remove an old partition table from a device, use +.BR wipefs (8). + +.SH OPTIONS +.TP +.BR \-h , " \-\-help" +Display help text and exit. +.TP +.BR \-L , " \-\-color" [ = \fIwhen\fR] +Colorize the output. The optional argument \fIwhen\fP +can be \fBauto\fR, \fBnever\fR or \fBalways\fR. If the \fIwhen\fR argument is omitted, +it defaults to \fBauto\fR. The colors can be disabled, for the current built-in default +see \fB\-\-help\fR output. See also the COLORS section. +.TP +.BR \-V , " \-\-version" +Display version information and exit. +.TP +.BR \-z , " \-\-zero" +Start with an in-memory zeroed partition table. This option does not zero the +partition table on the disk; rather, it simply starts the program without +reading the existing partition table. This option allows you to create a new +partition table from scratch or from an sfdisk-compatible script. + +.SH COMMANDS +The commands for +.B cfdisk +can be entered by pressing the corresponding key (pressing +.I Enter +after the command is not necessary). Here is a list of the available +commands: +.TP +.B b +Toggle the bootable flag of the current partition. This allows you to +select which primary partition is bootable on the drive. This command may not +be available for all partition label types. +.TP +.B d +Delete the current partition. This will convert the current partition +into free space and merge it with any free space immediately +surrounding the current partition. A partition already marked as free +space or marked as unusable cannot be deleted. +.TP +.B h +Show the help screen. +.TP +.B n +Create a new partition from free space. +.B cfdisk +then prompts you for the size of the partition you want to create. +The default size is equal to the entire available free space at the current +position. + +The size may be followed by a multiplicative suffix: KiB (=1024), +MiB (=1024*1024), and so on for GiB, TiB, PiB, EiB, ZiB and YiB +(the "iB" is optional, e.g. "K" has the same meaning as "KiB"). +.TP +.B q +Quit the program. This will exit the program without writing any data to +the disk. +.TP +.B s +Sort the partitions in ascending start-sector order. When deleting and +adding partitions, it is likely that the numbering of the partitions will +no longer match their order on the disk. This command restores that match. +.TP +.B t +Change the partition type. By default, new partitions are created as +.I Linux +partitions. +.TP +.B u +Dump the current in-memory partition table to an sfdisk-compatible script file. +.sp +The script files are compatible between \fBcfdisk\fR, \fBfdisk\fR, \fBsfdisk\fR +and other libfdisk applications. For more details see +.BR sfdisk (8). +.sp +It is also possible to load an sfdisk-script into \fBcfdisk\fR if there is +no partition table on the device or when you start \fBcfdisk\fR with the +\fB--zero\fR command-line option. +.TP +.B W +Write the partition table to disk (you must enter an uppercase W). Since +this might destroy data on the disk, you must either confirm or deny +the write by entering `yes' or `no'. If you enter `yes', +.B cfdisk +will write the partition table to disk and then tell the kernel to re-read the +partition table from the disk. + +The re-reading of the partition table does not always work. In such a +case you need to inform the kernel about any new partitions by using +.BR partprobe (8) +or +.BR partx (8), +or by rebooting the system. +.TP +.B x +Toggle extra information about a partition. +.TP +.IR "Up Arrow" , " Down Arrow" +Move the cursor to the previous or next partition. If there are more +partitions than can be displayed on a screen, you can display the next +(previous) set of partitions by moving down (up) at the last (first) +partition displayed on the screen. +.TP +.IR "Left Arrow" , " Right Arrow" +Select the preceding or the next menu item. Hitting \fIEnter\fR will +execute the currently selected item. + +.PP +All commands can be entered with either uppercase or lowercase +letters (except for +.BR W rite). +When in a submenu or at a prompt, you can hit the +.I Esc +key to return to the main menu. + +.SH COLORS +Implicit coloring can be disabled by creating the empty file +.IR /etc/terminal-colors.d/cfdisk.disable . + +See +.BR terminal-colors.d (5) +for more details about colorization configuration. + +.B cfdisk +does not support color customization with a color-scheme file. + +.SH ENVIRONMENT +.IP CFDISK_DEBUG=all +enables cfdisk debug output. +.IP LIBFDISK_DEBUG=all +enables libfdisk debug output. +.IP LIBBLKID_DEBUG=all +enables libblkid debug output. +.IP LIBSMARTCOLS_DEBUG=all +enables libsmartcols debug output. +.IP LIBSMARTCOLS_DEBUG_PADDING=on +use visible padding characters. Requires enabled LIBSMARTCOLS_DEBUG. + +.SH "SEE ALSO" +.BR fdisk (8), +.BR parted (8), +.BR partprobe (8), +.BR partx (8), +.BR sfdisk (8) +.SH AUTHOR +Karel Zak <kzak@redhat.com> +.PP +The current cfdisk implementation is based on the original cfdisk +from Kevin E. Martin (martin@cs.unc.edu). + +.SH AVAILABILITY +The cfdisk command is part of the util-linux package and is available from +https://www.kernel.org/pub/linux/utils/util-linux/. diff --git a/disk-utils/cfdisk.c b/disk-utils/cfdisk.c new file mode 100644 index 0000000..25f8e1a --- /dev/null +++ b/disk-utils/cfdisk.c @@ -0,0 +1,2747 @@ +/* + * cfdisk.c - Display or manipulate a disk partition table. + * + * Copyright (C) 2014-2015 Karel Zak <kzak@redhat.com> + * Copyright (C) 1994 Kevin E. Martin (martin@cs.unc.edu) + * + * The original cfdisk was inspired by the fdisk program + * by A. V. Le Blanc (leblanc@mcc.ac.uk. + * + * cfdisk is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <signal.h> +#include <ctype.h> +#include <getopt.h> +#include <assert.h> +#include <libsmartcols.h> +#include <sys/ioctl.h> +#include <libfdisk.h> + +#ifdef HAVE_LIBMOUNT +# include <libmount.h> /* keep it optional for non-linux systems */ +#endif + +#ifdef HAVE_SLANG_H +# include <slang.h> +#elif defined(HAVE_SLANG_SLANG_H) +# include <slang/slang.h> +#endif + +#ifndef _XOPEN_SOURCE +# define _XOPEN_SOURCE 500 /* for inclusion of get_wch */ +#endif + +#ifdef HAVE_SLCURSES_H +# include <slcurses.h> +#elif defined(HAVE_SLANG_SLCURSES_H) +# include <slang/slcurses.h> +#elif defined(HAVE_NCURSESW_NCURSES_H) && defined(HAVE_WIDECHAR) +# include <ncursesw/ncurses.h> +#elif defined(HAVE_NCURSES_H) +# include <ncurses.h> +#elif defined(HAVE_NCURSES_NCURSES_H) +# include <ncurses/ncurses.h> +#endif + +#ifdef HAVE_WIDECHAR +# include <wctype.h> +# include <wchar.h> +#endif + +#include "c.h" +#include "closestream.h" +#include "nls.h" +#include "strutils.h" +#include "xalloc.h" +#include "mbsalign.h" +#include "mbsedit.h" +#include "colors.h" +#include "debug.h" +#include "list.h" + +static const char *default_disks[] = { +#ifdef __GNU__ + "/dev/hd0", + "/dev/sd0", +#elif defined(__FreeBSD__) + "/dev/ad0", + "/dev/da0", +#else + "/dev/sda", + "/dev/vda", + "/dev/hda", +#endif +}; + +#define ARROW_CURSOR_STRING ">> " +#define ARROW_CURSOR_DUMMY " " +#define ARROW_CURSOR_WIDTH (sizeof(ARROW_CURSOR_STRING) - 1) + +/* vertical menu */ +#define MENU_V_SPADDING 1 /* space around menu item string */ + +/* horizontal menu */ +#define MENU_H_SPADDING 0 /* space around menu item string */ +#define MENU_H_BETWEEN 2 /* space between menu items */ +#define MENU_H_PRESTR "[" +#define MENU_H_POSTSTR "]" + +#define MENU_TITLE_PADDING 3 + +#define MENU_H_PRESTR_SZ (sizeof(MENU_H_PRESTR) - 1) +#define MENU_H_POSTSTR_SZ (sizeof(MENU_H_POSTSTR) - 1) + +#define TABLE_START_LINE 4 +#define MENU_START_LINE (ui_lines - 4) /* The menu maybe use two lines */ +#define INFO_LINE (ui_lines - 2) +#define WARN_LINE INFO_LINE +#define HINT_LINE (ui_lines - 1) + +#define CFDISK_ERR_ESC 5000 + +#ifndef KEY_ESC +# define KEY_ESC '\033' +#endif +#ifndef KEY_DELETE +# define KEY_DELETE '\177' +#endif +#ifndef KEY_DC +# define KEY_DC 0423 +#endif + + +/* colors */ +enum { + CFDISK_CL_NONE = 0, + CFDISK_CL_WARNING, + CFDISK_CL_FREESPACE, + CFDISK_CL_INFO +}; +static const int color_pairs[][2] = { + /* color foreground, background */ + [CFDISK_CL_WARNING] = { COLOR_RED, -1 }, + [CFDISK_CL_FREESPACE] = { COLOR_GREEN, -1 }, + [CFDISK_CL_INFO] = { COLOR_BLUE, -1 } +}; + +struct cfdisk; + +static struct cfdisk_menuitem *menu_get_menuitem(struct cfdisk *cf, size_t idx); +static struct cfdisk_menuitem *menu_get_menuitem_by_key(struct cfdisk *cf, int key, size_t *idx); +static struct cfdisk_menu *menu_push(struct cfdisk *cf, struct cfdisk_menuitem *item); +static struct cfdisk_menu *menu_pop(struct cfdisk *cf); +static void menu_refresh_size(struct cfdisk *cf); + +static int ui_end(void); +static int ui_refresh(struct cfdisk *cf); +static void ui_warnx(const char *fmt, ...); +static void ui_warn(const char *fmt, ...); +static void ui_info(const char *fmt, ...); +static void ui_draw_menu(struct cfdisk *cf); +static int ui_menu_move(struct cfdisk *cf, int key); +static void ui_menu_resize(struct cfdisk *cf); + +static int ui_get_size(struct cfdisk *cf, const char *prompt, uint64_t *res, + uint64_t low, uint64_t up, int *expsize); + +static int ui_enabled; +static volatile sig_atomic_t sig_resize; +static volatile sig_atomic_t sig_die; + +/* ncurses LINES and COLS may be actual variables or *macros*, but we need + * something portable and writable */ +static size_t ui_lines; +static size_t ui_cols; + +/* menu item */ +struct cfdisk_menuitem { + int key; /* keyboard shortcut */ + const char *name; /* item name */ + const char *desc; /* item description (hint) */ + void *userdata; +}; + +/* menu */ +struct cfdisk_menu { + char *title; /* optional menu title */ + struct cfdisk_menuitem *items; /* array with menu items */ + char *ignore;/* string with keys to ignore */ + size_t width; /* maximal width of the menu item */ + size_t nitems; /* number of the active menu items */ + size_t page_sz;/* when menu longer than screen */ + size_t idx; /* the current menu item */ + int prefkey;/* preferred menu item */ + struct cfdisk_menu *prev; + + /* @ignore keys generator */ + int (*ignore_cb) (struct cfdisk *, char *, size_t); + + unsigned int vertical : 1; /* enable vertical mode */ +}; + +/* main menu */ +static struct cfdisk_menuitem main_menuitems[] = { + { 'b', N_("Bootable"), N_("Toggle bootable flag of the current partition") }, + { 'd', N_("Delete"), N_("Delete the current partition") }, + { 'r', N_("Resize"), N_("Reduce or enlarge the current partition") }, + { 'n', N_("New"), N_("Create new partition from free space") }, + { 'q', N_("Quit"), N_("Quit program without writing changes") }, + { 't', N_("Type"), N_("Change the partition type") }, + { 'h', N_("Help"), N_("Print help screen") }, + { 's', N_("Sort"), N_("Fix partitions order") }, + { 'W', N_("Write"), N_("Write partition table to disk (this might destroy data)") }, + { 'u', N_("Dump"), N_("Dump partition table to sfdisk compatible script file") }, + { 0, NULL, NULL } +}; + +/* extra partinfo in name:value pairs */ +struct cfdisk_extra { + char *name; + char *data; + + struct list_head exs; +}; + +/* line and extra partinfo list_head */ +struct cfdisk_line { + char *data; /* line data */ + struct libscols_table *extra; /* extra info ('X') */ + WINDOW *w; /* window with extra info */ +}; + +/* top level control struct */ +struct cfdisk { + struct fdisk_context *cxt; /* libfdisk context */ + struct fdisk_table *table; /* partition table */ + struct fdisk_table *original_layout; /* original on-disk PT */ + + struct cfdisk_menu *menu; /* the current menu */ + + int *fields; /* output columns IDs */ + size_t nfields; /* number of columns IDs */ + + char *linesbuf; /* table as string */ + size_t linesbufsz; /* size of the tb_buf */ + + struct cfdisk_line *lines; /* list of lines */ + + size_t nlines; /* number of lines */ + size_t lines_idx; /* current line <0..N>, exclude header */ + size_t page_sz; + + unsigned int nwrites; /* fdisk_write_disklabel() counter */ + + WINDOW *act_win; /* the window currently on the screen */ + +#ifdef HAVE_LIBMOUNT + struct libmnt_table *mtab; + struct libmnt_table *fstab; + struct libmnt_cache *mntcache; +#endif + unsigned int wrong_order :1, /* PT not in right order */ + zero_start :1, /* ignore existing partition table */ + device_is_used : 1, /* don't use re-read ioctl */ + show_extra :1; /* show extra partinfo */ +}; + + +/* + * let's use include/debug.h stuff for cfdisk too + */ +static UL_DEBUG_DEFINE_MASK(cfdisk); +UL_DEBUG_DEFINE_MASKNAMES(cfdisk) = UL_DEBUG_EMPTY_MASKNAMES; + +#define CFDISK_DEBUG_INIT (1 << 1) +#define CFDISK_DEBUG_UI (1 << 2) +#define CFDISK_DEBUG_MENU (1 << 3) +#define CFDISK_DEBUG_MISC (1 << 4) +#define CFDISK_DEBUG_TABLE (1 << 5) +#define CFDISK_DEBUG_ALL 0xFFFF + +#define DBG(m, x) __UL_DBG(cfdisk, CFDISK_DEBUG_, m, x) + +static void cfdisk_init_debug(void) +{ + __UL_INIT_DEBUG_FROM_ENV(cfdisk, CFDISK_DEBUG_, 0, CFDISK_DEBUG); +} + +/* Initialize output columns -- we follow libfdisk fields (usually specific + * to the label type. + */ +static int cols_init(struct cfdisk *cf) +{ + assert(cf); + + free(cf->fields); + cf->fields = NULL; + cf->nfields = 0; + + return fdisk_label_get_fields_ids(NULL, cf->cxt, &cf->fields, &cf->nfields); +} + +static void die_on_signal(void) +{ + DBG(MISC, ul_debug("die on signal.")); + ui_end(); + exit(EXIT_FAILURE); +} + +static void resize(void) +{ + struct winsize ws; + + if (ioctl(fileno(stdout), TIOCGWINSZ, &ws) != -1 + && ws.ws_row && ws.ws_col) { + ui_lines = ws.ws_row; + ui_cols = ws.ws_col; +#if HAVE_RESIZETERM + resizeterm(ws.ws_row, ws.ws_col); +#endif + clearok(stdscr, TRUE); + } + touchwin(stdscr); + + DBG(UI, ul_debug("ui: resize refresh ui_cols=%zu, ui_lines=%zu", + ui_cols, ui_lines)); + sig_resize = 0; +} + +/* Reads partition in tree-like order from scols + */ +static int partition_from_scols(struct fdisk_table *tb, + struct libscols_line *ln) +{ + struct fdisk_partition *pa = scols_line_get_userdata(ln); + + fdisk_table_add_partition(tb, pa); + fdisk_unref_partition(pa); + + if (scols_line_has_children(ln)) { + struct libscols_line *chln; + struct libscols_iter *itr = scols_new_iter(SCOLS_ITER_FORWARD); + + if (!itr) + return -EINVAL; + while (scols_line_next_child(ln, itr, &chln) == 0) + partition_from_scols(tb, chln); + scols_free_iter(itr); + } + return 0; +} + +static char *table_to_string(struct cfdisk *cf, struct fdisk_table *tb) +{ + struct fdisk_partition *pa; + struct fdisk_label *lb; + struct fdisk_iter *itr; + struct libscols_table *table = NULL; + struct libscols_iter *s_itr = NULL; + char *res = NULL; + size_t i; + int tree = 0; + struct libscols_line *ln, *ln_cont = NULL; + + DBG(TABLE, ul_debug("convert to string")); + + assert(cf); + assert(cf->cxt); + assert(cf->fields); + assert(tb); + + lb = fdisk_get_label(cf->cxt, NULL); + assert(lb); + + itr = fdisk_new_iter(FDISK_ITER_FORWARD); + if (!itr) + goto done; + + /* get container (e.g. extended partition) */ + while (fdisk_table_next_partition(tb, itr, &pa) == 0) { + if (fdisk_partition_is_nested(pa)) { + DBG(TABLE, ul_debug("nested detected, using tree")); + tree = SCOLS_FL_TREE; + break; + } + } + + table = scols_new_table(); + if (!table) + goto done; + scols_table_enable_maxout(table, 1); + scols_table_enable_nowrap(table, 1); + +#if !defined(HAVE_LIBNCURSESW) || !defined(HAVE_WIDECHAR) + scols_table_enable_ascii(table, 1); +#endif + + /* headers */ + for (i = 0; i < cf->nfields; i++) { + int fl = 0; + const struct fdisk_field *field = + fdisk_label_get_field(lb, cf->fields[i]); + if (!field) + continue; + + if (fdisk_field_is_number(field)) + fl |= SCOLS_FL_RIGHT; + if (fdisk_field_get_id(field) == FDISK_FIELD_TYPE) + fl |= SCOLS_FL_TRUNC; + if (tree && fdisk_field_get_id(field) == FDISK_FIELD_DEVICE) + fl |= SCOLS_FL_TREE; + + if (!scols_table_new_column(table, + _(fdisk_field_get_name(field)), + fdisk_field_get_width(field), fl)) + goto done; + } + + /* data */ + fdisk_reset_iter(itr, FDISK_ITER_FORWARD); + + while (fdisk_table_next_partition(tb, itr, &pa) == 0) { + struct libscols_line *parent = fdisk_partition_is_nested(pa) ? ln_cont : NULL; + + ln = scols_table_new_line(table, parent); + if (!ln) + goto done; + for (i = 0; i < cf->nfields; i++) { + char *cdata = NULL; + + if (fdisk_partition_to_string(pa, cf->cxt, + cf->fields[i], &cdata)) + continue; + scols_line_refer_data(ln, i, cdata); + } + if (tree && fdisk_partition_is_container(pa)) + ln_cont = ln; + + scols_line_set_userdata(ln, (void *) pa); + fdisk_ref_partition(pa); + } + + if (scols_table_is_empty(table)) + goto done; + + scols_table_reduce_termwidth(table, ARROW_CURSOR_WIDTH); + scols_print_table_to_string(table, &res); + + /* scols_* code might reorder lines, let's reorder @tb according to the + * final output (it's no problem because partitions are addressed by + * parno stored within struct fdisk_partition) */ + + /* remove all */ + fdisk_reset_iter(itr, FDISK_ITER_FORWARD); + while (fdisk_table_next_partition(tb, itr, &pa) == 0) + fdisk_table_remove_partition(tb, pa); + + s_itr = scols_new_iter(SCOLS_ITER_FORWARD); + if (!s_itr) + goto done; + + /* add all in the right order (don't forget the output is tree) */ + while (scols_table_next_line(table, s_itr, &ln) == 0) { + if (scols_line_get_parent(ln)) + continue; + if (partition_from_scols(tb, ln)) + break; + } +done: + scols_unref_table(table); + scols_free_iter(s_itr); + fdisk_free_iter(itr); + + return res; +} + +static void cfdisk_free_lines(struct cfdisk *cf) +{ + size_t i = 0; + while(i < cf->nlines) { + scols_unref_table(cf->lines[i].extra); + + DBG(UI, ul_debug("delete window: %p", + cf->lines[i].w)); + + if (cf->lines[i].w) + delwin(cf->lines[i].w); + cf->lines[i].w = NULL; + ++i; + } + cf->act_win = NULL; + free(cf->lines); + cf->lines = NULL; +} +/* + * Read data about partitions from libfdisk and prepare output lines. + */ +static int lines_refresh(struct cfdisk *cf) +{ + int rc; + char *p; + size_t i; + + assert(cf); + + DBG(TABLE, ul_debug("refreshing buffer")); + + free(cf->linesbuf); + cfdisk_free_lines(cf); + cf->linesbuf = NULL; + cf->linesbufsz = 0; + cf->lines = NULL; + cf->nlines = 0; + + fdisk_unref_table(cf->table); + cf->table = NULL; + + /* read partitions and free spaces into cf->table */ + rc = fdisk_get_partitions(cf->cxt, &cf->table); + if (!rc) + rc = fdisk_get_freespaces(cf->cxt, &cf->table); + if (rc) + return rc; + + cf->linesbuf = table_to_string(cf, cf->table); + if (!cf->linesbuf) + return -ENOMEM; + + cf->linesbufsz = strlen(cf->linesbuf); + cf->nlines = fdisk_table_get_nents(cf->table) + 1; /* 1 for header line */ + cf->page_sz = 0; + cf->wrong_order = fdisk_table_wrong_order(cf->table) ? 1 : 0; + + if (MENU_START_LINE - TABLE_START_LINE < cf->nlines) + cf->page_sz = MENU_START_LINE - TABLE_START_LINE - 1; + + cf->lines = xcalloc(cf->nlines, sizeof(struct cfdisk_line)); + + for (p = cf->linesbuf, i = 0; p && i < cf->nlines; i++) { + cf->lines[i].data = p; + p = strchr(p, '\n'); + if (p) { + *p = '\0'; + p++; + } + cf->lines[i].extra = scols_new_table(); + scols_table_enable_noheadings(cf->lines[i].extra, 1); + scols_table_new_column(cf->lines[i].extra, NULL, 0, SCOLS_FL_RIGHT); + scols_table_new_column(cf->lines[i].extra, NULL, 0, SCOLS_FL_TRUNC); + } + + return 0; +} + +static struct fdisk_partition *get_current_partition(struct cfdisk *cf) +{ + assert(cf); + assert(cf->table); + + return fdisk_table_get_partition(cf->table, cf->lines_idx); +} + +static int is_freespace(struct cfdisk *cf, size_t i) +{ + struct fdisk_partition *pa; + + assert(cf); + assert(cf->table); + + pa = fdisk_table_get_partition(cf->table, i); + return fdisk_partition_is_freespace(pa); +} + +/* converts libfdisk FDISK_ASKTYPE_MENU to cfdisk menu and returns user's + * response back to libfdisk + */ +static int ask_menu(struct fdisk_ask *ask, struct cfdisk *cf) +{ + struct cfdisk_menuitem *d, *cm; + int key; + size_t i = 0, nitems; + const char *name, *desc; + + assert(ask); + assert(cf); + + /* create cfdisk menu according to libfdisk ask-menu, note that the + * last cm[] item has to be empty -- so nitems + 1 */ + nitems = fdisk_ask_menu_get_nitems(ask); + cm = xcalloc(nitems + 1, sizeof(struct cfdisk_menuitem)); + + for (i = 0; i < nitems; i++) { + if (fdisk_ask_menu_get_item(ask, i, &key, &name, &desc)) + break; + cm[i].key = key; + cm[i].desc = desc; + cm[i].name = name; + } + + /* make the new menu active */ + menu_push(cf, cm); + ui_draw_menu(cf); + refresh(); + + /* wait for keys */ + while (!sig_die) { + key = getch(); + + if (sig_die) + break; + if (sig_resize) + ui_menu_resize(cf); + if (ui_menu_move(cf, key) == 0) + continue; + + switch (key) { + case KEY_ENTER: + case '\n': + case '\r': + d = menu_get_menuitem(cf, cf->menu->idx); + if (d) + fdisk_ask_menu_set_result(ask, d->key); + menu_pop(cf); + free(cm); + return 0; + } + } + + if (sig_die) + die_on_signal(); + + menu_pop(cf); + free(cm); + return -1; +} + +/* libfdisk callback + */ +static int ask_callback(struct fdisk_context *cxt __attribute__((__unused__)), + struct fdisk_ask *ask, + void *data __attribute__((__unused__))) +{ + int rc = 0; + + assert(ask); + + switch(fdisk_ask_get_type(ask)) { + case FDISK_ASKTYPE_INFO: + ui_info(fdisk_ask_print_get_mesg(ask)); + break; + case FDISK_ASKTYPE_WARNX: + ui_warnx(fdisk_ask_print_get_mesg(ask)); + break; + case FDISK_ASKTYPE_WARN: + ui_warn(fdisk_ask_print_get_mesg(ask)); + break; + case FDISK_ASKTYPE_MENU: + ask_menu(ask, (struct cfdisk *) data); + break; + default: + ui_warnx(_("internal error: unsupported dialog type %d"), + fdisk_ask_get_type(ask)); + return -EINVAL; + } + return rc; +} + +static int ui_end(void) +{ + if (!ui_enabled) + return -EINVAL; + +#if defined(HAVE_SLCURSES_H) || defined(HAVE_SLANG_SLCURSES_H) + SLsmg_gotorc(ui_lines - 1, 0); + SLsmg_refresh(); +#else + mvcur(0, ui_cols - 1, ui_lines-1, 0); +#endif + curs_set(1); + nl(); + endwin(); + printf("\n"); + ui_enabled = 0; + return 0; +} + +static void ui_vprint_center(size_t line, int attrs, const char *fmt, va_list ap) +{ + size_t width; + char *buf = NULL; + + move(line, 0); + clrtoeol(); + + xvasprintf(&buf, fmt, ap); + + width = mbs_safe_width(buf); + if (width > (size_t) ui_cols) { + char *p = strrchr(buf + ui_cols, ' '); + if (!p) + p = buf + ui_cols; + *p = '\0'; + if (line + 1 >= ui_lines) + line--; + attron(attrs); + mvaddstr(line, 0, buf); + mvaddstr(line + 1, 0, p+1); + attroff(attrs); + } else { + attron(attrs); + mvaddstr(line, (ui_cols - width) / 2, buf); + attroff(attrs); + } + free(buf); +} + +static void ui_center(size_t line, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + ui_vprint_center(line, 0, fmt, ap); + va_end(ap); +} + +static void ui_warnx(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + if (ui_enabled) + ui_vprint_center(WARN_LINE, + colors_wanted() ? COLOR_PAIR(CFDISK_CL_WARNING) : 0, + fmt, ap); + else { + vfprintf(stderr, fmt, ap); + fputc('\n', stderr); + } + va_end(ap); +} + +static void ui_warn(const char *fmt, ...) +{ + char *fmt_m; + va_list ap; + + xasprintf(&fmt_m, "%s: %m", fmt); + + va_start(ap, fmt); + if (ui_enabled) + ui_vprint_center(WARN_LINE, + colors_wanted() ? COLOR_PAIR(CFDISK_CL_WARNING) : 0, + fmt_m, ap); + else { + vfprintf(stderr, fmt_m, ap); + fputc('\n', stderr); + } + va_end(ap); + free(fmt_m); +} + +static void ui_clean_warn(void) +{ + move(WARN_LINE, 0); + clrtoeol(); +} + +static int __attribute__((__noreturn__)) ui_errx(int rc, const char *fmt, ...) + { + va_list ap; + ui_end(); + + va_start(ap, fmt); + fprintf(stderr, "%s: ", program_invocation_short_name); + vfprintf(stderr, fmt, ap); + fputc('\n', stderr); + va_end(ap); + + exit(rc); +} + +static void ui_info(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + if (ui_enabled) + ui_vprint_center(INFO_LINE, + colors_wanted() ? COLOR_PAIR(CFDISK_CL_INFO) : 0, + fmt, ap); + else { + vfprintf(stdout, fmt, ap); + fputc('\n', stdout); + } + va_end(ap); +} + +static void ui_clean_info(void) +{ + move(INFO_LINE, 0); + clrtoeol(); +} + +static void ui_hint(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + if (ui_enabled) + ui_vprint_center(HINT_LINE, A_BOLD, fmt, ap); + else { + vfprintf(stdout, fmt, ap); + fputc('\n', stdout); + } + va_end(ap); +} + +static void ui_clean_hint(void) +{ + move(HINT_LINE, 0); + clrtoeol(); +} + + +static void sig_handler_die(int dummy __attribute__((__unused__))) +{ + sig_die = 1; +} + +static void sig_handler_resize(int dummy __attribute__((__unused__))) +{ + sig_resize = 1; +} + +static void menu_refresh_size(struct cfdisk *cf) +{ + if (cf->menu && cf->menu->nitems) + cf->menu->page_sz = (cf->menu->nitems / (ui_lines - 4)) ? ui_lines - 4 : 0; +} + +static void menu_update_ignore(struct cfdisk *cf) +{ + char ignore[128] = { 0 }; + int i = 0; + struct cfdisk_menu *m; + struct cfdisk_menuitem *d, *org = NULL; + size_t idx; + + assert(cf); + assert(cf->menu); + assert(cf->menu->ignore_cb); + + m = cf->menu; + DBG(MENU, ul_debug("update menu ignored keys")); + + i = m->ignore_cb(cf, ignore, sizeof(ignore)); + ignore[i] = '\0'; + + /* return if no change */ + if ((!m->ignore && !*ignore) + || (m->ignore && *ignore && strcmp(m->ignore, ignore) == 0)) { + return; + } + + if (!m->prefkey) + org = menu_get_menuitem(cf, m->idx); + + free(m->ignore); + m->ignore = xstrdup(ignore); + m->nitems = 0; + + for (d = m->items; d->name; d++) { + if (m->ignore && strchr(m->ignore, d->key)) + continue; + m->nitems++; + } + + DBG(MENU, ul_debug("update menu preferred keys")); + + /* refresh menu index to be at the same menuitem or go to the first */ + if (org && menu_get_menuitem_by_key(cf, org->key, &idx)) + m->idx = idx; + else if (m->prefkey && menu_get_menuitem_by_key(cf, m->prefkey, &idx)) + m->idx = idx; + else + m->idx = 0; + + menu_refresh_size(cf); +} + +static struct cfdisk_menu *menu_push( + struct cfdisk *cf, + struct cfdisk_menuitem *items) +{ + struct cfdisk_menu *m = xcalloc(1, sizeof(*m)); + struct cfdisk_menuitem *d; + + assert(cf); + + DBG(MENU, ul_debug("new menu")); + + m->prev = cf->menu; + m->items = items; + + for (d = m->items; d->name; d++) { + const char *name = _(d->name); + size_t len = mbs_safe_width(name); + if (len > m->width) + m->width = len; + m->nitems++; + } + + cf->menu = m; + + menu_refresh_size(cf); + return m; +} + +static struct cfdisk_menu *menu_pop(struct cfdisk *cf) +{ + struct cfdisk_menu *m = NULL; + + assert(cf); + + DBG(MENU, ul_debug("pop menu")); + + if (cf->menu) { + m = cf->menu->prev; + free(cf->menu->ignore); + free(cf->menu->title); + free(cf->menu); + } + cf->menu = m; + return cf->menu; +} + +static void menu_set_title(struct cfdisk_menu *m, const char *title) +{ + char *str = NULL; + + if (title) { + size_t len = mbs_safe_width(title); + if (len + MENU_TITLE_PADDING > m->width) + m->width = len + MENU_TITLE_PADDING; + str = xstrdup(title); + } + m->title = str; +} + + +static int ui_init(struct cfdisk *cf __attribute__((__unused__))) +{ + struct sigaction sa; + + DBG(UI, ul_debug("init")); + + /* setup SIGCHLD handler */ + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = sig_handler_die; + sigaction(SIGINT, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); + + sa.sa_handler = sig_handler_resize; + sigaction(SIGWINCH, &sa, NULL); + + ui_enabled = 1; + initscr(); + +#ifdef HAVE_USE_DEFAULT_COLORS + if (colors_wanted() && has_colors()) { + size_t i; + + start_color(); + use_default_colors(); + for (i = 1; i < ARRAY_SIZE(color_pairs); i++) /* yeah, start from 1! */ + init_pair(i, color_pairs[i][0], color_pairs[i][1]); + } +#else + colors_off(); +#endif + + cbreak(); + noecho(); + nonl(); + curs_set(0); + keypad(stdscr, TRUE); + + return 0; +} + +/* "[ string ]" */ +#define MENU_H_ITEMWIDTH(m) ( MENU_H_PRESTR_SZ \ + + MENU_H_SPADDING \ + + (m)->width \ + + MENU_H_SPADDING \ + + MENU_H_POSTSTR_SZ) + +#define MENU_V_ITEMWIDTH(m) (MENU_V_SPADDING + (m)->width + MENU_V_SPADDING) + + +static size_t menuitem_get_line(struct cfdisk *cf, size_t idx) +{ + struct cfdisk_menu *m = cf->menu; + + if (m->vertical) { + if (!m->page_sz) /* small menu */ + return (ui_lines - (cf->menu->nitems + 1)) / 2 + idx; + return (idx % m->page_sz) + 1; + } else { + size_t len = MENU_H_ITEMWIDTH(m) + MENU_H_BETWEEN; /** item width */ + size_t items = ui_cols / len; /* items per line */ + + if (items == 0) + return 0; + return MENU_START_LINE + ((idx / items)); + } +} + +static int menuitem_get_column(struct cfdisk *cf, size_t idx) +{ + if (cf->menu->vertical) { + size_t nc = MENU_V_ITEMWIDTH(cf->menu); + if ((size_t) ui_cols <= nc) + return 0; + return (ui_cols - nc) / 2; + } else { + size_t len = MENU_H_ITEMWIDTH(cf->menu) + MENU_H_BETWEEN; /* item width */ + size_t items = ui_cols / len; /* items per line */ + size_t extra = items < cf->menu->nitems ? /* extra space on line */ + ui_cols % len : /* - multi-line menu */ + ui_cols - (cf->menu->nitems * len); /* - one line menu */ + + if (items == 0) + return 0; /* hmm... no space */ + + extra += MENU_H_BETWEEN; /* add padding after last item to extra */ + + if (idx < items) + return (idx * len) + (extra / 2); + return ((idx % items) * len) + (extra / 2); + } +} + +static int menuitem_on_page(struct cfdisk *cf, size_t idx) +{ + struct cfdisk_menu *m = cf->menu; + + if (m->page_sz == 0 || + m->idx / m->page_sz == idx / m->page_sz) + return 1; + return 0; +} + +static struct cfdisk_menuitem *menu_get_menuitem(struct cfdisk *cf, size_t idx) +{ + struct cfdisk_menuitem *d; + size_t i; + + for (i = 0, d = cf->menu->items; d->name; d++) { + if (cf->menu->ignore && strchr(cf->menu->ignore, d->key)) + continue; + if (i++ == idx) + return d; + } + + return NULL; +} + +static struct cfdisk_menuitem *menu_get_menuitem_by_key(struct cfdisk *cf, + int key, size_t *idx) +{ + struct cfdisk_menuitem *d; + + for (*idx = 0, d = cf->menu->items; d->name; d++) { + if (cf->menu->ignore && strchr(cf->menu->ignore, d->key)) + continue; + if (key == d->key) + return d; + (*idx)++; + } + + return NULL; +} + +static void ui_draw_menuitem(struct cfdisk *cf, + struct cfdisk_menuitem *d, + size_t idx) +{ + char *buf, *ptr; + const char *name; + size_t width; + const size_t buf_sz = 80 * MB_CUR_MAX; + int ln, cl, vert = cf->menu->vertical; + + if (!menuitem_on_page(cf, idx)) + return; /* no visible item */ + ln = menuitem_get_line(cf, idx); + cl = menuitem_get_column(cf, idx); + + ptr = buf = xmalloc(buf_sz); + /* string width */ + if (vert) { + width = cf->menu->width + MENU_V_SPADDING; + memset(ptr, ' ', MENU_V_SPADDING); + ptr += MENU_V_SPADDING; + } else + width = MENU_H_SPADDING + cf->menu->width + MENU_H_SPADDING; + + name = _(d->name); + mbsalign(name, ptr, buf_sz, &width, + vert ? MBS_ALIGN_LEFT : MBS_ALIGN_CENTER, + 0); + + DBG(MENU, ul_debug("menuitem: cl=%d, ln=%d, item='%s'", + cl, ln, buf)); + + if (vert) { + mvaddch(ln, cl - 1, ACS_VLINE); + mvaddch(ln, cl + MENU_V_ITEMWIDTH(cf->menu), ACS_VLINE); + } + + if (cf->menu->idx == idx) + standout(); + + if (vert) + mvprintw(ln, cl, "%s", buf); + else + mvprintw(ln, cl, "%s%s%s", MENU_H_PRESTR, buf, MENU_H_POSTSTR); + free(buf); + + if (cf->menu->idx == idx) { + standend(); + if (d->desc) + ui_hint(_(d->desc)); + } +} + +static void ui_clean_menu(struct cfdisk *cf) +{ + size_t i; + size_t lastline; + struct cfdisk_menu *m = cf->menu; + size_t ln = menuitem_get_line(cf, 0); + + if (m->vertical) + lastline = ln + (m->page_sz ? m->page_sz : m->nitems); + else + lastline = menuitem_get_line(cf, m->nitems); + + for (i = ln; i <= lastline; i++) { + move(i, 0); + clrtoeol(); + DBG(MENU, ul_debug("clean_menu: line %zu", i)); + } + if (m->vertical) { + move(ln - 1, 0); + clrtoeol(); + } + ui_clean_hint(); +} + +static void ui_draw_menu(struct cfdisk *cf) +{ + struct cfdisk_menuitem *d; + struct cfdisk_menu *m; + size_t i = 0; + size_t ln = menuitem_get_line(cf, 0); + size_t nlines; + + assert(cf); + assert(cf->menu); + + DBG(MENU, ul_debug("draw start")); + + ui_clean_menu(cf); + m = cf->menu; + + if (m->vertical) + nlines = m->page_sz ? m->page_sz : m->nitems; + else + nlines = menuitem_get_line(cf, m->nitems); + + if (m->ignore_cb) + menu_update_ignore(cf); + i = 0; + while ((d = menu_get_menuitem(cf, i))) + ui_draw_menuitem(cf, d, i++); + + if (m->vertical) { + size_t cl = menuitem_get_column(cf, 0); + size_t curpg = m->page_sz ? m->idx / m->page_sz : 0; + + /* corners and horizontal lines */ + mvaddch(ln - 1, cl - 1, ACS_ULCORNER); + mvaddch(ln + nlines, cl - 1, ACS_LLCORNER); + + for (i = 0; i < MENU_V_ITEMWIDTH(m); i++) { + mvaddch(ln - 1, cl + i, ACS_HLINE); + mvaddch(ln + nlines, cl + i, ACS_HLINE); + } + + mvaddch(ln - 1, cl + i, ACS_URCORNER); + mvaddch(ln + nlines, cl + i, ACS_LRCORNER); + + /* draw also lines around empty lines on last page */ + if (m->page_sz && + m->nitems / m->page_sz == m->idx / m->page_sz) { + for (i = m->nitems % m->page_sz + 1; i <= m->page_sz; i++) { + mvaddch(i, cl - 1, ACS_VLINE); + mvaddch(i, cl + MENU_V_ITEMWIDTH(m), ACS_VLINE); + } + } + if (m->title) { + attron(A_BOLD); + mvprintw(ln - 1, cl, " %s ", m->title); + attroff(A_BOLD); + } + if (curpg != 0) + mvaddch(ln - 1, cl + MENU_V_ITEMWIDTH(m) - 2, ACS_UARROW); + if (m->page_sz && curpg < m->nitems / m->page_sz) + mvaddch(ln + nlines, cl + MENU_V_ITEMWIDTH(m) - 2, ACS_DARROW); + } + + DBG(MENU, ul_debug("draw end.")); +} + +inline static int extra_insert_pair(struct cfdisk_line *l, const char *name, const char *data) +{ + struct libscols_line *lsl; + int rc; + + assert(l); + assert(l->extra); + + if (!data || !*data) + return 0; + + lsl = scols_table_new_line(l->extra, NULL); + if (!lsl) + return -ENOMEM; + + rc = scols_line_set_data(lsl, 0, name); + if (!rc) + rc = scols_line_set_data(lsl, 1, data); + + return rc; +} + +#ifndef HAVE_LIBMOUNT +static char *get_mountpoint( struct cfdisk *cf __attribute__((unused)), + const char *tagname __attribute__((unused)), + const char *tagdata __attribute__((unused))) +{ + return NULL; +} +#else +static char *get_mountpoint(struct cfdisk *cf, const char *tagname, const char *tagdata) +{ + struct libmnt_fs *fs = NULL; + char *target = NULL; + int mounted = 0; + + assert(tagname); + assert(tagdata); + + DBG(UI, ul_debug("asking for mountpoint [%s=%s]", tagname, tagdata)); + + if (!cf->mntcache) + cf->mntcache = mnt_new_cache(); + + /* 1st try between mounted filesystems */ + if (!cf->mtab) { + cf->mtab = mnt_new_table(); + if (cf->mtab) { + mnt_table_set_cache(cf->mtab, cf->mntcache); + mnt_table_parse_mtab(cf->mtab, NULL); + } + } + + if (cf->mtab) + fs = mnt_table_find_tag(cf->mtab, tagname, tagdata, MNT_ITER_FORWARD); + + /* 2nd try fstab */ + if (!fs) { + if (!cf->fstab) { + cf->fstab = mnt_new_table(); + if (cf->fstab) { + mnt_table_set_cache(cf->fstab, cf->mntcache); + mnt_table_parse_fstab(cf->fstab, NULL); + } + } + if (cf->fstab) + fs = mnt_table_find_tag(cf->fstab, tagname, tagdata, MNT_ITER_FORWARD); + } else + mounted = 1; + + if (fs) { + if (mounted) + xasprintf(&target, _("%s (mounted)"), mnt_fs_get_target(fs)); + else + target = xstrdup(mnt_fs_get_target(fs)); + } + + return target; +} +#endif /* HAVE_LIBMOUNT */ + +static void extra_prepare_data(struct cfdisk *cf) +{ + struct fdisk_partition *pa = get_current_partition(cf); + struct cfdisk_line *l = &cf->lines[cf->lines_idx]; + char *data = NULL; + char *mountpoint = NULL; + + DBG(UI, ul_debug("preparing extra data")); + + /* string data should not equal an empty string */ + if (!fdisk_partition_to_string(pa, cf->cxt, FDISK_FIELD_NAME, &data) && data) { + extra_insert_pair(l, _("Partition name:"), data); + if (!mountpoint) + mountpoint = get_mountpoint(cf, "PARTLABEL", data); + free(data); + } + + if (!fdisk_partition_to_string(pa, cf->cxt, FDISK_FIELD_UUID, &data) && data) { + extra_insert_pair(l, _("Partition UUID:"), data); + if (!mountpoint) + mountpoint = get_mountpoint(cf, "PARTUUID", data); + free(data); + } + + if (!fdisk_partition_to_string(pa, cf->cxt, FDISK_FIELD_TYPE, &data) && data) { + char *code = NULL, *type = NULL; + + fdisk_partition_to_string(pa, cf->cxt, FDISK_FIELD_TYPEID, &code); + xasprintf(&type, "%s (%s)", data, code); + + extra_insert_pair(l, _("Partition type:"), type); + free(data); + free(code); + free(type); + } + + if (!fdisk_partition_to_string(pa, cf->cxt, FDISK_FIELD_ATTR, &data) && data) { + extra_insert_pair(l, _("Attributes:"), data); + free(data); + } + + /* for numeric data, only show non-zero rows */ + if (!fdisk_partition_to_string(pa, cf->cxt, FDISK_FIELD_BSIZE, &data) && data) { + if (atoi(data)) + extra_insert_pair(l, "BSIZE:", data); + free(data); + } + + if (!fdisk_partition_to_string(pa, cf->cxt, FDISK_FIELD_CPG, &data) && data) { + if (atoi(data)) + extra_insert_pair(l, "CPG:", data); + free(data); + } + + if (!fdisk_partition_to_string(pa, cf->cxt, FDISK_FIELD_FSIZE, &data) && data) { + if (atoi(data)) + extra_insert_pair(l, "FSIZE:", data); + free(data); + } + + if (!fdisk_partition_to_string(pa, cf->cxt, FDISK_FIELD_FSUUID, &data) && data) { + extra_insert_pair(l, _("Filesystem UUID:"), data); + if (!mountpoint) + mountpoint = get_mountpoint(cf, "UUID", data); + free(data); + } + + if (!fdisk_partition_to_string(pa, cf->cxt, FDISK_FIELD_FSLABEL, &data) && data) { + extra_insert_pair(l, _("Filesystem LABEL:"), data); + if (!mountpoint) + mountpoint = get_mountpoint(cf, "LABEL", data); + free(data); + } + if (!fdisk_partition_to_string(pa, cf->cxt, FDISK_FIELD_FSTYPE, &data) && data) { + extra_insert_pair(l, _("Filesystem:"), data); + free(data); + } + + if (mountpoint) { + extra_insert_pair(l, _("Mountpoint:"), mountpoint); + free(mountpoint); + } +} + +static int ui_draw_extra(struct cfdisk *cf) +{ + WINDOW *win_ex; + int wline = 1; + struct cfdisk_line *ln = &cf->lines[cf->lines_idx]; + char *tbstr = NULL, *end; + int win_ex_start_line, win_height, tblen; + int ndatalines; + + if (!cf->show_extra) + return 0; + + DBG(UI, ul_debug("draw extra")); + + assert(ln->extra); + + if (cf->act_win) { + wclear(cf->act_win); + touchwin(stdscr); + } + + if (scols_table_is_empty(ln->extra)) { + extra_prepare_data(cf); + if (scols_table_is_empty(ln->extra)) + return 0; + } + + ndatalines = fdisk_table_get_nents(cf->table) + 1; + + /* nents + header + one free line */ + win_ex_start_line = TABLE_START_LINE + ndatalines; + win_height = MENU_START_LINE - win_ex_start_line; + tblen = scols_table_get_nlines(ln->extra); + + /* we can't get a single line of data under the partlist*/ + if (win_height < 3) + return 1; + + /* number of data lines + 2 for top/bottom lines */ + win_height = win_height < tblen + 2 ? win_height : tblen + 2; + + if ((size_t) win_ex_start_line + win_height + 1 < MENU_START_LINE) + win_ex_start_line = MENU_START_LINE - win_height; + + win_ex = subwin(stdscr, win_height, ui_cols - 2, win_ex_start_line, 1); + + scols_table_reduce_termwidth(ln->extra, 4); + scols_print_table_to_string(ln->extra, &tbstr); + + end = tbstr; + while ((end = strchr(end, '\n'))) + *end++ = '\0'; + + box(win_ex, 0, 0); + + end = tbstr; + while (--win_height > 1) { + mvwaddstr(win_ex, wline++, 1 /* window column*/, tbstr); + tbstr += strlen(tbstr) + 1; + } + free(end); + + if (ln->w) + delwin(ln->w); + + DBG(UI, ul_debug("draw window: %p", win_ex)); + touchwin(stdscr); + wrefresh(win_ex); + + cf->act_win = ln->w = win_ex; + return 0; +} + +static void ui_menu_goto(struct cfdisk *cf, int where) +{ + struct cfdisk_menuitem *d; + size_t old; + + /* stop and begin/end for vertical menus */ + if (cf->menu->vertical) { + if (where < 0) + where = 0; + else if (where > (int) cf->menu->nitems - 1) + where = cf->menu->nitems - 1; + } else { + /* continue from begin/end */ + if (where < 0) + where = cf->menu->nitems - 1; + else if ((size_t) where > cf->menu->nitems - 1) + where = 0; + } + if ((size_t) where == cf->menu->idx) + return; + + ui_clean_info(); + + old = cf->menu->idx; + cf->menu->idx = where; + + if (!menuitem_on_page(cf, old)) { + ui_draw_menu(cf); + return; + } + + d = menu_get_menuitem(cf, old); + ui_draw_menuitem(cf, d, old); + + d = menu_get_menuitem(cf, where); + ui_draw_menuitem(cf, d, where); + +} + +static int ui_menu_move(struct cfdisk *cf, int key) +{ + struct cfdisk_menu *m; + + assert(cf); + assert(cf->menu); + + if (key == (int) ERR) + return 0; /* ignore errors */ + + m = cf->menu; + + DBG(MENU, ul_debug("menu move key >%c<.", key)); + + if (m->vertical) + { + switch (key) { + case KEY_DOWN: + case '\016': /* ^N */ + case 'j': /* Vi-like alternative */ + ui_menu_goto(cf, m->idx + 1); + return 0; + case KEY_UP: + case '\020': /* ^P */ + case 'k': /* Vi-like alternative */ + ui_menu_goto(cf, (int) m->idx - 1); + return 0; + case KEY_PPAGE: + if (m->page_sz) { + ui_menu_goto(cf, (int) m->idx - m->page_sz); + return 0; + } + /* fallthrough */ + case KEY_HOME: + ui_menu_goto(cf, 0); + return 0; + case KEY_NPAGE: + if (m->page_sz) { + ui_menu_goto(cf, m->idx + m->page_sz); + return 0; + } + /* fallthrough */ + case KEY_END: + ui_menu_goto(cf, m->nitems); + return 0; + } + } else { + switch (key) { + case KEY_RIGHT: + case '\t': + ui_menu_goto(cf, m->idx + 1); + return 0; + case KEY_LEFT: +#ifdef KEY_BTAB + case KEY_BTAB: +#endif + ui_menu_goto(cf, (int) m->idx - 1); + return 0; + } + } + + if (key == '\014') { /* ^L refresh */ + ui_menu_resize(cf); + return 0; + } + + DBG(MENU, ul_debug(" no menu move key")); + return 1; +} + +/* but don't call me from ui_run(), this is for pop-up menus only */ +static void ui_menu_resize(struct cfdisk *cf) +{ + DBG(MENU, ul_debug("menu resize/refresh")); + resize(); + ui_clean_menu(cf); + menu_refresh_size(cf); + ui_draw_menu(cf); + refresh(); +} + +static int partition_on_page(struct cfdisk *cf, size_t i) +{ + if (cf->page_sz == 0 || + cf->lines_idx / cf->page_sz == i / cf->page_sz) + return 1; + return 0; +} + +static void ui_draw_partition(struct cfdisk *cf, size_t i) +{ + int ln = TABLE_START_LINE + 1 + i; /* skip table header */ + int cl = ARROW_CURSOR_WIDTH; /* we need extra space for cursor */ + int cur = cf->lines_idx == i; + size_t curpg = 0; + + if (cf->page_sz) { + if (!partition_on_page(cf, i)) + return; + ln = TABLE_START_LINE + (i % cf->page_sz) + 1; + curpg = cf->lines_idx / cf->page_sz; + } + + DBG(UI, ul_debug( + "draw partition %zu [page_sz=%zu, " + "line=%d, idx=%zu]", + i, cf->page_sz, ln, cf->lines_idx)); + + if (cur) { + attron(A_REVERSE); + mvaddstr(ln, 0, ARROW_CURSOR_STRING); + mvaddstr(ln, cl, cf->lines[i + 1].data); + attroff(A_REVERSE); + } else { + int at = 0; + + if (colors_wanted() && is_freespace(cf, i)) { + attron(COLOR_PAIR(CFDISK_CL_FREESPACE)); + at = 1; + } + mvaddstr(ln, 0, ARROW_CURSOR_DUMMY); + mvaddstr(ln, cl, cf->lines[i + 1].data); + if (at) + attroff(COLOR_PAIR(CFDISK_CL_FREESPACE)); + } + + if ((size_t) ln == MENU_START_LINE - 1 && + cf->page_sz && curpg < cf->nlines / cf->page_sz) { + if (cur) + attron(A_REVERSE); + mvaddch(ln, ui_cols - 1, ACS_DARROW); + mvaddch(ln, 0, ACS_DARROW); + if (cur) + attroff(A_REVERSE); + } + +} + +static int ui_draw_table(struct cfdisk *cf) +{ + int cl = ARROW_CURSOR_WIDTH; + size_t i, nparts = fdisk_table_get_nents(cf->table); + size_t curpg = cf->page_sz ? cf->lines_idx / cf->page_sz : 0; + + DBG(UI, ul_debug("draw table")); + + for (i = TABLE_START_LINE; i <= TABLE_START_LINE + cf->page_sz; i++) { + move(i, 0); + clrtoeol(); + } + + if (nparts == 0 || (size_t) cf->lines_idx > nparts - 1) + cf->lines_idx = nparts ? nparts - 1 : 0; + + /* print header */ + attron(A_BOLD); + mvaddstr(TABLE_START_LINE, cl, cf->lines[0].data); + attroff(A_BOLD); + + /* print partitions */ + for (i = 0; i < nparts; i++) + ui_draw_partition(cf, i); + + if (curpg != 0) { + mvaddch(TABLE_START_LINE, ui_cols - 1, ACS_UARROW); + mvaddch(TABLE_START_LINE, 0, ACS_UARROW); + } + if (cf->page_sz && curpg < cf->nlines / cf->page_sz) { + mvaddch(MENU_START_LINE - 1, ui_cols - 1, ACS_DARROW); + mvaddch(MENU_START_LINE - 1, 0, ACS_DARROW); + } + return 0; +} + +static int ui_table_goto(struct cfdisk *cf, int where) +{ + size_t old; + size_t nparts = fdisk_table_get_nents(cf->table); + + DBG(UI, ul_debug("goto table %d", where)); + + if (where < 0) + where = 0; + else if ((size_t) where > nparts - 1) + where = nparts - 1; + + if ((size_t) where == cf->lines_idx) + return 0; + + old = cf->lines_idx; + cf->lines_idx = where; + + if (!partition_on_page(cf, old) ||!partition_on_page(cf, where)) + ui_draw_table(cf); + else { + ui_draw_partition(cf, old); /* cleanup old */ + ui_draw_partition(cf, where); /* draw new */ + } + ui_clean_info(); + ui_draw_menu(cf); + ui_draw_extra(cf); + refresh(); + + return 0; +} + +static int ui_refresh(struct cfdisk *cf) +{ + struct fdisk_label *lb; + char *id = NULL; + uint64_t bytes = fdisk_get_nsectors(cf->cxt) * fdisk_get_sector_size(cf->cxt); + char *strsz; + + if (!ui_enabled) + return -EINVAL; + + strsz = size_to_human_string(SIZE_SUFFIX_SPACE + | SIZE_SUFFIX_3LETTER, bytes); + + lb = fdisk_get_label(cf->cxt, NULL); + assert(lb); + + clear(); + + /* header */ + attron(A_BOLD); + ui_center(0, _("Disk: %s"), fdisk_get_devname(cf->cxt)); + attroff(A_BOLD); + ui_center(1, _("Size: %s, %"PRIu64" bytes, %ju sectors"), + strsz, bytes, (uintmax_t) fdisk_get_nsectors(cf->cxt)); + if (fdisk_get_disklabel_id(cf->cxt, &id) == 0 && id) + ui_center(2, _("Label: %s, identifier: %s"), + fdisk_label_get_name(lb), id); + else + ui_center(2, _("Label: %s"), fdisk_label_get_name(lb)); + free(strsz); + + ui_draw_table(cf); + ui_draw_menu(cf); + refresh(); + return 0; +} + +static ssize_t ui_get_string(const char *prompt, + const char *hint, char *buf, size_t len) +{ + int ln = MENU_START_LINE, cl = 1; + ssize_t rc = -1; + struct mbs_editor *edit; + + DBG(UI, ul_debug("ui get string")); + + assert(buf); + assert(len); + + move(ln, 0); + clrtoeol(); + + move(ln + 1, 0); + clrtoeol(); + + if (prompt) { + mvaddstr(ln, cl, prompt); + cl += mbs_safe_width(prompt); + } + + edit = mbs_new_edit(buf, len, ui_cols - cl); + if (!edit) + goto done; + + mbs_edit_goto(edit, MBS_EDIT_END); + + if (hint) + ui_hint(hint); + else + ui_clean_hint(); + + curs_set(1); + + while (!sig_die) { + wint_t c; /* we have fallback in widechar.h */ + + move(ln, cl); + clrtoeol(); + mvaddstr(ln, cl, edit->buf); + move(ln, cl + edit->cursor_cells); + refresh(); + +#if !defined(HAVE_SLCURSES_H) && !defined(HAVE_SLANG_SLCURSES_H) && \ + defined(HAVE_LIBNCURSESW) && defined(HAVE_WIDECHAR) + if (get_wch(&c) == ERR) { +#else + if ((c = getch()) == (wint_t) ERR) { +#endif + if (sig_die) + break; + if (sig_resize) { + resize(); + continue; + } + if (!isatty(STDIN_FILENO)) + exit(2); + else + goto done; + } + + DBG(UI, ul_debug("ui get string: key=%lc", c)); + + if (c == '\r' || c == '\n' || c == KEY_ENTER) + break; + + rc = 1; + + switch (c) { + case KEY_ESC: + rc = -CFDISK_ERR_ESC; + goto done; + case KEY_LEFT: + rc = mbs_edit_goto(edit, MBS_EDIT_LEFT); + break; + case KEY_RIGHT: + rc = mbs_edit_goto(edit, MBS_EDIT_RIGHT); + break; + case KEY_END: + rc = mbs_edit_goto(edit, MBS_EDIT_END); + break; + case KEY_HOME: + rc = mbs_edit_goto(edit, MBS_EDIT_HOME); + break; + case KEY_UP: + case KEY_DOWN: + break; + case KEY_DC: + rc = mbs_edit_delete(edit); + break; + case '\b': + case KEY_DELETE: + case KEY_BACKSPACE: + rc = mbs_edit_backspace(edit); + break; + default: + rc = mbs_edit_insert(edit, c); + break; + } + if (rc == 1) + beep(); + } + + if (sig_die) + die_on_signal(); + + rc = strlen(edit->buf); /* success */ +done: + move(ln, 0); + clrtoeol(); + curs_set(0); + refresh(); + mbs_free_edit(edit); + + return rc; +} + +static int ui_get_size(struct cfdisk *cf, /* context */ + const char *prompt, /* UI dialog string */ + uint64_t *res, /* result in bytes */ + uint64_t low, /* minimal size */ + uint64_t up, /* maximal size */ + int *expsize) /* explicitly specified size */ +{ + char buf[128]; + uint64_t user = 0; + ssize_t rc; + char *dflt = size_to_human_string(0, *res); + + DBG(UI, ul_debug("get_size (default=%"PRIu64")", *res)); + + ui_clean_info(); + + snprintf(buf, sizeof(buf), "%s", dflt); + + do { + int pwr = 0, insec = 0; + + rc = ui_get_string(prompt, + _("May be followed by M for MiB, G for GiB, " + "T for TiB, or S for sectors."), + buf, sizeof(buf)); + ui_clean_warn(); + + if (rc == 0) { + ui_warnx(_("Please, specify size.")); + continue; /* nothing specified */ + } else if (rc == -CFDISK_ERR_ESC) + break; /* cancel dialog */ + + if (strcmp(buf, dflt) == 0) + user = *res, rc = 0; /* no change, use default */ + else { + size_t len = strlen(buf); + if (buf[len - 1] == 'S' || buf[len - 1] == 's') { + insec = 1; + buf[len - 1] = '\0'; + } + rc = parse_size(buf, (uintmax_t *)&user, &pwr); /* parse */ + } + + if (rc == 0) { + DBG(UI, ul_debug("get_size user=%"PRIu64", power=%d, in-sectors=%s", + user, pwr, insec ? "yes" : "no")); + if (insec) + user *= fdisk_get_sector_size(cf->cxt); + if (user < low) { + ui_warnx(_("Minimum size is %"PRIu64" bytes."), low); + rc = -ERANGE; + } + if (user > up && pwr && user < up + (1ULL << pwr * 10)) + /* ignore when the user specified size overflow + * with in range specified by suffix (e.g. MiB) */ + user = up; + + if (user > up) { + ui_warnx(_("Maximum size is %"PRIu64" bytes."), up); + rc = -ERANGE; + } + if (rc == 0 && insec && expsize) + *expsize = 1; + + } else + ui_warnx(_("Failed to parse size.")); + } while (rc != 0); + + if (rc == 0) + *res = user; + free(dflt); + + DBG(UI, ul_debug("get_size (result=%"PRIu64", rc=%zd)", *res, rc)); + return rc; +} + +static struct fdisk_parttype *ui_get_parttype(struct cfdisk *cf, + struct fdisk_parttype *cur) +{ + struct cfdisk_menuitem *d, *cm; + size_t i = 0, nitems, idx = 0; + struct fdisk_parttype *t = NULL; + struct fdisk_label *lb; + int codetypes = 0; + + DBG(UI, ul_debug("asking for parttype.")); + + lb = fdisk_get_label(cf->cxt, NULL); + + /* create cfdisk menu according to label types, note that the + * last cm[] item has to be empty -- so nitems + 1 */ + nitems = fdisk_label_get_nparttypes(lb); + if (!nitems) + return NULL; + + cm = xcalloc(nitems + 1, sizeof(struct cfdisk_menuitem)); + if (!cm) + return NULL; + + codetypes = fdisk_label_has_code_parttypes(lb); + + for (i = 0; i < nitems; i++) { + const struct fdisk_parttype *x = fdisk_label_get_parttype(lb, i); + char *name; + + cm[i].userdata = (void *) x; + if (codetypes) + xasprintf(&name, "%2x %s", + fdisk_parttype_get_code(x), + _(fdisk_parttype_get_name(x))); + else { + name = (char *) _(fdisk_parttype_get_name(x)); + cm[i].desc = fdisk_parttype_get_string(x); + } + cm[i].name = name; + if (x == cur) + idx = i; + } + + /* make the new menu active */ + menu_push(cf, cm); + cf->menu->vertical = 1; + cf->menu->idx = idx; + menu_set_title(cf->menu, _("Select partition type")); + ui_draw_menu(cf); + refresh(); + + while (!sig_die) { + int key = getch(); + + if (sig_die) + break; + if (sig_resize) + ui_menu_resize(cf); + if (ui_menu_move(cf, key) == 0) + continue; + + switch (key) { + case KEY_ENTER: + case '\n': + case '\r': + d = menu_get_menuitem(cf, cf->menu->idx); + if (d) + t = (struct fdisk_parttype *) d->userdata; + goto done; + case KEY_ESC: + case 'q': + case 'Q': + goto done; + } + } + + if (sig_die) + die_on_signal(); +done: + menu_pop(cf); + if (codetypes) { + for (i = 0; i < nitems; i++) + free((char *) cm[i].name); + } + free(cm); + DBG(UI, ul_debug("get parrtype done [type=%s] ", t ? + fdisk_parttype_get_name(t) : NULL)); + return t; +} + +static int ui_script_read(struct cfdisk *cf) +{ + struct fdisk_script *sc = NULL; + char buf[PATH_MAX] = { 0 }; + int rc; + + erase(); + rc = ui_get_string( _("Enter script file name: "), + _("The script file will be applied to in-memory partition table."), + buf, sizeof(buf)); + if (rc <= 0) + return rc; + + rc = -1; + errno = 0; + sc = fdisk_new_script_from_file(cf->cxt, buf); + if (!sc && errno) + ui_warn(_("Cannot open %s"), buf); + else if (!sc) + ui_warnx(_("Failed to parse script file %s"), buf); + else if (fdisk_apply_script(cf->cxt, sc) != 0) + ui_warnx(_("Failed to apply script %s"), buf); + else + rc = 0; + + ui_clean_hint(); + fdisk_unref_script(sc); + return rc; +} + +static int ui_script_write(struct cfdisk *cf) +{ + struct fdisk_script *sc = NULL; + char buf[PATH_MAX] = { 0 }; + FILE *f = NULL; + int rc; + + rc = ui_get_string( _("Enter script file name: "), + _("The current in-memory partition table will be dumped to the file."), + buf, sizeof(buf)); + if (rc <= 0) + return rc; + + rc = 0; + sc = fdisk_new_script(cf->cxt); + if (!sc) { + ui_warn(_("Failed to allocate script handler")); + goto done; + } + + rc = fdisk_script_read_context(sc, NULL); + if (rc) { + ui_warnx(_("Failed to read disk layout into script.")); + goto done; + } + + DBG(UI, ul_debug("writing dump into: '%s'", buf)); + f = fopen(buf, "w"); + if (!f) { + ui_warn(_("Cannot open %s"), buf); + rc = -errno; + goto done; + } + + rc = fdisk_script_write_file(sc, f); + if (!rc) + ui_info(_("Disk layout successfully dumped.")); +done: + if (rc) + ui_warn(_("Failed to write script %s"), buf); + if (f) + fclose(f); + fdisk_unref_script(sc); + return rc; +} + +/* prints menu with libfdisk labels and waits for users response */ +static int ui_create_label(struct cfdisk *cf) +{ + struct cfdisk_menuitem *d, *cm; + int rc = 1, refresh_menu = 1; + size_t i = 0, nitems; + struct fdisk_label *lb = NULL; + + assert(cf); + + DBG(UI, ul_debug("asking for new disklabe.")); + + /* create cfdisk menu according to libfdisk labels, note that the + * last cm[] item has to be empty -- so nitems + 1 */ + nitems = fdisk_get_nlabels(cf->cxt); + cm = xcalloc(nitems + 1, sizeof(struct cfdisk_menuitem)); + + while (fdisk_next_label(cf->cxt, &lb) == 0) { + if (fdisk_label_is_disabled(lb) || + fdisk_label_get_type(lb) == FDISK_DISKLABEL_BSD) + continue; + cm[i++].name = fdisk_label_get_name(lb); + } + + erase(); + + /* make the new menu active */ + menu_push(cf, cm); + cf->menu->vertical = 1; + menu_set_title(cf->menu, _("Select label type")); + + if (!cf->zero_start) + ui_info(_("Device does not contain a recognized partition table.")); + + + while (!sig_die) { + int key; + + if (refresh_menu) { + ui_draw_menu(cf); + ui_hint(_("Select a type to create a new label or press 'L' to load script file.")); + refresh(); + refresh_menu = 0; + } + + key = getch(); + + if (sig_die) + break; + if (sig_resize) + ui_menu_resize(cf); + if (ui_menu_move(cf, key) == 0) + continue; + switch (key) { + case KEY_ENTER: + case '\n': + case '\r': + d = menu_get_menuitem(cf, cf->menu->idx); + if (d) + rc = fdisk_create_disklabel(cf->cxt, d->name); + goto done; + case KEY_ESC: + case 'q': + case 'Q': + goto done; + case 'l': + case 'L': + rc = ui_script_read(cf); + if (rc == 0) + goto done; + refresh_menu = 1; + break; + } + } + + if (sig_die) + die_on_signal(); +done: + menu_pop(cf); + free(cm); + DBG(UI, ul_debug("create label done [rc=%d] ", rc)); + return rc; +} + + +static int ui_help(void) +{ + size_t i; + static const char *help[] = { + N_("This is cfdisk, a curses-based disk partitioning program."), + N_("It lets you create, delete, and modify partitions on a block device."), + " ", + N_("Command Meaning"), + N_("------- -------"), + N_(" b Toggle bootable flag of the current partition"), + N_(" d Delete the current partition"), + N_(" h Print this screen"), + N_(" n Create new partition from free space"), + N_(" q Quit program without writing partition table"), + N_(" s Fix partitions order (only when in disarray)"), + N_(" t Change the partition type"), + N_(" u Dump disk layout to sfdisk compatible script file"), + N_(" W Write partition table to disk (you must enter uppercase W);"), + N_(" since this might destroy data on the disk, you must either"), + N_(" confirm or deny the write by entering 'yes' or 'no'"), + N_(" x Display/hide extra information about a partition"), + N_("Up Arrow Move cursor to the previous partition"), + N_("Down Arrow Move cursor to the next partition"), + N_("Left Arrow Move cursor to the previous menu item"), + N_("Right Arrow Move cursor to the next menu item"), + " ", + N_("Note: All of the commands can be entered with either upper or lower"), + N_("case letters (except for Write)."), + " ", + N_("Use lsblk(8) or partx(8) to see more details about the device."), + " ", + " ", + "Copyright (C) 2014-2017 Karel Zak <kzak@redhat.com>" + }; + + erase(); + for (i = 0; i < ARRAY_SIZE(help); i++) + mvaddstr(i, 1, _(help[i])); + + ui_info(_("Press a key to continue.")); + + getch(); + + if (sig_die) + die_on_signal(); + return 0; +} + +/* TODO: use @sz, now 128bytes */ +static int main_menu_ignore_keys(struct cfdisk *cf, char *ignore, + size_t sz __attribute__((__unused__))) +{ + struct fdisk_partition *pa = get_current_partition(cf); + size_t i = 0; + + if (!pa) + return 0; + if (fdisk_partition_is_freespace(pa)) { + ignore[i++] = 'd'; /* delete */ + ignore[i++] = 't'; /* set type */ + ignore[i++] = 'b'; /* set bootable */ + ignore[i++] = 'r'; /* resize */ + cf->menu->prefkey = 'n'; + } else { + cf->menu->prefkey = 'q'; + ignore[i++] = 'n'; + if (!fdisk_is_label(cf->cxt, DOS) && + !fdisk_is_label(cf->cxt, SGI)) + ignore[i++] = 'b'; + } + + if (!cf->wrong_order) + ignore[i++] = 's'; + + if (fdisk_is_readonly(cf->cxt)) + ignore[i++] = 'W'; + + return i; +} + + +/* returns: error: < 0, success: 0, quit: 1 */ +static int main_menu_action(struct cfdisk *cf, int key) +{ + size_t n; + int ref = 0, rc, org_order = cf->wrong_order; + const char *info = NULL, *warn = NULL; + struct fdisk_partition *pa; + + assert(cf); + assert(cf->cxt); + assert(cf->menu); + + if (key == 0) { + struct cfdisk_menuitem *d = menu_get_menuitem(cf, cf->menu->idx); + if (!d) + return 0; + key = d->key; + + } else if (key != 'w' && key != 'W') + key = tolower(key); /* case insensitive except 'W'rite */ + + DBG(MENU, ul_debug("main menu action: key=%c", key)); + + if (cf->menu->ignore && strchr(cf->menu->ignore, key)) { + DBG(MENU, ul_debug(" ignore '%c'", key)); + return 0; + } + + pa = get_current_partition(cf); + if (!pa) + return -EINVAL; + n = fdisk_partition_get_partno(pa); + + DBG(MENU, ul_debug("menu action on %p", pa)); + ui_clean_hint(); + ui_clean_info(); + + switch (key) { + case 'b': /* Bootable flag */ + { + int fl = fdisk_is_label(cf->cxt, DOS) ? DOS_FLAG_ACTIVE : + fdisk_is_label(cf->cxt, SGI) ? SGI_FLAG_BOOT : 0; + + if (fl && fdisk_toggle_partition_flag(cf->cxt, n, fl)) + warn = _("Could not toggle the flag."); + else if (fl) + ref = 1; + break; + } +#ifdef KEY_DC + case KEY_DC: +#endif + case 'd': /* Delete */ + if (fdisk_delete_partition(cf->cxt, n) != 0) + warn = _("Could not delete partition %zu."); + else + info = _("Partition %zu has been deleted."); + ref = 1; + break; + case 'h': /* Help */ + case '?': + ui_help(); + ref = 1; + break; + case 'n': /* New */ + { + uint64_t start, size, dflt_size, secs, max_size; + struct fdisk_partition *npa; /* the new partition */ + int expsize = 0; /* size specified explicitly in sectors */ + + if (!fdisk_partition_is_freespace(pa) || !fdisk_partition_has_start(pa)) + return -EINVAL; + + /* free space range */ + start = fdisk_partition_get_start(pa); + size = max_size = dflt_size = fdisk_partition_get_size(pa) * fdisk_get_sector_size(cf->cxt); + + if (ui_get_size(cf, _("Partition size: "), &size, + fdisk_get_sector_size(cf->cxt), + max_size, &expsize) == -CFDISK_ERR_ESC) + break; + + secs = size / fdisk_get_sector_size(cf->cxt); + + npa = fdisk_new_partition(); + if (!npa) + return -ENOMEM; + + if (dflt_size == size) /* default is to fillin all free space */ + fdisk_partition_end_follow_default(npa, 1); + else + fdisk_partition_set_size(npa, secs); + + if (expsize) + fdisk_partition_size_explicit(pa, 1); + + fdisk_partition_set_start(npa, start); + fdisk_partition_partno_follow_default(npa, 1); + /* add to disk label -- libfdisk will ask for missing details */ + rc = fdisk_add_partition(cf->cxt, npa, NULL); + fdisk_unref_partition(npa); + if (rc == 0) + ref = 1; + break; + } + case 'q': /* Quit */ + return 1; + case 't': /* Type */ + { + struct fdisk_parttype *t; + + if (fdisk_partition_is_freespace(pa)) + return -EINVAL; + t = (struct fdisk_parttype *) fdisk_partition_get_type(pa); + t = ui_get_parttype(cf, t); + ref = 1; + + if (t && fdisk_set_partition_type(cf->cxt, n, t) == 0) + info = _("Changed type of partition %zu."); + else + info = _("The type of partition %zu is unchanged."); + break; + } + case 'r': /* resize */ + { + struct fdisk_partition *npa, *next; + uint64_t size, max_size, secs; + + if (fdisk_partition_is_freespace(pa) || !fdisk_partition_has_start(pa)) + return -EINVAL; + + size = fdisk_partition_get_size(pa); + + /* is the next freespace? */ + next = fdisk_table_get_partition(cf->table, cf->lines_idx + 1); + if (next && fdisk_partition_is_freespace(next)) + size += fdisk_partition_get_size(next); + + size *= fdisk_get_sector_size(cf->cxt); + max_size = size; + + if (ui_get_size(cf, _("New size: "), &size, + fdisk_get_sector_size(cf->cxt), + max_size, NULL) == -CFDISK_ERR_ESC) + break; + secs = size / fdisk_get_sector_size(cf->cxt); + npa = fdisk_new_partition(); + if (!npa) + return -ENOMEM; + + fdisk_partition_set_size(npa, secs); + + rc = fdisk_set_partition(cf->cxt, n, npa); + fdisk_unref_partition(npa); + if (rc == 0) { + ref = 1; + info = _("Partition %zu resized."); + } + break; + } + case 's': /* Sort */ + if (cf->wrong_order) { + fdisk_reorder_partitions(cf->cxt); + ref = 1; + } + break; + case 'u': /* dUmp */ + ui_script_write(cf); + break; + case 'W': /* Write */ + { + char buf[64] = { 0 }; + + if (fdisk_is_readonly(cf->cxt)) { + warn = _("Device is open in read-only mode."); + break; + } + + rc = ui_get_string( + _("Are you sure you want to write the partition " + "table to disk? "), + _("Type \"yes\" or \"no\", or press ESC to leave this dialog."), + buf, sizeof(buf)); + + ref = 1; + if (rc <= 0 || (strcasecmp(buf, "yes") != 0 && + strcasecmp(buf, _("yes")) != 0)) { + info = _("Did not write partition table to disk."); + break; + } + rc = fdisk_write_disklabel(cf->cxt); + if (rc) + warn = _("Failed to write disklabel."); + else { + if (cf->device_is_used) + fdisk_reread_changes(cf->cxt, cf->original_layout); + else + fdisk_reread_partition_table(cf->cxt); + info = _("The partition table has been altered."); + } + cf->nwrites++; + break; + } + default: + break; + } + + if (ref) { + lines_refresh(cf); + ui_refresh(cf); + ui_draw_extra(cf); + } else + ui_draw_menu(cf); + + ui_clean_hint(); + + if (warn) + ui_warnx(warn, n + 1); + else if (info) + ui_info(info, n + 1); + else if (key == 'n' && cf->wrong_order && org_order == 0) + ui_info(_("Note that partition table entries are not in disk order now.")); + + return 0; +} + +static void ui_resize_refresh(struct cfdisk *cf) +{ + DBG(UI, ul_debug("ui resize/refresh")); + resize(); + menu_refresh_size(cf); + lines_refresh(cf); + ui_refresh(cf); + ui_draw_extra(cf); +} + +static void toggle_show_extra(struct cfdisk *cf) +{ + if (cf->show_extra && cf->act_win) { + wclear(cf->act_win); + touchwin(stdscr); + } + cf->show_extra = cf->show_extra ? 0 : 1; + + if (cf->show_extra) + ui_draw_extra(cf); + DBG(MENU, ul_debug("extra: %s", cf->show_extra ? "ENABLED" : "DISABLED" )); +} + +static int ui_run(struct cfdisk *cf) +{ + int rc = 0; + + ui_lines = LINES; + ui_cols = COLS; + DBG(UI, ul_debug("start cols=%zu, lines=%zu", ui_cols, ui_lines)); + + if (fdisk_get_collision(cf->cxt)) { + ui_warnx(_("Device already contains a %s signature; it will be removed by a write command."), + fdisk_get_collision(cf->cxt)); + fdisk_enable_wipe(cf->cxt, 1); + ui_hint(_("Press a key to continue.")); + getch(); + } + + if (!fdisk_has_label(cf->cxt) || cf->zero_start) { + rc = ui_create_label(cf); + if (rc < 0) + ui_errx(EXIT_FAILURE, + _("failed to create a new disklabel")); + if (rc) + return rc; + } + + cols_init(cf); + rc = lines_refresh(cf); + if (rc) + ui_errx(EXIT_FAILURE, _("failed to read partitions")); + + menu_push(cf, main_menuitems); + cf->menu->ignore_cb = main_menu_ignore_keys; + + rc = ui_refresh(cf); + if (rc) + return rc; + + cf->show_extra = 1; + ui_draw_extra(cf); + + if (fdisk_is_readonly(cf->cxt)) + ui_warnx(_("Device is open in read-only mode.")); + else if (cf->wrong_order) + ui_info(_("Note that partition table entries are not in disk order now.")); + + while (!sig_die) { + int key = getch(); + + rc = 0; + + if (sig_die) + break; + if (sig_resize) + /* Note that ncurses getch() returns ERR when interrupted + * by signal, but SLang does not interrupt at all. */ + ui_resize_refresh(cf); + if (key == ERR) + continue; + if (key == '\014') { /* ^L refresh */ + ui_resize_refresh(cf); + continue; + } + if (ui_menu_move(cf, key) == 0) + continue; + + DBG(UI, ul_debug("main action key >%1$c< [\\0%1$o].", key)); + + switch (key) { + case KEY_DOWN: + case '\016': /* ^N */ + case 'j': /* Vi-like alternative */ + ui_table_goto(cf, cf->lines_idx + 1); + break; + case KEY_UP: + case '\020': /* ^P */ + case 'k': /* Vi-like alternative */ + ui_table_goto(cf, (int) cf->lines_idx - 1); + break; + case KEY_PPAGE: + if (cf->page_sz) { + ui_table_goto(cf, (int) cf->lines_idx - cf->page_sz); + break; + } + /* fallthrough */ + case KEY_HOME: + ui_table_goto(cf, 0); + break; + case KEY_NPAGE: + if (cf->page_sz) { + ui_table_goto(cf, cf->lines_idx + cf->page_sz); + break; + } + /* fallthrough */ + case KEY_END: + ui_table_goto(cf, (int) cf->nlines - 1); + break; + case KEY_ENTER: + case '\n': + case '\r': + rc = main_menu_action(cf, 0); + break; + case 'X': + case 'x': /* Extra */ + toggle_show_extra(cf); + break; + default: + rc = main_menu_action(cf, key); + if (rc < 0) + beep(); + break; + } + + if (rc == 1) + break; /* quit */ + } + + menu_pop(cf); + + DBG(UI, ul_debug("end")); + return 0; +} + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *out = stdout; + fputs(USAGE_HEADER, out); + fprintf(out, + _(" %1$s [options] <disk>\n"), program_invocation_short_name); + + fputs(USAGE_SEPARATOR, out); + fputs(_("Display or manipulate a disk partition table.\n"), out); + + fputs(USAGE_OPTIONS, out); + fputs(_(" -L, --color[=<when>] colorize output (auto, always or never)\n"), out); + fprintf(out, + " %s\n", USAGE_COLORS_DEFAULT); + fputs(_(" -z, --zero start with zeroed partition table\n"), out); + + fputs(USAGE_SEPARATOR, out); + printf(USAGE_HELP_OPTIONS(26)); + + printf(USAGE_MAN_TAIL("cfdisk(8)")); + exit(EXIT_SUCCESS); +} + +int main(int argc, char *argv[]) +{ + const char *diskpath = NULL; + int rc, c, colormode = UL_COLORMODE_UNDEF; + struct cfdisk _cf = { .lines_idx = 0 }, + *cf = &_cf; + + static const struct option longopts[] = { + { "color", optional_argument, NULL, 'L' }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + { "zero", no_argument, NULL, 'z' }, + { NULL, 0, NULL, 0 }, + }; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + while((c = getopt_long(argc, argv, "L::hVz", longopts, NULL)) != -1) { + switch(c) { + case 'h': + usage(); + break; + case 'L': + colormode = UL_COLORMODE_AUTO; + if (optarg) + colormode = colormode_or_err(optarg, + _("unsupported color mode")); + break; + case 'V': + printf(UTIL_LINUX_VERSION); + return EXIT_SUCCESS; + case 'z': + cf->zero_start = 1; + break; + default: + errtryhelp(EXIT_FAILURE); + } + } + + colors_init(colormode, "cfdisk"); + + fdisk_init_debug(0); + scols_init_debug(0); + cfdisk_init_debug(); + cf->cxt = fdisk_new_context(); + if (!cf->cxt) + err(EXIT_FAILURE, _("failed to allocate libfdisk context")); + + fdisk_set_ask(cf->cxt, ask_callback, (void *) cf); + + if (optind == argc) { + size_t i; + + for (i = 0; i < ARRAY_SIZE(default_disks); i++) { + if (access(default_disks[i], F_OK) == 0) { + diskpath = default_disks[i]; + break; + } + } + if (!diskpath) + diskpath = default_disks[0]; /* default, used for "cannot open" */ + } else + diskpath = argv[optind]; + + rc = fdisk_assign_device(cf->cxt, diskpath, 0); + if (rc == -EACCES) + rc = fdisk_assign_device(cf->cxt, diskpath, 1); + if (rc != 0) + err(EXIT_FAILURE, _("cannot open %s"), diskpath); + + if (!fdisk_is_readonly(cf->cxt)) { + cf->device_is_used = fdisk_device_is_used(cf->cxt); + fdisk_get_partitions(cf->cxt, &cf->original_layout); + } + + /* Don't use err(), warn() from this point */ + ui_init(cf); + ui_run(cf); + ui_end(); + + cfdisk_free_lines(cf); + free(cf->linesbuf); + + fdisk_unref_table(cf->table); +#ifdef HAVE_LIBMOUNT + mnt_unref_table(cf->fstab); + mnt_unref_table(cf->mtab); + mnt_unref_cache(cf->mntcache); +#endif + rc = fdisk_deassign_device(cf->cxt, cf->nwrites == 0); + fdisk_unref_context(cf->cxt); + DBG(MISC, ul_debug("bye! [rc=%d]", rc)); + return rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/disk-utils/cramfs.h b/disk-utils/cramfs.h new file mode 100644 index 0000000..e0ddbda --- /dev/null +++ b/disk-utils/cramfs.h @@ -0,0 +1,114 @@ +/* + * cramfs_common - cramfs common code + * + * Copyright (c) 2008 Roy Peled, the.roy.peled -at- gmail + * Copyright (c) 2004-2006 by Juliane Holzt, kju -at- fqdn.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __CRAMFS_H +#define __CRAMFS_H + +#include <stdint.h> + +#define CRAMFS_MAGIC 0x28cd3d45 /* some random number */ +#define CRAMFS_SIGNATURE "Compressed ROMFS" + +/* + * Width of various bitfields in struct cramfs_inode. + * Primarily used to generate warnings in mkcramfs. + */ +#define CRAMFS_MODE_WIDTH 16 +#define CRAMFS_UID_WIDTH 16 +#define CRAMFS_SIZE_WIDTH 24 +#define CRAMFS_GID_WIDTH 8 +#define CRAMFS_NAMELEN_WIDTH 6 +#define CRAMFS_OFFSET_WIDTH 26 + +#ifndef HOST_IS_BIG_ENDIAN +#ifdef WORDS_BIGENDIAN +#define HOST_IS_BIG_ENDIAN 1 +#else +#define HOST_IS_BIG_ENDIAN 0 +#endif +#endif + +/* + * Reasonably terse representation of the inode data. + */ +struct cramfs_inode { + uint32_t mode:16, uid:16; + /* SIZE for device files is i_rdev */ + uint32_t size:24, gid:8; + /* + * NAMELEN is the length of the file name, divided by 4 and + * rounded up. (cramfs doesn't support hard links.) + * + * OFFSET: For symlinks and non-empty regular files, this + * contains the offset (divided by 4) of the file data in + * compressed form (starting with an array of block pointers; + * see README). For non-empty directories it is the offset + * (divided by 4) of the inode of the first file in that + * directory. For anything else, offset is zero. + */ + uint32_t namelen:6, offset:26; +}; + +struct cramfs_info { + uint32_t crc; + uint32_t edition; + uint32_t blocks; + uint32_t files; +}; + +/* + * Superblock information at the beginning of the FS. + */ +struct cramfs_super { + uint32_t magic; /* 0x28cd3d45 - random number */ + uint32_t size; /* Not used. mkcramfs currently + writes a constant 1<<16 here. */ + uint32_t flags; /* 0 */ + uint32_t future; /* 0 */ + uint8_t signature[16]; /* "Compressed ROMFS" */ + struct cramfs_info fsid;/* unique filesystem info */ + uint8_t name[16]; /* user-defined name */ + struct cramfs_inode root; /* Root inode data */ +}; + +#define CRAMFS_FLAG_FSID_VERSION_2 0x00000001 /* fsid version #2 */ +#define CRAMFS_FLAG_SORTED_DIRS 0x00000002 /* sorted dirs */ +#define CRAMFS_FLAG_HOLES 0x00000100 /* support for holes */ +#define CRAMFS_FLAG_WRONG_SIGNATURE 0x00000200 /* reserved */ +#define CRAMFS_FLAG_SHIFTED_ROOT_OFFSET 0x00000400 /* shifted root fs */ + +/* + * Valid values in super.flags. Currently we refuse to mount + * if (flags & ~CRAMFS_SUPPORTED_FLAGS). Maybe that should be + * changed to test super.future instead. + */ +#define CRAMFS_SUPPORTED_FLAGS (0xff) + +/* Uncompression interfaces to the underlying zlib */ +int cramfs_uncompress_block(void *dst, int dstlen, void *src, int srclen); +int cramfs_uncompress_init(void); +int cramfs_uncompress_exit(void); + +uint32_t u32_toggle_endianness(int big_endian, uint32_t what); +void super_toggle_endianness(int from_big_endian, struct cramfs_super *super); +void inode_to_host(int from_big_endian, struct cramfs_inode *inode_in, + struct cramfs_inode *inode_out); +void inode_from_host(int to_big_endian, struct cramfs_inode *inode_in, + struct cramfs_inode *inode_out); + +#endif diff --git a/disk-utils/cramfs_common.c b/disk-utils/cramfs_common.c new file mode 100644 index 0000000..9fe3fa1 --- /dev/null +++ b/disk-utils/cramfs_common.c @@ -0,0 +1,109 @@ +/* + * cramfs_common - cramfs common code + * + * Copyright (c) 2008 Roy Peled, the.roy.peled -at- gmail.com + * Copyright (c) 2004-2006 by Juliane Holzt, kju -at- fqdn.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <string.h> +#include "cramfs.h" +#include "../include/bitops.h" + +uint32_t u32_toggle_endianness(int big_endian, uint32_t what) +{ + return big_endian == HOST_IS_BIG_ENDIAN ? what : swab32(what); +} + +void super_toggle_endianness(int big_endian, struct cramfs_super *super) +{ + if (big_endian != HOST_IS_BIG_ENDIAN) { + super->magic = swab32(super->magic); + super->size = swab32(super->size); + super->flags = swab32(super->flags); + super->future = swab32(super->future); + super->fsid.crc = swab32(super->fsid.crc); + super->fsid.edition = swab32(super->fsid.edition); + super->fsid.blocks = swab32(super->fsid.blocks); + super->fsid.files = swab32(super->fsid.files); + } +} + +static void inode_toggle_endianness(int input_big_endian, int output_big_endian, + struct cramfs_inode *inode_in, + struct cramfs_inode *inode_out) +{ + if (input_big_endian == output_big_endian) { + memmove(inode_out, inode_in, sizeof(*inode_out)); + } else { + unsigned char inode_out_buf[sizeof(*inode_in)]; + unsigned char *inode_in_buf = (unsigned char*)inode_in; + + inode_out_buf[0] = inode_in_buf[1]; /* 16 bit: mode */ + inode_out_buf[1] = inode_in_buf[0]; + + inode_out_buf[2] = inode_in_buf[3]; /* 16 bit: uid */ + inode_out_buf[3] = inode_in_buf[2]; + + inode_out_buf[4] = inode_in_buf[6]; /* 24 bit: size */ + inode_out_buf[5] = inode_in_buf[5]; + inode_out_buf[6] = inode_in_buf[4]; + + inode_out_buf[7] = inode_in_buf[7]; /* 8 bit: gid width */ + + /* + * Stop the madness! Outlaw C bitfields! They are unportable + * and nasty! See for yourself what a mess this is: + */ + if (output_big_endian) { + inode_out_buf[ 8] = ( (inode_in_buf[ 8]&0x3F) << 2 ) | + ( (inode_in_buf[11]&0xC0) >> 6 ); + + inode_out_buf[ 9] = ( (inode_in_buf[11]&0x3F) << 2 ) | + ( (inode_in_buf[10]&0xC0) >> 6 ); + + inode_out_buf[10] = ( (inode_in_buf[10]&0x3F) << 2 ) | + ( (inode_in_buf[ 9]&0xC0) >> 6 ); + + inode_out_buf[11] = ( (inode_in_buf[ 9]&0x3F) << 2 ) | + ( (inode_in_buf[ 8]&0xC0) >> 6 ); + } else { + inode_out_buf[ 8] = ( (inode_in_buf[ 8]&0xFD) >> 2 ) | + ( (inode_in_buf[11]&0x03) << 6 ); + + inode_out_buf[ 9] = ( (inode_in_buf[11]&0xFD) >> 2 ) | + ( (inode_in_buf[10]&0x03) << 6 ); + + inode_out_buf[10] = ( (inode_in_buf[10]&0xFD) >> 2 ) | + ( (inode_in_buf[ 9]&0x03) << 6 ); + + inode_out_buf[11] = ( (inode_in_buf[ 9]&0xFD) >> 2 ) | + ( (inode_in_buf[ 8]&0x03) << 6 ); + } + memmove(inode_out, inode_out_buf, sizeof(*inode_out)); + } +} + +void inode_to_host(int from_big_endian, struct cramfs_inode *inode_in, + struct cramfs_inode *inode_out) +{ + inode_toggle_endianness(from_big_endian, HOST_IS_BIG_ENDIAN, inode_in, + inode_out); +} + +void inode_from_host(int to_big_endian, struct cramfs_inode *inode_in, + struct cramfs_inode *inode_out) +{ + inode_toggle_endianness(HOST_IS_BIG_ENDIAN, to_big_endian, inode_in, + inode_out); +} diff --git a/disk-utils/delpart.8 b/disk-utils/delpart.8 new file mode 100644 index 0000000..7384e25 --- /dev/null +++ b/disk-utils/delpart.8 @@ -0,0 +1,27 @@ +.\" delpart.8 -- man page for delpart +.\" Copyright 2007 Karel Zak <kzak@redhat.com> +.\" Copyright 2007 Red Hat, Inc. +.\" May be distributed under the GNU General Public License +.TH DELPART 8 "January 2015" "util-linux" "System Administration" +.SH NAME +delpart \- tell the kernel to forget about a partition +.SH SYNOPSIS +.B delpart +.I device partition +.SH DESCRIPTION +.B delpart +asks the Linux kernel to forget about the specified \fIpartition\fR +(a number) on the specified \fIdevice\fR. +The command is a simple wrapper around the "del partition" ioctl. + +This command doesn't manipulate partitions on a block device. + +.SH SEE ALSO +.BR addpart (8), +.BR fdisk (8), +.BR parted (8), +.BR partprobe (8), +.BR partx (8) +.SH AVAILABILITY +The delpart command is part of the util-linux package and is available from +https://www.kernel.org/pub/linux/utils/util-linux/. diff --git a/disk-utils/delpart.c b/disk-utils/delpart.c new file mode 100644 index 0000000..dc06997 --- /dev/null +++ b/disk-utils/delpart.c @@ -0,0 +1,66 @@ +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <fcntl.h> + +#include "c.h" +#include "nls.h" +#include "partx.h" +#include "strutils.h" + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *out = stdout; + fputs(USAGE_HEADER, out); + fprintf(out, _(" %s <disk device> <partition number>\n"), + program_invocation_short_name); + + fputs(USAGE_SEPARATOR, out); + fputs(_("Tell the kernel to forget about a specified partition.\n"), out); + + fputs(USAGE_OPTIONS, out); + printf(USAGE_HELP_OPTIONS(16)); + printf(USAGE_MAN_TAIL("delpart(8)")); + exit(EXIT_SUCCESS); +} + +int main(int argc, char **argv) +{ + int c, fd; + + static const struct option longopts[] = { + {"help", no_argument, NULL, 'h'}, + {"version", no_argument, NULL, 'V'}, + {NULL, 0, NULL, 0}, + }; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + + while ((c = getopt_long(argc, argv, "Vh", longopts, NULL)) != -1) + switch (c) { + case 'V': + printf(UTIL_LINUX_VERSION); + return EXIT_SUCCESS; + case 'h': + usage(); + default: + errtryhelp(EXIT_FAILURE); + } + + if (argc != 3) { + warnx(_("not enough arguments")); + errtryhelp(EXIT_FAILURE); + } + + + if ((fd = open(argv[1], O_RDONLY)) < 0) + err(EXIT_FAILURE, _("cannot open %s"), argv[1]); + + if (partx_del_partition(fd, + strtou32_or_err(argv[2], _("invalid partition number argument")))) + err(EXIT_FAILURE, _("failed to remove partition")); + + return EXIT_SUCCESS; +} diff --git a/disk-utils/fdformat.8 b/disk-utils/fdformat.8 new file mode 100644 index 0000000..8cc72bd --- /dev/null +++ b/disk-utils/fdformat.8 @@ -0,0 +1,74 @@ +.\" Copyright 1992, 1993 Rickard E. Faith (faith@cs.unc.edu) +.\" May be distributed under the GNU General Public License +.TH FDFORMAT 8 "July 2014" "util-linux" "System Administration" +.SH NAME +fdformat \- low-level format a floppy disk +.SH SYNOPSIS +.B fdformat +.RI [options] " device" +.SH DESCRIPTION +.B fdformat +does a low-level format on a floppy disk. +.I device +is usually one of the following (for floppy devices the major = 2, and the +minor is shown for informational purposes only): +.sp +.nf +.RS +/dev/fd0d360 (minor = 4) +/dev/fd0h1200 (minor = 8) +/dev/fd0D360 (minor = 12) +/dev/fd0H360 (minor = 12) +/dev/fd0D720 (minor = 16) +/dev/fd0H720 (minor = 16) +/dev/fd0h360 (minor = 20) +/dev/fd0h720 (minor = 24) +/dev/fd0H1440 (minor = 28) +.PP +/dev/fd1d360 (minor = 5) +/dev/fd1h1200 (minor = 9) +/dev/fd1D360 (minor = 13) +/dev/fd1H360 (minor = 13) +/dev/fd1D720 (minor = 17) +/dev/fd1H720 (minor = 17) +/dev/fd1h360 (minor = 21) +/dev/fd1h720 (minor = 25) +/dev/fd1H1440 (minor = 29) +.RE +.fi +.PP +The generic floppy devices, /dev/fd0 and /dev/fd1, will fail to work with +.B fdformat +when a non-standard format is being used, or if the format has not been +autodetected earlier. In this case, use +.BR setfdprm (8) +to load the disk parameters. +.SH OPTIONS +.TP +\fB\-f\fR, \fB\-\-from\fR \fIN\fR +Start at the track \fIN\fR (default is 0). +.TP +\fB\-t\fR, \fB\-\-to\fR \fIN\fR +Stop at the track \fIN\fR. +.TP +\fB\-r\fR, \fB\-\-repair\fR \fIN\fR +Try to repair tracks failed during the verification (max \fIN\fR retries). +.TP +\fB\-n\fR, \fB\-\-no\-verify\fR +Skip the verification that is normally performed after the formatting. +.TP +\fB\-V\fR, \fB\-\-version\fR +Display version information and exit. +.TP +\fB\-h\fR, \fB\-\-help\fR +Display help text and exit. +.SH "SEE ALSO" +.BR fd (4), +.BR emkfs (8), +.BR mkfs (8), +.BR setfdprm (8) +.SH AUTHOR +Werner Almesberger (almesber@nessie.cs.id.ethz.ch) +.SH AVAILABILITY +The fdformat command is part of the util-linux package and is available from +https://www.kernel.org/pub/linux/utils/util-linux/. diff --git a/disk-utils/fdformat.c b/disk-utils/fdformat.c new file mode 100644 index 0000000..f461c82 --- /dev/null +++ b/disk-utils/fdformat.c @@ -0,0 +1,256 @@ +/* + * fdformat.c - Low-level formats a floppy disk - Werner Almesberger + */ +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <linux/fd.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "c.h" +#include "blkdev.h" +#include "strutils.h" +#include "closestream.h" +#include "nls.h" +#include "xalloc.h" + +#define SECTOR_SIZE 512 + +static struct floppy_struct param; + + +static void format_begin(int ctrl) +{ + if (ioctl(ctrl, FDFMTBEG, NULL) < 0) + err(EXIT_FAILURE, "ioctl: FDFMTBEG"); +} + +static void format_end(int ctrl) +{ + if (ioctl(ctrl, FDFMTEND, NULL) < 0) + err(EXIT_FAILURE, "ioctl: FDFMTEND"); +} + +static void format_track_head(int ctrl, struct format_descr *descr) +{ + if (ioctl(ctrl, FDFMTTRK, (long) descr) < 0) + err(EXIT_FAILURE, "ioctl: FDFMTTRK"); +} + +static void seek_track_head(int ctrl, struct format_descr *descr) +{ + lseek(ctrl, (descr->track * param.head + descr->head) * param.sect * SECTOR_SIZE, SEEK_SET); +} + +static void format_disk(int ctrl, unsigned int track_from, unsigned int track_to) +{ + struct format_descr current; + + printf(_("Formatting ... ")); + fflush(stdout); + + format_begin(ctrl); + + for (current.track = track_from; current.track <= track_to; current.track++) { + for (current.head = 0; current.head < param.head; current.head++) { + printf("%3u/%u\b\b\b\b\b", current.track, current.head); + fflush(stdout); + format_track_head(ctrl, ¤t); + } + } + + format_end(ctrl); + + printf(" \b\b\b\b\b%s", _("done\n")); +} + +static void verify_disk(int ctrl, unsigned int track_from, unsigned int track_to, unsigned int repair) +{ + unsigned char *data; + struct format_descr current; + int track_size, count; + unsigned int retries_left; + + track_size = param.sect * SECTOR_SIZE; + data = xmalloc(track_size); + printf(_("Verifying ... ")); + fflush(stdout); + + current.track = track_from; + current.head = 0; + seek_track_head (ctrl, ¤t); + + for (current.track = track_from; current.track <= track_to; current.track++) { + for (current.head = 0; current.head < param.head; current.head++) { + int read_bytes; + + printf("%3u\b\b\b", current.track); + fflush(stdout); + + retries_left = repair; + do { + read_bytes = read(ctrl, data, track_size); + if (read_bytes != track_size) { + if (retries_left) { + format_begin(ctrl); + format_track_head(ctrl, ¤t); + format_end(ctrl); + seek_track_head (ctrl, ¤t); + retries_left--; + if (retries_left) + continue; + } + if (read_bytes < 0) + perror(_("Read: ")); + fprintf(stderr, + _("Problem reading track/head %u/%u," + " expected %d, read %d\n"), + current.track, current.head, track_size, read_bytes); + free(data); + exit(EXIT_FAILURE); + } + for (count = 0; count < track_size; count++) + if (data[count] != FD_FILL_BYTE) { + if (retries_left) { + format_begin(ctrl); + format_track_head(ctrl, ¤t); + format_end(ctrl); + seek_track_head (ctrl, ¤t); + retries_left--; + if (retries_left) + continue; + } + printf(_("bad data in track/head %u/%u\n" + "Continuing ... "), current.track, current.head); + fflush(stdout); + break; + } + break; + } while (retries_left); + } + } + + free(data); + printf(_("done\n")); +} + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *out = stdout; + fputs(USAGE_HEADER, out); + fprintf(out, _(" %s [options] <device>\n"), + program_invocation_short_name); + + fputs(USAGE_SEPARATOR, out); + fputs(_("Do a low-level formatting of a floppy disk.\n"), out); + + fputs(USAGE_OPTIONS, out); + fputs(_(" -f, --from <N> start at the track N (default 0)\n"), out); + fputs(_(" -t, --to <N> stop at the track N\n"), out); + fputs(_(" -r, --repair <N> try to repair tracks failed during\n" + " the verification (max N retries)\n"), out); + fputs(_(" -n, --no-verify disable the verification after the format\n"), out); + + fputs(USAGE_SEPARATOR, out); + printf(USAGE_HELP_OPTIONS(19)); + printf(USAGE_MAN_TAIL("fdformat(8)")); + + exit(EXIT_SUCCESS); +} + +int main(int argc, char **argv) +{ + int ch; + int ctrl; + int verify = 1; + unsigned int repair = 0; + unsigned int track_from = 0; + unsigned int track_to = 0; + int has_user_defined_track_to = 0; + struct stat st; + + static const struct option longopts[] = { + {"from", required_argument, NULL, 'f'}, + {"to", required_argument, NULL, 't'}, + {"repair", required_argument, NULL, 'r'}, + {"no-verify", no_argument, NULL, 'n'}, + {"version", no_argument, NULL, 'V'}, + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + while ((ch = getopt_long(argc, argv, "f:t:r:nVh", longopts, NULL)) != -1) + switch (ch) { + case 'f': + track_from = strtou32_or_err(optarg, _("invalid argument - from")); + break; + case 't': + has_user_defined_track_to = 1; + track_to = strtou32_or_err(optarg, _("invalid argument - to")); + break; + case 'r': + repair = strtou32_or_err(optarg, _("invalid argument - repair")); + break; + case 'n': + verify = 0; + break; + case 'V': + printf(UTIL_LINUX_VERSION); + exit(EXIT_SUCCESS); + case 'h': + usage(); + default: + errtryhelp(EXIT_FAILURE); + } + + argc -= optind; + argv += optind; + + if (argc < 1) { + warnx(_("no device specified")); + errtryhelp(EXIT_FAILURE); + } + if (stat(argv[0], &st) < 0) + err(EXIT_FAILURE, _("stat of %s failed"), argv[0]); + if (!S_ISBLK(st.st_mode)) + /* do not test major - perhaps this was an USB floppy */ + errx(EXIT_FAILURE, _("%s: not a block device"), argv[0]); + ctrl = open_blkdev_or_file(&st, argv[0], O_RDWR); + if (ctrl < 0) + err(EXIT_FAILURE, _("cannot open %s"), argv[0]); + if (ioctl(ctrl, FDGETPRM, (long) ¶m) < 0) + err(EXIT_FAILURE, _("could not determine current format type")); + + printf(_("%s-sided, %d tracks, %d sec/track. Total capacity %d kB.\n"), + (param.head == 2) ? _("Double") : _("Single"), + param.track, param.sect, param.size >> 1); + + if (!has_user_defined_track_to) + track_to = param.track - 1; + + if (track_from >= param.track) + err(EXIT_FAILURE, _("user defined start track exceeds the medium specific maximum")); + if (track_to >= param.track) + err(EXIT_FAILURE, _("user defined end track exceeds the medium specific maximum")); + if (track_from > track_to) + err(EXIT_FAILURE, _("user defined start track exceeds the user defined end track")); + + format_disk(ctrl, track_from, track_to); + + if (verify) + verify_disk(ctrl, track_from, track_to, repair); + + if (close_fd(ctrl) != 0) + err(EXIT_FAILURE, _("close failed")); + + return EXIT_SUCCESS; +} diff --git a/disk-utils/fdisk-list.c b/disk-utils/fdisk-list.c new file mode 100644 index 0000000..916621d --- /dev/null +++ b/disk-utils/fdisk-list.c @@ -0,0 +1,536 @@ +#include <libfdisk.h> +#include <libsmartcols.h> +#include <assert.h> + +#include "c.h" +#include "xalloc.h" +#include "nls.h" +#include "blkdev.h" +#include "mbsalign.h" +#include "pathnames.h" +#include "canonicalize.h" +#include "strutils.h" +#include "sysfs.h" +#include "colors.h" +#include "ttyutils.h" + +#include "fdisk-list.h" + +/* see init_fields() */ +static const char *fields_string; +static int *fields_ids; +static size_t fields_nids; +static const struct fdisk_label *fields_label; + +static int is_ide_cdrom_or_tape(char *device) +{ + int fd, ret; + + if ((fd = open(device, O_RDONLY)) < 0) + return 0; + ret = blkdev_is_cdrom(fd); + + close(fd); + return ret; +} + +void list_disk_identifier(struct fdisk_context *cxt) +{ + struct fdisk_label *lb = fdisk_get_label(cxt, NULL); + char *id = NULL; + + if (fdisk_has_label(cxt)) + fdisk_info(cxt, _("Disklabel type: %s"), + fdisk_label_get_name(lb)); + + if (!fdisk_is_details(cxt) && fdisk_get_disklabel_id(cxt, &id) == 0 && id) { + fdisk_info(cxt, _("Disk identifier: %s"), id); + free(id); + } +} + +void list_disk_geometry(struct fdisk_context *cxt) +{ + struct fdisk_label *lb = fdisk_get_label(cxt, NULL); + uint64_t bytes = fdisk_get_nsectors(cxt) * fdisk_get_sector_size(cxt); + char *strsz = size_to_human_string(SIZE_SUFFIX_SPACE + | SIZE_SUFFIX_3LETTER, bytes); + + color_scheme_enable("header", UL_COLOR_BOLD); + fdisk_info(cxt, _("Disk %s: %s, %ju bytes, %ju sectors"), + fdisk_get_devname(cxt), strsz, + bytes, (uintmax_t) fdisk_get_nsectors(cxt)); + color_disable(); + free(strsz); + + if (fdisk_get_devmodel(cxt)) + fdisk_info(cxt, _("Disk model: %s"), fdisk_get_devmodel(cxt)); + + if (lb && (fdisk_label_require_geometry(lb) || fdisk_use_cylinders(cxt))) + fdisk_info(cxt, _("Geometry: %d heads, %llu sectors/track, %llu cylinders"), + fdisk_get_geom_heads(cxt), + fdisk_get_geom_sectors(cxt), + fdisk_get_geom_cylinders(cxt)); + + fdisk_info(cxt, _("Units: %s of %d * %ld = %ld bytes"), + fdisk_get_unit(cxt, FDISK_PLURAL), + fdisk_get_units_per_sector(cxt), + fdisk_get_sector_size(cxt), + fdisk_get_units_per_sector(cxt) * fdisk_get_sector_size(cxt)); + + fdisk_info(cxt, _("Sector size (logical/physical): %lu bytes / %lu bytes"), + fdisk_get_sector_size(cxt), + fdisk_get_physector_size(cxt)); + fdisk_info(cxt, _("I/O size (minimum/optimal): %lu bytes / %lu bytes"), + fdisk_get_minimal_iosize(cxt), + fdisk_get_optimal_iosize(cxt)); + if (fdisk_get_alignment_offset(cxt)) + fdisk_info(cxt, _("Alignment offset: %lu bytes"), + fdisk_get_alignment_offset(cxt)); + + list_disk_identifier(cxt); +} + +void list_disklabel(struct fdisk_context *cxt) +{ + struct fdisk_table *tb = NULL; + struct fdisk_partition *pa = NULL; + struct fdisk_iter *itr = NULL; + struct fdisk_label *lb; + struct libscols_table *out = NULL; + const char *bold = NULL; + int *ids = NULL; /* IDs of fdisk_fields */ + size_t nids = 0, i; + int post = 0; + + /* print label specific stuff by libfdisk FDISK_ASK_INFO API */ + fdisk_list_disklabel(cxt); + + /* get partitions and generate output */ + if (fdisk_get_partitions(cxt, &tb) || fdisk_table_get_nents(tb) <= 0) + goto done; + + ids = init_fields(cxt, NULL, &nids); + if (!ids) + goto done; + + itr = fdisk_new_iter(FDISK_ITER_FORWARD); + if (!itr) { + fdisk_warn(cxt, _("failed to allocate iterator")); + goto done; + } + + out = scols_new_table(); + if (!out) { + fdisk_warn(cxt, _("failed to allocate output table")); + goto done; + } + + if (colors_wanted()) { + scols_table_enable_colors(out, 1); + bold = color_scheme_get_sequence("header", UL_COLOR_BOLD); + } + + lb = fdisk_get_label(cxt, NULL); + assert(lb); + + /* define output table columns */ + for (i = 0; i < nids; i++) { + int fl = 0; + struct libscols_column *co; + const struct fdisk_field *field = + fdisk_label_get_field(lb, ids[i]); + if (!field) + continue; + if (fdisk_field_is_number(field)) + fl |= SCOLS_FL_RIGHT; + if (fdisk_field_get_id(field) == FDISK_FIELD_TYPE) + fl |= SCOLS_FL_TRUNC; + + co = scols_table_new_column(out, + _(fdisk_field_get_name(field)), + fdisk_field_get_width(field), fl); + if (!co) + goto done; + + /* set column header color */ + if (bold) + scols_cell_set_color(scols_column_get_header(co), bold); + } + + /* fill-in output table */ + while (fdisk_table_next_partition(tb, itr, &pa) == 0) { + struct libscols_line *ln = scols_table_new_line(out, NULL); + + if (!ln) { + fdisk_warn(cxt, _("failed to allocate output line")); + goto done; + } + + for (i = 0; i < nids; i++) { + char *data = NULL; + + if (fdisk_partition_to_string(pa, cxt, ids[i], &data)) + continue; + if (scols_line_refer_data(ln, i, data)) { + fdisk_warn(cxt, _("failed to add output data")); + goto done; + } + } + } + + /* print */ + if (!scols_table_is_empty(out)) { + fdisk_info(cxt, ""); /* just line break */ + scols_print_table(out); + } + + /* print warnings */ + fdisk_reset_iter(itr, FDISK_ITER_FORWARD); + while (itr && fdisk_table_next_partition(tb, itr, &pa) == 0) { + if (!fdisk_partition_has_start(pa)) + continue; + if (!fdisk_lba_is_phy_aligned(cxt, fdisk_partition_get_start(pa))) { + if (!post) + fdisk_info(cxt, ""); /* line break */ + fdisk_warnx(cxt, _("Partition %zu does not start on physical sector boundary."), + fdisk_partition_get_partno(pa) + 1); + post++; + } + if (fdisk_partition_has_wipe(cxt, pa)) { + if (!post) + fdisk_info(cxt, ""); /* line break */ + fdisk_info(cxt, _("Filesystem/RAID signature on partition %zu will be wiped."), + fdisk_partition_get_partno(pa) + 1); + post++; + } + } + + if (fdisk_table_wrong_order(tb)) { + if (!post) + fdisk_info(cxt, ""); /* line break */ + fdisk_info(cxt, _("Partition table entries are not in disk order.")); + } +done: + scols_unref_table(out); + fdisk_unref_table(tb); + fdisk_free_iter(itr); +} + +void list_freespace(struct fdisk_context *cxt) +{ + struct fdisk_table *tb = NULL; + struct fdisk_partition *pa = NULL; + struct fdisk_iter *itr = NULL; + struct libscols_table *out = NULL; + const char *bold = NULL; + size_t i; + uintmax_t sumsize = 0, bytes = 0; + char *strsz; + + static const char *colnames[] = { N_("Start"), N_("End"), N_("Sectors"), N_("Size") }; + static const int colids[] = { FDISK_FIELD_START, FDISK_FIELD_END, FDISK_FIELD_SECTORS, FDISK_FIELD_SIZE }; + + if (fdisk_get_freespaces(cxt, &tb)) + goto done; + + itr = fdisk_new_iter(FDISK_ITER_FORWARD); + if (!itr) { + fdisk_warn(cxt, _("failed to allocate iterator")); + goto done; + } + + out = scols_new_table(); + if (!out) { + fdisk_warn(cxt, _("failed to allocate output table")); + goto done; + } + + if (colors_wanted()) { + scols_table_enable_colors(out, 1); + bold = color_scheme_get_sequence("header", UL_COLOR_BOLD); + } + + for (i = 0; i < ARRAY_SIZE(colnames); i++) { + struct libscols_column *co = scols_table_new_column(out, _(colnames[i]), 5, SCOLS_FL_RIGHT); + + if (!co) + goto done; + if (bold) + scols_cell_set_color(scols_column_get_header(co), bold); + } + + /* fill-in output table */ + while (fdisk_table_next_partition(tb, itr, &pa) == 0) { + struct libscols_line *ln = scols_table_new_line(out, NULL); + char *data; + + if (!ln) { + fdisk_warn(cxt, _("failed to allocate output line")); + goto done; + } + for (i = 0; i < ARRAY_SIZE(colids); i++) { + if (fdisk_partition_to_string(pa, cxt, colids[i], &data)) + continue; + if (scols_line_refer_data(ln, i, data)) { + fdisk_warn(cxt, _("failed to add output data")); + goto done; + } + } + + if (fdisk_partition_has_size(pa)) + sumsize += fdisk_partition_get_size(pa); + } + + bytes = sumsize * fdisk_get_sector_size(cxt); + strsz = size_to_human_string(SIZE_SUFFIX_SPACE + | SIZE_SUFFIX_3LETTER, bytes); + + color_scheme_enable("header", UL_COLOR_BOLD); + fdisk_info(cxt, _("Unpartitioned space %s: %s, %ju bytes, %ju sectors"), + fdisk_get_devname(cxt), strsz, + bytes, sumsize); + color_disable(); + free(strsz); + + fdisk_info(cxt, _("Units: %s of %d * %ld = %ld bytes"), + fdisk_get_unit(cxt, FDISK_PLURAL), + fdisk_get_units_per_sector(cxt), + fdisk_get_sector_size(cxt), + fdisk_get_units_per_sector(cxt) * fdisk_get_sector_size(cxt)); + + fdisk_info(cxt, _("Sector size (logical/physical): %lu bytes / %lu bytes"), + fdisk_get_sector_size(cxt), + fdisk_get_physector_size(cxt)); + + /* print */ + if (!scols_table_is_empty(out)) { + fdisk_info(cxt, ""); /* line break */ + scols_print_table(out); + } +done: + scols_unref_table(out); + fdisk_unref_table(tb); + fdisk_free_iter(itr); +} + +char *next_proc_partition(FILE **f) +{ + char line[128 + 1]; + + if (!*f) { + *f = fopen(_PATH_PROC_PARTITIONS, "r"); + if (!*f) { + warn(_("cannot open %s"), _PATH_PROC_PARTITIONS); + return NULL; + } + } + + while (fgets(line, sizeof(line), *f)) { + char buf[PATH_MAX], *cn; + dev_t devno; + + if (sscanf(line, " %*d %*d %*d %128[^\n ]", buf) != 1) + continue; + + devno = sysfs_devname_to_devno(buf); + if (devno <= 0) + continue; + + if (sysfs_devno_is_dm_private(devno, NULL) || + sysfs_devno_is_wholedisk(devno) <= 0) + continue; + + if (!sysfs_devno_to_devpath(devno, buf, sizeof(buf))) + continue; + + cn = canonicalize_path(buf); + if (!cn) + continue; + + if (!is_ide_cdrom_or_tape(cn)) + return cn; + } + fclose(*f); + *f = NULL; + + return NULL; +} + +int print_device_pt(struct fdisk_context *cxt, char *device, int warnme, int verify) +{ + if (fdisk_assign_device(cxt, device, 1) != 0) { /* read-only */ + if (warnme || errno == EACCES) + warn(_("cannot open %s"), device); + return -1; + } + + list_disk_geometry(cxt); + + if (fdisk_has_label(cxt)) { + list_disklabel(cxt); + if (verify) + fdisk_verify_disklabel(cxt); + } + fdisk_deassign_device(cxt, 1); + return 0; +} + +int print_device_freespace(struct fdisk_context *cxt, char *device, int warnme) +{ + if (fdisk_assign_device(cxt, device, 1) != 0) { /* read-only */ + if (warnme || errno == EACCES) + warn(_("cannot open %s"), device); + return -1; + } + + list_freespace(cxt); + fdisk_deassign_device(cxt, 1); + return 0; +} + +void print_all_devices_pt(struct fdisk_context *cxt, int verify) +{ + FILE *f = NULL; + int ct = 0; + char *dev; + + while ((dev = next_proc_partition(&f))) { + if (ct) + fputs("\n\n", stdout); + if (print_device_pt(cxt, dev, 0, verify) == 0) + ct++; + free(dev); + } +} + +void print_all_devices_freespace(struct fdisk_context *cxt) +{ + FILE *f = NULL; + int ct = 0; + char *dev; + + while ((dev = next_proc_partition(&f))) { + if (ct) + fputs("\n\n", stdout); + if (print_device_freespace(cxt, dev, 0) == 0) + ct++; + free(dev); + } +} + +/* usable for example in usage() */ +void list_available_columns(FILE *out) +{ + size_t i; + int termwidth; + struct fdisk_label *lb = NULL; + struct fdisk_context *cxt = fdisk_new_context(); + + if (!cxt) + return; + + termwidth = get_terminal_width(80); + + fprintf(out, USAGE_COLUMNS); + + while (fdisk_next_label(cxt, &lb) == 0) { + size_t width = 6; /* label name and separators */ + + fprintf(out, " %s:", fdisk_label_get_name(lb)); + for (i = 1; i < FDISK_NFIELDS; i++) { + const struct fdisk_field *fl = fdisk_label_get_field(lb, i); + const char *name = fl ? fdisk_field_get_name(fl) : NULL; + size_t len; + + if (!name) + continue; + len = strlen(name) + 1; + if (width + len > (size_t) termwidth) { + fputs("\n ", out); + width = 6; + } + fprintf(out, " %s", name); + width += len; + } + fputc('\n', out); + } + + fdisk_unref_context(cxt); +} + +static int fieldname_to_id(const char *name, size_t namesz) +{ + const struct fdisk_field *fl; + char buf[namesz + 1]; + + assert(name); + assert(namesz); + assert(fields_label); + + memcpy(buf, name, namesz); + buf[namesz] = '\0'; + + fl = fdisk_label_get_field_by_name(fields_label, buf); + if (!fl) { + warnx(_("%s unknown column: %s"), + fdisk_label_get_name(fields_label), buf); + return -1; + } + return fdisk_field_get_id(fl); +} + +/* + * Initialize array with output columns (fields_ids[]) according to + * comma delimited list of columns (@str). If the list string is not + * defined then use library defaults. This function is "-o <list>" + * backend. + * + * If the columns are already initialized then returns already existing columns. + */ +int *init_fields(struct fdisk_context *cxt, const char *str, size_t *n) +{ + int *dflt_ids = NULL; + struct fdisk_label *lb; + + if (!fields_string) + fields_string = str; + if (!cxt) + goto done; + + lb = fdisk_get_label(cxt, NULL); + + if (!lb || fields_label != lb) { /* label changed: reset */ + free(fields_ids); + fields_ids = NULL; + fields_label = lb; + fields_nids = 0; + } + + if (!fields_label) /* no label */ + goto done; + if (fields_nids) + goto done; /* already initialized */ + + /* library default */ + if (fdisk_label_get_fields_ids(NULL, cxt, &dflt_ids, &fields_nids)) + goto done; + + fields_ids = xcalloc(FDISK_NFIELDS * 2, sizeof(int)); + + /* copy defaults to the list with wanted fields */ + memcpy(fields_ids, dflt_ids, fields_nids * sizeof(int)); + free(dflt_ids); + + /* extend or replace fields_nids[] according to fields_string */ + if (fields_string && + string_add_to_idarray(fields_string, fields_ids, FDISK_NFIELDS * 2, + &fields_nids, fieldname_to_id) < 0) + exit(EXIT_FAILURE); +done: + fields_label = NULL; + if (n) + *n = fields_nids; + return fields_ids; +} + diff --git a/disk-utils/fdisk-list.h b/disk-utils/fdisk-list.h new file mode 100644 index 0000000..4ed5c25 --- /dev/null +++ b/disk-utils/fdisk-list.h @@ -0,0 +1,47 @@ +#ifndef UTIL_LINUX_FDISK_LIST_H +#define UTIL_LINUX_FDISK_LIST_H + +extern void list_disklabel(struct fdisk_context *cxt); +extern void list_disk_identifier(struct fdisk_context *cxt); +extern void list_disk_geometry(struct fdisk_context *cxt); +extern void list_freespace(struct fdisk_context *cxt); + +extern char *next_proc_partition(FILE **f); +extern int print_device_pt(struct fdisk_context *cxt, char *device, int warnme, int verify); +extern int print_device_freespace(struct fdisk_context *cxt, char *device, int warnme); + +extern void print_all_devices_pt(struct fdisk_context *cxt, int verify); +extern void print_all_devices_freespace(struct fdisk_context *cxt); + +extern void list_available_columns(FILE *out); +extern int *init_fields(struct fdisk_context *cxt, const char *str, size_t *n); + + +/* used by fdisk and sfdisk */ +enum { + WIPEMODE_AUTO = 0, + WIPEMODE_NEVER = 1, + WIPEMODE_ALWAYS = 2 +}; + +static inline int wipemode_from_string(const char *str) +{ + size_t i; + static const char *modes[] = { + [WIPEMODE_AUTO] = "auto", + [WIPEMODE_NEVER] = "never", + [WIPEMODE_ALWAYS] = "always" + }; + + if (!str || !*str) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(modes); i++) { + if (strcasecmp(str, modes[i]) == 0) + return i; + } + + return -EINVAL; +} + +#endif /* UTIL_LINUX_FDISK_LIST_H */ diff --git a/disk-utils/fdisk-menu.c b/disk-utils/fdisk-menu.c new file mode 100644 index 0000000..a6f4351 --- /dev/null +++ b/disk-utils/fdisk-menu.c @@ -0,0 +1,1102 @@ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <stdint.h> + +#include "c.h" +#include "rpmatch.h" +#include "fdisk.h" +#include "pt-sun.h" +#include "pt-mbr.h" + +struct menu_entry { + const char key; /* command key */ + const char *title; /* help string */ + unsigned int normal : 1, /* normal mode */ + expert : 1, /* expert mode */ + hidden : 1; /* be sensitive for this key, + but don't print it in help */ + + enum fdisk_labeltype label; /* only for this label */ + int exclude; /* all labels except these */ + enum fdisk_labeltype parent; /* for nested PT */ +}; + +#define IS_MENU_SEP(e) ((e)->key == '-') +#define IS_MENU_HID(e) ((e)->hidden) + +struct menu { + enum fdisk_labeltype label; /* only for this label */ + int exclude; /* all labels except these */ + + unsigned int nonested : 1; /* don't make this menu active in nested PT */ + + int (*callback)(struct fdisk_context **, + const struct menu *, + const struct menu_entry *); + + struct menu_entry entries[]; /* NULL terminated array */ +}; + +struct menu_context { + size_t menu_idx; /* the current menu */ + size_t entry_idx; /* index with in the current menu */ +}; + +#define MENU_CXT_EMPTY { 0, 0 } +#define DECLARE_MENU_CB(x) \ + static int x(struct fdisk_context **, \ + const struct menu *, \ + const struct menu_entry *) + +DECLARE_MENU_CB(gpt_menu_cb); +DECLARE_MENU_CB(sun_menu_cb); +DECLARE_MENU_CB(sgi_menu_cb); +DECLARE_MENU_CB(geo_menu_cb); +DECLARE_MENU_CB(dos_menu_cb); +DECLARE_MENU_CB(bsd_menu_cb); +DECLARE_MENU_CB(createlabel_menu_cb); +DECLARE_MENU_CB(generic_menu_cb); + +/* + * Menu entry macros: + * MENU_X* expert mode only + * MENU_B* both -- expert + normal mode + * + * *_E exclude this label + * *_H hidden + * *_L only for this label + */ + +/* separator */ +#define MENU_SEP(t) { .title = t, .key = '-', .normal = 1 } +#define MENU_XSEP(t) { .title = t, .key = '-', .expert = 1 } +#define MENU_BSEP(t) { .title = t, .key = '-', .expert = 1, .normal = 1 } + +/* entry */ +#define MENU_ENT(k, t) { .title = t, .key = k, .normal = 1 } +#define MENU_ENT_E(k, t, l) { .title = t, .key = k, .normal = 1, .exclude = l } +#define MENU_ENT_L(k, t, l) { .title = t, .key = k, .normal = 1, .label = l } + +#define MENU_XENT(k, t) { .title = t, .key = k, .expert = 1 } +#define MENU_XENT_H(k, t) { .title = t, .key = k, .expert = 1, .hidden = 1 } + +#define MENU_BENT(k, t) { .title = t, .key = k, .expert = 1, .normal = 1 } +#define MENU_BENT_E(k, t, l) { .title = t, .key = k, .expert = 1, .normal = 1, .exclude = l } + +#define MENU_ENT_NEST(k, t, l, p) { .title = t, .key = k, .normal = 1, .label = l, .parent = p } +#define MENU_XENT_NEST(k, t, l, p) { .title = t, .key = k, .expert = 1, .label = l, .parent = p } +#define MENU_BENT_NEST(k, t, l, p) { .title = t, .key = k, .expert = 1, .normal = 1, .label = l, .parent = p } + +/* Generic menu */ +static const struct menu menu_generic = { + .callback = generic_menu_cb, + .entries = { + MENU_BSEP(N_("Generic")), + MENU_ENT ('d', N_("delete a partition")), + MENU_ENT ('F', N_("list free unpartitioned space")), + MENU_ENT ('l', N_("list known partition types")), + MENU_ENT ('n', N_("add a new partition")), + MENU_BENT ('p', N_("print the partition table")), + MENU_ENT ('t', N_("change a partition type")), + MENU_BENT_E('v', N_("verify the partition table"), FDISK_DISKLABEL_BSD), + MENU_ENT ('i', N_("print information about a partition")), + + MENU_XENT('d', N_("print the raw data of the first sector from the device")), + MENU_XENT('D', N_("print the raw data of the disklabel from the device")), + MENU_XENT('f', N_("fix partitions order")), + + MENU_SEP(N_("Misc")), + MENU_BENT ('m', N_("print this menu")), + MENU_ENT_E('u', N_("change display/entry units"), FDISK_DISKLABEL_GPT), + MENU_ENT_E('x', N_("extra functionality (experts only)"), FDISK_DISKLABEL_BSD), + + MENU_SEP(N_("Script")), + MENU_ENT ('I', N_("load disk layout from sfdisk script file")), + MENU_ENT ('O', N_("dump disk layout to sfdisk script file")), + + MENU_BSEP(N_("Save & Exit")), + MENU_ENT_E('w', N_("write table to disk and exit"), FDISK_DISKLABEL_BSD), + MENU_ENT_L('w', N_("write table to disk"), FDISK_DISKLABEL_BSD), + MENU_BENT ('q', N_("quit without saving changes")), + MENU_XENT ('r', N_("return to main menu")), + + MENU_ENT_NEST('r', N_("return from BSD to DOS"), FDISK_DISKLABEL_BSD, FDISK_DISKLABEL_DOS), + + { 0, NULL } + } +}; + +static const struct menu menu_createlabel = { + .callback = createlabel_menu_cb, + .exclude = FDISK_DISKLABEL_BSD, + .nonested = 1, + .entries = { + MENU_SEP(N_("Create a new label")), + MENU_ENT('g', N_("create a new empty GPT partition table")), + MENU_ENT('G', N_("create a new empty SGI (IRIX) partition table")), + MENU_ENT('o', N_("create a new empty DOS partition table")), + MENU_ENT('s', N_("create a new empty Sun partition table")), + + /* backward compatibility -- be sensitive to 'g', but don't + * print it in the expert menu */ + MENU_XENT_H('g', N_("create an IRIX (SGI) partition table")), + { 0, NULL } + } +}; + +static const struct menu menu_geo = { + .callback = geo_menu_cb, + .exclude = FDISK_DISKLABEL_GPT | FDISK_DISKLABEL_BSD, + .entries = { + MENU_XSEP(N_("Geometry (for the current label)")), + MENU_XENT('c', N_("change number of cylinders")), + MENU_XENT('h', N_("change number of heads")), + MENU_XENT('s', N_("change number of sectors/track")), + { 0, NULL } + } +}; + +static const struct menu menu_gpt = { + .callback = gpt_menu_cb, + .label = FDISK_DISKLABEL_GPT, + .entries = { + MENU_BSEP(N_("GPT")), + MENU_XENT('i', N_("change disk GUID")), + MENU_XENT('n', N_("change partition name")), + MENU_XENT('u', N_("change partition UUID")), + MENU_XENT('l', N_("change table length")), + MENU_BENT('M', N_("enter protective/hybrid MBR")), + + MENU_XSEP(""), + MENU_XENT('A', N_("toggle the legacy BIOS bootable flag")), + MENU_XENT('B', N_("toggle the no block IO protocol flag")), + MENU_XENT('R', N_("toggle the required partition flag")), + MENU_XENT('S', N_("toggle the GUID specific bits")), + + { 0, NULL } + } +}; + +static const struct menu menu_sun = { + .callback = sun_menu_cb, + .label = FDISK_DISKLABEL_SUN, + .entries = { + MENU_BSEP(N_("Sun")), + MENU_ENT('a', N_("toggle the read-only flag")), + MENU_ENT('c', N_("toggle the mountable flag")), + + MENU_XENT('a', N_("change number of alternate cylinders")), + MENU_XENT('e', N_("change number of extra sectors per cylinder")), + MENU_XENT('i', N_("change interleave factor")), + MENU_XENT('o', N_("change rotation speed (rpm)")), + MENU_XENT('y', N_("change number of physical cylinders")), + { 0, NULL } + } +}; + +static const struct menu menu_sgi = { + .callback = sgi_menu_cb, + .label = FDISK_DISKLABEL_SGI, + .entries = { + MENU_SEP(N_("SGI")), + MENU_ENT('a', N_("select bootable partition")), + MENU_ENT('b', N_("edit bootfile entry")), + MENU_ENT('c', N_("select sgi swap partition")), + MENU_ENT('i', N_("create SGI info")), + { 0, NULL } + } +}; + +static const struct menu menu_dos = { + .callback = dos_menu_cb, + .label = FDISK_DISKLABEL_DOS, + .entries = { + MENU_BSEP(N_("DOS (MBR)")), + MENU_ENT('a', N_("toggle a bootable flag")), + MENU_ENT('b', N_("edit nested BSD disklabel")), + MENU_ENT('c', N_("toggle the dos compatibility flag")), + + MENU_XENT('b', N_("move beginning of data in a partition")), + MENU_XENT('i', N_("change the disk identifier")), + + MENU_BENT_NEST('M', N_("return from protective/hybrid MBR to GPT"), + FDISK_DISKLABEL_DOS, FDISK_DISKLABEL_GPT), + { 0, NULL } + } +}; + +static const struct menu menu_bsd = { + .callback = bsd_menu_cb, + .label = FDISK_DISKLABEL_BSD, + .entries = { + MENU_SEP(N_("BSD")), + MENU_ENT('e', N_("edit drive data")), + MENU_ENT('i', N_("install bootstrap")), + MENU_ENT('s', N_("show complete disklabel")), + MENU_ENT('x', N_("link BSD partition to non-BSD partition")), + { 0, NULL } + } +}; + +static const struct menu *menus[] = { + &menu_gpt, + &menu_sun, + &menu_sgi, + &menu_dos, + &menu_bsd, + &menu_geo, + &menu_generic, + &menu_createlabel, +}; + +static const struct menu_entry *next_menu_entry( + struct fdisk_context *cxt, + struct menu_context *mc) +{ + struct fdisk_label *lb = fdisk_get_label(cxt, NULL); + struct fdisk_context *parent = fdisk_get_parent(cxt); + unsigned int type = 0, pr_type = 0; + + assert(cxt); + + if (lb) + type = fdisk_label_get_type(lb); + if (parent) + pr_type = fdisk_label_get_type(fdisk_get_label(parent, NULL)); + + while (mc->menu_idx < ARRAY_SIZE(menus)) { + const struct menu *m = menus[mc->menu_idx]; + const struct menu_entry *e = &(m->entries[mc->entry_idx]); + + /* + * whole-menu filter + */ + + /* no more entries */ + if (e->title == NULL || + /* menu wanted for specified labels only */ + (m->label && lb && !(m->label & type)) || + /* unwanted for nested PT */ + (m->nonested && parent) || + /* menu excluded for specified labels */ + (m->exclude && lb && (m->exclude & type))) { + mc->menu_idx++; + mc->entry_idx = 0; + continue; + } + + /* + * per entry filter + */ + + /* excluded for the current label */ + if ((e->exclude && lb && e->exclude & type) || + /* entry wanted for specified labels only */ + (e->label && lb && !(e->label & type)) || + /* exclude non-expert entries in expect mode */ + (e->expert == 0 && fdisk_is_details(cxt)) || + /* nested only */ + (e->parent && (!parent || pr_type != e->parent)) || + /* exclude non-normal entries in normal mode */ + (e->normal == 0 && !fdisk_is_details(cxt))) { + mc->entry_idx++; + continue; + } + mc->entry_idx++; + return e; + + } + return NULL; +} + +/* returns @menu and menu entry for then @key */ +static const struct menu_entry *get_fdisk_menu_entry( + struct fdisk_context *cxt, + int key, + const struct menu **menu) +{ + struct menu_context mc = MENU_CXT_EMPTY; + const struct menu_entry *e; + + while ((e = next_menu_entry(cxt, &mc))) { + if (IS_MENU_SEP(e) || e->key != key) + continue; + + if (menu) + *menu = menus[mc.menu_idx]; + return e; + } + + return NULL; +} + +static int menu_detect_collisions(struct fdisk_context *cxt) +{ + struct menu_context mc = MENU_CXT_EMPTY; + const struct menu_entry *e, *r; + + while ((e = next_menu_entry(cxt, &mc))) { + if (IS_MENU_SEP(e)) + continue; + + r = get_fdisk_menu_entry(cxt, e->key, NULL); + if (!r) { + DBG(MENU, ul_debug("warning: not found " + "entry for %c", e->key)); + return -1; + } + if (r != e) { + DBG(MENU, ul_debug("warning: duplicate key '%c'", + e->key)); + DBG(MENU, ul_debug(" : %s", e->title)); + DBG(MENU, ul_debug(" : %s", r->title)); + abort(); + } + } + + return 0; +} + +static int print_fdisk_menu(struct fdisk_context *cxt) +{ + struct menu_context mc = MENU_CXT_EMPTY; + const struct menu_entry *e; + + ON_DBG(MENU, menu_detect_collisions(cxt)); + + if (fdisk_is_details(cxt)) + printf(_("\nHelp (expert commands):\n")); + else + printf(_("\nHelp:\n")); + + while ((e = next_menu_entry(cxt, &mc))) { + if (IS_MENU_HID(e)) + continue; /* hidden entry */ + if (IS_MENU_SEP(e) && (!e->title || !*e->title)) + printf("\n"); + else if (IS_MENU_SEP(e)) { + color_scheme_enable("help-title", UL_COLOR_BOLD); + printf("\n %s\n", _(e->title)); + color_disable(); + } else + printf(" %c %s\n", e->key, _(e->title)); + } + fputc('\n', stdout); + + if (fdisk_get_parent(cxt)) { + struct fdisk_label *l = fdisk_get_label(cxt, NULL), + *p = fdisk_get_label(fdisk_get_parent(cxt), NULL); + + fdisk_info(cxt, _("You're editing nested '%s' partition table, " + "primary partition table is '%s'."), + fdisk_label_get_name(l), + fdisk_label_get_name(p)); + } + + return 0; +} + +/* Asks for command, verify the key and perform the command or + * returns the command key if no callback for the command is + * implemented. + * + * Note that this function might exchange the context pointer to + * switch to another (nested) context. + * + * Returns: <0 on error + * 0 on success (the command performed) + * >0 if no callback (then returns the key) + */ +int process_fdisk_menu(struct fdisk_context **cxt0) +{ + struct fdisk_context *cxt = *cxt0; + const struct menu_entry *ent; + const struct menu *menu; + int key, rc; + const char *prompt; + char buf[BUFSIZ]; + + if (fdisk_is_details(cxt)) + prompt = _("Expert command (m for help): "); + else + prompt = _("Command (m for help): "); + + fputc('\n',stdout); + rc = get_user_reply(prompt, buf, sizeof(buf)); + + if (rc == -ECANCELED) { + /* Map ^C and ^D in main menu to 'q' */ + if (is_interactive + && fdisk_label_is_changed(fdisk_get_label(cxt, NULL))) { + rc = get_user_reply( + _("\nDo you really want to quit? "), + buf, sizeof(buf)); + if (rc || !rpmatch(buf)) + return 0; + } + key = 'q'; + } else if (rc) { + return rc; + } else + key = buf[0]; + + ent = get_fdisk_menu_entry(cxt, key, &menu); + if (!ent) { + fdisk_warnx(cxt, _("%c: unknown command"), key); + return -EINVAL; + } + + DBG(MENU, ul_debug("selected: key=%c, entry='%s'", + key, ent->title)); + + /* menu has implemented callback, use it */ + if (menu->callback) + rc = menu->callback(cxt0, menu, ent); + else { + DBG(MENU, ul_debug("no callback for key '%c'", key)); + rc = -EINVAL; + } + + DBG(MENU, ul_debug("process menu done [rc=%d]", rc)); + return rc; +} + +static int script_read(struct fdisk_context *cxt) +{ + struct fdisk_script *sc = NULL; + char *filename = NULL; + int rc; + + rc = fdisk_ask_string(cxt, _("Enter script file name"), &filename); + if (rc) + return rc; + + errno = 0; + sc = fdisk_new_script_from_file(cxt, filename); + if (!sc && errno) + fdisk_warn(cxt, _("Cannot open %s"), filename); + else if (!sc) + fdisk_warnx(cxt, _("Failed to parse script file %s"), filename); + else if (fdisk_apply_script(cxt, sc) != 0) { + fdisk_warnx(cxt, _("Failed to apply script %s"), filename); + fdisk_warnx(cxt, _("Resetting fdisk!")); + rc = fdisk_reassign_device(cxt); + if (rc == 0 && !fdisk_has_label(cxt)) { + fdisk_info(cxt, _("Device does not contain a recognized partition table.")); + fdisk_create_disklabel(cxt, NULL); + } + } else + fdisk_info(cxt, _("Script successfully applied.")); + + fdisk_unref_script(sc); + free(filename); + return rc; +} + +static int script_write(struct fdisk_context *cxt) +{ + struct fdisk_script *sc = NULL; + char *filename = NULL; + FILE *f = NULL; + int rc; + + rc = fdisk_ask_string(cxt, _("Enter script file name"), &filename); + if (rc) + return rc; + + sc = fdisk_new_script(cxt); + if (!sc) { + fdisk_warn(cxt, _("Failed to allocate script handler")); + goto done; + } + + rc = fdisk_script_read_context(sc, NULL); + if (rc) { + fdisk_warnx(cxt, _("Failed to transform disk layout into script")); + goto done; + } + + f = fopen(filename, "w"); + if (!f) { + fdisk_warn(cxt, _("Cannot open %s"), filename); + goto done; + } + + rc = fdisk_script_write_file(sc, f); + if (rc) + fdisk_warn(cxt, _("Failed to write script %s"), filename); + else + fdisk_info(cxt, _("Script successfully saved.")); +done: + if (f) + fclose(f); + fdisk_unref_script(sc); + free(filename); + return rc; +} + +static int ask_for_wipe(struct fdisk_context *cxt, size_t partno) +{ + struct fdisk_partition *tmp = NULL; + char *fstype = NULL; + int rc, yes = 0; + + rc = fdisk_get_partition(cxt, partno, &tmp); + if (rc) + goto done; + + rc = fdisk_partition_to_string(tmp, cxt, FDISK_FIELD_FSTYPE, &fstype); + if (rc || fstype == NULL) + goto done; + + fdisk_warnx(cxt, _("Partition #%zu contains a %s signature."), partno + 1, fstype); + + if (pwipemode == WIPEMODE_AUTO && isatty(STDIN_FILENO)) + fdisk_ask_yesno(cxt, _("Do you want to remove the signature?"), &yes); + else if (pwipemode == WIPEMODE_ALWAYS) + yes = 1; + + if (yes) { + fdisk_info(cxt, _("The signature will be removed by a write command.")); + rc = fdisk_wipe_partition(cxt, partno, TRUE); + } +done: + fdisk_unref_partition(tmp); + free(fstype); + return rc; +} + +/* + * Basic fdisk actions + */ +static int generic_menu_cb(struct fdisk_context **cxt0, + const struct menu *menu __attribute__((__unused__)), + const struct menu_entry *ent) +{ + struct fdisk_context *cxt = *cxt0; + int rc = 0; + size_t n; + + /* actions shared between expert and normal mode */ + switch (ent->key) { + case 'p': + list_disk_geometry(cxt); + list_disklabel(cxt); + break; + case 'w': + if (fdisk_is_readonly(cxt)) { + fdisk_warnx(cxt, _("Device is open in read-only mode.")); + break; + } + rc = fdisk_write_disklabel(cxt); + if (rc) + err(EXIT_FAILURE, _("failed to write disklabel")); + if (fdisk_get_parent(cxt)) + break; /* nested PT, don't leave */ + fdisk_info(cxt, _("The partition table has been altered.")); + + if (device_is_used) + rc = fdisk_reread_changes(cxt, original_layout); + else + rc = fdisk_reread_partition_table(cxt); + if (!rc) + rc = fdisk_deassign_device(cxt, 0); + /* fallthrough */ + case 'q': + fdisk_unref_context(cxt); + fputc('\n', stdout); + exit(rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE); + case 'm': + rc = print_fdisk_menu(cxt); + break; + case 'v': + rc = fdisk_verify_disklabel(cxt); + break; + case 'i': + rc = print_partition_info(cxt); + break; + case 'F': + list_freespace(cxt); + break; + } + + /* expert mode */ + if (ent->expert) { + switch (ent->key) { + case 'd': + dump_firstsector(cxt); + break; + case 'D': + dump_disklabel(cxt); + break; + case 'f': + rc = fdisk_reorder_partitions(cxt); + if (rc) + fdisk_warnx(cxt, _("Failed to fix partitions order.")); + else + fdisk_info(cxt, _("Partitions order fixed.")); + break; + case 'r': + rc = fdisk_enable_details(cxt, 0); + break; + } + return rc; + } + + /* normal mode */ + switch (ent->key) { + case 'd': + rc = fdisk_ask_partnum(cxt, &n, FALSE); + if (rc) + break; /* no partitions yet (or ENOMEM, ...) */ + + rc = fdisk_delete_partition(cxt, n); + if (rc) + fdisk_warnx(cxt, _("Could not delete partition %zu"), n + 1); + else + fdisk_info(cxt, _("Partition %zu has been deleted."), n + 1); + break; + case 'I': + script_read(cxt); + break; + case 'O': + script_write(cxt); + break; + case 'l': + list_partition_types(cxt); + break; + case 'n': + { + size_t partno; + rc = fdisk_add_partition(cxt, NULL, &partno); + if (!rc) + rc = ask_for_wipe(cxt, partno); + break; + } + case 't': + change_partition_type(cxt); + break; + case 'u': + fdisk_set_unit(cxt, + fdisk_use_cylinders(cxt) ? "sectors" : + "cylinders"); + if (fdisk_use_cylinders(cxt)) + fdisk_info(cxt, _("Changing display/entry units to cylinders (DEPRECATED!).")); + else + fdisk_info(cxt, _("Changing display/entry units to sectors.")); + break; + case 'x': + fdisk_enable_details(cxt, 1); + break; + case 'r': + /* return from nested BSD to DOS */ + if (fdisk_get_parent(cxt)) { + *cxt0 = fdisk_get_parent(cxt); + + fdisk_info(cxt, _("Leaving nested disklabel.")); + fdisk_unref_context(cxt); + } + break; + } + + return rc; +} + + +/* + * This is fdisk frontend for GPT specific libfdisk functions that + * are not exported by generic libfdisk API. + */ +static int gpt_menu_cb(struct fdisk_context **cxt0, + const struct menu *menu __attribute__((__unused__)), + const struct menu_entry *ent) +{ + struct fdisk_context *cxt = *cxt0; + struct fdisk_context *mbr; + struct fdisk_partition *pa = NULL; + size_t n; + int rc = 0; + uintmax_t length = 0; + + assert(cxt); + assert(ent); + assert(fdisk_is_label(cxt, GPT)); + + DBG(MENU, ul_debug("enter GPT menu")); + + if (ent->expert) { + switch (ent->key) { + case 'i': + return fdisk_set_disklabel_id(cxt); + case 'l': + rc = fdisk_ask_number(cxt, 1, fdisk_get_npartitions(cxt), + ~(uint32_t)0, _("New maximum entries"), &length); + if (rc) + return rc; + return fdisk_gpt_set_npartitions(cxt, (uint32_t) length); + case 'M': + mbr = fdisk_new_nested_context(cxt, "dos"); + if (!mbr) + return -ENOMEM; + *cxt0 = cxt = mbr; + if (fdisk_is_details(cxt)) + fdisk_enable_details(cxt, 1); /* keep us in expert mode */ + fdisk_info(cxt, _("Entering protective/hybrid MBR disklabel.")); + return 0; + } + + /* actions where is necessary partnum */ + rc = fdisk_ask_partnum(cxt, &n, FALSE); + if (rc) + return rc; + + switch(ent->key) { + case 'u': + pa = fdisk_new_partition(); /* new template */ + if (!pa) + rc = -ENOMEM; + else { + char *str = NULL; + rc = fdisk_ask_string(cxt, _("New UUID (in 8-4-4-4-12 format)"), &str); + if (!rc) + rc = fdisk_partition_set_uuid(pa, str); + if (!rc) + rc = fdisk_set_partition(cxt, n, pa); + free(str); + fdisk_unref_partition(pa); + } + break; + case 'n': + pa = fdisk_new_partition(); /* new template */ + if (!pa) + rc = -ENOMEM; + else { + char *str = NULL; + rc = fdisk_ask_string(cxt, _("New name"), &str); + if (!rc) + rc = fdisk_partition_set_name(pa, str); + if (!rc) + rc = fdisk_set_partition(cxt, n, pa); + free(str); + fdisk_unref_partition(pa); + } + break; + case 'A': + rc = fdisk_toggle_partition_flag(cxt, n, GPT_FLAG_LEGACYBOOT); + break; + case 'B': + rc = fdisk_toggle_partition_flag(cxt, n, GPT_FLAG_NOBLOCK); + break; + case 'R': + rc = fdisk_toggle_partition_flag(cxt, n, GPT_FLAG_REQUIRED); + break; + case 'S': + rc = fdisk_toggle_partition_flag(cxt, n, GPT_FLAG_GUIDSPECIFIC); + break; + } + } + + return rc; +} + + +/* + * This is fdisk frontend for MBR specific libfdisk functions that + * are not exported by generic libfdisk API. + */ +static int dos_menu_cb(struct fdisk_context **cxt0, + const struct menu *menu __attribute__((__unused__)), + const struct menu_entry *ent) +{ + struct fdisk_context *cxt = *cxt0; + int rc = 0; + + DBG(MENU, ul_debug("enter DOS menu")); + + if (!ent->expert) { + switch (ent->key) { + case 'a': + { + size_t n; + rc = fdisk_ask_partnum(cxt, &n, FALSE); + if (!rc) + rc = fdisk_toggle_partition_flag(cxt, n, DOS_FLAG_ACTIVE); + break; + } + case 'b': + { + struct fdisk_context *bsd + = fdisk_new_nested_context(cxt, "bsd"); + if (!bsd) + return -ENOMEM; + if (!fdisk_has_label(bsd)) + rc = fdisk_create_disklabel(bsd, "bsd"); + if (rc) + fdisk_unref_context(bsd); + else { + *cxt0 = cxt = bsd; + fdisk_info(cxt, _("Entering nested BSD disklabel.")); + } + break; + } + case 'c': + toggle_dos_compatibility_flag(cxt); + break; + } + return rc; + } + + /* expert mode */ + switch (ent->key) { + case 'b': + { + size_t n; + rc = fdisk_ask_partnum(cxt, &n, FALSE); + if (!rc) + rc = fdisk_dos_move_begin(cxt, n); + break; + } + case 'i': + rc = fdisk_set_disklabel_id(cxt); + break; + case 'M': + /* return from nested MBR to GPT */ + if (fdisk_get_parent(cxt)) { + *cxt0 = fdisk_get_parent(cxt); + + fdisk_info(cxt, _("Leaving nested disklabel.")); + fdisk_unref_context(cxt); + } + break; + } + return rc; +} + +static int sun_menu_cb(struct fdisk_context **cxt0, + const struct menu *menu __attribute__((__unused__)), + const struct menu_entry *ent) +{ + struct fdisk_context *cxt = *cxt0; + int rc = 0; + + DBG(MENU, ul_debug("enter SUN menu")); + + assert(cxt); + assert(ent); + assert(fdisk_is_label(cxt, SUN)); + + DBG(MENU, ul_debug("enter SUN menu")); + + /* normal mode */ + if (!ent->expert) { + size_t n; + + rc = fdisk_ask_partnum(cxt, &n, FALSE); + if (rc) + return rc; + switch (ent->key) { + case 'a': + rc = fdisk_toggle_partition_flag(cxt, n, SUN_FLAG_RONLY); + break; + case 'c': + rc = fdisk_toggle_partition_flag(cxt, n, SUN_FLAG_UNMNT); + break; + } + return rc; + } + + /* expert mode */ + switch (ent->key) { + case 'a': + rc = fdisk_sun_set_alt_cyl(cxt); + break; + case 'e': + rc = fdisk_sun_set_xcyl(cxt); + break; + case 'i': + rc = fdisk_sun_set_ilfact(cxt); + break; + case 'o': + rc = fdisk_sun_set_rspeed(cxt); + break; + case 'y': + rc = fdisk_sun_set_pcylcount(cxt); + break; + } + return rc; +} + +static int sgi_menu_cb(struct fdisk_context **cxt0, + const struct menu *menu __attribute__((__unused__)), + const struct menu_entry *ent) +{ + struct fdisk_context *cxt = *cxt0; + int rc = -EINVAL; + size_t n = 0; + + DBG(MENU, ul_debug("enter SGI menu")); + + assert(cxt); + assert(ent); + assert(fdisk_is_label(cxt, SGI)); + + if (ent->expert) + return rc; + + switch (ent->key) { + case 'a': + rc = fdisk_ask_partnum(cxt, &n, FALSE); + if (!rc) + rc = fdisk_toggle_partition_flag(cxt, n, SGI_FLAG_BOOT); + break; + case 'b': + fdisk_sgi_set_bootfile(cxt); + break; + case 'c': + rc = fdisk_ask_partnum(cxt, &n, FALSE); + if (!rc) + rc = fdisk_toggle_partition_flag(cxt, n, SGI_FLAG_SWAP); + break; + case 'i': + rc = fdisk_sgi_create_info(cxt); + break; + } + + return rc; +} + +/* + * This is fdisk frontend for BSD specific libfdisk functions that + * are not exported by generic libfdisk API. + */ +static int bsd_menu_cb(struct fdisk_context **cxt0, + const struct menu *menu __attribute__((__unused__)), + const struct menu_entry *ent) +{ + struct fdisk_context *cxt = *cxt0; + int rc = 0, org; + + assert(cxt); + assert(ent); + assert(fdisk_is_label(cxt, BSD)); + + DBG(MENU, ul_debug("enter BSD menu")); + + switch(ent->key) { + case 'e': + rc = fdisk_bsd_edit_disklabel(cxt); + break; + case 'i': + rc = fdisk_bsd_write_bootstrap(cxt); + break; + case 's': + org = fdisk_is_details(cxt); + + fdisk_enable_details(cxt, 1); + list_disklabel(cxt); + fdisk_enable_details(cxt, org); + break; + case 'x': + rc = fdisk_bsd_link_partition(cxt); + break; + } + return rc; +} + +/* C/H/S commands + * + * The geometry setting from this dialog is not persistent and maybe reset by + * fdisk_reset_device_properties() (for example when you create a new disk + * label). Note that on command line specified -C/-H/-S setting is persistent + * as it's based on fdisk_save_user_geometry(). + */ +static int geo_menu_cb(struct fdisk_context **cxt0, + const struct menu *menu __attribute__((__unused__)), + const struct menu_entry *ent) +{ + struct fdisk_context *cxt = *cxt0; + struct fdisk_label *lb = fdisk_get_label(cxt, NULL); + int rc = -EINVAL; + uintmax_t c = 0, h = 0, s = 0; + fdisk_sector_t mi, ma; + + DBG(MENU, ul_debug("enter GEO menu")); + + assert(cxt); + assert(ent); + + /* default */ + if (!lb) + lb = fdisk_get_label(cxt, "dos"); + + switch (ent->key) { + case 'c': + fdisk_label_get_geomrange_cylinders(lb, &mi, &ma); + rc = fdisk_ask_number(cxt, mi, fdisk_get_geom_cylinders(cxt), + ma, _("Number of cylinders"), &c); + break; + case 'h': + { + unsigned int i, a; + fdisk_label_get_geomrange_heads(lb, &i, &a); + rc = fdisk_ask_number(cxt, i, fdisk_get_geom_heads(cxt), + a, _("Number of heads"), &h); + break; + } + case 's': + fdisk_label_get_geomrange_sectors(lb, &mi, &ma); + rc = fdisk_ask_number(cxt, mi, fdisk_get_geom_sectors(cxt), + ma, _("Number of sectors"), &s); + break; + } + + if (!rc) + fdisk_override_geometry(cxt, c, h, s); + return rc; +} + +static int createlabel_menu_cb(struct fdisk_context **cxt0, + const struct menu *menu __attribute__((__unused__)), + const struct menu_entry *ent) +{ + struct fdisk_context *cxt = *cxt0; + int rc = -EINVAL; + + DBG(MENU, ul_debug("enter Create label menu")); + + assert(cxt); + assert(ent); + + if (ent->expert) { + switch (ent->key) { + case 'g': + /* Deprecated, use 'G' in main menu, just for backward + * compatibility only. */ + rc = fdisk_create_disklabel(cxt, "sgi"); + break; + } + } else { + switch (ent->key) { + case 'g': + rc = fdisk_create_disklabel(cxt, "gpt"); + break; + case 'G': + rc = fdisk_create_disklabel(cxt, "sgi"); + break; + case 'o': + rc = fdisk_create_disklabel(cxt, "dos"); + break; + case 's': + rc = fdisk_create_disklabel(cxt, "sun"); + break; + } + } + + if (rc == 0 && fdisk_get_collision(cxt)) + follow_wipe_mode(cxt); + + return rc; +} diff --git a/disk-utils/fdisk.8 b/disk-utils/fdisk.8 new file mode 100644 index 0000000..430c96c --- /dev/null +++ b/disk-utils/fdisk.8 @@ -0,0 +1,371 @@ +.\" Copyright 1992, 1993 Rickard E. Faith (faith@cs.unc.edu) +.\" Copyright 1998 Andries E. Brouwer (aeb@cwi.nl) +.\" Copyright 2012 Davidlohr Bueso <dave@gnu.org> +.\" Copyright (C) 2013 Karel Zak <kzak@redhat.com> +.\" May be distributed under the GNU General Public License +.TH FDISK 8 "February 2016" "util-linux" "System Administration" + +.SH NAME +fdisk \- manipulate disk partition table + +.SH SYNOPSIS +.B fdisk +[options] +.I device +.sp +.B fdisk \-l +.RI [ device ...] + +.SH DESCRIPTION +.B fdisk +is a dialog-driven program for creation and manipulation of partition tables. +It understands GPT, MBR, Sun, SGI and BSD partition tables. + +Block devices can be divided into one or more logical disks called +.IR partitions . +This division is recorded in the +.IR "partition table" , +usually found in sector 0 of the disk. +(In the BSD world one talks about `disk slices' and a `disklabel'.) + +All partitioning is driven by device I/O limits (the topology) by default. +.B fdisk +is able to optimize the disk layout for a 4K-sector size and use an alignment offset on +modern devices for MBR and GPT. It is always a good idea to follow \fBfdisk\fR's defaults +as the default values (e.g. first and last partition sectors) and partition +sizes specified by the +/-<size>{M,G,...} notation are always aligned according +to the device properties. + +Note that +.BR partx (8) +provides a rich interface for scripts to print disk layouts, +.B fdisk +is mostly designed for humans. Backward compatibility in the output of +.B fdisk +is not guaranteed. The input (the commands) should always be backward compatible. + +.SH OPTIONS +.TP +\fB\-b\fR, \fB\-\-sector\-size\fR \fIsectorsize\fP +Specify the sector size of the disk. Valid values are 512, 1024, 2048, and 4096. +(Recent kernels know the sector size. Use this option only on old kernels or +to override the kernel's ideas.) Since util-linux-2.17, \fBfdisk\fR differentiates +between logical and physical sector size. This option changes both sector sizes to +.IB sectorsize . +.TP +\fB\-B\fR, \fB\-\-protect\-boot\fP +Don't erase the begin of the first disk sector when create a new disk label. This +feature is supported for GPT and MBR. +.TP +\fB\-c\fR, \fB\-\-compatibility\fR[=\fImode\fR] +Specify the compatibility mode, 'dos' or 'nondos'. The default is non-DOS +mode. For backward compatibility, it is possible to use the option without +the \fImode\fR argument -- then the default is used. Note that the optional +\fImode\fR argument cannot be separated from the \fB-c\fR option by a space, +the correct form is for example '-c=dos'. +.TP +\fB\-h\fR, \fB\-\-help\fR +Display a help text and exit. +.TP +\fB\-L\fR, \fB\-\-color\fR[=\fIwhen\fR] +Colorize the output. The optional argument \fIwhen\fP +can be \fBauto\fR, \fBnever\fR or \fBalways\fR. If the \fIwhen\fR argument is omitted, +it defaults to \fBauto\fR. The colors can be disabled; for the current built-in default +see the \fB\-\-help\fR output. See also the \fBCOLORS\fR section. +.TP +\fB\-l\fR, \fB\-\-list\fR +List the partition tables for the specified devices and then exit. +If no devices are given, those mentioned in +.I /proc/partitions +(if that file exists) are used. +.TP +.BR \-o , " \-\-output " \fIlist\fP +Specify which output columns to print. Use +.B \-\-help +to get a list of all supported columns. + +The default list of columns may be extended if \fIlist\fP is +specified in the format \fI+list\fP (e.g. \fB-o +UUID\fP). +.TP +\fB\-s\fR, \fB\-\-getsz\fR +Print the size in 512-byte sectors of each given block device. This option is DEPRECATED +in favour of +.BR blockdev (1). +.TP +\fB\-t\fR, \fB\-\-type\fR \fItype\fR +Enable support only for disklabels of the specified \fItype\fP, and disable +support for all other types. +.TP +\fB\-u\fR, \fB\-\-units\fR[=\fIunit\fR] +When listing partition tables, show sizes in 'sectors' or in 'cylinders'. The +default is to show sizes in sectors. For backward compatibility, it is possible +to use the option without the \fIunit\fR argument -- then the default is used. +Note that the optional \fIunit\fR argument cannot be separated from the \fB-u\fR +option by a space, the correct form is for example '-u=cylinders'. + +.TP +\fB\-C\fR, \fB\-\-cylinders\fR \fInumber\fR +Specify the number of cylinders of the disk. +I have no idea why anybody would want to do so. +.TP +\fB\-H\fR, \fB\-\-heads\fR \fInumber\fR +Specify the number of heads of the disk. (Not the physical number, +of course, but the number used for partition tables.) +Reasonable values are 255 and 16. +.TP +\fB\-S\fR, \fB\-\-sectors\fR \fInumber\fR +Specify the number of sectors per track of the disk. +(Not the physical number, of course, but the number used for +partition tables.) A reasonable value is 63. + +.TP +\fB\-w\fR, \fB\-\-wipe\fR \fIwhen\fR +Wipe filesystem, RAID and partition-table signatures from the device, in order +to avoid possible collisions. The argument \fIwhen\fR can be \fBauto\fR, +\fBnever\fR or \fBalways\fR. When this option is not given, the default is +\fBauto\fR, in which case signatures are wiped only when in interactive mode. +In all cases detected signatures are reported by warning messages +before a new partition table is created. See also +.BR wipefs (8) +command. + +.TP +\fB\-W\fR, \fB\-\-wipe-partition\fR \fIwhen\fR +Wipe filesystem, RAID and partition-table signatures from a newly created +partitions, in order to avoid possible collisions. The argument \fIwhen\fR can +be \fBauto\fR, \fBnever\fR or \fBalways\fR. When this option is not given, the +default is \fBauto\fR, in which case signatures are wiped only when in +interactive mode and after confirmation by user. In all cases detected +signatures are reported by warning messages before a new partition is +created. See also +.BR wipefs (8) +command. + +.TP +\fB\-V\fR, \fB\-\-version\fR +Display version information and exit. + +.SH DEVICES +The +.I device +is usually /dev/sda, /dev/sdb or so. A device name refers to the entire disk. +Old systems without libata (a library used inside the Linux kernel to support +ATA host controllers and devices) make a difference between IDE and SCSI disks. +In such cases the device name will be /dev/hd* (IDE) or /dev/sd* (SCSI). + +The +.I partition +is a device name followed by a partition number. For example, /dev/sda1 is the +first partition on the first hard disk in the system. See also Linux kernel +documentation (the Documentation/devices.txt file). + +.SH SIZES +The "last sector" dialog accepts partition size specified by number of sectors +or by +/-<size>{K,B,M,G,...} notation. + +If the size is prefixed by '+' then it is interpreted as relative to the +partition first sector. If the size is prefixed by '-' then it is interpreted +as relative to the high limit (last available sector for the partition). + +In the case the size is specified in bytes than the number may be followed by +the multiplicative suffixes KiB=1024, MiB=1024*1024, and so on for GiB, TiB, +PiB, EiB, ZiB and YiB. The "iB" is optional, e.g. "K" has the same meaning as +"KiB". + +The relative sizes are always aligned according to device I/O limits. The ++/-<size>{K,B,M,G,...} notation is recommended. + +For backward compatibility fdisk also accepts the suffixes KB=1000, +MB=1000*1000, and so on for GB, TB, PB, EB, ZB and YB. These 10^N suffixes +are deprecated. + +.SH SCRIPT FILES +.B fdisk +allows to read (by 'I' command) sfdisk compatible script files. The script is +applied to in-memory partition table, and then it is possible to modify the +partition table before you write it to the device. +.PP +And vice-versa it is possible to write the current in-memory disk layout +to the script file by command 'O'. +.PP +The script files are compatible between cfdisk, sfdisk, fdisk and another +libfdisk applications. For more details see +.BR sfdisk (8). + +.SH DISK LABELS +.B GPT (GUID Partition Table) +.RS +GPT is modern standard for the layout of the partition table. GPT uses 64-bit +logical block addresses, checksums, UUIDs and names for partitions and an +unlimited number of partitions (although the number of partitions is +usually restricted to 128 in many partitioning tools). + +Note that the first sector is still reserved for a +.B protective MBR +in the GPT specification. It prevents MBR-only partitioning tools +from mis-recognizing and overwriting GPT disks. + +GPT is always a better choice than MBR, especially on modern hardware with a UEFI +boot loader. +.RE + +.B DOS-type (MBR) +.RS +A DOS-type partition table can describe an unlimited number of partitions. In sector 0 +there is room for the description of 4 partitions (called `primary'). One of +these may be an extended partition; this is a box holding logical partitions, +with descriptors found in a linked list of sectors, each preceding the +corresponding logical partitions. The four primary partitions, present or not, +get numbers 1-4. Logical partitions are numbered starting from 5. + +In a DOS-type partition table the starting offset and the size of each +partition is stored in two ways: as an absolute number of sectors (given in 32 +bits), and as a +.B Cylinders/Heads/Sectors +triple (given in 10+8+6 bits). The former is OK -- with 512-byte sectors this +will work up to 2 TB. The latter has two problems. First, these C/H/S fields +can be filled only when the number of heads and the number of sectors per track +are known. And second, even if we know what these numbers should be, the 24 +bits that are available do not suffice. DOS uses C/H/S only, Windows uses +both, Linux never uses C/H/S. The +.B C/H/S addressing is deprecated +and may be unsupported in some later fdisk version. + +.B Please, read the DOS-mode section if you want DOS-compatible partitions. +.B fdisk +does not care about cylinder boundaries by default. +.RE + +.B BSD/Sun-type +.RS +A BSD/Sun disklabel can describe 8 partitions, the third of which should be a `whole +disk' partition. Do not start a partition that actually uses its first sector +(like a swap partition) at cylinder 0, since that will destroy the disklabel. +Note that a +.B BSD label +is usually nested within a DOS partition. +.RE + +.B IRIX/SGI-type +.RS +An IRIX/SGI disklabel can describe 16 partitions, the eleventh of which should be an entire +`volume' partition, while the ninth should be labeled `volume header'. The +volume header will also cover the partition table, i.e., it starts at block +zero and extends by default over five cylinders. The remaining space in the +volume header may be used by header directory entries. No partitions may +overlap with the volume header. Also do not change its type or make some +filesystem on it, since you will lose the partition table. Use this type of +label only when working with Linux on IRIX/SGI machines or IRIX/SGI disks under +Linux. +.RE + +A sync() and an ioctl(BLKRRPART) (rereading the partition table from disk) +are performed before exiting when the partition table has been updated. + +.SH "DOS mode and DOS 6.x WARNING" +.B Note that all this is deprecated. You don't have to care about things like +.B geometry and cylinders on modern operating systems. If you really want +.B DOS-compatible partitioning then you have to enable DOS mode and cylinder +.B units by using the '-c=dos -u=cylinders' fdisk command-line options. + +The DOS 6.x FORMAT command looks for some information in the first sector of +the data area of the partition, and treats this information as more reliable +than the information in the partition table. DOS FORMAT expects DOS FDISK to +clear the first 512 bytes of the data area of a partition whenever a size +change occurs. DOS FORMAT will look at this extra information even if the /U +flag is given -- we consider this a bug in DOS FORMAT and DOS FDISK. + +The bottom line is that if you use \fBfdisk\fR or \fBcfdisk\fR to change the +size of a DOS partition table entry, then you must also use +.BR dd "(1) to " "zero the first 512 bytes" +of that partition before using DOS FORMAT to format the partition. For +example, if you were using \fBfdisk\fR to make a DOS partition table entry for +/dev/sda1, then (after exiting \fBfdisk\fR and rebooting Linux so that the +partition table information is valid) you would use the command "dd +if=/dev/zero of=/dev/sda1 bs=512 count=1" to zero the first 512 bytes of the +partition. + +.B fdisk +usually obtains the disk geometry automatically. This is not necessarily the +physical disk geometry (indeed, modern disks do not really have anything like a +physical geometry, certainly not something that can be described in the simplistic +Cylinders/Heads/Sectors form), but it is the disk geometry that MS-DOS uses for +the partition table. + +Usually all goes well by default, and there are no problems if Linux is the +only system on the disk. However, if the disk has to be shared with other +operating systems, it is often a good idea to let an fdisk from another +operating system make at least one partition. When Linux boots it looks at the +partition table, and tries to deduce what (fake) geometry is required for good +cooperation with other systems. + +Whenever a partition table is printed out in DOS mode, a consistency check is +performed on the partition table entries. This check verifies that the +physical and logical start and end points are identical, and that each +partition starts and ends on a cylinder boundary (except for the first +partition). + +Some versions of MS-DOS create a first partition which does not begin +on a cylinder boundary, but on sector 2 of the first cylinder. +Partitions beginning in cylinder 1 cannot begin on a cylinder boundary, but +this is unlikely to cause difficulty unless you have OS/2 on your machine. + +For best results, you should always use an OS-specific partition table +program. For example, you should make DOS partitions with the DOS FDISK +program and Linux partitions with the Linux fdisk or Linux cfdisk programs. +.SH COLORS +Implicit coloring can be disabled by an empty file \fI/etc/terminal-colors.d/fdisk.disable\fR. + +See +.BR terminal-colors.d (5) +for more details about colorization configuration. The logical color names +supported by +.B fdisk +are: +.TP +.B header +The header of the output tables. +.TP +.B help-title +The help section titles. +.TP +.B warn +The warning messages. +.TP +.B welcome +The welcome message. + +.SH AUTHORS +.MT kzak@redhat.com +Karel Zak +.ME +.br +.MT dave@gnu.org +Davidlohr Bueso +.ME +.br +.PP +The original version was written by +Andries E. Brouwer, A. V. Le Blanc and others. + +.SH ENVIRONMENT +.IP FDISK_DEBUG=all +enables fdisk debug output. +.IP LIBFDISK_DEBUG=all +enables libfdisk debug output. +.IP LIBBLKID_DEBUG=all +enables libblkid debug output. +.IP LIBSMARTCOLS_DEBUG=all +enables libsmartcols debug output. +.IP LIBSMARTCOLS_DEBUG_PADDING=on +use visible padding characters. Requires enabled LIBSMARTCOLS_DEBUG. + +.SH "SEE ALSO" +.BR cfdisk (8), +.BR mkfs (8), +.BR partx (8), +.BR sfdisk (8) + +.SH AVAILABILITY +The fdisk command is part of the util-linux package and is available from +https://www.kernel.org/pub/linux/utils/util-linux/. diff --git a/disk-utils/fdisk.c b/disk-utils/fdisk.c new file mode 100644 index 0000000..ed4db8b --- /dev/null +++ b/disk-utils/fdisk.c @@ -0,0 +1,1097 @@ +/* + * Copyright (C) 1992 A. V. Le Blanc (LeBlanc@mcc.ac.uk) + * Copyright (C) 2012 Davidlohr Bueso <dave@gnu.org> + * + * Copyright (C) 2007-2013 Karel Zak <kzak@redhat.com> + * + * This program is free software. You can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation: either version 1 or + * (at your option) any later version. + */ +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <ctype.h> +#include <errno.h> +#include <getopt.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <time.h> +#include <limits.h> +#include <signal.h> +#include <poll.h> +#include <libsmartcols.h> +#ifdef HAVE_LIBREADLINE +# define _FUNCTION_DEF +# include <readline/readline.h> +#endif + +#include "c.h" +#include "xalloc.h" +#include "all-io.h" +#include "nls.h" +#include "rpmatch.h" +#include "blkdev.h" +#include "mbsalign.h" +#include "pathnames.h" +#include "canonicalize.h" +#include "strutils.h" +#include "closestream.h" +#include "pager.h" + +#include "fdisk.h" + +#include "pt-sun.h" /* to toggle flags */ + +#ifdef HAVE_LINUX_COMPILER_H +# include <linux/compiler.h> +#endif +#ifdef HAVE_LINUX_BLKPG_H +# include <linux/blkpg.h> +#endif + +int pwipemode = WIPEMODE_AUTO; +int device_is_used; +int is_interactive; +struct fdisk_table *original_layout; + +static int wipemode = WIPEMODE_AUTO; + +/* + * fdisk debug stuff (see fdisk.h and include/debug.h) + */ +UL_DEBUG_DEFINE_MASK(fdisk); +UL_DEBUG_DEFINE_MASKNAMES(fdisk) = UL_DEBUG_EMPTY_MASKNAMES; + +static void fdiskprog_init_debug(void) +{ + __UL_INIT_DEBUG_FROM_ENV(fdisk, FDISKPROG_DEBUG_, 0, FDISK_DEBUG); +} + +static void reply_sighandler(int sig __attribute__((unused))) +{ + DBG(ASK, ul_debug("got signal")); +} + +static int reply_running; + +#ifdef HAVE_LIBREADLINE +static char *reply_line; + +static void reply_linehandler(char *line) +{ + reply_line = line; + reply_running = 0; + rl_callback_handler_remove(); /* avoid duplicate prompt */ +} +#endif + +int get_user_reply(const char *prompt, char *buf, size_t bufsz) +{ + struct sigaction oldact, act = { + .sa_handler = reply_sighandler + }; + struct pollfd fds[] = { + { .fd = fileno(stdin), .events = POLLIN } + }; + size_t sz; + int ret = 0; + + DBG(ASK, ul_debug("asking for user reply %s", is_interactive ? "[interactive]" : "")); + + sigemptyset(&act.sa_mask); + sigaction(SIGINT, &act, &oldact); + +#ifdef HAVE_LIBREADLINE + if (is_interactive) + rl_callback_handler_install(prompt, reply_linehandler); +#endif + reply_running = 1; + do { + int rc; + + *buf = '\0'; +#ifdef HAVE_LIBREADLINE + if (!is_interactive) +#endif + { + fputs(prompt, stdout); + fflush(stdout); + } + + rc = poll(fds, 1, -1); + if (rc == -1 && errno == EINTR) { /* interrupted by signal */ + DBG(ASK, ul_debug("cancel by CTRL+C")); + ret = -ECANCELED; + goto done; + } + if (rc == -1 && errno != EAGAIN) { /* error */ + ret = -errno; + goto done; + } +#ifdef HAVE_LIBREADLINE + if (is_interactive) { + /* read input and copy to buf[] */ + rl_callback_read_char(); + if (!reply_running && reply_line) { + sz = strlen(reply_line); + if (sz == 0) + buf[sz++] = '\n'; + else + memcpy(buf, reply_line, min(sz, bufsz)); + buf[min(sz, bufsz - 1)] = '\0'; + free(reply_line); + reply_line = NULL; + } + } else +#endif + { + if (!fgets(buf, bufsz, stdin)) + *buf = '\0'; + break; + } + } while (reply_running); + + if (!*buf) { + DBG(ASK, ul_debug("cancel by CTRL+D")); + ret = -ECANCELED; + goto done; + } + + /* + * cleanup the reply + */ + sz = ltrim_whitespace((unsigned char *) buf); + if (sz && *(buf + sz - 1) == '\n') + *(buf + sz - 1) = '\0'; + + DBG(ASK, ul_debug("user's reply: >>>%s<<<", buf)); +done: +#ifdef HAVE_LIBREADLINE + if (is_interactive) + rl_callback_handler_remove(); +#endif + sigaction(SIGINT, &oldact, NULL); + return ret; +} + +static int ask_menu(struct fdisk_context *cxt, struct fdisk_ask *ask, + char *buf, size_t bufsz) + +{ + const char *q = fdisk_ask_get_query(ask); + int dft = fdisk_ask_menu_get_default(ask); + + if (q) { + fputs(q, stdout); /* print header */ + fputc('\n', stdout); + } + + do { + char prompt[128]; + int key, c, rc; + const char *name, *desc; + size_t i = 0; + + /* print menu items */ + while (fdisk_ask_menu_get_item(ask, i++, &key, &name, &desc) == 0) + fprintf(stdout, " %c %s (%s)\n", key, name, desc); + + /* ask for key */ + snprintf(prompt, sizeof(prompt), _("Select (default %c): "), dft); + rc = get_user_reply(prompt, buf, bufsz); + if (rc) + return rc; + if (!*buf) { + fdisk_info(cxt, _("Using default response %c."), dft); + c = dft; + } else + c = tolower(buf[0]); + + /* check result */ + i = 0; + while (fdisk_ask_menu_get_item(ask, i++, &key, NULL, NULL) == 0) { + if (c == key) { + fdisk_ask_menu_set_result(ask, c); + return 0; /* success */ + } + } + fdisk_warnx(cxt, _("Value out of range.")); + } while (1); + + return -EINVAL; +} + + +#define tochar(num) ((int) ('a' + num - 1)) +static int ask_number(struct fdisk_context *cxt, + struct fdisk_ask *ask, + char *buf, size_t bufsz) +{ + char prompt[128] = { '\0' }; + const char *q = fdisk_ask_get_query(ask); + const char *range = fdisk_ask_number_get_range(ask); + + uint64_t dflt = fdisk_ask_number_get_default(ask), + low = fdisk_ask_number_get_low(ask), + high = fdisk_ask_number_get_high(ask); + int inchar = fdisk_ask_number_inchars(ask); + + assert(q); + + DBG(ASK, ul_debug("asking for number " + "['%s', <%"PRIu64",%"PRIu64">, default=%"PRIu64", range: %s]", + q, low, high, dflt, range)); + + if (range && dflt >= low && dflt <= high) { + if (inchar) + snprintf(prompt, sizeof(prompt), _("%s (%s, default %c): "), + q, range, tochar(dflt)); + else + snprintf(prompt, sizeof(prompt), _("%s (%s, default %"PRIu64"): "), + q, range, dflt); + + } else if (dflt >= low && dflt <= high) { + if (inchar) + snprintf(prompt, sizeof(prompt), _("%s (%c-%c, default %c): "), + q, tochar(low), tochar(high), tochar(dflt)); + else + snprintf(prompt, sizeof(prompt), + _("%s (%"PRIu64"-%"PRIu64", default %"PRIu64"): "), + q, low, high, dflt); + } else if (inchar) + snprintf(prompt, sizeof(prompt), _("%s (%c-%c): "), + q, tochar(low), tochar(high)); + else + snprintf(prompt, sizeof(prompt), _("%s (%"PRIu64"-%"PRIu64"): "), + q, low, high); + + do { + int rc = get_user_reply(prompt, buf, bufsz); + uint64_t num = 0; + + if (rc) + return rc; + if (!*buf && dflt >= low && dflt <= high) + return fdisk_ask_number_set_result(ask, dflt); + + if (isdigit_string(buf)) { + char *end; + + errno = 0; + num = strtoumax(buf, &end, 10); + if (errno || buf == end || (end && *end)) + continue; + } else if (inchar && isalpha(*buf)) { + num = tolower(*buf) - 'a' + 1; + } else + rc = -EINVAL; + + if (rc == 0 && num >= low && num <= high) + return fdisk_ask_number_set_result(ask, num); + + fdisk_warnx(cxt, _("Value out of range.")); + } while (1); + + return -1; +} + +static int ask_offset(struct fdisk_context *cxt, + struct fdisk_ask *ask, + char *buf, size_t bufsz) +{ + char prompt[128] = { '\0' }; + const char *q = fdisk_ask_get_query(ask); + const char *range = fdisk_ask_number_get_range(ask); + + uint64_t dflt = fdisk_ask_number_get_default(ask), + low = fdisk_ask_number_get_low(ask), + high = fdisk_ask_number_get_high(ask), + base = fdisk_ask_number_get_base(ask); + + assert(q); + + DBG(ASK, ul_debug("asking for offset ['%s', <%"PRIu64",%"PRIu64">, base=%"PRIu64", default=%"PRIu64", range: %s]", + q, low, high, base, dflt, range)); + + if (range && dflt >= low && dflt <= high) + snprintf(prompt, sizeof(prompt), _("%s (%s, default %"PRIu64"): "), + q, range, dflt); + else if (dflt >= low && dflt <= high) + snprintf(prompt, sizeof(prompt), + _("%s (%"PRIu64"-%"PRIu64", default %"PRIu64"): "), + q, low, high, dflt); + else + snprintf(prompt, sizeof(prompt), _("%s (%"PRIu64"-%"PRIu64"): "), + q, low, high); + + do { + uintmax_t num = 0; + char sig = 0, *p; + int pwr = 0; + + int rc = get_user_reply(prompt, buf, bufsz); + if (rc) + return rc; + if (!*buf && dflt >= low && dflt <= high) + return fdisk_ask_number_set_result(ask, dflt); + + p = buf; + if (*p == '+' || *p == '-') { + sig = *buf; + p++; + } + + rc = parse_size(p, &num, &pwr); + if (rc) + continue; + DBG(ASK, ul_debug("parsed size: %ju", num)); + if (sig && pwr) { + /* +{size}{K,M,...} specified, the "num" is in bytes */ + uint64_t unit = fdisk_ask_number_get_unit(ask); + num += unit/2; /* round */ + num /= unit; + } + if (sig == '+') + num += base; + else if (sig == '-' && fdisk_ask_number_is_wrap_negative(ask)) + num = high - num; + else if (sig == '-') + num = base - num; + + DBG(ASK, ul_debug("final offset: %ju [sig: %c, power: %d, %s]", + num, sig, pwr, + sig ? "relative" : "absolute")); + if (num >= low && num <= high) { + if (sig && pwr) + fdisk_ask_number_set_relative(ask, 1); + return fdisk_ask_number_set_result(ask, (uint64_t)num); + } + fdisk_warnx(cxt, _("Value out of range.")); + } while (1); + + return -1; +} + +static unsigned int info_count; + +static void fputs_info(struct fdisk_ask *ask, FILE *out) +{ + const char *msg; + assert(ask); + + msg = fdisk_ask_print_get_mesg(ask); + if (!msg) + return; + if (info_count == 1) + fputc('\n', out); + + fputs(msg, out); + fputc('\n', out); +} + +int ask_callback(struct fdisk_context *cxt, struct fdisk_ask *ask, + void *data __attribute__((__unused__))) +{ + int rc = 0; + char buf[BUFSIZ]; + + assert(cxt); + assert(ask); + + if (fdisk_ask_get_type(ask) != FDISK_ASKTYPE_INFO) + info_count = 0; + + switch(fdisk_ask_get_type(ask)) { + case FDISK_ASKTYPE_MENU: + return ask_menu(cxt, ask, buf, sizeof(buf)); + case FDISK_ASKTYPE_NUMBER: + return ask_number(cxt, ask, buf, sizeof(buf)); + case FDISK_ASKTYPE_OFFSET: + return ask_offset(cxt, ask, buf, sizeof(buf)); + case FDISK_ASKTYPE_INFO: + if (!fdisk_is_listonly(cxt)) + info_count++; + fputs_info(ask, stdout); + break; + case FDISK_ASKTYPE_WARNX: + fflush(stdout); + color_scheme_fenable("warn", UL_COLOR_RED, stderr); + fputs(fdisk_ask_print_get_mesg(ask), stderr); + color_fdisable(stderr); + fputc('\n', stderr); + break; + case FDISK_ASKTYPE_WARN: + fflush(stdout); + color_scheme_fenable("warn", UL_COLOR_RED, stderr); + fputs(fdisk_ask_print_get_mesg(ask), stderr); + errno = fdisk_ask_print_get_errno(ask); + fprintf(stderr, ": %m\n"); + color_fdisable(stderr); + break; + case FDISK_ASKTYPE_YESNO: + fputc('\n', stdout); + do { + int x; + fputs(fdisk_ask_get_query(ask), stdout); + rc = get_user_reply(_(" [Y]es/[N]o: "), buf, sizeof(buf)); + if (rc) + break; + x = rpmatch(buf); + if (x == RPMATCH_YES || x == RPMATCH_NO) { + fdisk_ask_yesno_set_result(ask, x); + break; + } + } while(1); + DBG(ASK, ul_debug("yes-no ask: reply '%s' [rc=%d]", buf, rc)); + break; + case FDISK_ASKTYPE_STRING: + { + char prmt[BUFSIZ]; + snprintf(prmt, sizeof(prmt), "%s: ", fdisk_ask_get_query(ask)); + fputc('\n', stdout); + rc = get_user_reply(prmt, buf, sizeof(buf)); + if (rc == 0) + fdisk_ask_string_set_result(ask, xstrdup(buf)); + DBG(ASK, ul_debug("string ask: reply '%s' [rc=%d]", buf, rc)); + break; + } + default: + warnx(_("internal error: unsupported dialog type %d"), fdisk_ask_get_type(ask)); + return -EINVAL; + } + return rc; +} + +static struct fdisk_parttype *ask_partition_type(struct fdisk_context *cxt) +{ + const char *q; + struct fdisk_label *lb; + + assert(cxt); + lb = fdisk_get_label(cxt, NULL); + + if (!lb) + return NULL; + + q = fdisk_label_has_code_parttypes(lb) ? + _("Hex code (type L to list all codes): ") : + _("Partition type (type L to list all types): "); + do { + char buf[256]; + int rc = get_user_reply(q, buf, sizeof(buf)); + + if (rc) + break; + + if (buf[1] == '\0' && toupper(*buf) == 'L') + list_partition_types(cxt); + else if (*buf) + return fdisk_label_parse_parttype(lb, buf); + } while (1); + + return NULL; +} + + +void list_partition_types(struct fdisk_context *cxt) +{ + size_t ntypes = 0; + struct fdisk_label *lb; + + assert(cxt); + lb = fdisk_get_label(cxt, NULL); + if (!lb) + return; + ntypes = fdisk_label_get_nparttypes(lb); + if (!ntypes) + return; + + if (fdisk_label_has_code_parttypes(lb)) { + /* + * Prints in 4 columns in format <hex> <name> + */ + size_t last[4], done = 0, next = 0, size; + int i; + + size = ntypes; + + for (i = 3; i >= 0; i--) + last[3 - i] = done += (size + i - done) / (i + 1); + i = done = 0; + + do { + #define NAME_WIDTH 15 + char name[NAME_WIDTH * MB_LEN_MAX]; + size_t width = NAME_WIDTH; + const struct fdisk_parttype *t = fdisk_label_get_parttype(lb, next); + size_t ret; + + if (fdisk_parttype_get_name(t)) { + printf("%c%2x ", i ? ' ' : '\n', + fdisk_parttype_get_code(t)); + ret = mbsalign(_(fdisk_parttype_get_name(t)), + name, sizeof(name), + &width, MBS_ALIGN_LEFT, 0); + + if (ret == (size_t)-1 || ret >= sizeof(name)) + printf("%-15.15s", + _(fdisk_parttype_get_name(t))); + else + fputs(name, stdout); + } + + next = last[i++] + done; + if (i > 3 || next >= last[i]) { + i = 0; + next = ++done; + } + } while (done < last[0]); + + } else { + /* + * Prints 1 column in format <idx> <name> <typestr> + */ + size_t i; + + pager_open(); + + for (i = 0; i < ntypes; i++) { + const struct fdisk_parttype *t = fdisk_label_get_parttype(lb, i); + printf("%3zu %-30s %s\n", i + 1, + fdisk_parttype_get_name(t), + fdisk_parttype_get_string(t)); + } + + pager_close(); + } + putchar('\n'); +} + +void toggle_dos_compatibility_flag(struct fdisk_context *cxt) +{ + struct fdisk_label *lb = fdisk_get_label(cxt, "dos"); + int flag; + + if (!lb) + return; + + flag = !fdisk_dos_is_compatible(lb); + fdisk_info(cxt, flag ? + _("DOS Compatibility flag is set (DEPRECATED!)") : + _("DOS Compatibility flag is not set")); + + fdisk_dos_enable_compatible(lb, flag); + + if (fdisk_is_label(cxt, DOS)) + fdisk_reset_alignment(cxt); /* reset the current label */ +} + +void change_partition_type(struct fdisk_context *cxt) +{ + size_t i; + struct fdisk_parttype *t = NULL; + struct fdisk_partition *pa = NULL; + const char *old = NULL; + + assert(cxt); + + if (fdisk_ask_partnum(cxt, &i, FALSE)) + return; + + if (fdisk_get_partition(cxt, i, &pa)) { + fdisk_warnx(cxt, _("Partition %zu does not exist yet!"), i + 1); + return; + } + + t = (struct fdisk_parttype *) fdisk_partition_get_type(pa); + old = t ? fdisk_parttype_get_name(t) : _("Unknown"); + + do { + t = ask_partition_type(cxt); + } while (!t); + + if (fdisk_set_partition_type(cxt, i, t) == 0) + fdisk_info(cxt, + _("Changed type of partition '%s' to '%s'."), + old, t ? fdisk_parttype_get_name(t) : _("Unknown")); + else + fdisk_info(cxt, + _("Type of partition %zu is unchanged: %s."), + i + 1, old); + + fdisk_unref_partition(pa); + fdisk_unref_parttype(t); +} + +int print_partition_info(struct fdisk_context *cxt) +{ + struct fdisk_partition *pa = NULL; + int rc = 0; + size_t i, nfields; + int *fields = NULL; + struct fdisk_label *lb = fdisk_get_label(cxt, NULL); + + if ((rc = fdisk_ask_partnum(cxt, &i, FALSE))) + return rc; + + if ((rc = fdisk_get_partition(cxt, i, &pa))) { + fdisk_warnx(cxt, _("Partition %zu does not exist yet!"), i + 1); + return rc; + } + + if ((rc = fdisk_label_get_fields_ids_all(lb, cxt, &fields, &nfields))) + goto clean_data; + + for (i = 0; i < nfields; ++i) { + int id = fields[i]; + char *data = NULL; + const struct fdisk_field *fd = fdisk_label_get_field(lb, id); + + if (!fd) + continue; + + rc = fdisk_partition_to_string(pa, cxt, id, &data); + if (rc < 0) + goto clean_data; + if (!data || !*data) + continue; + fdisk_info(cxt, "%15s: %s", fdisk_field_get_name(fd), data); + free(data); + } + +clean_data: + fdisk_unref_partition(pa); + free(fields); + return rc; +} + +static size_t skip_empty(const unsigned char *buf, size_t i, size_t sz) +{ + size_t next; + const unsigned char *p0 = buf + i; + + for (next = i + 16; next < sz; next += 16) { + if (memcmp(p0, buf + next, 16) != 0) + break; + } + + return next == i + 16 ? i : next; +} + +static void dump_buffer(off_t base, unsigned char *buf, size_t sz, int all) +{ + size_t i, l, next = 0; + + if (!buf) + return; + for (i = 0, l = 0; i < sz; i++, l++) { + if (l == 0) { + if (all == 0 && !next) + next = skip_empty(buf, i, sz); + printf("%08jx ", (intmax_t)base + i); + } + printf(" %02x", buf[i]); + if (l == 7) /* words separator */ + fputs(" ", stdout); + else if (l == 15) { + fputc('\n', stdout); /* next line */ + l = -1; + if (next > i) { + printf("*\n"); + i = next - 1; + } + next = 0; + } + } + if (l > 0) + printf("\n"); +} + +static void dump_blkdev(struct fdisk_context *cxt, const char *name, + uint64_t offset, size_t size, int all) +{ + int fd = fdisk_get_devfd(cxt); + + fdisk_info(cxt, _("\n%s: offset = %"PRIu64", size = %zu bytes."), + name, offset, size); + + assert(fd >= 0); + + if (lseek(fd, (off_t) offset, SEEK_SET) == (off_t) -1) + fdisk_warn(cxt, _("cannot seek")); + else { + unsigned char *buf = xmalloc(size); + + if (read_all(fd, (char *) buf, size) != (ssize_t) size) + fdisk_warn(cxt, _("cannot read")); + else + dump_buffer(offset, buf, size, all); + free(buf); + } +} + +void dump_firstsector(struct fdisk_context *cxt) +{ + int all = !isatty(STDOUT_FILENO); + + assert(cxt); + + dump_blkdev(cxt, _("First sector"), 0, fdisk_get_sector_size(cxt), all); +} + +void dump_disklabel(struct fdisk_context *cxt) +{ + int all = !isatty(STDOUT_FILENO); + int i = 0; + const char *name = NULL; + uint64_t offset = 0; + size_t size = 0; + + assert(cxt); + + while (fdisk_locate_disklabel(cxt, i++, &name, &offset, &size) == 0 && size) + dump_blkdev(cxt, name, offset, size, all); +} + +static fdisk_sector_t get_dev_blocks(char *dev) +{ + int fd, ret; + fdisk_sector_t size; + + if ((fd = open(dev, O_RDONLY)) < 0) + err(EXIT_FAILURE, _("cannot open %s"), dev); + ret = blkdev_get_sectors(fd, (unsigned long long *) &size); + close(fd); + if (ret < 0) + err(EXIT_FAILURE, _("BLKGETSIZE ioctl failed on %s"), dev); + return size/2; +} + + +void follow_wipe_mode(struct fdisk_context *cxt) +{ + int dowipe = wipemode == WIPEMODE_ALWAYS ? 1 : 0; + + if (isatty(STDIN_FILENO) && wipemode == WIPEMODE_AUTO) + dowipe = 1; /* do it in interactive mode */ + + if (fdisk_is_ptcollision(cxt) && wipemode != WIPEMODE_NEVER) + dowipe = 1; /* always remove old PT */ + + fdisk_enable_wipe(cxt, dowipe); + if (dowipe) + fdisk_warnx(cxt, _( + "The old %s signature will be removed by a write command."), + fdisk_get_collision(cxt)); + else + fdisk_warnx(cxt, _( + "The old %s signature may remain on the device. " + "It is recommended to wipe the device with wipefs(8) or " + "fdisk --wipe, in order to avoid possible collisions."), + fdisk_get_collision(cxt)); +} + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *out = stdout; + + fputs(USAGE_HEADER, out); + + fprintf(out, + _(" %1$s [options] <disk> change partition table\n" + " %1$s [options] -l [<disk>] list partition table(s)\n"), + program_invocation_short_name); + + fputs(USAGE_SEPARATOR, out); + fputs(_("Display or manipulate a disk partition table.\n"), out); + + fputs(USAGE_OPTIONS, out); + fputs(_(" -b, --sector-size <size> physical and logical sector size\n"), out); + fputs(_(" -B, --protect-boot don't erase bootbits when creating a new label\n"), out); + fputs(_(" -c, --compatibility[=<mode>] mode is 'dos' or 'nondos' (default)\n"), out); + fputs(_(" -L, --color[=<when>] colorize output (auto, always or never)\n"), out); + fprintf(out, + " %s\n", USAGE_COLORS_DEFAULT); + fputs(_(" -l, --list display partitions and exit\n"), out); + fputs(_(" -o, --output <list> output columns\n"), out); + fputs(_(" -t, --type <type> recognize specified partition table type only\n"), out); + fputs(_(" -u, --units[=<unit>] display units: 'cylinders' or 'sectors' (default)\n"), out); + fputs(_(" -s, --getsz display device size in 512-byte sectors [DEPRECATED]\n"), out); + fputs(_(" --bytes print SIZE in bytes rather than in human readable format\n"), out); + fputs(_(" -w, --wipe <mode> wipe signatures (auto, always or never)\n"), out); + fputs(_(" -W, --wipe-partitions <mode> wipe signatures from new partitions (auto, always or never)\n"), out); + + fputs(USAGE_SEPARATOR, out); + fputs(_(" -C, --cylinders <number> specify the number of cylinders\n"), out); + fputs(_(" -H, --heads <number> specify the number of heads\n"), out); + fputs(_(" -S, --sectors <number> specify the number of sectors per track\n"), out); + + fputs(USAGE_SEPARATOR, out); + printf(USAGE_HELP_OPTIONS(31)); + + list_available_columns(out); + + printf(USAGE_MAN_TAIL("fdisk(8)")); + exit(EXIT_SUCCESS); +} + + +enum { + ACT_FDISK = 0, /* default */ + ACT_LIST, + ACT_SHOWSIZE +}; + +int main(int argc, char **argv) +{ + int rc, i, c, act = ACT_FDISK; + int colormode = UL_COLORMODE_UNDEF; + struct fdisk_context *cxt; + char *outarg = NULL; + enum { + OPT_BYTES = CHAR_MAX + 1 + }; + static const struct option longopts[] = { + { "bytes", no_argument, NULL, OPT_BYTES }, + { "color", optional_argument, NULL, 'L' }, + { "compatibility", optional_argument, NULL, 'c' }, + { "cylinders", required_argument, NULL, 'C' }, + { "heads", required_argument, NULL, 'H' }, + { "sectors", required_argument, NULL, 'S' }, + { "getsz", no_argument, NULL, 's' }, + { "help", no_argument, NULL, 'h' }, + { "list", no_argument, NULL, 'l' }, + { "sector-size", required_argument, NULL, 'b' }, + { "type", required_argument, NULL, 't' }, + { "units", optional_argument, NULL, 'u' }, + { "version", no_argument, NULL, 'V' }, + { "output", no_argument, NULL, 'o' }, + { "protect-boot", no_argument, NULL, 'B' }, + { "wipe", required_argument, NULL, 'w' }, + { "wipe-partitions",required_argument, NULL, 'W' }, + { NULL, 0, NULL, 0 } + }; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + fdisk_init_debug(0); + scols_init_debug(0); + fdiskprog_init_debug(); + + cxt = fdisk_new_context(); + if (!cxt) + err(EXIT_FAILURE, _("failed to allocate libfdisk context")); + + fdisk_set_ask(cxt, ask_callback, NULL); + + while ((c = getopt_long(argc, argv, "b:Bc::C:hH:lL::o:sS:t:u::vVw:W:", + longopts, NULL)) != -1) { + switch (c) { + case 'b': + { + size_t sz = strtou32_or_err(optarg, + _("invalid sector size argument")); + if (sz != 512 && sz != 1024 && sz != 2048 && sz != 4096) + errx(EXIT_FAILURE, _("invalid sector size argument")); + fdisk_save_user_sector_size(cxt, sz, sz); + break; + } + case 'B': + fdisk_enable_bootbits_protection(cxt, 1); + break; + case 'C': + fdisk_save_user_geometry(cxt, + strtou32_or_err(optarg, + _("invalid cylinders argument")), + 0, 0); + break; + case 'c': + if (optarg) { + /* this setting is independent on the current + * actively used label + */ + char *p = *optarg == '=' ? optarg + 1 : optarg; + struct fdisk_label *lb = fdisk_get_label(cxt, "dos"); + + if (!lb) + err(EXIT_FAILURE, _("not found DOS label driver")); + if (strcmp(p, "dos") == 0) + fdisk_dos_enable_compatible(lb, TRUE); + else if (strcmp(p, "nondos") == 0) + fdisk_dos_enable_compatible(lb, FALSE); + else + errx(EXIT_FAILURE, _("unknown compatibility mode '%s'"), p); + } + /* use default if no optarg specified */ + break; + case 'H': + fdisk_save_user_geometry(cxt, 0, + strtou32_or_err(optarg, + _("invalid heads argument")), + 0); + break; + case 'S': + fdisk_save_user_geometry(cxt, 0, 0, + strtou32_or_err(optarg, + _("invalid sectors argument"))); + break; + case 'l': + act = ACT_LIST; + break; + case 'L': + colormode = UL_COLORMODE_AUTO; + if (optarg) + colormode = colormode_or_err(optarg, + _("unsupported color mode")); + break; + case 'o': + outarg = optarg; + break; + case 's': + act = ACT_SHOWSIZE; + break; + case 't': + { + struct fdisk_label *lb = NULL; + + while (fdisk_next_label(cxt, &lb) == 0) + fdisk_label_set_disabled(lb, 1); + + lb = fdisk_get_label(cxt, optarg); + if (!lb) + errx(EXIT_FAILURE, _("unsupported disklabel: %s"), optarg); + fdisk_label_set_disabled(lb, 0); + break; + } + case 'u': + if (optarg && *optarg == '=') + optarg++; + if (fdisk_set_unit(cxt, optarg) != 0) + errx(EXIT_FAILURE, _("unsupported unit")); + break; + case 'V': /* preferred for util-linux */ + case 'v': /* for backward compatibility only */ + printf(UTIL_LINUX_VERSION); + return EXIT_SUCCESS; + case 'w': + wipemode = wipemode_from_string(optarg); + if (wipemode < 0) + errx(EXIT_FAILURE, _("unsupported wipe mode")); + break; + case 'W': + pwipemode = wipemode_from_string(optarg); + if (pwipemode < 0) + errx(EXIT_FAILURE, _("unsupported wipe mode")); + break; + case 'h': + usage(); + case OPT_BYTES: + fdisk_set_size_unit(cxt, FDISK_SIZEUNIT_BYTES); + break; + default: + errtryhelp(EXIT_FAILURE); + } + } + + if (argc-optind != 1 && fdisk_has_user_device_properties(cxt)) + warnx(_("The device properties (sector size and geometry) should" + " be used with one specified device only.")); + + colors_init(colormode, "fdisk"); + is_interactive = isatty(STDIN_FILENO); + + switch (act) { + case ACT_LIST: + fdisk_enable_listonly(cxt, 1); + init_fields(cxt, outarg, NULL); + + if (argc > optind) { + int k; + int ct = 0; + + for (rc = 0, k = optind; k < argc; k++) { + if (ct) + fputs("\n\n", stdout); + + rc += print_device_pt(cxt, argv[k], 1, 0); + ct++; + } + if (rc) + return EXIT_FAILURE; + } else + print_all_devices_pt(cxt, 0); + break; + + case ACT_SHOWSIZE: + /* deprecated */ + if (argc - optind <= 0) { + warnx(_("bad usage")); + errtryhelp(EXIT_FAILURE); + } + for (i = optind; i < argc; i++) { + uintmax_t blks = get_dev_blocks(argv[i]); + + if (argc - optind == 1) + printf("%ju\n", blks); + else + printf("%s: %ju\n", argv[i], blks); + } + break; + + case ACT_FDISK: + if (argc-optind != 1) { + warnx(_("bad usage")); + errtryhelp(EXIT_FAILURE); + } + + /* Here starts interactive mode, use fdisk_{warn,info,..} functions */ + color_scheme_enable("welcome", UL_COLOR_GREEN); + fdisk_info(cxt, _("Welcome to fdisk (%s)."), PACKAGE_STRING); + color_disable(); + fdisk_info(cxt, _("Changes will remain in memory only, until you decide to write them.\n" + "Be careful before using the write command.\n")); + + rc = fdisk_assign_device(cxt, argv[optind], 0); + if (rc == -EACCES) { + rc = fdisk_assign_device(cxt, argv[optind], 1); + if (rc == 0) + fdisk_warnx(cxt, _("Device is open in read-only mode.")); + } + if (rc) + err(EXIT_FAILURE, _("cannot open %s"), argv[optind]); + + fflush(stdout); + + if (fdisk_get_collision(cxt)) + follow_wipe_mode(cxt); + + if (!fdisk_has_label(cxt)) { + fdisk_info(cxt, _("Device does not contain a recognized partition table.")); + fdisk_create_disklabel(cxt, NULL); + + } else if (fdisk_is_label(cxt, GPT) && fdisk_gpt_is_hybrid(cxt)) + fdisk_warnx(cxt, _( + "A hybrid GPT was detected. You have to sync " + "the hybrid MBR manually (expert command 'M').")); + + init_fields(cxt, outarg, NULL); /* -o <columns> */ + + if (!fdisk_is_readonly(cxt)) { + fdisk_get_partitions(cxt, &original_layout); + device_is_used = fdisk_device_is_used(cxt); + } + + while (1) + process_fdisk_menu(&cxt); + } + + fdisk_unref_context(cxt); + return EXIT_SUCCESS; +} diff --git a/disk-utils/fdisk.h b/disk-utils/fdisk.h new file mode 100644 index 0000000..e1147e2 --- /dev/null +++ b/disk-utils/fdisk.h @@ -0,0 +1,56 @@ +#ifndef UTIL_LINUX_FDISK_H +#define UTIL_LINUX_FDISK_H +/* + * fdisk.h + */ + +#include "c.h" +#include <assert.h> +#include <libfdisk.h> + +/* Let's temporary include private libfdisk header file. The final libfdisk.h + * maybe included when fdisk.c and libfdisk code will be completely spit. + */ +#include "blkdev.h" +#include "colors.h" +#include "debug.h" +#include "nls.h" + +#include "fdisk-list.h" + +#define FDISKPROG_DEBUG_INIT (1 << 1) +#define FDISKPROG_DEBUG_MENU (1 << 3) +#define FDISKPROG_DEBUG_MISC (1 << 4) +#define FDISKPROG_DEBUG_ASK (1 << 5) +#define FDISKPROG_DEBUG_ALL 0xFFFF + +extern int pwipemode; +extern struct fdisk_table *original_layout; +extern int device_is_used; +extern int is_interactive; + +UL_DEBUG_DECLARE_MASK(fdisk); +#define DBG(m, x) __UL_DBG(fdisk, FDISKPROG_DEBUG_, m, x) +#define ON_DBG(m, x) __UL_DBG_CALL(fdisk, FDISKPROG_DEBUG_, m, x) + +extern int get_user_reply(const char *prompt, + char *buf, size_t bufsz); +extern int process_fdisk_menu(struct fdisk_context **cxt); + +extern int ask_callback(struct fdisk_context *cxt, struct fdisk_ask *ask, + void *data __attribute__((__unused__))); + +extern int print_partition_info(struct fdisk_context *cxt); + +/* prototypes for fdisk.c */ +extern void dump_firstsector(struct fdisk_context *cxt); +extern void dump_disklabel(struct fdisk_context *cxt); + +extern void list_partition_types(struct fdisk_context *cxt); +extern void change_partition_type(struct fdisk_context *cxt); + +extern void toggle_dos_compatibility_flag(struct fdisk_context *cxt); + +extern void follow_wipe_mode(struct fdisk_context *cxt); + +#endif /* UTIL_LINUX_FDISK_H */ diff --git a/disk-utils/fsck.8 b/disk-utils/fsck.8 new file mode 100644 index 0000000..90759f2 --- /dev/null +++ b/disk-utils/fsck.8 @@ -0,0 +1,418 @@ +.\" Copyright 1993, 1994, 1995 by Theodore Ts'o. All Rights Reserved. +.\" This file may be copied under the terms of the GNU Public License. +.\" +.TH FSCK 8 "February 2009" "util-linux" "System Administration" +.SH NAME +fsck \- check and repair a Linux filesystem +.SH SYNOPSIS +.B fsck +.RB [ \-lsAVRTMNP ] +.RB [ \-r +.RI [ fd ]] +.RB [ \-C +.RI [ fd ]] +.RB [ \-t +.IR fstype ] +.RI [ filesystem \&...\&] +.RB [ \-\- ] +.RI [ fs-specific-options ] +.SH DESCRIPTION +.B fsck +is used to check and optionally repair one or more Linux filesystems. +.I filesys +can be a device name (e.g. +.IR /dev/hdc1 ", " /dev/sdb2 ), +a mount point (e.g. +.IR / ", " /usr ", " /home ), +or an ext2 label or UUID specifier (e.g. +UUID=8868abf6-88c5-4a83-98b8-bfc24057f7bd or LABEL=root). +Normally, the +.B fsck +program will try to handle filesystems on different physical disk drives +in parallel to reduce the total amount of time needed to check all of them. +.PP +If no filesystems are specified on the command line, and the +.B \-A +option is not specified, +.B fsck +will default to checking filesystems in +.I /etc/fstab +serially. This is equivalent to the +.B \-As +options. +.PP +The exit code returned by +.B fsck +is the sum of the following conditions: +.PP +.RS +.PD 0 +.TP +.B 0 +No errors +.TP +.B 1 +Filesystem errors corrected +.TP +.B 2 +System should be rebooted +.TP +.B 4 +Filesystem errors left uncorrected +.TP +.B 8 +Operational error +.TP +.B 16 +Usage or syntax error +.TP +.B 32 +Checking canceled by user request +.TP +.B 128 +Shared-library error +.PD +.RE +.PP +The exit code returned when multiple filesystems are checked +is the bit-wise OR of the exit codes for each +filesystem that is checked. +.PP +In actuality, +.B fsck +is simply a front-end for the various filesystem checkers +(\fBfsck\fR.\fIfstype\fR) available under Linux. The +filesystem-specific checker is searched for in the +PATH environment variable. If the PATH is undefined then +fallback to "/sbin". +.PP +Please see the filesystem-specific checker manual pages for +further details. +.SH OPTIONS +.TP +.B \-l +Create an exclusive +.BR flock (2) +lock file (/run/fsck/<diskname>.lock) for whole-disk device. +This option can be used with one device only (this means that \fB\-A\fR and +\fB\-l\fR are mutually exclusive). This option is recommended when more +.BR fsck (8) +instances are executed in the same time. The option is ignored when used for +multiple devices or for non-rotating disks. \fBfsck\fR does not lock underlying +devices when executed to check stacked devices (e.g.\& MD or DM) \(en this feature is +not implemented yet. +.TP +.BR \-r \ [ \fIfd\fR ] +Report certain statistics for each fsck when it completes. These statistics +include the exit status, the maximum run set size (in kilobytes), the elapsed +all-clock time and the user and system CPU time used by the fsck run. For +example: + +/dev/sda1: status 0, rss 92828, real 4.002804, user 2.677592, sys 0.86186 + +GUI front-ends may specify a file descriptor +.IR fd , +in which case the progress bar information will be sent to that file descriptor +in a machine parsable format. For example: + +/dev/sda1 0 92828 4.002804 2.677592 0.86186 +.TP +.B \-s +Serialize +.B fsck +operations. This is a good idea if you are checking multiple +filesystems and the checkers are in an interactive mode. (Note: +.BR e2fsck (8) +runs in an interactive mode by default. To make +.BR e2fsck (8) +run in a non-interactive mode, you must either specify the +.B \-p +or +.B \-a +option, if you wish for errors to be corrected automatically, or the +.B \-n +option if you do not.) +.TP +.BI \-t " fslist" +Specifies the type(s) of filesystem to be checked. When the +.B \-A +flag is specified, only filesystems that match +.I fslist +are checked. The +.I fslist +parameter is a comma-separated list of filesystems and options +specifiers. All of the filesystems in this comma-separated list may be +prefixed by a negation operator +.RB ' no ' +or +.RB ' ! ', +which requests that only those filesystems not listed in +.I fslist +will be checked. If none of the filesystems in +.I fslist +is prefixed by a negation operator, then only those listed filesystems +will be checked. +.sp +Options specifiers may be included in the comma-separated +.IR fslist . +They must have the format +.BI opts= fs-option\fR. +If an options specifier is present, then only filesystems which contain +.I fs-option +in their mount options field of +.I /etc/fstab +will be checked. If the options specifier is prefixed by a negation +operator, then only +those filesystems that do not have +.I fs-option +in their mount options field of +.I /etc/fstab +will be checked. +.sp +For example, if +.B opts=ro +appears in +.IR fslist , +then only filesystems listed in +.I /etc/fstab +with the +.B ro +option will be checked. +.sp +For compatibility with Mandrake distributions whose boot scripts +depend upon an unauthorized UI change to the +.B fsck +program, if a filesystem type of +.B loop +is found in +.IR fslist , +it is treated as if +.B opts=loop +were specified as an argument to the +.B \-t +option. +.sp +Normally, the filesystem type is deduced by searching for +.I filesys +in the +.I /etc/fstab +file and using the corresponding entry. +If the type cannot be deduced, and there is only a single filesystem +given as an argument to the +.B \-t +option, +.B fsck +will use the specified filesystem type. If this type is not +available, then the default filesystem type (currently ext2) is used. +.TP +.B \-A +Walk through the +.I /etc/fstab +file and try to check all filesystems in one run. This option is +typically used from the +.I /etc/rc +system initialization file, instead of multiple commands for checking +a single filesystem. +.sp +The root filesystem will be checked first unless the +.B \-P +option is specified (see below). After that, +filesystems will be checked in the order specified by the +.I fs_passno +(the sixth) field in the +.I /etc/fstab +file. +Filesystems with a +.I fs_passno +value of 0 are skipped and are not checked at all. Filesystems with a +.I fs_passno +value of greater than zero will be checked in order, +with filesystems with the lowest +.I fs_passno +number being checked first. +If there are multiple filesystems with the same pass number, +.B fsck +will attempt to check them in parallel, although it will avoid running +multiple filesystem checks on the same physical disk. +.sp +.B fsck +does not check stacked devices (RAIDs, dm-crypt, \&...\&) in parallel with any other +device. See below for FSCK_FORCE_ALL_PARALLEL setting. The /sys filesystem is +used to determine dependencies between devices. +.sp +Hence, a very common configuration in +.I /etc/fstab +files is to set the root filesystem to have a +.I fs_passno +value of 1 +and to set all other filesystems to have a +.I fs_passno +value of 2. This will allow +.B fsck +to automatically run filesystem checkers in parallel if it is advantageous +to do so. System administrators might choose +not to use this configuration if they need to avoid multiple filesystem +checks running in parallel for some reason \(en for example, if the +machine in question is short on memory so that +excessive paging is a concern. +.sp +.B fsck +normally does not check whether the device actually exists before +calling a filesystem specific checker. Therefore non-existing +devices may cause the system to enter filesystem repair mode during +boot if the filesystem specific checker returns a fatal error. The +.I /etc/fstab +mount option +.B nofail +may be used to have +.B fsck +skip non-existing devices. +.B fsck +also skips non-existing devices that have the special filesystem type +.BR auto . +.TP +.BR \-C \ [ \fIfd\fR ] +Display completion/progress bars for those filesystem checkers (currently +only for ext[234]) which support them. \fBfsck\fR will manage the +filesystem checkers so that only one of them will display +a progress bar at a time. GUI front-ends may specify a file descriptor +.IR fd , +in which case the progress bar information will be sent to that file descriptor. +.TP +.B \-M +Do not check mounted filesystems and return an exit code of 0 +for mounted filesystems. +.TP +.B \-N +Don't execute, just show what would be done. +.TP +.B \-P +When the +.B \-A +flag is set, check the root filesystem in parallel with the other filesystems. +This is not the safest thing in the world to do, +since if the root filesystem is in doubt things like the +.BR e2fsck (8) +executable might be corrupted! This option is mainly provided +for those sysadmins who don't want to repartition the root +filesystem to be small and compact (which is really the right solution). +.TP +.B \-R +When checking all filesystems with the +.B \-A +flag, skip the root filesystem. (This is useful in case the root +filesystem has already been mounted read-write.) +.TP +.B \-T +Don't show the title on startup. +.TP +.B \-V +Produce verbose output, including all filesystem-specific commands +that are executed. +.TP +\fB\-?\fR, \fB\-\-help\fR +Display help text and exit. +.TP +\fB\-\-version\fR +Display version information and exit. +.SH FILESYSTEM SPECIFIC OPTIONS +.B Options which are not understood by fsck are passed to the filesystem-specific checker! +.PP +These options +.B must +not take arguments, as there is no +way for +.B fsck +to be able to properly guess which options take arguments and which +don't. +.PP +Options and arguments which follow the +.B \-\- +are treated as filesystem-specific options to be passed to the +filesystem-specific checker. +.PP +Please note that \fBfsck\fR is not +designed to pass arbitrarily complicated options to filesystem-specific +checkers. If you're doing something complicated, please just +execute the filesystem-specific checker directly. If you pass +.B fsck +some horribly complicated options and arguments, and it doesn't do +what you expect, +.B don't bother reporting it as a bug. +You're almost certainly doing something that you shouldn't be doing +with +.BR fsck . +Options to different filesystem-specific fsck's are not standardized. +.SH FILES +.I /etc/fstab +.SH ENVIRONMENT VARIABLES +The +.B fsck +program's behavior is affected by the following environment variables: +.TP +.B FSCK_FORCE_ALL_PARALLEL +If this environment variable is set, +.B fsck +will attempt to check all of the specified filesystems in parallel, regardless of +whether the filesystems appear to be on the same device. (This is useful for +RAID systems or high-end storage systems such as those sold by companies such +as IBM or EMC.) Note that the fs_passno value is still used. +.TP +.B FSCK_MAX_INST +This environment variable will limit the maximum number of filesystem +checkers that can be running at one time. This allows configurations +which have a large number of disks to avoid +.B fsck +starting too many filesystem checkers at once, which might overload +CPU and memory resources available on the system. If this value is +zero, then an unlimited number of processes can be spawned. This is +currently the default, but future versions of +.B fsck +may attempt to automatically determine how many filesystem checks can +be run based on gathering accounting data from the operating system. +.TP +.B PATH +The +.B PATH +environment variable is used to find filesystem checkers. +.TP +.B FSTAB_FILE +This environment variable allows the system administrator +to override the standard location of the +.I /etc/fstab +file. It is also useful for developers who are testing +.BR fsck . +.TP +.B LIBBLKID_DEBUG=all +enables libblkid debug output. +.TP +.B LIBMOUNT_DEBUG=all +enables libmount debug output. +.SH SEE ALSO +.na +.BR fstab (5), +.BR mkfs (8), +.BR fsck.ext2 (8) +or +.BR fsck.ext3 (8) +or +.BR e2fsck (8), +.BR cramfsck (8), +.BR fsck.jfs (8), +.BR fsck.nfs (8), +.BR fsck.minix (8), +.BR fsck.msdos (8), +.BR fsck.vfat (8), +.BR fsck.xfs (8), +.BR reiserfsck (8) +.ad +.SH AUTHORS +.nf +Theodore Ts'o <tytso@mit.edu> +Karel Zak <kzak@redhat.com> +.fi +.SH AVAILABILITY +The fsck command is part of the util-linux package and is available from +.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/ +Linux Kernel Archive +.UE . diff --git a/disk-utils/fsck.c b/disk-utils/fsck.c new file mode 100644 index 0000000..3afc886 --- /dev/null +++ b/disk-utils/fsck.c @@ -0,0 +1,1692 @@ +/* + * fsck --- A generic, parallelizing front-end for the fsck program. + * It will automatically try to run fsck programs in parallel if the + * devices are on separate spindles. It is based on the same ideas as + * the generic front end for fsck by David Engel and Fred van Kempen, + * but it has been completely rewritten from scratch to support + * parallel execution. + * + * Written by Theodore Ts'o, <tytso@mit.edu> + * + * Miquel van Smoorenburg (miquels@drinkel.ow.org) 20-Oct-1994: + * o Changed -t fstype to behave like with mount when -A (all file + * systems) or -M (like mount) is specified. + * o fsck looks if it can find the fsck.type program to decide + * if it should ignore the fs type. This way more fsck programs + * can be added without changing this front-end. + * o -R flag skip root file system. + * + * Copyright (C) 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, + * 2001, 2002, 2003, 2004, 2005 by Theodore Ts'o. + * + * Copyright (C) 2009-2014 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the GNU Public + * License. + */ + +#define _XOPEN_SOURCE 600 /* for inclusion of sa_handler in Solaris */ + +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <sys/file.h> +#include <fcntl.h> +#include <limits.h> +#include <stdio.h> +#include <ctype.h> +#include <string.h> +#include <time.h> +#include <stdlib.h> +#include <paths.h> +#include <unistd.h> +#include <errno.h> +#include <signal.h> +#include <dirent.h> +#include <sys/resource.h> +#include <sys/time.h> +#include <blkid.h> +#include <libmount.h> + +#include "nls.h" +#include "pathnames.h" +#include "exitcodes.h" +#include "c.h" +#include "fileutils.h" +#include "monotonic.h" +#include "strutils.h" + +#define XALLOC_EXIT_CODE FSCK_EX_ERROR +#include "xalloc.h" + +#define CLOSE_EXIT_CODE FSCK_EX_ERROR +#include "closestream.h" + +#ifndef DEFAULT_FSTYPE +# define DEFAULT_FSTYPE "ext2" +#endif + +#define MAX_DEVICES 32 +#define MAX_ARGS 32 + +#define FSCK_RUNTIME_DIRNAME "/run/fsck" + +static const char *ignored_types[] = { + "ignore", + "iso9660", + "sw", + NULL +}; + +static const char *really_wanted[] = { + "minix", + "ext2", + "ext3", + "ext4", + "ext4dev", + "jfs", + "reiserfs" +}; + +/* + * Internal structure for mount table entries. + */ +struct fsck_fs_data +{ + const char *device; + dev_t disk; + unsigned int stacked:1, + done:1, + eval_device:1; +}; + +/* + * Structure to allow exit codes to be stored + */ +struct fsck_instance { + int pid; + int flags; /* FLAG_{DONE|PROGRESS} */ + + int lock; /* flock()ed lockpath file descriptor or -1 */ + char *lockpath; /* /run/fsck/<diskname>.lock or NULL */ + + int exit_status; + struct timeval start_time; + struct timeval end_time; + char * prog; + char * type; + + struct rusage rusage; + struct libmnt_fs *fs; + struct fsck_instance *next; +}; + +#define FLAG_DONE 1 +#define FLAG_PROGRESS 2 + +/* + * Global variables for options + */ +static char *devices[MAX_DEVICES]; +static char *args[MAX_ARGS]; +static int num_devices, num_args; + +static int lockdisk; +static int verbose; +static int doall; +static int noexecute; +static int serialize; +static int skip_root; +static int ignore_mounted; +static int notitle; +static int parallel_root; +static int progress; +static int progress_fd; +static int force_all_parallel; +static int report_stats; +static FILE *report_stats_file; + +static int num_running; +static int max_running; + +static volatile int cancel_requested; +static int kill_sent; +static char *fstype; +static struct fsck_instance *instance_list; + +#define FSCK_DEFAULT_PATH "/sbin" +static char *fsck_path; + + +/* parsed fstab and mtab */ +static struct libmnt_table *fstab, *mtab; +static struct libmnt_cache *mntcache; + +static int count_slaves(dev_t disk); + +static int string_to_int(const char *s) +{ + long l; + char *p; + + l = strtol(s, &p, 0); + if (*p || l == LONG_MIN || l == LONG_MAX || l < 0 || l > INT_MAX) + return -1; + else + return (int) l; +} + +/* Do we really really want to check this fs? */ +static int fs_check_required(const char *type) +{ + size_t i; + + for(i = 0; i < ARRAY_SIZE(really_wanted); i++) { + if (strcmp(type, really_wanted[i]) == 0) + return 1; + } + + return 0; +} + +static int is_mounted(struct libmnt_fs *fs) +{ + int rc; + const char *src; + + src = mnt_fs_get_source(fs); + if (!src) + return 0; + if (!mntcache) + mntcache = mnt_new_cache(); + if (!mtab) { + mtab = mnt_new_table(); + if (!mtab) + err(FSCK_EX_ERROR, ("failed to initialize libmount table")); + mnt_table_set_cache(mtab, mntcache); + mnt_table_parse_mtab(mtab, NULL); + } + + rc = mnt_table_find_source(mtab, src, MNT_ITER_BACKWARD) ? 1 : 0; + if (verbose) { + if (rc) + printf(_("%s is mounted\n"), src); + else + printf(_("%s is not mounted\n"), src); + } + return rc; +} + +static int ignore(struct libmnt_fs *); + +static struct fsck_fs_data *fs_create_data(struct libmnt_fs *fs) +{ + struct fsck_fs_data *data = mnt_fs_get_userdata(fs); + + if (!data) { + data = xcalloc(1, sizeof(*data)); + mnt_fs_set_userdata(fs, data); + } + return data; +} + +/* + * fs from fstab might contains real device name as well as symlink, + * LABEL or UUID, this function returns canonicalized result. + */ +static const char *fs_get_device(struct libmnt_fs *fs) +{ + struct fsck_fs_data *data = mnt_fs_get_userdata(fs); + + if (!data || !data->eval_device) { + const char *spec = mnt_fs_get_source(fs); + + if (!data) + data = fs_create_data(fs); + + data->eval_device = 1; + data->device = mnt_resolve_spec(spec, mnt_table_get_cache(fstab)); + if (!data->device) + data->device = xstrdup(spec); + } + + return data->device; +} + +static dev_t fs_get_disk(struct libmnt_fs *fs, int check) +{ + struct fsck_fs_data *data; + const char *device; + struct stat st; + + data = mnt_fs_get_userdata(fs); + if (data && data->disk) + return data->disk; + + if (!check) + return 0; + + if (mnt_fs_is_netfs(fs) || mnt_fs_is_pseudofs(fs)) + return 0; + + device = fs_get_device(fs); + if (!device) + return 0; + + data = fs_create_data(fs); + + if (!stat(device, &st) && + !blkid_devno_to_wholedisk(st.st_rdev, NULL, 0, &data->disk)) { + + if (data->disk) + data->stacked = count_slaves(data->disk) > 0 ? 1 : 0; + return data->disk; + } + return 0; +} + +static int fs_is_stacked(struct libmnt_fs *fs) +{ + struct fsck_fs_data *data = mnt_fs_get_userdata(fs); + return data ? data->stacked : 0; +} + +static int fs_is_done(struct libmnt_fs *fs) +{ + struct fsck_fs_data *data = mnt_fs_get_userdata(fs); + return data ? data->done : 0; +} + +static void fs_set_done(struct libmnt_fs *fs) +{ + struct fsck_fs_data *data = fs_create_data(fs); + + if (data) + data->done = 1; +} + +static int is_irrotational_disk(dev_t disk) +{ + char path[PATH_MAX]; + FILE *f; + int rc, x; + + + rc = snprintf(path, sizeof(path), + "/sys/dev/block/%d:%d/queue/rotational", + major(disk), minor(disk)); + + if (rc < 0 || (unsigned int) rc >= sizeof(path)) + return 0; + + f = fopen(path, "r"); + if (!f) + return 0; + + rc = fscanf(f, "%d", &x); + if (rc != 1) { + if (ferror(f)) + warn(_("cannot read %s"), path); + else + warnx(_("parse error: %s"), path); + } + fclose(f); + + return rc == 1 ? !x : 0; +} + +static void lock_disk(struct fsck_instance *inst) +{ + dev_t disk = fs_get_disk(inst->fs, 1); + char *diskpath = NULL, *diskname; + + inst->lock = -1; + + if (!disk || is_irrotational_disk(disk)) + goto done; + + diskpath = blkid_devno_to_devname(disk); + if (!diskpath) + goto done; + + if (access(FSCK_RUNTIME_DIRNAME, F_OK) != 0) { + int rc = mkdir(FSCK_RUNTIME_DIRNAME, + S_IWUSR| + S_IRUSR|S_IRGRP|S_IROTH| + S_IXUSR|S_IXGRP|S_IXOTH); + if (rc && errno != EEXIST) { + warn(_("cannot create directory %s"), + FSCK_RUNTIME_DIRNAME); + goto done; + } + } + + diskname = stripoff_last_component(diskpath); + if (!diskname) + diskname = diskpath; + + xasprintf(&inst->lockpath, FSCK_RUNTIME_DIRNAME "/%s.lock", diskname); + + if (verbose) + printf(_("Locking disk by %s ... "), inst->lockpath); + + inst->lock = open(inst->lockpath, O_RDONLY|O_CREAT|O_CLOEXEC, + S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH); + if (inst->lock >= 0) { + int rc = -1; + + /* inform users that we're waiting on the lock */ + if (verbose && + (rc = flock(inst->lock, LOCK_EX | LOCK_NB)) != 0 && + errno == EWOULDBLOCK) + printf(_("(waiting) ")); + + if (rc != 0 && flock(inst->lock, LOCK_EX) != 0) { + close(inst->lock); /* failed */ + inst->lock = -1; + } + } + + if (verbose) + /* TRANSLATORS: These are followups to "Locking disk...". */ + printf("%s.\n", inst->lock >= 0 ? _("succeeded") : _("failed")); + + +done: + if (inst->lock < 0) { + free(inst->lockpath); + inst->lockpath = NULL; + } + free(diskpath); + return; +} + +static void unlock_disk(struct fsck_instance *inst) +{ + if (inst->lock < 0) + return; + + if (verbose) + printf(_("Unlocking %s.\n"), inst->lockpath); + + close(inst->lock); /* unlock */ + + free(inst->lockpath); + + inst->lock = -1; + inst->lockpath = NULL; +} + +static void free_instance(struct fsck_instance *i) +{ + if (lockdisk) + unlock_disk(i); + free(i->prog); + free(i->lockpath); + mnt_unref_fs(i->fs); + free(i); + return; +} + +static struct libmnt_fs *add_dummy_fs(const char *device) +{ + struct libmnt_fs *fs = mnt_new_fs(); + + if (fs && mnt_fs_set_source(fs, device) == 0 && + mnt_table_add_fs(fstab, fs) == 0) { + mnt_unref_fs(fs); + return fs; + } + + mnt_unref_fs(fs); + err(FSCK_EX_ERROR, _("failed to setup description for %s"), device); +} + +static void fs_interpret_type(struct libmnt_fs *fs) +{ + const char *device; + const char *type = mnt_fs_get_fstype(fs); + + if (type && strcmp(type, "auto") != 0) + return; + + mnt_fs_set_fstype(fs, NULL); + + device = fs_get_device(fs); + if (device) { + int ambi = 0; + char *tp; + struct libmnt_cache *cache = mnt_table_get_cache(fstab); + + tp = mnt_get_fstype(device, &ambi, cache); + if (!ambi) + mnt_fs_set_fstype(fs, tp); + if (!cache) + free(tp); + } +} + +static int parser_errcb(struct libmnt_table *tb __attribute__ ((__unused__)), + const char *filename, int line) +{ + warnx(_("%s: parse error at line %d -- ignored"), filename, line); + return 1; +} + +/* + * Load the filesystem database from /etc/fstab + */ +static void load_fs_info(void) +{ + const char *path; + + fstab = mnt_new_table(); + if (!fstab) + err(FSCK_EX_ERROR, ("failed to initialize libmount table")); + + mnt_table_set_parser_errcb(fstab, parser_errcb); + mnt_table_set_cache(fstab, mntcache); + + errno = 0; + + /* + * Let's follow libmount defaults if $FSTAB_FILE is not specified + */ + path = getenv("FSTAB_FILE"); + + if (mnt_table_parse_fstab(fstab, path)) { + if (!path) + path = mnt_get_fstab_path(); + + /* don't print error when there is no fstab at all */ + if (access(path, F_OK) == 0) { + if (errno) + warn(_("%s: failed to parse fstab"), path); + else + warnx(_("%s: failed to parse fstab"), path); + } + } +} + +/* + * Lookup filesys in /etc/fstab and return the corresponding entry. + * The @path has to be real path (no TAG) by mnt_resolve_spec(). + */ +static struct libmnt_fs *lookup(char *path) +{ + struct libmnt_fs *fs; + + if (!path) + return NULL; + + fs = mnt_table_find_srcpath(fstab, path, MNT_ITER_FORWARD); + if (!fs) { + /* + * Maybe mountpoint has been specified on fsck command line. + * Yeah, crazy feature... + * + * Note that mnt_table_find_target() may canonicalize paths in + * the fstab to support symlinks. This is really unwanted, + * because readlink() on mountpoints may trigger automounts. + * + * So, disable the cache and compare the paths as strings + * without care about symlinks... + */ + mnt_table_set_cache(fstab, NULL); + fs = mnt_table_find_target(fstab, path, MNT_ITER_FORWARD); + mnt_table_set_cache(fstab, mntcache); + } + return fs; +} + +/* Find fsck program for a given fs type. */ +static int find_fsck(const char *type, char **progpath) +{ + char *s; + const char *tpl; + char *prog = NULL; + char *p = xstrdup(fsck_path); + int rc; + + /* Are we looking for a program or just a type? */ + tpl = (strncmp(type, "fsck.", 5) ? "%s/fsck.%s" : "%s/%s"); + + for(s = strtok(p, ":"); s; s = strtok(NULL, ":")) { + xasprintf(&prog, tpl, s, type); + if (access(prog, X_OK) == 0) + break; + free(prog); + prog = NULL; + } + + free(p); + rc = prog ? 1 : 0; + + if (progpath) + *progpath = prog; + else + free(prog); + + return rc; +} + +static int progress_active(void) +{ + struct fsck_instance *inst; + + for (inst = instance_list; inst; inst = inst->next) { + if (inst->flags & FLAG_DONE) + continue; + if (inst->flags & FLAG_PROGRESS) + return 1; + } + return 0; +} + +/* + * Process run statistics for finished fsck instances. + * + * If report_stats is 0, do nothing, otherwise print a selection of + * interesting rusage statistics as well as elapsed wallclock time. + */ +static void print_stats(struct fsck_instance *inst) +{ + struct timeval delta; + + if (!inst || !report_stats || noexecute) + return; + + timersub(&inst->end_time, &inst->start_time, &delta); + + if (report_stats_file) + fprintf(report_stats_file, "%s %d %ld " + "%ld.%06ld %ld.%06ld %ld.%06ld\n", + fs_get_device(inst->fs), + inst->exit_status, + inst->rusage.ru_maxrss, + (long)delta.tv_sec, (long)delta.tv_usec, + (long)inst->rusage.ru_utime.tv_sec, + (long)inst->rusage.ru_utime.tv_usec, + (long)inst->rusage.ru_stime.tv_sec, + (long)inst->rusage.ru_stime.tv_usec); + else + fprintf(stdout, "%s: status %d, rss %ld, " + "real %ld.%06ld, user %ld.%06ld, sys %ld.%06ld\n", + fs_get_device(inst->fs), + inst->exit_status, + inst->rusage.ru_maxrss, + (long)delta.tv_sec, (long)delta.tv_usec, + (long)inst->rusage.ru_utime.tv_sec, + (long)inst->rusage.ru_utime.tv_usec, + (long)inst->rusage.ru_stime.tv_sec, + (long)inst->rusage.ru_stime.tv_usec); +} + +/* + * Execute a particular fsck program, and link it into the list of + * child processes we are waiting for. + */ +static int execute(const char *progname, const char *progpath, + const char *type, struct libmnt_fs *fs, int interactive) +{ + char *argv[80]; + int argc, i; + struct fsck_instance *inst, *p; + pid_t pid; + + inst = xcalloc(1, sizeof(*inst)); + + argv[0] = xstrdup(progname); + argc = 1; + + for (i=0; i <num_args; i++) + argv[argc++] = xstrdup(args[i]); + + if (progress && + ((strcmp(type, "ext2") == 0) || + (strcmp(type, "ext3") == 0) || + (strcmp(type, "ext4") == 0) || + (strcmp(type, "ext4dev") == 0))) { + + char tmp[80]; + tmp[0] = 0; + if (!progress_active()) { + snprintf(tmp, 80, "-C%d", progress_fd); + inst->flags |= FLAG_PROGRESS; + } else if (progress_fd) + snprintf(tmp, 80, "-C%d", progress_fd * -1); + if (tmp[0]) + argv[argc++] = xstrdup(tmp); + } + + argv[argc++] = xstrdup(fs_get_device(fs)); + argv[argc] = NULL; + + if (verbose || noexecute) { + const char *tgt = mnt_fs_get_target(fs); + + if (!tgt) + tgt = fs_get_device(fs); + printf("[%s (%d) -- %s] ", progpath, num_running, tgt); + for (i=0; i < argc; i++) + printf("%s ", argv[i]); + printf("\n"); + } + + mnt_ref_fs(fs); + inst->fs = fs; + inst->lock = -1; + + if (lockdisk) + lock_disk(inst); + + /* Fork and execute the correct program. */ + if (noexecute) + pid = -1; + else if ((pid = fork()) < 0) { + warn(_("fork failed")); + free_instance(inst); + return errno; + } else if (pid == 0) { + if (!interactive) + close(0); + execv(progpath, argv); + err(FSCK_EX_ERROR, _("%s: execute failed"), progpath); + } + + for (i=0; i < argc; i++) + free(argv[i]); + + inst->pid = pid; + inst->prog = xstrdup(progname); + inst->type = xstrdup(type); + gettime_monotonic(&inst->start_time); + inst->next = NULL; + + /* + * Find the end of the list, so we add the instance on at the end. + */ + for (p = instance_list; p && p->next; p = p->next); + + if (p) + p->next = inst; + else + instance_list = inst; + + return 0; +} + +/* + * Send a signal to all outstanding fsck child processes + */ +static int kill_all(int signum) +{ + struct fsck_instance *inst; + int n = 0; + + for (inst = instance_list; inst; inst = inst->next) { + if (inst->flags & FLAG_DONE) + continue; + kill(inst->pid, signum); + n++; + } + return n; +} + +/* + * Wait for one child process to exit; when it does, unlink it from + * the list of executing child processes, and return it. + */ +static struct fsck_instance *wait_one(int flags) +{ + int status = 0; + int sig; + struct fsck_instance *inst, *inst2, *prev; + pid_t pid; + struct rusage rusage; + + if (!instance_list) + return NULL; + + if (noexecute) { + inst = instance_list; + prev = NULL; +#ifdef RANDOM_DEBUG + while (inst->next && (random() & 1)) { + prev = inst; + inst = inst->next; + } +#endif + inst->exit_status = 0; + goto ret_inst; + } + + /* + * gcc -Wall fails saving throw against stupidity + * (inst and prev are thought to be uninitialized variables) + */ + inst = prev = NULL; + + do { + pid = wait4(-1, &status, flags, &rusage); + if (cancel_requested && !kill_sent) { + kill_all(SIGTERM); + kill_sent++; + } + if ((pid == 0) && (flags & WNOHANG)) + return NULL; + if (pid < 0) { + if ((errno == EINTR) || (errno == EAGAIN)) + continue; + if (errno == ECHILD) { + warnx(_("wait: no more child process?!?")); + return NULL; + } + warn(_("waitpid failed")); + continue; + } + for (prev = NULL, inst = instance_list; + inst; + prev = inst, inst = inst->next) { + if (inst->pid == pid) + break; + } + } while (!inst); + + if (WIFEXITED(status)) + status = WEXITSTATUS(status); + else if (WIFSIGNALED(status)) { + sig = WTERMSIG(status); + if (sig == SIGINT) { + status = FSCK_EX_UNCORRECTED; + } else { + warnx(_("Warning... %s for device %s exited " + "with signal %d."), + inst->prog, fs_get_device(inst->fs), sig); + status = FSCK_EX_ERROR; + } + } else { + warnx(_("%s %s: status is %x, should never happen."), + inst->prog, fs_get_device(inst->fs), status); + status = FSCK_EX_ERROR; + } + + inst->exit_status = status; + inst->flags |= FLAG_DONE; + gettime_monotonic(&inst->end_time); + memcpy(&inst->rusage, &rusage, sizeof(struct rusage)); + + if (progress && (inst->flags & FLAG_PROGRESS) && + !progress_active()) { + for (inst2 = instance_list; inst2; inst2 = inst2->next) { + if (inst2->flags & FLAG_DONE) + continue; + if (strcmp(inst2->type, "ext2") && + strcmp(inst2->type, "ext3") && + strcmp(inst2->type, "ext4") && + strcmp(inst2->type, "ext4dev")) + continue; + /* + * If we've just started the fsck, wait a tiny + * bit before sending the kill, to give it + * time to set up the signal handler + */ + if (inst2->start_time.tv_sec < time(NULL) + 2) { + if (fork() == 0) { + sleep(1); + kill(inst2->pid, SIGUSR1); + exit(FSCK_EX_OK); + } + } else + kill(inst2->pid, SIGUSR1); + inst2->flags |= FLAG_PROGRESS; + break; + } + } +ret_inst: + if (prev) + prev->next = inst->next; + else + instance_list = inst->next; + + print_stats(inst); + + if (verbose > 1) + printf(_("Finished with %s (exit status %d)\n"), + fs_get_device(inst->fs), inst->exit_status); + num_running--; + return inst; +} + +#define FLAG_WAIT_ALL 0 +#define FLAG_WAIT_ATLEAST_ONE 1 +/* + * Wait until all executing child processes have exited; return the + * logical OR of all of their exit code values. + */ +static int wait_many(int flags) +{ + struct fsck_instance *inst; + int global_status = 0; + int wait_flags = 0; + + while ((inst = wait_one(wait_flags))) { + global_status |= inst->exit_status; + free_instance(inst); +#ifdef RANDOM_DEBUG + if (noexecute && (flags & WNOHANG) && !(random() % 3)) + break; +#endif + if (flags & FLAG_WAIT_ATLEAST_ONE) + wait_flags = WNOHANG; + } + return global_status; +} + +/* + * Run the fsck program on a particular device + * + * If the type is specified using -t, and it isn't prefixed with "no" + * (as in "noext2") and only one filesystem type is specified, then + * use that type regardless of what is specified in /etc/fstab. + * + * If the type isn't specified by the user, then use either the type + * specified in /etc/fstab, or DEFAULT_FSTYPE. + */ +static int fsck_device(struct libmnt_fs *fs, int interactive) +{ + char *progname, *progpath; + const char *type; + int retval; + + fs_interpret_type(fs); + + type = mnt_fs_get_fstype(fs); + + if (type && strcmp(type, "auto") != 0) + ; + else if (fstype && strncmp(fstype, "no", 2) && + strncmp(fstype, "opts=", 5) && strncmp(fstype, "loop", 4) && + !strchr(fstype, ',')) + type = fstype; + else + type = DEFAULT_FSTYPE; + + xasprintf(&progname, "fsck.%s", type); + + if (!find_fsck(progname, &progpath)) { + free(progname); + if (fs_check_required(type)) { + retval = ENOENT; + goto err; + } + return 0; + } + + num_running++; + retval = execute(progname, progpath, type, fs, interactive); + free(progname); + free(progpath); + if (retval) { + num_running--; + goto err; + } + return 0; +err: + warnx(_("error %d (%m) while executing fsck.%s for %s"), + retval, type, fs_get_device(fs)); + return FSCK_EX_ERROR; +} + + +/* + * Deal with the fsck -t argument. + */ +static struct fs_type_compile { + char **list; + int *type; + int negate; +} fs_type_compiled; + +#define FS_TYPE_NORMAL 0 +#define FS_TYPE_OPT 1 +#define FS_TYPE_NEGOPT 2 + +static void compile_fs_type(char *fs_type, struct fs_type_compile *cmp) +{ + char *cp, *list, *s; + int num = 2; + int negate, first_negate = 1; + + if (fs_type) { + for (cp=fs_type; *cp; cp++) { + if (*cp == ',') + num++; + } + } + + cmp->list = xcalloc(num, sizeof(char *)); + cmp->type = xcalloc(num, sizeof(int)); + cmp->negate = 0; + + if (!fs_type) + return; + + list = xstrdup(fs_type); + num = 0; + s = strtok(list, ","); + while(s) { + negate = 0; + if (strncmp(s, "no", 2) == 0) { + s += 2; + negate = 1; + } else if (*s == '!') { + s++; + negate = 1; + } + if (strcmp(s, "loop") == 0) + /* loop is really short-hand for opts=loop */ + goto loop_special_case; + else if (strncmp(s, "opts=", 5) == 0) { + s += 5; + loop_special_case: + cmp->type[num] = negate ? FS_TYPE_NEGOPT : FS_TYPE_OPT; + } else { + if (first_negate) { + cmp->negate = negate; + first_negate = 0; + } + if ((negate && !cmp->negate) || + (!negate && cmp->negate)) { + errx(FSCK_EX_USAGE, + _("Either all or none of the filesystem types passed to -t must be prefixed\n" + "with 'no' or '!'.")); + } + } + + cmp->list[num++] = xstrdup(s); + s = strtok(NULL, ","); + } + free(list); +} + +/* + * This function returns true if a particular option appears in a + * comma-delimited options list + */ +static int opt_in_list(const char *opt, const char *optlist) +{ + char *list, *s; + + if (!optlist) + return 0; + list = xstrdup(optlist); + + s = strtok(list, ","); + while(s) { + if (strcmp(s, opt) == 0) { + free(list); + return 1; + } + s = strtok(NULL, ","); + } + free(list); + return 0; +} + +/* See if the filesystem matches the criteria given by the -t option */ +static int fs_match(struct libmnt_fs *fs, struct fs_type_compile *cmp) +{ + int n, ret = 0, checked_type = 0; + char *cp; + + if (cmp->list == NULL || cmp->list[0] == NULL) + return 1; + + for (n=0; (cp = cmp->list[n]); n++) { + switch (cmp->type[n]) { + case FS_TYPE_NORMAL: + { + const char *type = mnt_fs_get_fstype(fs); + + checked_type++; + if (type && strcmp(cp, type) == 0) + ret = 1; + break; + } + case FS_TYPE_NEGOPT: + if (opt_in_list(cp, mnt_fs_get_options(fs))) + return 0; + break; + case FS_TYPE_OPT: + if (!opt_in_list(cp, mnt_fs_get_options(fs))) + return 0; + break; + } + } + if (checked_type == 0) + return 1; + return (cmp->negate ? !ret : ret); +} + +/* + * Check if a device exists + */ +static int device_exists(const char *device) +{ + struct stat st; + + if (stat(device, &st) == -1) + return 0; + if (!S_ISBLK(st.st_mode)) + return 0; + return 1; +} + +static int fs_ignored_type(struct libmnt_fs *fs) +{ + const char **ip, *type; + + if (mnt_fs_is_netfs(fs) || mnt_fs_is_pseudofs(fs) || mnt_fs_is_swaparea(fs)) + return 1; + + type = mnt_fs_get_fstype(fs); + + for(ip = ignored_types; type && *ip; ip++) { + if (strcmp(type, *ip) == 0) + return 1; + } + + return 0; +} + +/* Check if we should ignore this filesystem. */ +static int ignore(struct libmnt_fs *fs) +{ + const char *type; + + /* + * If the pass number is 0, ignore it. + */ + if (mnt_fs_get_passno(fs) == 0) + return 1; + + /* + * If this is a bind mount, ignore it. + */ + if (opt_in_list("bind", mnt_fs_get_options(fs))) { + warnx(_("%s: skipping bad line in /etc/fstab: " + "bind mount with nonzero fsck pass number"), + mnt_fs_get_target(fs)); + return 1; + } + + /* + * ignore devices that don't exist and have the "nofail" mount option + */ + if (!device_exists(fs_get_device(fs))) { + if (opt_in_list("nofail", mnt_fs_get_options(fs))) { + if (verbose) + printf(_("%s: skipping nonexistent device\n"), + fs_get_device(fs)); + return 1; + } + if (verbose) + printf(_("%s: nonexistent device (\"nofail\" fstab " + "option may be used to skip this device)\n"), + fs_get_device(fs)); + } + + fs_interpret_type(fs); + + /* + * If a specific fstype is specified, and it doesn't match, + * ignore it. + */ + if (!fs_match(fs, &fs_type_compiled)) + return 1; + + type = mnt_fs_get_fstype(fs); + if (!type) { + if (verbose) + printf(_("%s: skipping unknown filesystem type\n"), + fs_get_device(fs)); + return 1; + } + + /* Are we ignoring this type? */ + if (fs_ignored_type(fs)) + return 1; + + + + /* See if the <fsck.fs> program is available. */ + if (!find_fsck(type, NULL)) { + if (fs_check_required(type)) + warnx(_("cannot check %s: fsck.%s not found"), + fs_get_device(fs), type); + return 1; + } + + /* We can and want to check this file system type. */ + return 0; +} + +static int count_slaves(dev_t disk) +{ + DIR *dir; + struct dirent *dp; + char dirname[PATH_MAX]; + int count = 0; + + snprintf(dirname, sizeof(dirname), + "/sys/dev/block/%u:%u/slaves/", + major(disk), minor(disk)); + + if (!(dir = opendir(dirname))) + return -1; + + while ((dp = readdir(dir)) != NULL) { +#ifdef _DIRENT_HAVE_D_TYPE + if (dp->d_type != DT_UNKNOWN && dp->d_type != DT_LNK) + continue; +#endif + if (dp->d_name[0] == '.' && + ((dp->d_name[1] == 0) || + ((dp->d_name[1] == '.') && (dp->d_name[2] == 0)))) + continue; + + count++; + } + + closedir(dir); + return count; +} + +/* + * Returns TRUE if a partition on the same disk is already being + * checked. + */ +static int disk_already_active(struct libmnt_fs *fs) +{ + struct fsck_instance *inst; + dev_t disk; + + if (force_all_parallel) + return 0; + + if (instance_list && fs_is_stacked(instance_list->fs)) + /* any instance for a stacked device is already running */ + return 1; + + disk = fs_get_disk(fs, 1); + + /* + * If we don't know the base device, assume that the device is + * already active if there are any fsck instances running. + * + * Don't check a stacked device with any other disk too. + */ + if (!disk || fs_is_stacked(fs)) + return (instance_list != NULL); + + for (inst = instance_list; inst; inst = inst->next) { + dev_t idisk = fs_get_disk(inst->fs, 0); + + if (!idisk || disk == idisk) + return 1; + } + + return 0; +} + +/* Check all file systems, using the /etc/fstab table. */ +static int check_all(void) +{ + int not_done_yet = 1; + int passno = 1; + int pass_done; + int status = FSCK_EX_OK; + + struct libmnt_fs *fs; + struct libmnt_iter *itr = mnt_new_iter(MNT_ITER_FORWARD); + + if (!itr) + err(FSCK_EX_ERROR, _("failed to allocate iterator")); + + /* + * Do an initial scan over the filesystem; mark filesystems + * which should be ignored as done, and resolve any "auto" + * filesystem types (done as a side-effect of calling ignore()). + */ + while (mnt_table_next_fs(fstab, itr, &fs) == 0) { + if (ignore(fs)) { + fs_set_done(fs); + continue; + } + } + + if (verbose) + fputs(_("Checking all file systems.\n"), stdout); + + /* + * Find and check the root filesystem. + */ + if (!parallel_root) { + fs = mnt_table_find_target(fstab, "/", MNT_ITER_FORWARD); + if (fs) { + if (!skip_root && + !fs_is_done(fs) && + !(ignore_mounted && is_mounted(fs))) { + status |= fsck_device(fs, 1); + status |= wait_many(FLAG_WAIT_ALL); + if (status > FSCK_EX_NONDESTRUCT) { + mnt_free_iter(itr); + return status; + } + } + fs_set_done(fs); + } + } + + /* + * This is for the bone-headed user who enters the root + * filesystem twice. Skip root will skip all root entries. + */ + if (skip_root) { + mnt_reset_iter(itr, MNT_ITER_FORWARD); + + while(mnt_table_next_fs(fstab, itr, &fs) == 0) { + const char *tgt = mnt_fs_get_target(fs); + + if (tgt && strcmp(tgt, "/") == 0) + fs_set_done(fs); + } + } + + while (not_done_yet) { + not_done_yet = 0; + pass_done = 1; + + mnt_reset_iter(itr, MNT_ITER_FORWARD); + + while(mnt_table_next_fs(fstab, itr, &fs) == 0) { + + if (cancel_requested) + break; + if (fs_is_done(fs)) + continue; + /* + * If the filesystem's pass number is higher + * than the current pass number, then we don't + * do it yet. + */ + if (mnt_fs_get_passno(fs) > passno) { + not_done_yet++; + continue; + } + if (ignore_mounted && is_mounted(fs)) { + fs_set_done(fs); + continue; + } + /* + * If a filesystem on a particular device has + * already been spawned, then we need to defer + * this to another pass. + */ + if (disk_already_active(fs)) { + pass_done = 0; + continue; + } + /* + * Spawn off the fsck process + */ + status |= fsck_device(fs, serialize); + fs_set_done(fs); + + /* + * Only do one filesystem at a time, or if we + * have a limit on the number of fsck's extant + * at one time, apply that limit. + */ + if (serialize || + (max_running && (num_running >= max_running))) { + pass_done = 0; + break; + } + } + if (cancel_requested) + break; + if (verbose > 1) + printf(_("--waiting-- (pass %d)\n"), passno); + + status |= wait_many(pass_done ? FLAG_WAIT_ALL : + FLAG_WAIT_ATLEAST_ONE); + if (pass_done) { + if (verbose > 1) + printf("----------------------------------\n"); + passno++; + } else + not_done_yet++; + } + + if (cancel_requested && !kill_sent) { + kill_all(SIGTERM); + kill_sent++; + } + + status |= wait_many(FLAG_WAIT_ATLEAST_ONE); + mnt_free_iter(itr); + return status; +} + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *out = stdout; + fputs(USAGE_HEADER, out); + fprintf(out, _(" %s [options] -- [fs-options] [<filesystem> ...]\n"), + program_invocation_short_name); + + fputs(USAGE_SEPARATOR, out); + fputs(_("Check and repair a Linux filesystem.\n"), out); + + fputs(USAGE_OPTIONS, out); + fputs(_(" -A check all filesystems\n"), out); + fputs(_(" -C [<fd>] display progress bar; file descriptor is for GUIs\n"), out); + fputs(_(" -l lock the device to guarantee exclusive access\n"), out); + fputs(_(" -M do not check mounted filesystems\n"), out); + fputs(_(" -N do not execute, just show what would be done\n"), out); + fputs(_(" -P check filesystems in parallel, including root\n"), out); + fputs(_(" -R skip root filesystem; useful only with '-A'\n"), out); + fputs(_(" -r [<fd>] report statistics for each device checked;\n" + " file descriptor is for GUIs\n"), out); + fputs(_(" -s serialize the checking operations\n"), out); + fputs(_(" -T do not show the title on startup\n"), out); + fputs(_(" -t <type> specify filesystem types to be checked;\n" + " <type> is allowed to be a comma-separated list\n"), out); + fputs(_(" -V explain what is being done\n"), out); + + fputs(USAGE_SEPARATOR, out); + printf( " -?, --help %s\n", USAGE_OPTSTR_HELP); + printf( " --version %s\n", USAGE_OPTSTR_VERSION); + fputs(USAGE_SEPARATOR, out); + fputs(_("See the specific fsck.* commands for available fs-options."), out); + printf(USAGE_MAN_TAIL("fsck(8)")); + exit(FSCK_EX_OK); +} + +static void signal_cancel(int sig __attribute__((__unused__))) +{ + cancel_requested++; +} + +static void parse_argv(int argc, char *argv[]) +{ + int i, j; + char *arg, *dev, *tmp = NULL; + char options[128]; + int opt = 0; + int opts_for_fsck = 0; + struct sigaction sa; + int report_stats_fd = -1; + + /* + * Set up signal action + */ + memset(&sa, 0, sizeof(struct sigaction)); + sa.sa_handler = signal_cancel; + sigaction(SIGINT, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); + + num_devices = 0; + num_args = 0; + instance_list = NULL; + + for (i=1; i < argc; i++) { + arg = argv[i]; + if (!arg) + continue; + + /* the only two longopts to satisfy UL standards */ + if (!opts_for_fsck && !strcmp(arg, "--help")) + usage(); + if (!opts_for_fsck && !strcmp(arg, "--version")) { + printf(UTIL_LINUX_VERSION); + exit(FSCK_EX_OK); + } + + if ((arg[0] == '/' && !opts_for_fsck) || strchr(arg, '=')) { + if (num_devices >= MAX_DEVICES) + errx(FSCK_EX_ERROR, _("too many devices")); + + dev = mnt_resolve_spec(arg, mntcache); + + if (!dev && strchr(arg, '=')) { + /* + * Check to see if we failed because + * /proc/partitions isn't found. + */ + if (access(_PATH_PROC_PARTITIONS, R_OK) < 0) { + warn(_("cannot open %s"), + _PATH_PROC_PARTITIONS); + errx(FSCK_EX_ERROR, _("Is /proc mounted?")); + } + /* + * Check to see if this is because + * we're not running as root + */ + if (geteuid()) + errx(FSCK_EX_ERROR, + _("must be root to scan for matching filesystems: %s"), + arg); + else + errx(FSCK_EX_ERROR, + _("couldn't find matching filesystem: %s"), + arg); + } + devices[num_devices++] = dev ? dev : xstrdup(arg); + continue; + } + if (arg[0] != '-' || opts_for_fsck) { + if (num_args >= MAX_ARGS) + errx(FSCK_EX_ERROR, _("too many arguments")); + args[num_args++] = xstrdup(arg); + continue; + } + for (j=1; arg[j]; j++) { + if (opts_for_fsck) { + options[++opt] = arg[j]; + continue; + } + switch (arg[j]) { + case 'A': + doall = 1; + break; + case 'C': + progress = 1; + if (arg[j+1]) { /* -C<fd> */ + progress_fd = string_to_int(arg+j+1); + if (progress_fd < 0) + progress_fd = 0; + else + goto next_arg; + } else if (i+1 < argc && *argv[i+1] != '-') { /* -C <fd> */ + progress_fd = string_to_int(argv[i+1]); + if (progress_fd < 0) + progress_fd = 0; + else { + ++i; + goto next_arg; + } + } + break; + case 'l': + lockdisk = 1; + break; + case 'V': + verbose++; + break; + case 'N': + noexecute = 1; + break; + case 'R': + skip_root = 1; + break; + case 'T': + notitle = 1; + break; + case 'M': + ignore_mounted = 1; + break; + case 'P': + parallel_root = 1; + break; + case 'r': + report_stats = 1; + if (arg[j+1]) { /* -r<fd> */ + report_stats_fd = strtou32_or_err(arg+j+1, _("invalid argument of -r")); + goto next_arg; + } else if (i+1 < argc && *argv[i+1] >= '0' && *argv[i+1] <= '9') { /* -r <fd> */ + report_stats_fd = strtou32_or_err(argv[i+1], _("invalid argument of -r")); + ++i; + goto next_arg; + } + break; + case 's': + serialize = 1; + break; + case 't': + tmp = NULL; + if (fstype) + errx(FSCK_EX_USAGE, + _("option '%s' may be specified only once"), "-t"); + if (arg[j+1]) + tmp = arg+j+1; + else if ((i+1) < argc) + tmp = argv[++i]; + else + errx(FSCK_EX_USAGE, + _("option '%s' requires an argument"), "-t"); + fstype = xstrdup(tmp); + compile_fs_type(fstype, &fs_type_compiled); + goto next_arg; + case '-': + opts_for_fsck++; + break; + case '?': + usage(); + break; + default: + options[++opt] = arg[j]; + break; + } + } + next_arg: + if (opt) { + options[0] = '-'; + options[++opt] = '\0'; + if (num_args >= MAX_ARGS) + errx(FSCK_EX_ERROR, _("too many arguments")); + args[num_args++] = xstrdup(options); + opt = 0; + } + } + + /* Validate the report stats file descriptor to avoid disasters */ + if (report_stats_fd >= 0) { + report_stats_file = fdopen(report_stats_fd, "w"); + if (!report_stats_file) + err(FSCK_EX_ERROR, + _("invalid argument of -r: %d"), + report_stats_fd); + } + + if (getenv("FSCK_FORCE_ALL_PARALLEL")) + force_all_parallel++; + if ((tmp = getenv("FSCK_MAX_INST"))) + max_running = atoi(tmp); +} + +int main(int argc, char *argv[]) +{ + int i, status = 0; + int interactive = 0; + struct libmnt_fs *fs; + const char *path = getenv("PATH"); + + setvbuf(stdout, NULL, _IONBF, BUFSIZ); + setvbuf(stderr, NULL, _IONBF, BUFSIZ); + + setlocale(LC_MESSAGES, ""); + setlocale(LC_CTYPE, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + strutils_set_exitcode(FSCK_EX_USAGE); + mnt_init_debug(0); /* init libmount debug mask */ + mntcache = mnt_new_cache(); /* no fatal error if failed */ + + parse_argv(argc, argv); + + if (!notitle) + printf(UTIL_LINUX_VERSION); + + load_fs_info(); + + fsck_path = xstrdup(path && *path ? path : FSCK_DEFAULT_PATH); + + if ((num_devices == 1) || (serialize)) + interactive = 1; + + if (lockdisk && (doall || num_devices > 1)) { + warnx(_("the -l option can be used with one " + "device only -- ignore")); + lockdisk = 0; + } + + /* If -A was specified ("check all"), do that! */ + if (doall) + return check_all(); + + if (num_devices == 0) { + serialize++; + interactive++; + return check_all(); + } + for (i = 0 ; i < num_devices; i++) { + if (cancel_requested) { + if (!kill_sent) { + kill_all(SIGTERM); + kill_sent++; + } + break; + } + fs = lookup(devices[i]); + if (!fs) + fs = add_dummy_fs(devices[i]); + else if (fs_ignored_type(fs)) + continue; + if (ignore_mounted && is_mounted(fs)) + continue; + status |= fsck_device(fs, interactive); + if (serialize || + (max_running && (num_running >= max_running))) { + struct fsck_instance *inst; + + inst = wait_one(0); + if (inst) { + status |= inst->exit_status; + free_instance(inst); + } + if (verbose > 1) + printf("----------------------------------\n"); + } + } + status |= wait_many(FLAG_WAIT_ALL); + free(fsck_path); + mnt_unref_cache(mntcache); + mnt_unref_table(fstab); + mnt_unref_table(mtab); + return status; +} diff --git a/disk-utils/fsck.cramfs.8 b/disk-utils/fsck.cramfs.8 new file mode 100644 index 0000000..ec486d6 --- /dev/null +++ b/disk-utils/fsck.cramfs.8 @@ -0,0 +1,61 @@ +.TH FSCK.CRAMFS 8 "April 2013" "util-linux" "System Administration" +.SH NAME +fsck.cramfs \- fsck compressed ROM file system +.SH SYNOPSIS +.B fsck.cramfs +[options] +.I file +.SH DESCRIPTION +.I fsck.cramfs +is used to check the cramfs file system. +.SH OPTIONS +.TP +\fB\-v\fR, \fB\-\-verbose\fR +Enable verbose messaging. +.TP +\fB\-b\fR, \fB\-\-blocksize\fR \fIblocksize\fR +Use this blocksize, defaults to page size. Must be equal to what was set at +creation time. Only used for \-\-extract. +.TP +\fB\-\-extract\fR[=\fIdirectory\fR] +Test to uncompress the whole file system. Optionally extract contents of the +.I file +to +.IR directory . +.TP +\fB\-a\fR +This option is silently ignored. +.TP +\fB\-y\fR +This option is silently ignored. +.TP +\fB\-V\fR, \fB\-\-version\fR +Display version information and exit. +.TP +\fB\-h\fR, \fB\-\-help\fR +Display help text and exit. +.SH "EXIT STATUS" +.RS +.PD 0 +.TP +.B 0 +success +.TP +.B 4 +file system was left uncorrected +.TP +.B 8 +operation error, such as unable to allocate memory +.TP +.B 16 +usage information was printed +.PD +.RE +.SH "SEE ALSO" +.BR mount (8), +.BR mkfs.cramfs (8) +.SH AVAILABILITY +The example command is part of the util-linux package and is available from +.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/ +Linux Kernel Archive +.UE . diff --git a/disk-utils/fsck.cramfs.c b/disk-utils/fsck.cramfs.c new file mode 100644 index 0000000..0d9bdad --- /dev/null +++ b/disk-utils/fsck.cramfs.c @@ -0,0 +1,714 @@ +/* + * cramfsck - check a cramfs file system + * + * Copyright (C) 2000-2002 Transmeta Corporation + * 2005 Adrian Bunk + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * 1999/12/03: Linus Torvalds (cramfs tester and unarchive program) + * 2000/06/03: Daniel Quinlan (CRC and length checking program) + * 2000/06/04: Daniel Quinlan (merged programs, added options, support + * for special files, preserve permissions and + * ownership, cramfs superblock v2, bogus mode + * test, pathname length test, etc.) + * 2000/06/06: Daniel Quinlan (support for holes, pretty-printing, + * symlink size test) + * 2000/07/11: Daniel Quinlan (file length tests, start at offset 0 or 512, + * fsck-compatible exit codes) + * 2000/07/15: Daniel Quinlan (initial support for block devices) + * 2002/01/10: Daniel Quinlan (additional checks, test more return codes, + * use read if mmap fails, standardize messages) + */ + +#include <stdio.h> +#include <stdarg.h> +#include <stdint.h> +#include <unistd.h> +#include <dirent.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> +#include <getopt.h> +#include <utime.h> +#include <fcntl.h> + +/* We don't use our include/crc32.h, but crc32 from zlib! + * + * The zlib implementation performs pre/post-conditioning. The util-linux + * imlemenation requires post-conditioning (xor) in the applications. + */ +#include <zlib.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/mman.h> + +#include "c.h" +#include "cramfs.h" +#include "nls.h" +#include "blkdev.h" +#include "exitcodes.h" +#include "strutils.h" +#include "closestream.h" + +#define XALLOC_EXIT_CODE FSCK_EX_ERROR +#include "xalloc.h" + +static int fd; /* ROM image file descriptor */ +static char *filename; /* ROM image filename */ +static struct cramfs_super super; /* just find the cramfs superblock once */ +static int cramfs_is_big_endian = 0; /* source is big endian */ +static int opt_verbose = 0; /* 1 = verbose (-v), 2+ = very verbose (-vv) */ +static int opt_extract = 0; /* extract cramfs (-x) */ +static char *extract_dir = ""; /* optional extraction directory (-x) */ + +#define PAD_SIZE 512 + +static uid_t euid; /* effective UID */ + +/* (cramfs_super + start) <= start_dir < end_dir <= start_data <= end_data */ +static unsigned long start_dir = ~0UL; /* start of first non-root inode */ +static unsigned long end_dir = 0; /* end of the directory structure */ +static unsigned long start_data = ~0UL; /* start of the data (256 MB = max) */ +static unsigned long end_data = 0; /* end of the data */ + + +/* Guarantee access to at least 8kB at a time */ +#define ROMBUFFER_BITS 13 +#define ROMBUFFERSIZE (1 << ROMBUFFER_BITS) +#define ROMBUFFERMASK (ROMBUFFERSIZE - 1) +static char read_buffer[ROMBUFFERSIZE * 2]; +static unsigned long read_buffer_block = ~0UL; + +static z_stream stream; + +/* Prototypes */ +static void expand_fs(char *, struct cramfs_inode *); + +static char *outbuffer; + +static size_t blksize = 0; + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *out = stdout; + + fputs(USAGE_HEADER, out); + fprintf(out, + _(" %s [options] <file>\n"), program_invocation_short_name); + + fputs(USAGE_SEPARATOR, out); + fputs(_("Check and repair a compressed ROM filesystem.\n"), out); + + fputs(USAGE_OPTIONS, out); + fputs(_(" -a for compatibility only, ignored\n"), out); + fputs(_(" -v, --verbose be more verbose\n"), out); + fputs(_(" -y for compatibility only, ignored\n"), out); + fputs(_(" -b, --blocksize <size> use this blocksize, defaults to page size\n"), out); + fputs(_(" --extract[=<dir>] test uncompression, optionally extract into <dir>\n"), out); + fputs(USAGE_SEPARATOR, out); + printf(USAGE_HELP_OPTIONS(26)); + + printf(USAGE_MAN_TAIL("fsck.cramfs(8)")); + exit(FSCK_EX_OK); +} + +static int get_superblock_endianness(uint32_t magic) +{ + if (magic == CRAMFS_MAGIC) { + cramfs_is_big_endian = HOST_IS_BIG_ENDIAN; + return 0; + } else if (magic == + u32_toggle_endianness(!HOST_IS_BIG_ENDIAN, CRAMFS_MAGIC)) { + cramfs_is_big_endian = !HOST_IS_BIG_ENDIAN; + return 0; + } else + return -1; +} + +static void test_super(int *start, size_t * length) +{ + struct stat st; + + /* find the physical size of the file or block device */ + if (stat(filename, &st) < 0) + err(FSCK_EX_ERROR, _("stat of %s failed"), filename); + + fd = open(filename, O_RDONLY); + if (fd < 0) + err(FSCK_EX_ERROR, _("cannot open %s"), filename); + + if (S_ISBLK(st.st_mode)) { + unsigned long long bytes; + if (blkdev_get_size(fd, &bytes)) + err(FSCK_EX_ERROR, + _("ioctl failed: unable to determine device size: %s"), + filename); + *length = bytes; + } else if (S_ISREG(st.st_mode)) + *length = st.st_size; + else + errx(FSCK_EX_ERROR, _("not a block device or file: %s"), filename); + + if (*length < sizeof(struct cramfs_super)) + errx(FSCK_EX_UNCORRECTED, _("file length too short")); + + /* find superblock */ + if (read(fd, &super, sizeof(super)) != sizeof(super)) + err(FSCK_EX_ERROR, _("cannot read %s"), filename); + if (get_superblock_endianness(super.magic) != -1) + *start = 0; + else if (*length >= (PAD_SIZE + sizeof(super))) { + if (lseek(fd, PAD_SIZE, SEEK_SET) == (off_t) -1) + err(FSCK_EX_ERROR, _("seek on %s failed"), filename); + if (read(fd, &super, sizeof(super)) != sizeof(super)) + err(FSCK_EX_ERROR, _("cannot read %s"), filename); + if (get_superblock_endianness(super.magic) != -1) + *start = PAD_SIZE; + else + errx(FSCK_EX_UNCORRECTED, _("superblock magic not found")); + } else + errx(FSCK_EX_UNCORRECTED, _("superblock magic not found")); + + if (opt_verbose) + printf(_("cramfs endianness is %s\n"), + cramfs_is_big_endian ? _("big") : _("little")); + + super_toggle_endianness(cramfs_is_big_endian, &super); + if (super.flags & ~CRAMFS_SUPPORTED_FLAGS) + errx(FSCK_EX_ERROR, _("unsupported filesystem features")); + + /* What are valid superblock sizes? */ + if (super.size < *start + sizeof(struct cramfs_super)) + errx(FSCK_EX_UNCORRECTED, _("superblock size (%d) too small"), + super.size); + + if (super.flags & CRAMFS_FLAG_FSID_VERSION_2) { + if (super.fsid.files == 0) + errx(FSCK_EX_UNCORRECTED, _("zero file count")); + if (*length < super.size) + errx(FSCK_EX_UNCORRECTED, _("file length too short")); + else if (*length > super.size) + warnx(_("file extends past end of filesystem")); + } else + warnx(_("old cramfs format")); +} + +static void test_crc(int start) +{ + void *buf; + uint32_t crc; + + if (!(super.flags & CRAMFS_FLAG_FSID_VERSION_2)) { + warnx(_("unable to test CRC: old cramfs format")); + return; + } + + crc = crc32(0L, NULL, 0); + + buf = + mmap(NULL, super.size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); + if (buf == MAP_FAILED) { + buf = + mmap(NULL, super.size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (buf != MAP_FAILED) { + ssize_t tmp; + if (lseek(fd, 0, SEEK_SET) == (off_t) -1) + err(FSCK_EX_ERROR, _("seek on %s failed"), filename); + tmp = read(fd, buf, super.size); + if (tmp < 0) + err(FSCK_EX_ERROR, _("cannot read %s"), filename); + if (tmp != (ssize_t) super.size) + errx(FSCK_EX_ERROR, _("failed to read %"PRIu32" bytes from file %s"), + super.size, filename); + } + } + if (buf != MAP_FAILED) { + ((struct cramfs_super *)((unsigned char *) buf + start))->fsid.crc = + crc32(0L, NULL, 0); + crc = crc32(crc, (unsigned char *) buf + start, super.size - start); + munmap(buf, super.size); + } else { + int retval; + size_t length = 0; + + buf = xmalloc(4096); + if (lseek(fd, start, SEEK_SET) == (off_t) -1) + err(FSCK_EX_ERROR, _("seek on %s failed"), filename); + for (;;) { + retval = read(fd, buf, 4096); + if (retval < 0) + err(FSCK_EX_ERROR, _("cannot read %s"), filename); + else if (retval == 0) + break; + if (length == 0) + ((struct cramfs_super *)buf)->fsid.crc = + crc32(0L, NULL, 0); + length += retval; + if (length > (super.size - start)) { + crc = crc32(crc, buf, + retval - (length - + (super.size - start))); + break; + } + crc = crc32(crc, buf, retval); + } + free(buf); + } + + if (crc != super.fsid.crc) + errx(FSCK_EX_UNCORRECTED, _("crc error")); +} + +static void print_node(char type, struct cramfs_inode *i, char *name) +{ + char info[10]; + + if (S_ISCHR(i->mode) || (S_ISBLK(i->mode))) + /* major/minor numbers can be as high as 2^12 or 4096 */ + snprintf(info, 10, "%4d,%4d", major(i->size), minor(i->size)); + else + /* size be as high as 2^24 or 16777216 */ + snprintf(info, 10, "%9d", i->size); + + printf("%c %04o %s %5d:%-3d %s\n", + type, i->mode & ~S_IFMT, info, i->uid, i->gid, + !*name && type == 'd' ? "/" : name); +} + +/* + * Create a fake "blocked" access + */ +static void *romfs_read(unsigned long offset) +{ + unsigned int block = offset >> ROMBUFFER_BITS; + if (block != read_buffer_block) { + ssize_t x; + + read_buffer_block = block; + if (lseek(fd, block << ROMBUFFER_BITS, SEEK_SET) == (off_t) -1) + warn(_("seek failed")); + + x = read(fd, read_buffer, ROMBUFFERSIZE * 2); + if (x < 0) + warn(_("read romfs failed")); + } + return read_buffer + (offset & ROMBUFFERMASK); +} + +static struct cramfs_inode *cramfs_iget(struct cramfs_inode *i) +{ + struct cramfs_inode *inode = xmalloc(sizeof(struct cramfs_inode)); + + inode_to_host(cramfs_is_big_endian, i, inode); + return inode; +} + +static struct cramfs_inode *iget(unsigned int ino) +{ + return cramfs_iget(romfs_read(ino)); +} + +static void iput(struct cramfs_inode *inode) +{ + free(inode); +} + +/* + * Return the offset of the root directory + */ +static struct cramfs_inode *read_super(void) +{ + struct cramfs_inode *root = cramfs_iget(&super.root); + unsigned long offset = root->offset << 2; + + if (!S_ISDIR(root->mode)) + errx(FSCK_EX_UNCORRECTED, _("root inode is not directory")); + if (!(super.flags & CRAMFS_FLAG_SHIFTED_ROOT_OFFSET) && + ((offset != sizeof(struct cramfs_super)) && + (offset != PAD_SIZE + sizeof(struct cramfs_super)))) { + errx(FSCK_EX_UNCORRECTED, _("bad root offset (%lu)"), offset); + } + return root; +} + +static int uncompress_block(void *src, size_t len) +{ + int err; + + stream.next_in = src; + stream.avail_in = len; + + stream.next_out = (unsigned char *)outbuffer; + stream.avail_out = blksize * 2; + + inflateReset(&stream); + + if (len > blksize * 2) + errx(FSCK_EX_UNCORRECTED, _("data block too large")); + + err = inflate(&stream, Z_FINISH); + if (err != Z_STREAM_END) + errx(FSCK_EX_UNCORRECTED, _("decompression error: %s"), + zError(err)); + return stream.total_out; +} + +#if !HAVE_LCHOWN +#define lchown chown +#endif + +static void do_uncompress(char *path, int outfd, unsigned long offset, + unsigned long size) +{ + unsigned long curr = offset + 4 * ((size + blksize - 1) / blksize); + + do { + unsigned long out = blksize; + unsigned long next = u32_toggle_endianness(cramfs_is_big_endian, + *(uint32_t *) + romfs_read(offset)); + + if (next > end_data) + end_data = next; + + offset += 4; + if (curr == next) { + if (opt_verbose > 1) + printf(_(" hole at %lu (%zu)\n"), curr, + blksize); + if (size < blksize) + out = size; + memset(outbuffer, 0x00, out); + } else { + if (opt_verbose > 1) + printf(_(" uncompressing block at %lu to %lu (%lu)\n"), + curr, next, next - curr); + out = uncompress_block(romfs_read(curr), next - curr); + } + if (size >= blksize) { + if (out != blksize) + errx(FSCK_EX_UNCORRECTED, + _("non-block (%ld) bytes"), out); + } else { + if (out != size) + errx(FSCK_EX_UNCORRECTED, + _("non-size (%ld vs %ld) bytes"), out, + size); + } + size -= out; + if (*extract_dir != '\0' && write(outfd, outbuffer, out) < 0) + err(FSCK_EX_ERROR, _("write failed: %s"), path); + curr = next; + } while (size); +} + +static void change_file_status(char *path, struct cramfs_inode *i) +{ + struct utimbuf epoch = { 0, 0 }; + + if (euid == 0) { + if (lchown(path, i->uid, i->gid) < 0) + err(FSCK_EX_ERROR, _("lchown failed: %s"), path); + if (S_ISLNK(i->mode)) + return; + if (((S_ISUID | S_ISGID) & i->mode) && chmod(path, i->mode) < 0) + err(FSCK_EX_ERROR, _("chown failed: %s"), path); + } + if (S_ISLNK(i->mode)) + return; + if (utime(path, &epoch) < 0) + err(FSCK_EX_ERROR, _("utime failed: %s"), path); +} + +static void do_directory(char *path, struct cramfs_inode *i) +{ + int pathlen = strlen(path); + int count = i->size; + unsigned long offset = i->offset << 2; + char *newpath = xmalloc(pathlen + 256); + + if (offset == 0 && count != 0) + errx(FSCK_EX_UNCORRECTED, + _("directory inode has zero offset and non-zero size: %s"), + path); + + if (offset != 0 && offset < start_dir) + start_dir = offset; + + /* TODO: Do we need to check end_dir for empty case? */ + memcpy(newpath, path, pathlen); + newpath[pathlen] = '/'; + pathlen++; + if (opt_verbose) + print_node('d', i, path); + + if (*extract_dir != '\0') { + if (mkdir(path, i->mode) < 0) + err(FSCK_EX_ERROR, _("mkdir failed: %s"), path); + change_file_status(path, i); + } + while (count > 0) { + struct cramfs_inode *child = iget(offset); + int size; + int newlen = child->namelen << 2; + + size = sizeof(struct cramfs_inode) + newlen; + count -= size; + + offset += sizeof(struct cramfs_inode); + + memcpy(newpath + pathlen, romfs_read(offset), newlen); + newpath[pathlen + newlen] = 0; + if (newlen == 0) + errx(FSCK_EX_UNCORRECTED, _("filename length is zero")); + if ((pathlen + newlen) - strlen(newpath) > 3) + errx(FSCK_EX_UNCORRECTED, _("bad filename length")); + expand_fs(newpath, child); + + offset += newlen; + + if (offset <= start_dir) + errx(FSCK_EX_UNCORRECTED, _("bad inode offset")); + if (offset > end_dir) + end_dir = offset; + iput(child); /* free(child) */ + } + free(newpath); +} + +static void do_file(char *path, struct cramfs_inode *i) +{ + unsigned long offset = i->offset << 2; + int outfd = 0; + + if (offset == 0 && i->size != 0) + errx(FSCK_EX_UNCORRECTED, + _("file inode has zero offset and non-zero size")); + if (i->size == 0 && offset != 0) + errx(FSCK_EX_UNCORRECTED, + _("file inode has zero size and non-zero offset")); + if (offset != 0 && offset < start_data) + start_data = offset; + if (opt_verbose) + print_node('f', i, path); + if (*extract_dir != '\0') { + outfd = open(path, O_WRONLY | O_CREAT | O_TRUNC, i->mode); + if (outfd < 0) + err(FSCK_EX_ERROR, _("cannot open %s"), path); + } + if (i->size) + do_uncompress(path, outfd, offset, i->size); + if ( *extract_dir != '\0') { + if (close_fd(outfd) != 0) + err(FSCK_EX_ERROR, _("write failed: %s"), path); + change_file_status(path, i); + } +} + +static void do_symlink(char *path, struct cramfs_inode *i) +{ + unsigned long offset = i->offset << 2; + unsigned long curr = offset + 4; + unsigned long next = + u32_toggle_endianness(cramfs_is_big_endian, + *(uint32_t *) romfs_read(offset)); + unsigned long size; + + if (offset == 0) + errx(FSCK_EX_UNCORRECTED, _("symbolic link has zero offset")); + if (i->size == 0) + errx(FSCK_EX_UNCORRECTED, _("symbolic link has zero size")); + + if (offset < start_data) + start_data = offset; + if (next > end_data) + end_data = next; + + size = uncompress_block(romfs_read(curr), next - curr); + if (size != i->size) + errx(FSCK_EX_UNCORRECTED, _("size error in symlink: %s"), path); + outbuffer[size] = 0; + if (opt_verbose) { + char *str; + + xasprintf(&str, "%s -> %s", path, outbuffer); + print_node('l', i, str); + if (opt_verbose > 1) + printf(_(" uncompressing block at %lu to %lu (%lu)\n"), + curr, next, next - curr); + free(str); + } + if (*extract_dir != '\0') { + if (symlink(outbuffer, path) < 0) + err(FSCK_EX_ERROR, _("symlink failed: %s"), path); + change_file_status(path, i); + } +} + +static void do_special_inode(char *path, struct cramfs_inode *i) +{ + dev_t devtype = 0; + char type; + + if (i->offset) + /* no need to shift offset */ + errx(FSCK_EX_UNCORRECTED, + _("special file has non-zero offset: %s"), path); + + if (S_ISCHR(i->mode)) { + devtype = i->size; + type = 'c'; + } else if (S_ISBLK(i->mode)) { + devtype = i->size; + type = 'b'; + } else if (S_ISFIFO(i->mode)) { + if (i->size != 0) + errx(FSCK_EX_UNCORRECTED, _("fifo has non-zero size: %s"), + path); + type = 'p'; + } else if (S_ISSOCK(i->mode)) { + if (i->size != 0) + errx(FSCK_EX_UNCORRECTED, + _("socket has non-zero size: %s"), path); + type = 's'; + } else { + errx(FSCK_EX_UNCORRECTED, _("bogus mode: %s (%o)"), path, i->mode); + return; /* not reached */ + } + + if (opt_verbose) + print_node(type, i, path); + + if (*extract_dir != '\0') { + if (mknod(path, i->mode, devtype) < 0) + err(FSCK_EX_ERROR, _("mknod failed: %s"), path); + change_file_status(path, i); + } +} + +static void expand_fs(char *path, struct cramfs_inode *inode) +{ + if (S_ISDIR(inode->mode)) + do_directory(path, inode); + else if (S_ISREG(inode->mode)) + do_file(path, inode); + else if (S_ISLNK(inode->mode)) + do_symlink(path, inode); + else + do_special_inode(path, inode); +} + +static void test_fs(int start) +{ + struct cramfs_inode *root; + + root = read_super(); + umask(0); + euid = geteuid(); + stream.next_in = NULL; + stream.avail_in = 0; + inflateInit(&stream); + expand_fs(extract_dir, root); + inflateEnd(&stream); + if (start_data != ~0UL) { + if (start_data < (sizeof(struct cramfs_super) + start)) + errx(FSCK_EX_UNCORRECTED, + _("directory data start (%lu) < sizeof(struct cramfs_super) + start (%zu)"), + start_data, sizeof(struct cramfs_super) + start); + if (end_dir != start_data) + errx(FSCK_EX_UNCORRECTED, + _("directory data end (%lu) != file data start (%lu)"), + end_dir, start_data); + } + if (super.flags & CRAMFS_FLAG_FSID_VERSION_2 && end_data > super.size) + errx(FSCK_EX_UNCORRECTED, _("invalid file data offset")); + + iput(root); /* free(root) */ +} + +int main(int argc, char **argv) +{ + int c; /* for getopt */ + int start = 0; + size_t length = 0; + + static const struct option longopts[] = { + {"verbose", no_argument, NULL, 'v'}, + {"version", no_argument, NULL, 'V'}, + {"help", no_argument, NULL, 'h'}, + {"blocksize", required_argument, NULL, 'b'}, + {"extract", optional_argument, NULL, 'x'}, + {NULL, 0, NULL, 0}, + }; + + setlocale(LC_MESSAGES, ""); + setlocale(LC_CTYPE, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + strutils_set_exitcode(FSCK_EX_USAGE); + + /* command line options */ + while ((c = getopt_long(argc, argv, "ayvVhb:", longopts, NULL)) != EOF) + switch (c) { + case 'a': /* ignore */ + case 'y': + break; + case 'h': + usage(); + break; + case 'V': + printf(UTIL_LINUX_VERSION); + return FSCK_EX_OK; + case 'x': + opt_extract = 1; + if(optarg) + extract_dir = optarg; + break; + case 'v': + opt_verbose++; + break; + case 'b': + blksize = strtou32_or_err(optarg, _("invalid blocksize argument")); + break; + default: + errtryhelp(FSCK_EX_USAGE); + } + + if ((argc - optind) != 1){ + warnx(_("bad usage")); + errtryhelp(FSCK_EX_USAGE); + } + filename = argv[optind]; + + test_super(&start, &length); + test_crc(start); + + if(opt_extract) { + if (blksize == 0) + blksize = getpagesize(); + outbuffer = xmalloc(blksize * 2); + test_fs(start); + } + + if (opt_verbose) + printf(_("%s: OK\n"), filename); + + exit(FSCK_EX_OK); +} diff --git a/disk-utils/fsck.minix.8 b/disk-utils/fsck.minix.8 new file mode 100644 index 0000000..f7abb9b --- /dev/null +++ b/disk-utils/fsck.minix.8 @@ -0,0 +1,168 @@ +.\" Copyright 1992, 1993, 1994 Rickard E. Faith (faith@cs.unc.edu) +.\" May be freely distributed. +.TH FSCK.MINIX 8 "June 2015" "util-linux" "System Administration" +.SH NAME +fsck.minix \- check consistency of Minix filesystem +.SH SYNOPSIS +.B fsck.minix +[options] +.I device +.SH DESCRIPTION +.B fsck.minix +performs a consistency check for the Linux MINIX filesystem. +.PP +The program assumes the filesystem is quiescent. +.B fsck.minix +should not be used on a mounted device unless you can be sure nobody is +writing to it. Remember that the kernel can write to device when it +searches for files. +.PP +The \fIdevice\fR name will usually have the following form: +.RS +.TS +tab(:); +l l. +/dev/hda[1\(en63]:IDE disk 1 +/dev/hdb[1\(en63]:IDE disk 2 +/dev/sda[1\(en15]:SCSI disk 1 +/dev/sdb[1\(en15]:SCSI disk 2 +.TE +.RE +.PP +If the filesystem was changed, i.e., repaired, then +.B fsck.minix +will print "FILE SYSTEM HAS CHANGED" and will +.BR sync (2) +three times before exiting. There is +.I no +need to reboot after check. +.SH WARNING +.B fsck.minix +should +.B not +be used on a mounted filesystem. Using +.B fsck.minix +on a mounted filesystem is very dangerous, due to the possibility that +deleted files are still in use, and can seriously damage a perfectly good +filesystem! If you absolutely have to run +.B fsck.minix +on a mounted filesystem, such as the root filesystem, make sure nothing +is writing to the disk, and that no files are "zombies" waiting for +deletion. +.SH OPTIONS +.TP +\fB\-l\fR, \fB\-\-list\fR +List all filenames. +.TP +\fB\-r\fR, \fB\-\-repair\fR +Perform interactive repairs. +.TP +\fB\-a\fR, \fB\-\-auto\fR +Perform automatic repairs. This option implies +.B \-\-repair +and serves to answer all of the questions asked with the default. Note +that this can be extremely dangerous in the case of extensive filesystem +damage. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +Be verbose. +.TP +\fB\-s\fR, \fB\-\-super\fR +Output super-block information. +.TP +\fB\-m\fR, \fB\-\-uncleared\fR +Activate MINIX-like "mode not cleared" warnings. +.TP +\fB\-f\fR, \fB\-\-force\fR +Force a filesystem check even if the filesystem was marked as valid. +Marking is done by the kernel when the filesystem is unmounted. +.TP +\fB\-V\fR, \fB\-\-version\fR +Display version information and exit. +.TP +\fB\-h\fR, \fB\-\-help\fR +Display help text and exit. +.SH DIAGNOSTICS +There are numerous diagnostic messages. The ones mentioned here are the +most commonly seen in normal usage. +.PP +If the device does not exist, +.B fsck.minix +will print "unable to read super block". If the device exists, but is not +a MINIX filesystem, +.B fsck.minix +will print "bad magic number in super-block". +.SH "EXIT CODES" +The exit code returned by +.B fsck.minix +is the sum of the following: +.PP +.RS +.PD 0 +.TP +.B 0 +No errors +.TP +.B 3 +Filesystem errors corrected, system should be rebooted if filesystem was +mounted +.TP +.B 4 +Filesystem errors left uncorrected +.TP +.B 7 +Combination of exit codes 3 and 4 +.TP +.B 8 +Operational error +.TP +.B 16 +Usage or syntax error +.PD +.RE +.PP +.SH AUTHORS +.MT torvalds@\:cs.\:helsinki.\:fi +Linus Torvalds +.ME +.br +Error code values by +.MT faith@\:cs.\:unc.\:edu +Rik Faith +.ME +.br +Added support for filesystem valid flag: +.MT greg%\:wind.\:uucp@\:plains.\:nodak.\:edu +Dr.\& Wettstein +.ME . +.br +Check to prevent fsck of mounted filesystem added by +.MT quinlan@\:yggdrasil.\:com +Daniel Quinlan +.ME . +.br +Minix v2 fs support by +.MT schwab@\:issan.\:informatik.\:uni-dortmund.\:de +Andreas Schwab +.ME , +updated by +.MT janl@\:math.\:uio.\:no +Nicolai Langfeldt +.ME . +.br +Portability patch by +.MT rmk@\:ecs.\:soton.\:ac.\:uk +Russell King +.ME . +.SH "SEE ALSO" +.BR fsck (8), +.BR fsck.ext2 (8), +.BR mkfs (8), +.BR mkfs.ext2 (8), +.BR mkfs.minix (8), +.BR reboot (8) +.SH AVAILABILITY +The fsck.minix command is part of the util-linux package and is available from +.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/ +Linux Kernel Archive +.UE . diff --git a/disk-utils/fsck.minix.c b/disk-utils/fsck.minix.c new file mode 100644 index 0000000..b978684 --- /dev/null +++ b/disk-utils/fsck.minix.c @@ -0,0 +1,1438 @@ +/* + * fsck.minix.c - a file system consistency checker for Linux. + * + * (C) 1991, 1992 Linus Torvalds. This file may be redistributed + * as per the GNU copyleft. + */ + +/* + * 09.11.91 - made the first rudimentary functions + * + * 10.11.91 - updated, does checking, no repairs yet. + * Sent out to the mailing-list for testing. + * + * 14.11.91 - Testing seems to have gone well. Added some + * correction-code, and changed some functions. + * + * 15.11.91 - More correction code. Hopefully it notices most + * cases now, and tries to do something about them. + * + * 16.11.91 - More corrections (thanks to Mika Jalava). Most + * things seem to work now. Yeah, sure. + * + * + * 19.04.92 - Had to start over again from this old version, as a + * kernel bug ate my enhanced fsck in February. + * + * 28.02.93 - added support for different directory entry sizes.. + * + * Sat Mar 6 18:59:42 1993, faith@cs.unc.edu: Output namelen with + * super-block information + * + * Sat Oct 9 11:17:11 1993, faith@cs.unc.edu: make exit status conform + * to that required by fsutil + * + * Mon Jan 3 11:06:52 1994 - Dr. Wettstein (greg%wind.uucp@plains.nodak.edu) + * Added support for file system valid flag. Also + * added program_version variable and output of + * program name and version number when program + * is executed. + * + * 30.10.94 - added support for v2 filesystem + * (Andreas Schwab, schwab@issan.informatik.uni-dortmund.de) + * + * 10.12.94 - added test to prevent checking of mounted fs adapted + * from Theodore Ts'o's (tytso@athena.mit.edu) e2fsck + * program. (Daniel Quinlan, quinlan@yggdrasil.com) + * + * 01.07.96 - Fixed the v2 fs stuff to use the right #defines and such + * for modern libcs (janl@math.uio.no, Nicolai Langfeldt) + * + * 02.07.96 - Added C bit fiddling routines from rmk@ecs.soton.ac.uk + * (Russell King). He made them for ARM. It would seem + * that the ARM is powerful enough to do this in C whereas + * i386 and m64k must use assembly to get it fast >:-) + * This should make minix fsck systemindependent. + * (janl@math.uio.no, Nicolai Langfeldt) + * + * 04.11.96 - Added minor fixes from Andreas Schwab to avoid compiler + * warnings. Added mc68k bitops from + * Joerg Dorchain <dorchain@mpi-sb.mpg.de>. + * + * 06.11.96 - Added v2 code submitted by Joerg Dorchain, but written by + * Andreas Schwab. + * + * 1999-02-22 Arkadiusz MiÅ›kiewicz <misiek@pld.ORG.PL> + * - added Native Language Support + * + * 2008-04-06 James Youngman <jay@gnu.org> + * - Issue better error message if we fail to open the device. + * - Restore terminal state if we get a fatal signal. + * + * + * I've had no time to add comments - hopefully the function names + * are comments enough. As with all file system checkers, this assumes + * the file system is quiescent - don't use it on a mounted device + * unless you can be sure nobody is writing to it (and remember that the + * kernel can write to it when it searches for files). + * + */ + +#include <stdio.h> +#include <stdarg.h> +#include <errno.h> +#include <unistd.h> +#include <string.h> +#include <fcntl.h> +#include <ctype.h> +#include <stdlib.h> +#include <termios.h> +#include <sys/stat.h> +#include <signal.h> +#include <getopt.h> + +#include "c.h" +#include "exitcodes.h" +#include "minix_programs.h" +#include "nls.h" +#include "pathnames.h" +#include "bitops.h" +#include "ismounted.h" +#include "all-io.h" +#include "closestream.h" +#include "rpmatch.h" +#include "strutils.h" + +#define ROOT_INO 1 +#define YESNO_LENGTH 64 + +/* Global variables used in minix_programs.h inline functions */ +int fs_version = 1; +char *super_block_buffer; + +static char *inode_buffer; + +#define Inode (((struct minix_inode *) inode_buffer) - 1) +#define Inode2 (((struct minix2_inode *) inode_buffer) - 1) + +static char *device_name; +static int device_fd; +static int repair, automatic, verbose, list, show, warn_mode, force; +static int directory, regular, blockdev, chardev, links, symlinks, total; + +static int changed; /* flags if the filesystem has been changed */ +static int errors_uncorrected; /* flag if some error was not corrected */ +static size_t dirsize = 16; +static size_t namelen = 14; +static struct termios termios; +static volatile sig_atomic_t termios_set; + +/* File-name data */ +#define MAX_DEPTH 50 +static int name_depth; +static char name_list[MAX_DEPTH][MINIX_NAME_MAX + 1]; + +/* Copy of the previous, just for error reporting - see get_current_name. This + * is a waste of 12kB or so. */ +static char current_name[MAX_DEPTH * (MINIX_NAME_MAX + 1) + 1]; + +static unsigned char *inode_count = NULL; +static unsigned char *zone_count = NULL; + +static void recursive_check(unsigned int ino); +static void recursive_check2(unsigned int ino); + +static char *inode_map; +static char *zone_map; + +#define inode_in_use(x) (isset(inode_map,(x)) != 0) +#define zone_in_use(x) (isset(zone_map,(x)-get_first_zone()+1) != 0) + +#define mark_inode(x) (setbit(inode_map,(x)),changed=1) +#define unmark_inode(x) (clrbit(inode_map,(x)),changed=1) + +#define mark_zone(x) (setbit(zone_map,(x)-get_first_zone()+1),changed=1) +#define unmark_zone(x) (clrbit(zone_map,(x)-get_first_zone()+1),changed=1) + +static void +reset(void) { + if (termios_set) + tcsetattr(STDIN_FILENO, TCSANOW, &termios); +} + +static void +fatalsig(int sig) { + /* We received a fatal signal. Reset the terminal. Also reset the + * signal handler and re-send the signal, so that the parent process + * knows which signal actually caused our death. */ + signal(sig, SIG_DFL); + reset(); + raise(sig); +} + +static void __attribute__((__noreturn__)) +leave(int status) { + reset(); + exit(status); +} + +static void __attribute__((__noreturn__)) +usage(void) { + FILE *out = stdout; + fputs(USAGE_HEADER, out); + fprintf(out, _(" %s [options] <device>\n"), program_invocation_short_name); + fputs(USAGE_SEPARATOR, out); + fputs(_("Check the consistency of a Minix filesystem.\n"), out); + fputs(USAGE_OPTIONS, out); + fputs(_(" -l, --list list all filenames\n"), out); + fputs(_(" -a, --auto automatic repair\n"), out); + fputs(_(" -r, --repair interactive repair\n"), out); + fputs(_(" -v, --verbose be verbose\n"), out); + fputs(_(" -s, --super output super-block information\n"), out); + fputs(_(" -m, --uncleared activate mode not cleared warnings\n"), out); + fputs(_(" -f, --force force check\n"), out); + fputs(USAGE_SEPARATOR, out); + printf(USAGE_HELP_OPTIONS(18)); + printf(USAGE_MAN_TAIL("fsck.minix(8)")); + exit(FSCK_EX_OK); +} + +static void die(const char *fmt, ...) + __attribute__ ((__format__(__printf__, 1, 2))); + +static void +die(const char *fmt, ...) { + va_list ap; + + fprintf(stderr, UTIL_LINUX_VERSION); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fputc('\n', stderr); + leave(FSCK_EX_ERROR); +} + +/* This simply goes through the file-name data and prints out the current file. */ +static void +get_current_name(void) { + int i = 0, ct; + char *p, *q; + + q = current_name; + while (i < name_depth) { + p = name_list[i++]; + ct = namelen; + *q++ = '/'; + while (ct-- && *p) + *q++ = *p++; + } + if (i == 0) + *q++ = '/'; + *q = 0; +} + +static int +ask(const char *string, int def) { + int resp; + char input[YESNO_LENGTH]; + + if (!repair) { + printf("\n"); + errors_uncorrected = 1; + return 0; + } + if (automatic) { + printf("\n"); + if (!def) + errors_uncorrected = 1; + return def; + } + /* TRANSLATORS: these yes no questions uses rpmatch(), and should be + * translated. */ + printf(def ? _("%s (y/n)? ") : _("%s (n/y)? "), string); + fflush(stdout); + ignore_result( fgets(input, YESNO_LENGTH, stdin) ); + resp = rpmatch(input); + switch (resp) { + case RPMATCH_INVALID: + /* def = def */ + break; + case RPMATCH_NO: + case RPMATCH_YES: + def = resp; + break; + default: + /* rpmatch bug? */ + abort(); + } + if (def) + printf(_("y\n")); + else { + printf(_("n\n")); + errors_uncorrected = 1; + } + return def; +} + +/* Make certain that we aren't checking a filesystem that is on a mounted + * partition. Code adapted from e2fsck, Copyright (C) 1993, 1994 Theodore + * Ts'o. Also licensed under GPL. */ +static void +check_mount(void) { + int cont; + + if (!is_mounted(device_name)) + return; + + printf(_("%s is mounted. "), device_name); + if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)) + cont = ask(_("Do you really want to continue"), 0); + else + cont = 0; + if (!cont) { + printf(_("check aborted.\n")); + exit(FSCK_EX_OK); + } + return; +} + + +static int is_valid_zone_nr(unsigned short nr) +{ + if (nr < get_first_zone()) + return 0; + else if (nr >= get_nzones()) + return 0; + return 1; +} + +/* check_zone_nr checks to see that *nr is a valid zone nr. If it isn't, it + * will possibly be repaired. Check_zone_nr sets *corrected if an error was + * corrected, and returns the zone (0 for no zone or a bad zone-number). */ +static int +check_zone_nr(unsigned short *nr, int *corrected) { + if (!*nr) + return 0; + + if (*nr < get_first_zone()) { + get_current_name(); + printf(_("Zone nr < FIRSTZONE in file `%s'."), current_name); + } else if (*nr >= get_nzones()) { + get_current_name(); + printf(_("Zone nr >= ZONES in file `%s'."), current_name); + } else + return *nr; + + if (ask(_("Remove block"), 1)) { + *nr = 0; + *corrected = 1; + } + return 0; +} + +static int +check_zone_nr2(unsigned int *nr, int *corrected) { + if (!*nr) + return 0; + + if (*nr < get_first_zone()) { + get_current_name(); + printf(_("Zone nr < FIRSTZONE in file `%s'."), current_name); + } else if (*nr >= get_nzones()) { + get_current_name(); + printf(_("Zone nr >= ZONES in file `%s'."), current_name); + } else + return *nr; + + if (ask(_("Remove block"), 1)) { + *nr = 0; + *corrected = 1; + } + return 0; +} + +/* read-block reads block nr into the buffer at addr. */ +static void +read_block(unsigned int nr, char *addr) { + if (!nr) { + memset(addr, 0, MINIX_BLOCK_SIZE); + return; + } + if (MINIX_BLOCK_SIZE * nr != lseek(device_fd, MINIX_BLOCK_SIZE * nr, SEEK_SET)) { + get_current_name(); + printf(_("Read error: unable to seek to block in file '%s'\n"), + current_name); + memset(addr, 0, MINIX_BLOCK_SIZE); + errors_uncorrected = 1; + } else if (MINIX_BLOCK_SIZE != read(device_fd, addr, MINIX_BLOCK_SIZE)) { + get_current_name(); + printf(_("Read error: bad block in file '%s'\n"), current_name); + memset(addr, 0, MINIX_BLOCK_SIZE); + errors_uncorrected = 1; + } +} + +/* write_block writes block nr to disk. */ +static void +write_block(unsigned int nr, char *addr) { + if (!nr) + return; + if (nr < get_first_zone() || nr >= get_nzones()) { + printf(_("Internal error: trying to write bad block\n" + "Write request ignored\n")); + errors_uncorrected = 1; + return; + } + if (MINIX_BLOCK_SIZE * nr != lseek(device_fd, MINIX_BLOCK_SIZE * nr, SEEK_SET)) + die(_("seek failed in write_block")); + if (MINIX_BLOCK_SIZE != write(device_fd, addr, MINIX_BLOCK_SIZE)) { + get_current_name(); + printf(_("Write error: bad block in file '%s'\n"), + current_name); + errors_uncorrected = 1; + } +} + +/* map-block calculates the absolute block nr of a block in a file. It sets + * 'changed' if the inode has needed changing, and re-writes any indirect + * blocks with errors. */ +static int +map_block(struct minix_inode *inode, unsigned int blknr) { + unsigned short ind[MINIX_BLOCK_SIZE >> 1]; + unsigned short dind[MINIX_BLOCK_SIZE >> 1]; + int blk_chg, block, result; + size_t range; + + if (blknr < 7) + return check_zone_nr(inode->i_zone + blknr, &changed); + blknr -= 7; + if (blknr < 512) { + block = check_zone_nr(inode->i_zone + 7, &changed); + read_block(block, (char *)ind); + blk_chg = 0; + result = check_zone_nr(blknr + ind, &blk_chg); + if (blk_chg) + write_block(block, (char *)ind); + return result; + } + blknr -= 512; + block = check_zone_nr(inode->i_zone + 8, &changed); + read_block(block, (char *)dind); + blk_chg = 0; + range = blknr / 512; + if (ARRAY_SIZE(dind) <= range) { + printf(_("Warning: block out of range\n")); + return 1; + } + result = check_zone_nr(dind + range, &blk_chg); + if (blk_chg) + write_block(block, (char *)dind); + block = result; + read_block(block, (char *)ind); + blk_chg = 0; + result = check_zone_nr(ind + (blknr % 512), &blk_chg); + if (blk_chg) + write_block(block, (char *)ind); + return result; +} + +static int +map_block2(struct minix2_inode *inode, unsigned int blknr) { + unsigned int ind[MINIX_BLOCK_SIZE >> 2]; + unsigned int dind[MINIX_BLOCK_SIZE >> 2]; + unsigned int tind[MINIX_BLOCK_SIZE >> 2]; + int blk_chg, block, result; + + if (blknr < 7) + return check_zone_nr2(inode->i_zone + blknr, &changed); + blknr -= 7; + if (blknr < 256) { + block = check_zone_nr2(inode->i_zone + 7, &changed); + read_block(block, (char *)ind); + blk_chg = 0; + result = check_zone_nr2(blknr + ind, &blk_chg); + if (blk_chg) + write_block(block, (char *)ind); + return result; + } + blknr -= 256; + if (blknr < 256 * 256) { + block = check_zone_nr2(inode->i_zone + 8, &changed); + read_block(block, (char *)dind); + blk_chg = 0; + result = check_zone_nr2(dind + blknr / 256, &blk_chg); + if (blk_chg) + write_block(block, (char *)dind); + block = result; + read_block(block, (char *)ind); + blk_chg = 0; + result = check_zone_nr2(ind + blknr % 256, &blk_chg); + if (blk_chg) + write_block(block, (char *)ind); + return result; + } + blknr -= 256 * 256; + block = check_zone_nr2(inode->i_zone + 9, &changed); + read_block(block, (char *)tind); + blk_chg = 0; + result = check_zone_nr2(tind + blknr / (256 * 256), &blk_chg); + if (blk_chg) + write_block(block, (char *)tind); + block = result; + read_block(block, (char *)dind); + blk_chg = 0; + result = check_zone_nr2(dind + (blknr / 256) % 256, &blk_chg); + if (blk_chg) + write_block(block, (char *)dind); + block = result; + read_block(block, (char *)ind); + blk_chg = 0; + result = check_zone_nr2(ind + blknr % 256, &blk_chg); + if (blk_chg) + write_block(block, (char *)ind); + return result; +} + +static void +write_super_block(void) { + /* v3 super block does not track state */ + if (fs_version == 3) + return; + /* Set the state of the filesystem based on whether or not there are + * uncorrected errors. The filesystem valid flag is unconditionally + * set if we get this far. */ + Super.s_state |= MINIX_VALID_FS; + if (errors_uncorrected) + Super.s_state |= MINIX_ERROR_FS; + else + Super.s_state &= ~MINIX_ERROR_FS; + + if (MINIX_BLOCK_SIZE != lseek(device_fd, MINIX_BLOCK_SIZE, SEEK_SET)) + die(_("seek failed in write_super_block")); + if (MINIX_BLOCK_SIZE != write(device_fd, super_block_buffer, MINIX_BLOCK_SIZE)) + die(_("unable to write super-block")); + return; +} + +static void +write_tables(void) { + unsigned long buffsz = get_inode_buffer_size(); + unsigned long imaps = get_nimaps(); + unsigned long zmaps = get_nzmaps(); + + write_super_block(); + + if (write_all(device_fd, inode_map, imaps * MINIX_BLOCK_SIZE)) + die(_("Unable to write inode map")); + + if (write_all(device_fd, zone_map, zmaps * MINIX_BLOCK_SIZE)) + die(_("Unable to write zone map")); + + if (write_all(device_fd, inode_buffer, buffsz)) + die(_("Unable to write inodes")); +} + +static void +get_dirsize(void) { + int block; + char blk[MINIX_BLOCK_SIZE]; + size_t size; + + if (fs_version == 2 || fs_version == 3) + block = Inode2[ROOT_INO].i_zone[0]; + else + block = Inode[ROOT_INO].i_zone[0]; + read_block(block, blk); + + for (size = 16; size < MINIX_BLOCK_SIZE; size <<= 1) { + if (strcmp(blk + size + 2, "..") == 0) { + dirsize = size; + namelen = size - 2; + return; + } + } + /* use defaults */ +} + +static void +read_superblock(void) { + if (MINIX_BLOCK_SIZE != lseek(device_fd, MINIX_BLOCK_SIZE, SEEK_SET)) + die(_("seek failed")); + + super_block_buffer = calloc(1, MINIX_BLOCK_SIZE); + if (!super_block_buffer) + die(_("unable to alloc buffer for superblock")); + + if (MINIX_BLOCK_SIZE != read(device_fd, super_block_buffer, MINIX_BLOCK_SIZE)) + die(_("unable to read super block")); + if (Super.s_magic == MINIX_SUPER_MAGIC) { + namelen = 14; + dirsize = 16; + fs_version = 1; + } else if (Super.s_magic == MINIX_SUPER_MAGIC2) { + namelen = 30; + dirsize = 32; + fs_version = 1; + } else if (Super.s_magic == MINIX2_SUPER_MAGIC) { + namelen = 14; + dirsize = 16; + fs_version = 2; + } else if (Super.s_magic == MINIX2_SUPER_MAGIC2) { + namelen = 30; + dirsize = 32; + fs_version = 2; + } else if (Super3.s_magic == MINIX3_SUPER_MAGIC) { + namelen = 60; + dirsize = 64; + fs_version = 3; + } else + die(_("bad magic number in super-block")); + if (get_zone_size() != 0 || MINIX_BLOCK_SIZE != 1024) + die(_("Only 1k blocks/zones supported")); + if (get_ninodes() == 0 || get_ninodes() == UINT32_MAX) + die(_("bad s_ninodes field in super-block")); + if (get_nimaps() * MINIX_BLOCK_SIZE * 8 < get_ninodes() + 1) + die(_("bad s_imap_blocks field in super-block")); + if (get_first_zone() > (off_t) get_nzones()) + die(_("bad s_firstdatazone field in super-block")); + if (get_nzmaps() * MINIX_BLOCK_SIZE * 8 < + get_nzones() - get_first_zone() + 1) + die(_("bad s_zmap_blocks field in super-block")); +} + +static void +read_tables(void) { + unsigned long inodes = get_ninodes(); + size_t buffsz = get_inode_buffer_size(); + off_t norm_first_zone = first_zone_data(); + off_t first_zone = get_first_zone(); + unsigned long zones = get_nzones(); + unsigned long imaps = get_nimaps(); + unsigned long zmaps = get_nzmaps(); + ssize_t rc; + + inode_map = malloc(imaps * MINIX_BLOCK_SIZE); + if (!inode_map) + die(_("Unable to allocate buffer for inode map")); + zone_map = malloc(zmaps * MINIX_BLOCK_SIZE); + if (!zone_map) + die(_("Unable to allocate buffer for zone map")); + inode_buffer = malloc(buffsz); + if (!inode_buffer) + die(_("Unable to allocate buffer for inodes")); + inode_count = calloc(1, inodes + 1); + if (!inode_count) + die(_("Unable to allocate buffer for inode count")); + zone_count = calloc(1, zones); + if (!zone_count) + die(_("Unable to allocate buffer for zone count")); + + rc = read(device_fd, inode_map, imaps * MINIX_BLOCK_SIZE); + if (rc < 0 || imaps * MINIX_BLOCK_SIZE != (size_t) rc) + die(_("Unable to read inode map")); + + rc = read(device_fd, zone_map, zmaps * MINIX_BLOCK_SIZE); + if (rc < 0 || zmaps * MINIX_BLOCK_SIZE != (size_t) rc) + die(_("Unable to read zone map")); + + rc = read(device_fd, inode_buffer, buffsz); + if (rc < 0 || buffsz != (size_t) rc) + die(_("Unable to read inodes")); + if (norm_first_zone != first_zone) { + printf(_("Warning: Firstzone != Norm_firstzone\n")); + errors_uncorrected = 1; + } + get_dirsize(); + if (show) { + printf(_("%ld inodes\n"), inodes); + printf(_("%ld blocks\n"), zones); + printf(_("Firstdatazone=%jd (%jd)\n"), + (intmax_t)first_zone, (intmax_t)norm_first_zone); + printf(_("Zonesize=%d\n"), MINIX_BLOCK_SIZE << get_zone_size()); + printf(_("Maxsize=%zu\n"), get_max_size()); + if (fs_version < 3) + printf(_("Filesystem state=%d\n"), Super.s_state); + printf(_("namelen=%zd\n\n"), namelen); + } +} + +static struct minix_inode * +get_inode(unsigned int nr) { + struct minix_inode *inode; + + if (!nr || nr > get_ninodes()) + return NULL; + total++; + inode = Inode + nr; + if (!inode_count[nr]) { + if (!inode_in_use(nr)) { + get_current_name(); + printf(_("Inode %d marked unused, " + "but used for file '%s'\n"), nr, current_name); + if (repair) { + if (ask(_("Mark in use"), 1)) + mark_inode(nr); + } else { + errors_uncorrected = 1; + } + } + if (S_ISDIR(inode->i_mode)) + directory++; + else if (S_ISREG(inode->i_mode)) + regular++; + else if (S_ISCHR(inode->i_mode)) + chardev++; + else if (S_ISBLK(inode->i_mode)) + blockdev++; + else if (S_ISLNK(inode->i_mode)) + symlinks++; + else if (S_ISSOCK(inode->i_mode)) + ; + else if (S_ISFIFO(inode->i_mode)) + ; + else { + get_current_name(); + printf(_("The file `%s' has mode %05o\n"), + current_name, inode->i_mode); + } + + } else + links++; + if (!++inode_count[nr]) { + printf(_("Warning: inode count too big.\n")); + inode_count[nr]--; + errors_uncorrected = 1; + } + return inode; +} + +static struct minix2_inode * +get_inode2(unsigned int nr) { + struct minix2_inode *inode; + + if (!nr || nr > get_ninodes()) + return NULL; + total++; + inode = Inode2 + nr; + if (!inode_count[nr]) { + if (!inode_in_use(nr)) { + get_current_name(); + printf(_("Inode %d marked unused, " + "but used for file '%s'\n"), nr, current_name); + if (repair) { + if (ask(_("Mark in use"), 1)) + mark_inode(nr); + else + errors_uncorrected = 1; + } + } + if (S_ISDIR(inode->i_mode)) + directory++; + else if (S_ISREG(inode->i_mode)) + regular++; + else if (S_ISCHR(inode->i_mode)) + chardev++; + else if (S_ISBLK(inode->i_mode)) + blockdev++; + else if (S_ISLNK(inode->i_mode)) + symlinks++; + else if (S_ISSOCK(inode->i_mode)) ; + else if (S_ISFIFO(inode->i_mode)) ; + else { + get_current_name(); + printf(_("The file `%s' has mode %05o\n"), + current_name, inode->i_mode); + } + } else + links++; + if (!++inode_count[nr]) { + printf(_("Warning: inode count too big.\n")); + inode_count[nr]--; + errors_uncorrected = 1; + } + return inode; +} + +static void +check_root(void) { + struct minix_inode *inode = Inode + ROOT_INO; + + if (!inode || !S_ISDIR(inode->i_mode)) + die(_("root inode isn't a directory")); +} + +static void +check_root2(void) { + struct minix2_inode *inode = Inode2 + ROOT_INO; + + if (!inode || !S_ISDIR(inode->i_mode)) + die(_("root inode isn't a directory")); +} + +static int +add_zone(unsigned short *znr, int *corrected) { + int block; + + block = check_zone_nr(znr, corrected); + if (!block) + return 0; + if (zone_count[block]) { + get_current_name(); + printf(_("Block has been used before. Now in file `%s'."), + current_name); + if (ask(_("Clear"), 1)) { + *znr = 0; + block = 0; + *corrected = 1; + } + } + if (!block) + return 0; + if (!zone_in_use(block)) { + get_current_name(); + printf(_("Block %d in file `%s' is marked not in use."), + block, current_name); + if (ask(_("Correct"), 1)) + mark_zone(block); + } + if (!++zone_count[block]) + zone_count[block]--; + return block; +} + +static int +add_zone2(unsigned int *znr, int *corrected) { + int block; + + block = check_zone_nr2(znr, corrected); + if (!block) + return 0; + if (zone_count[block]) { + get_current_name(); + printf(_("Block has been used before. Now in file `%s'."), + current_name); + if (ask(_("Clear"), 1)) { + *znr = 0; + block = 0; + *corrected = 1; + } + } + if (!block) + return 0; + if (!zone_in_use(block)) { + get_current_name(); + printf(_("Block %d in file `%s' is marked not in use."), + block, current_name); + if (ask(_("Correct"), 1)) + mark_zone(block); + } + if (!++zone_count[block]) + zone_count[block]--; + return block; +} + +static void +add_zone_ind(unsigned short *znr, int *corrected) { + static char blk[MINIX_BLOCK_SIZE]; + int i, chg_blk = 0; + int block; + + block = add_zone(znr, corrected); + if (!block) + return; + read_block(block, blk); + for (i = 0; i < (MINIX_BLOCK_SIZE >> 1); i++) + add_zone(i + (unsigned short *)blk, &chg_blk); + if (chg_blk) + write_block(block, blk); +} + +static void +add_zone_ind2(unsigned int *znr, int *corrected) { + static char blk[MINIX_BLOCK_SIZE]; + int i, chg_blk = 0; + int block; + + block = add_zone2(znr, corrected); + if (!block) + return; + read_block(block, blk); + for (i = 0; i < MINIX_BLOCK_SIZE >> 2; i++) + add_zone2(i + (unsigned int *)blk, &chg_blk); + if (chg_blk) + write_block(block, blk); +} + +static void +add_zone_dind(unsigned short *znr, int *corrected) { + static char blk[MINIX_BLOCK_SIZE]; + int i, blk_chg = 0; + int block; + + block = add_zone(znr, corrected); + if (!block) + return; + read_block(block, blk); + for (i = 0; i < (MINIX_BLOCK_SIZE >> 1); i++) + add_zone_ind(i + (unsigned short *)blk, &blk_chg); + if (blk_chg) + write_block(block, blk); +} + +static void +add_zone_dind2(unsigned int *znr, int *corrected) { + static char blk[MINIX_BLOCK_SIZE]; + int i, blk_chg = 0; + int block; + + block = add_zone2(znr, corrected); + if (!block) + return; + read_block(block, blk); + for (i = 0; i < MINIX_BLOCK_SIZE >> 2; i++) + add_zone_ind2(i + (unsigned int *)blk, &blk_chg); + if (blk_chg) + write_block(block, blk); +} + +static void +add_zone_tind2(unsigned int *znr, int *corrected) { + static char blk[MINIX_BLOCK_SIZE]; + int i, blk_chg = 0; + int block; + + block = add_zone2(znr, corrected); + if (!block) + return; + read_block(block, blk); + for (i = 0; i < MINIX_BLOCK_SIZE >> 2; i++) + add_zone_dind2(i + (unsigned int *)blk, &blk_chg); + if (blk_chg) + write_block(block, blk); +} + +static void +check_zones(unsigned int i) { + struct minix_inode *inode; + + if (!i || i > get_ninodes()) + return; + if (inode_count[i] > 1) /* have we counted this file already? */ + return; + inode = Inode + i; + if (!S_ISDIR(inode->i_mode) && !S_ISREG(inode->i_mode) && + !S_ISLNK(inode->i_mode)) + return; + for (i = 0; i < 7; i++) + add_zone(i + inode->i_zone, &changed); + add_zone_ind(7 + inode->i_zone, &changed); + add_zone_dind(8 + inode->i_zone, &changed); +} + +static void +check_zones2(unsigned int i) { + struct minix2_inode *inode; + + if (!i || i > get_ninodes()) + return; + if (inode_count[i] > 1) /* have we counted this file already? */ + return; + inode = Inode2 + i; + if (!S_ISDIR(inode->i_mode) && !S_ISREG(inode->i_mode) + && !S_ISLNK(inode->i_mode)) + return; + for (i = 0; i < 7; i++) + add_zone2(i + inode->i_zone, &changed); + add_zone_ind2(7 + inode->i_zone, &changed); + add_zone_dind2(8 + inode->i_zone, &changed); + add_zone_tind2(9 + inode->i_zone, &changed); +} + +static void +check_file(struct minix_inode *dir, unsigned int offset) { + static char blk[MINIX_BLOCK_SIZE + 2]; + struct minix_inode *inode; + unsigned int ino; + char *name; + int block; + + block = map_block(dir, offset / MINIX_BLOCK_SIZE); + read_block(block, blk); + name = blk + (offset % MINIX_BLOCK_SIZE) + 2; + ino = *(unsigned short *)(name - 2); + if (ino > get_ninodes()) { + get_current_name(); + printf(_("The directory '%s' contains a bad inode number " + "for file '%.*s'."), current_name, (int)namelen, name); + if (ask(_(" Remove"), 1)) { + *(unsigned short *)(name - 2) = 0; + write_block(block, blk); + } + ino = 0; + } + if (name_depth < MAX_DEPTH) + xstrncpy(name_list[name_depth], name, namelen); + else + return; + name_depth++; + inode = get_inode(ino); + name_depth--; + if (!offset) { + if (!inode || strcmp(".", name)) { + get_current_name(); + printf(_("%s: bad directory: '.' isn't first\n"), + current_name); + errors_uncorrected = 1; + } else + return; + } + if (offset == dirsize) { + if (!inode || strcmp("..", name)) { + get_current_name(); + printf(_("%s: bad directory: '..' isn't second\n"), + current_name); + errors_uncorrected = 1; + } else + return; + } + if (!inode) + return; + if (name_depth < MAX_DEPTH) + xstrncpy(name_list[name_depth], name, namelen); + else + return; + name_depth++; + if (list) { + if (verbose) + printf("%6d %07o %3d ", ino, + inode->i_mode, inode->i_nlinks); + get_current_name(); + printf("%s", current_name); + if (S_ISDIR(inode->i_mode)) + printf(":\n"); + else + printf("\n"); + } + check_zones(ino); + if (inode && S_ISDIR(inode->i_mode)) + recursive_check(ino); + name_depth--; + return; +} + +static void +check_file2(struct minix2_inode *dir, unsigned int offset) { + static char blk[MINIX_BLOCK_SIZE + 4]; + struct minix2_inode *inode; + ino_t ino; + char *name; + int block; + const int version_offset = fs_version == 3 ? 4 : 2; + + block = map_block2(dir, offset / MINIX_BLOCK_SIZE); + read_block(block, blk); + name = blk + (offset % MINIX_BLOCK_SIZE) + version_offset; + ino = version_offset == 4 ? *(uint32_t *)(name - version_offset) + : *(uint16_t *)(name - version_offset); + if (ino > get_ninodes()) { + get_current_name(); + printf(_("The directory '%s' contains a bad inode number " + "for file '%.*s'."), current_name, (int)namelen, name); + if (ask(_(" Remove"), 1)) { + memset(name - version_offset, 0, version_offset); + write_block(block, blk); + } + ino = 0; + } + if (name_depth < MAX_DEPTH) + xstrncpy(name_list[name_depth], name, namelen); + else + return; + name_depth++; + inode = get_inode2(ino); + name_depth--; + if (!offset) { + if (!inode || strcmp(".", name)) { + get_current_name(); + printf(_("%s: bad directory: '.' isn't first\n"), + current_name); + errors_uncorrected = 1; + } else + return; + } + if (offset == dirsize) { + if (!inode || strcmp("..", name)) { + get_current_name(); + printf(_("%s: bad directory: '..' isn't second\n"), + current_name); + errors_uncorrected = 1; + } else + return; + } + if (!inode) + return; + name_depth++; + if (list) { + if (verbose) + printf("%6ju %07o %3d ", (uintmax_t)ino, inode->i_mode, + inode->i_nlinks); + get_current_name(); + printf("%s", current_name); + if (S_ISDIR(inode->i_mode)) + printf(":\n"); + else + printf("\n"); + } + check_zones2(ino); + if (inode && S_ISDIR(inode->i_mode)) + recursive_check2(ino); + name_depth--; + return; +} + +static void +recursive_check(unsigned int ino) { + struct minix_inode *dir; + off_t offset; + + dir = Inode + ino; + if (!S_ISDIR(dir->i_mode)) + die(_("internal error")); + if (dir->i_size < 2 * dirsize) { + get_current_name(); + printf(_("%s: bad directory: size < 32"), current_name); + errors_uncorrected = 1; + } + + if ((!repair || automatic) && !is_valid_zone_nr(*dir->i_zone)) { + get_current_name(); + printf(_("%s: bad directory: invalid i_zone, use --repair to fix\n"), current_name); + return; + } + for (offset = 0; offset < dir->i_size; offset += dirsize) + check_file(dir, offset); +} + +static void +recursive_check2(unsigned int ino) { + struct minix2_inode *dir; + off_t offset; + + dir = Inode2 + ino; + if (!S_ISDIR(dir->i_mode)) + die(_("internal error")); + if (dir->i_size < 2 * dirsize) { + get_current_name(); + printf(_("%s: bad directory: size < 32"), current_name); + errors_uncorrected = 1; + } + for (offset = 0; offset < dir->i_size; offset += dirsize) + check_file2(dir, offset); +} + +static int +bad_zone(int i) { + char buffer[1024]; + + if (MINIX_BLOCK_SIZE * i != lseek(device_fd, MINIX_BLOCK_SIZE * i, SEEK_SET)) + die(_("seek failed in bad_zone")); + return (MINIX_BLOCK_SIZE != read(device_fd, buffer, MINIX_BLOCK_SIZE)); +} + +static void +check_counts(void) { + unsigned long i; + + for (i = 1; i <= get_ninodes(); i++) { + if (!inode_in_use(i) && Inode[i].i_mode && warn_mode) { + printf(_("Inode %lu mode not cleared."), i); + if (ask(_("Clear"), 1)) { + Inode[i].i_mode = 0; + changed = 1; + } + } + if (!inode_count[i]) { + if (!inode_in_use(i)) + continue; + printf(_("Inode %lu not used, marked used in the bitmap."), i); + if (ask(_("Clear"), 1)) + unmark_inode(i); + continue; + } + if (!inode_in_use(i)) { + printf(_("Inode %lu used, marked unused in the bitmap."), i); + if (ask(_("Set"), 1)) + mark_inode(i); + } + if (Inode[i].i_nlinks != inode_count[i]) { + printf(_("Inode %lu (mode = %07o), i_nlinks=%d, counted=%d."), + i, Inode[i].i_mode, Inode[i].i_nlinks, + inode_count[i]); + if (ask(_("Set i_nlinks to count"), 1)) { + Inode[i].i_nlinks = inode_count[i]; + changed = 1; + } + } + } + for (i = get_first_zone(); i < get_nzones(); i++) { + if (zone_in_use(i) == zone_count[i]) + continue; + if (!zone_count[i]) { + if (bad_zone(i)) + continue; + printf(_("Zone %lu: marked in use, no file uses it."), + i); + if (ask(_("Unmark"), 1)) + unmark_zone(i); + continue; + } + if (zone_in_use(i)) + printf(_("Zone %lu: in use, counted=%d\n"), + i, zone_count[i]); + else + printf(_("Zone %lu: not in use, counted=%d\n"), + i, zone_count[i]); + } +} + +static void +check_counts2(void) { + unsigned long i; + + for (i = 1; i <= get_ninodes(); i++) { + if (!inode_in_use(i) && Inode2[i].i_mode && warn_mode) { + printf(_("Inode %lu mode not cleared."), i); + if (ask(_("Clear"), 1)) { + Inode2[i].i_mode = 0; + changed = 1; + } + } + if (!inode_count[i]) { + if (!inode_in_use(i)) + continue; + printf(_("Inode %lu not used, marked used in the bitmap."), i); + if (ask(_("Clear"), 1)) + unmark_inode(i); + continue; + } + if (!inode_in_use(i)) { + printf(_("Inode %lu used, marked unused in the bitmap."), i); + if (ask(_("Set"), 1)) + mark_inode(i); + } + if (Inode2[i].i_nlinks != inode_count[i]) { + printf(_("Inode %lu (mode = %07o), i_nlinks=%d, counted=%d."), + i, Inode2[i].i_mode, Inode2[i].i_nlinks, + inode_count[i]); + if (ask(_("Set i_nlinks to count"), 1)) { + Inode2[i].i_nlinks = inode_count[i]; + changed = 1; + } + } + } + for (i = get_first_zone(); i < get_nzones(); i++) { + if (zone_in_use(i) == zone_count[i]) + continue; + if (!zone_count[i]) { + if (bad_zone(i)) + continue; + printf(_("Zone %lu: marked in use, no file uses it."), + i); + if (ask(_("Unmark"), 1)) + unmark_zone(i); + continue; + } + if (zone_in_use(i)) + printf(_("Zone %lu: in use, counted=%d\n"), + i, zone_count[i]); + else + printf(_("Zone %lu: not in use, counted=%d\n"), + i, zone_count[i]); + } +} + +static void +check(void) { + memset(inode_count, 0, (get_ninodes() + 1) * sizeof(*inode_count)); + memset(zone_count, 0, get_nzones() * sizeof(*zone_count)); + check_zones(ROOT_INO); + recursive_check(ROOT_INO); + check_counts(); +} + +static void +check2(void) { + memset(inode_count, 0, (get_ninodes() + 1) * sizeof(*inode_count)); + memset(zone_count, 0, get_nzones() * sizeof(*zone_count)); + check_zones2(ROOT_INO); + recursive_check2(ROOT_INO); + check_counts2(); +} + +int +main(int argc, char **argv) { + struct termios tmp; + int count; + int retcode = FSCK_EX_OK; + int i; + static const struct option longopts[] = { + {"list", no_argument, NULL, 'l'}, + {"auto", no_argument, NULL, 'a'}, + {"repair", no_argument, NULL, 'r'}, + {"verbose", no_argument, NULL, 'v'}, + {"super", no_argument, NULL, 's'}, + {"uncleared", no_argument, NULL, 'm'}, + {"force", no_argument, NULL, 'f'}, + {"version", no_argument, NULL, 'V'}, + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + strutils_set_exitcode(FSCK_EX_USAGE); + + if (INODE_SIZE * MINIX_INODES_PER_BLOCK != MINIX_BLOCK_SIZE) + die(_("bad inode size")); + if (INODE2_SIZE * MINIX2_INODES_PER_BLOCK != MINIX_BLOCK_SIZE) + die(_("bad v2 inode size")); + + while ((i = getopt_long(argc, argv, "larvsmfVh", longopts, NULL)) != -1) + switch (i) { + case 'l': + list = 1; + break; + case 'a': + automatic = 1; + repair = 1; + break; + case 'r': + automatic = 0; + repair = 1; + break; + case 'v': + verbose = 1; + break; + case 's': + show = 1; + break; + case 'm': + warn_mode = 1; + break; + case 'f': + force = 1; + break; + case 'V': + printf(UTIL_LINUX_VERSION); + return FSCK_EX_OK; + case 'h': + usage(); + default: + errtryhelp(FSCK_EX_USAGE); + } + argc -= optind; + argv += optind; + if (0 < argc) { + device_name = argv[0]; + } else { + warnx(_("no device specified")); + errtryhelp(FSCK_EX_USAGE); + } + check_mount(); /* trying to check a mounted filesystem? */ + if (repair && !automatic && (!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO))) + die(_("need terminal for interactive repairs")); + + device_fd = open(device_name, repair ? O_RDWR : O_RDONLY); + if (device_fd < 0) + die(_("cannot open %s: %s"), device_name, strerror(errno)); + for (count = 0; count < 3; count++) + sync(); + read_superblock(); + + /* Determine whether or not we should continue with the checking. This + * is based on the status of the filesystem valid and error flags and + * whether or not the -f switch was specified on the command line. */ + if (fs_version < 3 && !(Super.s_state & MINIX_ERROR_FS) && + (Super.s_state & MINIX_VALID_FS) && !force) { + if (repair) + printf(_("%s is clean, no check.\n"), device_name); + return retcode; + } else if (force) + printf(_("Forcing filesystem check on %s.\n"), device_name); + else if (repair) + printf(_("Filesystem on %s is dirty, needs checking.\n"), + device_name); + + read_tables(); + + /* Restore the terminal state on fatal signals. We don't do this for + * SIGALRM, SIGUSR1 or SIGUSR2. */ + signal(SIGINT, fatalsig); + signal(SIGQUIT, fatalsig); + signal(SIGTERM, fatalsig); + + if (repair && !automatic) { + tcgetattr(STDIN_FILENO, &termios); + tmp = termios; + tmp.c_lflag &= ~(ICANON | ECHO); + tcsetattr(STDIN_FILENO, TCSANOW, &tmp); + termios_set = 1; + } + + if (fs_version == 2 || fs_version == 3) { + check_root2(); + check2(); + } else { + check_root(); + check(); + } + if (verbose) { + unsigned long inode, free; + + for (inode = 1, free = 0; inode <= get_ninodes(); inode++) + if (!inode_in_use(inode)) + free++; + printf(_("\n%6ld inodes used (%ld%%)\n"), + (get_ninodes() - free), + 100 * (get_ninodes() - free) / get_ninodes()); + for (inode = get_first_zone(), free = 0; inode < get_nzones(); inode++) + if (!zone_in_use(inode)) + free++; + printf(_("%6ld zones used (%ld%%)\n"), (get_nzones() - free), + 100 * (get_nzones() - free) / get_nzones()); + printf(_("\n%6d regular files\n" + "%6d directories\n" + "%6d character device files\n" + "%6d block device files\n" + "%6d links\n" + "%6d symbolic links\n" + "------\n" + "%6d files\n"), + regular, directory, chardev, blockdev, + links - 2 * directory + 1, symlinks, + total - 2 * directory + 1); + } + if (changed) { + write_tables(); + printf(_("----------------------------\n" + "FILE SYSTEM HAS BEEN CHANGED\n" + "----------------------------\n")); + for (count = 0; count < 3; count++) + sync(); + } else if (repair) + write_super_block(); + + if (repair && !automatic) + tcsetattr(STDIN_FILENO, TCSANOW, &termios); + + if (close_fd(device_fd) != 0) + err(FSCK_EX_ERROR, _("write failed")); + if (changed) + retcode += 3; + if (errors_uncorrected) + retcode += 4; + return retcode; +} diff --git a/disk-utils/isosize.8 b/disk-utils/isosize.8 new file mode 100644 index 0000000..baa5f7d --- /dev/null +++ b/disk-utils/isosize.8 @@ -0,0 +1,56 @@ +.TH ISOSIZE 8 "June 2011" "util-linux" "System Administration" +.SH NAME +isosize \- output the length of an iso9660 filesystem +.SH SYNOPSIS +.B isosize +.RI [options] " iso9660_image_file" +.SH DESCRIPTION +.PP +This command outputs the length of an iso9660 filesystem that +is contained in the specified file. This file may be a normal file or +a block device (e.g.\& /dev/hdd or /dev/sr0). In the absence of +any options (and errors), it will output the size of the iso9660 +filesystem in bytes. This can now be a large number (>> 4\ GB). +.SH OPTIONS +.TP +.BR \-x , " \-\-sectors" +Show the block count and block size in human-readable form. +The output uses the term "sectors" for "blocks". +.TP +.BR \-d , " \-\-divisor " \fInumber\fR +Only has an effect when +.B \-x +is not given. The value shown (if no errors) +is the iso9660 file size in bytes divided by +.IR number . +So if +.I number +is the block size then the shown value will be the block count. +.PP +The size of the file (or block device) holding an iso9660 +filesystem can be marginally larger than the actual size of the +iso9660 filesystem. One reason for this is that cd writers +are allowed to add "run out" sectors at the end of an iso9660 +image. +.SH "EXIT STATUS" +.RS +.PD 0 +.TP +.B 0 +success +.TP +.B 1 +generic failure, such as invalid usage +.TP +.B 32 +all failed +.TP +.B 64 +some failed +.PD +.RE +.SH AVAILABILITY +The isosize command is part of the util-linux package and is available from +.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/ +Linux Kernel Archive +.UE . diff --git a/disk-utils/isosize.c b/disk-utils/isosize.c new file mode 100644 index 0000000..85a549a --- /dev/null +++ b/disk-utils/isosize.c @@ -0,0 +1,217 @@ +/* + * isosize.c - Andries Brouwer, 000608 + * + * use header info to find size of iso9660 file system + * output a number - useful in scripts + * + * Synopsis: + * isosize [-x] [-d <num>] <filename> + * where "-x" gives length in sectors and sector size while + * without this argument the size is given in bytes + * without "-x" gives length in bytes unless "-d <num>" is + * given. In the latter case the length in bytes divided + * by <num> is given + * + * Version 2.03 2000/12/21 + * - add "-d <num>" option and use long long to fix things > 2 GB + * Version 2.02 2000/10/11 + * - error messages on IO failures [D. Gilbert] + * + */ +#include <stdio.h> +#include <stdlib.h> +#include <getopt.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> + +#include "nls.h" +#include "c.h" +#include "strutils.h" +#include "closestream.h" + +#define ISOSIZE_EXIT_ALLFAILED 32 +#define ISOSIZE_EXIT_SOMEOK 64 + +static int is_iso(int fd) +{ + char label[8]; + + if (pread(fd, &label, 8, 0x8000) == -1) + return 1; + return memcmp(&label, &"\1CD001\1", 8); +} + +static int isonum_721(unsigned char *p) +{ + return ((p[0] & 0xff) + | ((p[1] & 0xff) << 8)); +} + +static int isonum_722(unsigned char *p) +{ + return ((p[1] & 0xff) + | ((p[0] & 0xff) << 8)); +} + +static int isonum_723(unsigned char *p, int xflag) +{ + int le = isonum_721(p); + int be = isonum_722(p + 2); + + if (xflag && le != be) + /* translation is useless */ + warnx("723error: le=%d be=%d", le, be); + return (le); +} + +static int isonum_731(unsigned char *p) +{ + return ((p[0] & 0xff) + | ((p[1] & 0xff) << 8) + | ((p[2] & 0xff) << 16) + | ((p[3] & 0xff) << 24)); +} + +static int isonum_732(unsigned char *p) +{ + return ((p[3] & 0xff) + | ((p[2] & 0xff) << 8) + | ((p[1] & 0xff) << 16) + | ((p[0] & 0xff) << 24)); +} + +static int isonum_733(unsigned char *p, int xflag) +{ + int le = isonum_731(p); + int be = isonum_732(p + 4); + + if (xflag && le != be) + /* translation is useless */ + warnx("733error: le=%d be=%d", le, be); + return (le); +} + +static int isosize(int argc, char *filenamep, int xflag, long divisor) +{ + int fd, nsecs, ssize, rc = -1; + unsigned char volume_space_size[8]; + unsigned char logical_block_size[4]; + + if ((fd = open(filenamep, O_RDONLY)) < 0) { + warn(_("cannot open %s"), filenamep); + goto done; + } + if (is_iso(fd)) + warnx(_("%s: might not be an ISO filesystem"), filenamep); + + if (pread(fd, volume_space_size, sizeof(volume_space_size), 0x8050) != sizeof(volume_space_size) || + pread(fd, logical_block_size, sizeof(logical_block_size), 0x8080) != sizeof(logical_block_size)) { + if (errno) + warn(_("read error on %s"), filenamep); + else + warnx(_("read error on %s"), filenamep); + goto done; + } + + nsecs = isonum_733(volume_space_size, xflag); + /* isonum_723 returns nowadays always 2048 */ + ssize = isonum_723(logical_block_size, xflag); + + if (1 < argc) + printf("%s: ", filenamep); + if (xflag) + printf(_("sector count: %d, sector size: %d\n"), nsecs, ssize); + else { + long long product = nsecs; + + if (divisor == 0) + printf("%lld\n", product * ssize); + else if (divisor == ssize) + printf("%d\n", nsecs); + else + printf("%lld\n", (product * ssize) / divisor); + } + + rc = 0; +done: + if (fd >= 0) + close(fd); + return rc; +} + +static void __attribute__((__noreturn__)) usage(void) +{ + + fputs(USAGE_HEADER, stdout); + fprintf(stdout, + _(" %s [options] <iso9660_image_file> ...\n"), + program_invocation_short_name); + + fputs(USAGE_SEPARATOR, stdout); + fputs(_("Show the length of an ISO-9660 filesystem.\n"), stdout); + + fputs(USAGE_OPTIONS, stdout); + fputs(_(" -d, --divisor=<number> divide the amount of bytes by <number>\n"), stdout); + fputs(_(" -x, --sectors show sector count and size\n"), stdout); + + printf(USAGE_HELP_OPTIONS(25)); + printf(USAGE_MAN_TAIL("isosize(8)")); + + exit(EXIT_SUCCESS); +} + +int main(int argc, char **argv) +{ + int j, ct_err = 0, ct, opt, xflag = 0; + long divisor = 0; + + static const struct option longopts[] = { + {"divisor", required_argument, NULL, 'd'}, + {"sectors", no_argument, NULL, 'x'}, + {"version", no_argument, NULL, 'V'}, + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + while ((opt = getopt_long(argc, argv, "d:xVh", longopts, NULL)) != -1) { + switch (opt) { + case 'd': + divisor = + strtol_or_err(optarg, + _("invalid divisor argument")); + break; + case 'x': + xflag = 1; + break; + case 'V': + printf(UTIL_LINUX_VERSION); + return EXIT_SUCCESS; + case 'h': + usage(); + default: + errtryhelp(EXIT_FAILURE); + } + } + + ct = argc - optind; + + if (ct <= 0) { + warnx(_("no device specified")); + errtryhelp(EXIT_FAILURE); + } + + for (j = optind; j < argc; j++) { + if (isosize(ct, argv[j], xflag, divisor) != 0) + ct_err++; + } + + return ct == ct_err ? ISOSIZE_EXIT_ALLFAILED : /* all failed */ + ct_err ? ISOSIZE_EXIT_SOMEOK : /* some ok */ + EXIT_SUCCESS; /* all success */ +} diff --git a/disk-utils/minix_programs.h b/disk-utils/minix_programs.h new file mode 100644 index 0000000..7ad308d --- /dev/null +++ b/disk-utils/minix_programs.h @@ -0,0 +1,118 @@ +#ifndef UTIL_LINUX_MINIX_PROGRAMS_H +#define UTIL_LINUX_MINIX_PROGRAMS_H + +#include "minix.h" + +/* + * Global variables. + */ +extern int fs_version; +extern char *super_block_buffer; + +#define Super (*(struct minix_super_block *) super_block_buffer) +#define Super3 (*(struct minix3_super_block *) super_block_buffer) + +#define INODE_SIZE (sizeof(struct minix_inode)) +#define INODE2_SIZE (sizeof(struct minix2_inode)) + +#define BITS_PER_BLOCK (MINIX_BLOCK_SIZE << 3) + +#define UPPER(size,n) ((size+((n)-1))/(n)) + +/* + * Inline functions. + */ +static inline unsigned long get_ninodes(void) +{ + switch (fs_version) { + case 3: + return Super3.s_ninodes; + default: + return Super.s_ninodes; + } +} + +static inline unsigned long get_nzones(void) +{ + switch (fs_version) { + case 3: + return Super3.s_zones; + case 2: + return Super.s_zones; + default: + return Super.s_nzones; + } +} + +static inline unsigned long get_nimaps(void) +{ + switch (fs_version) { + case 3: + return Super3.s_imap_blocks; + default: + return Super.s_imap_blocks; + } +} + +static inline unsigned long get_nzmaps(void) +{ + switch (fs_version) { + case 3: + return Super3.s_zmap_blocks; + default: + return Super.s_zmap_blocks; + } +} + +static inline off_t get_first_zone(void) +{ + switch (fs_version) { + case 3: + return Super3.s_firstdatazone; + default: + return Super.s_firstdatazone; + } +} + +static inline size_t get_zone_size(void) +{ + switch (fs_version) { + case 3: + return Super3.s_log_zone_size; + default: + return Super.s_log_zone_size; + } +} + +static inline size_t get_max_size(void) +{ + switch (fs_version) { + case 3: + return Super3.s_max_size; + default: + return Super.s_max_size; + } +} + +static inline unsigned long inode_blocks(void) +{ + switch (fs_version) { + case 3: + case 2: + return UPPER(get_ninodes(), MINIX2_INODES_PER_BLOCK); + default: + return UPPER(get_ninodes(), MINIX_INODES_PER_BLOCK); + } +} + +static inline off_t first_zone_data(void) +{ + return 2 + get_nimaps() + get_nzmaps() + inode_blocks(); +} + +static inline size_t get_inode_buffer_size(void) +{ + return inode_blocks() * MINIX_BLOCK_SIZE; +} + +#endif /* UTIL_LINUX_MINIX_PROGRAMS_H */ diff --git a/disk-utils/mkfs.8 b/disk-utils/mkfs.8 new file mode 100644 index 0000000..e3a7f8b --- /dev/null +++ b/disk-utils/mkfs.8 @@ -0,0 +1,94 @@ +.TH MKFS 8 "June 2011" "util-linux" "System Administration" +.SH NAME +mkfs \- build a Linux filesystem +.SH SYNOPSIS +.B mkfs +[options] +.RB [ \-t +.IR type "] [" fs-options ] " device " [ size ] +.SH DESCRIPTION +.B This mkfs frontend is deprecated in favour of filesystem specific mkfs.<type> utils. +.PP +.B mkfs +is used to build a Linux filesystem on a device, usually +a hard disk partition. The +.I device +argument is either the device name (e.g. +.IR /dev/hda1 , +.IR /dev/sdb2 ), +or a regular file that shall contain the filesystem. The +.I size +argument is the number of blocks to be used for the filesystem. +.PP +The exit code returned by +.B mkfs +is 0 on success and 1 on failure. +.PP +In actuality, +.B mkfs +is simply a front-end for the various filesystem builders +(\fBmkfs.\fIfstype\fR) +available under Linux. +The filesystem-specific builder is searched for via your PATH +environment setting only. +Please see the filesystem-specific builder manual pages for +further details. +.SH OPTIONS +.TP +.BR \-t , " \-\-type " \fItype\fR +Specify the \fItype\fR of filesystem to be built. +If not specified, the default filesystem type +(currently ext2) is used. +.TP +.I fs-options +Filesystem-specific options to be passed to the real filesystem builder. +.TP +.BR \-V , " \-\-verbose" +Produce verbose output, including all filesystem-specific commands +that are executed. +Specifying this option more than once inhibits execution of any +filesystem-specific commands. +This is really only useful for testing. +.TP +.BR \-V , " \-\-version" +Display version information and exit. (Option \fB\-V\fR will display +version information only when it is the only parameter, otherwise it +will work as \fB\-\-verbose\fR.) +.TP +.BR \-h , " \-\-help" +Display help text and exit. +.SH BUGS +All generic options must precede and not be combined with +filesystem-specific options. +Some filesystem-specific programs do not automatically +detect the device size and require the +.I size +parameter to be specified. +.SH AUTHORS +David Engel (david@ods.com) +.br +Fred N.\& van Kempen (waltje@uwalt.nl.mugnet.org) +.br +Ron Sommeling (sommel@sci.kun.nl) +.br +The manual page was shamelessly adapted from Remy Card's version +for the ext2 filesystem. +.SH SEE ALSO +.na +.BR fs (5), +.BR badblocks (8), +.BR fsck (8), +.BR mkdosfs (8), +.BR mke2fs (8), +.BR mkfs.bfs (8), +.BR mkfs.ext2 (8), +.BR mkfs.ext3 (8), +.BR mkfs.ext4 (8), +.BR mkfs.minix (8), +.BR mkfs.msdos (8), +.BR mkfs.vfat (8), +.BR mkfs.xfs (8) +.ad +.SH AVAILABILITY +The mkfs command is part of the util-linux package and is available from +https://www.kernel.org/pub/linux/utils/util-linux/. diff --git a/disk-utils/mkfs.bfs.8 b/disk-utils/mkfs.bfs.8 new file mode 100644 index 0000000..f32202a --- /dev/null +++ b/disk-utils/mkfs.bfs.8 @@ -0,0 +1,58 @@ +.\" Copyright 1999 Andries E. Brouwer (aeb@cwi.nl) +.\" May be freely distributed. +.TH MKFS.BFS 8 "July 2011" "util-linux" "System Administration" +.SH NAME +mkfs.bfs \- make an SCO bfs filesystem +.SH SYNOPSIS +.B mkfs.bfs +.RI [options] " device " [ block-count ] +.SH DESCRIPTION +.B mkfs.bfs +creates an SCO bfs filesystem on a block device +(usually a disk partition or a file accessed via the loop device). +.PP +The +.I block-count +parameter is the desired size of the filesystem, in blocks. +If nothing is specified, the entire partition will be used. +.SH OPTIONS +.TP +.BR \-N , " \-\-inodes " \fInumber\fR +Specify the desired \fInumber\fR of inodes (at most 512). +If nothing is specified, some default number in the range 48\(en512 is picked +depending on the size of the partition. +.TP +.BR \-V , " \-\-vname " \fIlabel\fR +Specify the volume \fIlabel\fR. I have no idea if/where this is used. +.TP +.BR \-F , " \-\-fname " \fIname\fR +Specify the filesystem \fIname\fR. I have no idea if/where this is used. +.TP +.BR \-v , " \-\-verbose" +Explain what is being done. +.TP +.B \-c +This option is silently ignored. +.TP +.B \-l +This option is silently ignored. +.TP +.BR \-h , " \-\-help" +Display help text and exit. +.TP +.BR \-V , " \-\-version" +Display version information and exit. +Option +.B \-V +only works as +.B \-\-version +when it is the only option. +.SH "EXIT CODES" +The exit code returned by +.B mkfs.bfs +is 0 when all went well, and 1 when something went wrong. +.SH "SEE ALSO" +.BR mkfs (8) +.SH AVAILABILITY +The mkfs.bfs command is part of the util-linux package and is available from +https://www.kernel.org/pub/linux/utils/util-linux/. diff --git a/disk-utils/mkfs.bfs.c b/disk-utils/mkfs.bfs.c new file mode 100644 index 0000000..5bf8fdf --- /dev/null +++ b/disk-utils/mkfs.bfs.c @@ -0,0 +1,307 @@ +/* + * mkfs.bfs - Create SCO BFS filesystem - aeb, 1999-09-07 + * + * Usage: mkfs.bfs [-N nr-of-inodes] [-V volume-name] [-F fsname] device + */ + +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <time.h> +#include <unistd.h> + +#include "blkdev.h" +#include "c.h" +#include "closestream.h" +#include "nls.h" +#include "strutils.h" +#include "xalloc.h" +#include "bitops.h" + +#define BFS_ROOT_INO 2 +#define BFS_NAMELEN 14 +#define BFS_BLOCKSIZE 512 +#define BFS_SUPER_MAGIC 0x1badface + +/* superblock - 512 bytes */ +struct bfssb { + uint32_t s_magic; + uint32_t s_start; /* byte offset of start of data */ + uint32_t s_end; /* sizeof(slice)-1 */ + + /* for recovery during compaction */ + uint32_t s_from, s_to; /* src and dest block of current transfer */ + int32_t s_backup_from, s_backup_to; + + /* labels - may well contain garbage */ + char s_fsname[6]; + char s_volume[6]; + char s_pad[472]; +}; + +/* inode - 64 bytes */ +struct bfsi { + uint16_t i_ino; + unsigned char i_pad1[2]; + uint32_t i_first_block; + uint32_t i_last_block; + uint32_t i_bytes_to_end; + uint32_t i_type; /* 1: file, 2: the unique dir */ + uint32_t i_mode; + uint32_t i_uid, i_gid; + uint32_t i_nlinks; + uint32_t i_atime, i_mtime, i_ctime; + unsigned char i_pad2[16]; +}; + +#define BFS_DIR_TYPE 2 + +/* directory entry - 16 bytes */ +struct bfsde { + uint16_t d_ino; + char d_name[BFS_NAMELEN]; +}; + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *out = stdout; + fprintf(out, + _("Usage: %s [options] device [block-count]\n"), + program_invocation_short_name); + + fputs(USAGE_SEPARATOR, out); + fputs(_("Make an SCO bfs filesystem.\n"), out); + + fprintf(out, _("\nOptions:\n" + " -N, --inodes=NUM specify desired number of inodes\n" + " -V, --vname=NAME specify volume name\n" + " -F, --fname=NAME specify file system name\n" + " -v, --verbose explain what is being done\n" + " -c this option is silently ignored\n" + " -l this option is silently ignored\n" + )); + printf(USAGE_HELP_OPTIONS(21)); + + printf(USAGE_MAN_TAIL("mkfs.bfs(8)")); + exit(EXIT_SUCCESS); +} + +static void __attribute__ ((__noreturn__)) print_version(void) +{ + printf(UTIL_LINUX_VERSION); + exit(EXIT_SUCCESS); +} + +int main(int argc, char **argv) +{ + char *device, *volume, *fsname; + long inodes; + unsigned long long total_blocks, ino_bytes, ino_blocks, data_blocks; + unsigned long long user_specified_total_blocks = 0; + int verbose = 0; + int fd; + uint32_t first_block; + struct bfssb sb; + struct bfsi ri; + struct bfsde de; + struct stat statbuf; + time_t now; + int c, i, len; + + enum { VERSION_OPTION = CHAR_MAX + 1 }; + static const struct option longopts[] = { + {"inodes", required_argument, NULL, 'N'}, + {"vname", required_argument, NULL, 'V'}, + {"fname", required_argument, NULL, 'F'}, + {"verbose", no_argument, NULL, 'v'}, + {"version", no_argument, NULL, VERSION_OPTION}, + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + if (argc < 2) { + warnx(_("not enough arguments")); + errtryhelp(EXIT_FAILURE); + } + if (argc == 2 && !strcmp(argv[1], "-V")) + print_version(); + + volume = fsname = " "; /* is there a default? */ + inodes = 0; + + while ((c = getopt_long(argc, argv, "N:V:F:vhcl", longopts, NULL)) != -1) { + switch (c) { + case 'N': + inodes = strtol_or_err(optarg, _("invalid number of inodes")); + break; + + case 'V': + len = strlen(optarg); + if (len <= 0 || len > 6) + errx(EXIT_FAILURE, _("volume name too long")); + volume = xstrdup(optarg); + break; + + case 'F': + len = strlen(optarg); + if (len <= 0 || len > 6) + errx(EXIT_FAILURE, _("fsname name too long")); + fsname = xstrdup(optarg); + break; + + case 'v': + verbose = 1; + break; + + case 'c': + case 'l': + /* when called via mkfs we may get options c,l,v */ + break; + + case VERSION_OPTION: + print_version(); + case 'h': + usage(); + default: + errtryhelp(EXIT_FAILURE); + } + } + + if (optind == argc) { + warnx(_("no device specified")); + errtryhelp(EXIT_FAILURE); + } + + device = argv[optind++]; + + if (stat(device, &statbuf) < 0) + err(EXIT_FAILURE, _("stat of %s failed"), device); + + fd = open_blkdev_or_file(&statbuf, device, O_RDWR); + if (fd < 0) + err(EXIT_FAILURE, _("cannot open %s"), device); + + if (optind == argc - 1) + user_specified_total_blocks = + strtou64_or_err(argv[optind], _("invalid block-count")); + else if (optind != argc) { + warnx(_("bad usage")); + errtryhelp(EXIT_FAILURE); + } + + if (blkdev_get_sectors(fd, &total_blocks) == -1) { + if (!user_specified_total_blocks) + err(EXIT_FAILURE, _("cannot get size of %s"), device); + total_blocks = user_specified_total_blocks; + } else if (user_specified_total_blocks) { + if (user_specified_total_blocks > total_blocks) + errx(EXIT_FAILURE, + _("blocks argument too large, max is %llu"), + total_blocks); + total_blocks = user_specified_total_blocks; + } + + if (!inodes) { + /* pick some reasonable default */ + inodes = 8 * (total_blocks / 800); + if (inodes < 48) + inodes = 48; + if (512 < inodes) + inodes = 512; + } else { + /* believe the user */ + if (512 < inodes) + errx(EXIT_FAILURE, _("too many inodes - max is 512")); + } + + ino_bytes = inodes * sizeof(struct bfsi); + ino_blocks = (ino_bytes + BFS_BLOCKSIZE - 1) / BFS_BLOCKSIZE; + data_blocks = total_blocks - ino_blocks - 1; + + /* mimic the behavior of SCO's mkfs - maybe this limit is needed */ + if (data_blocks < 32) + errx(EXIT_FAILURE, + _("not enough space, need at least %llu blocks"), + ino_blocks + 33); + + memset(&sb, 0, sizeof(sb)); + sb.s_magic = cpu_to_le32(BFS_SUPER_MAGIC); + sb.s_start = cpu_to_le32(ino_bytes + sizeof(struct bfssb)); + sb.s_end = cpu_to_le32(total_blocks * BFS_BLOCKSIZE - 1); + sb.s_from = sb.s_to = sb.s_backup_from = sb.s_backup_to = -1; + memcpy(sb.s_fsname, fsname, 6); + memcpy(sb.s_volume, volume, 6); + + if (verbose) { + fprintf(stderr, _("Device: %s\n"), device); + fprintf(stderr, _("Volume: <%-6s>\n"), volume); + fprintf(stderr, _("FSname: <%-6s>\n"), fsname); + fprintf(stderr, _("BlockSize: %d\n"), BFS_BLOCKSIZE); + if (ino_blocks == 1) + fprintf(stderr, _("Inodes: %ld (in 1 block)\n"), + inodes); + else + fprintf(stderr, _("Inodes: %ld (in %llu blocks)\n"), + inodes, ino_blocks); + fprintf(stderr, _("Blocks: %llu\n"), total_blocks); + fprintf(stderr, _("Inode end: %d, Data end: %d\n"), + le32_to_cpu(sb.s_start) - 1, le32_to_cpu(sb.s_end)); + } + + if (write(fd, &sb, sizeof(sb)) != sizeof(sb)) + err(EXIT_FAILURE, _("error writing superblock")); + + memset(&ri, 0, sizeof(ri)); + ri.i_ino = cpu_to_le16(BFS_ROOT_INO); + first_block = 1 + ino_blocks; + ri.i_first_block = cpu_to_le32(first_block); + ri.i_last_block = cpu_to_le32(first_block + + (inodes * sizeof(de) - 1) / BFS_BLOCKSIZE); + ri.i_bytes_to_end = cpu_to_le32(first_block * BFS_BLOCKSIZE + + 2 * sizeof(struct bfsde) - 1); + ri.i_type = cpu_to_le32(BFS_DIR_TYPE); + ri.i_mode = cpu_to_le32(S_IFDIR | 0755); /* or just 0755 */ + ri.i_uid = cpu_to_le32(0); + ri.i_gid = cpu_to_le32(1); /* random */ + ri.i_nlinks = 2; + time(&now); + ri.i_atime = cpu_to_le32(now); + ri.i_mtime = cpu_to_le32(now); + ri.i_ctime = cpu_to_le32(now); + + if (write(fd, &ri, sizeof(ri)) != sizeof(ri)) + err(EXIT_FAILURE, _("error writing root inode")); + + memset(&ri, 0, sizeof(ri)); + for (i = 1; i < inodes; i++) + if (write(fd, &ri, sizeof(ri)) != sizeof(ri)) + err(EXIT_FAILURE, _("error writing inode")); + + if (lseek(fd, (1 + ino_blocks) * BFS_BLOCKSIZE, SEEK_SET) == -1) + err(EXIT_FAILURE, _("seek error")); + + memset(&de, 0, sizeof(de)); + de.d_ino = cpu_to_le16(BFS_ROOT_INO); + memcpy(de.d_name, ".", 1); + if (write(fd, &de, sizeof(de)) != sizeof(de)) + err(EXIT_FAILURE, _("error writing . entry")); + + memcpy(de.d_name, "..", 2); + if (write(fd, &de, sizeof(de)) != sizeof(de)) + err(EXIT_FAILURE, _("error writing .. entry")); + + if (close_fd(fd) != 0) + err(EXIT_FAILURE, _("error closing %s"), device); + + return EXIT_SUCCESS; +} diff --git a/disk-utils/mkfs.c b/disk-utils/mkfs.c new file mode 100644 index 0000000..5bcd961 --- /dev/null +++ b/disk-utils/mkfs.c @@ -0,0 +1,140 @@ +/* + * mkfs A simple generic frontend for the for the mkfs program + * under Linux. See the manual page for details. + * + * Authors: David Engel, <david@ods.com> + * Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG> + * Ron Sommeling, <sommel@sci.kun.nl> + * + * Mon Jul 1 18:52:58 1996: janl@math.uio.no (Nicolai Langfeldt): + * Incorporated fix by Jonathan Kamens <jik@annex-1-slip-jik.cam.ov.com> + * 1999-02-22 Arkadiusz MiÅ›kiewicz <misiek@pld.ORG.PL> + * - added Native Language Support + * + */ + +/* + * This command is deprecated. The utility is in maintenance mode, + * meaning we keep them in source tree for backward compatibility + * only. Do not waste time making this command better, unless the + * fix is about security or other very critical issue. + * + * See Documentation/deprecated.txt for more information. + */ + +#include <getopt.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "c.h" +#include "closestream.h" +#include "nls.h" +#include "xalloc.h" + +#ifndef DEFAULT_FSTYPE +#define DEFAULT_FSTYPE "ext2" +#endif + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *out = stdout; + fputs(USAGE_HEADER, out); + fprintf(out, _(" %s [options] [-t <type>] [fs-options] <device> [<size>]\n"), + program_invocation_short_name); + + fputs(USAGE_SEPARATOR, out); + fputs(_("Make a Linux filesystem.\n"), out); + + fputs(USAGE_OPTIONS, out); + fprintf(out, _(" -t, --type=<type> filesystem type; when unspecified, ext2 is used\n")); + fprintf(out, _(" fs-options parameters for the real filesystem builder\n")); + fprintf(out, _(" <device> path to the device to be used\n")); + fprintf(out, _(" <size> number of blocks to be used on the device\n")); + fprintf(out, _(" -V, --verbose explain what is being done;\n" + " specifying -V more than once will cause a dry-run\n")); + printf(USAGE_HELP_OPTIONS(20)); + + printf(USAGE_MAN_TAIL("mkfs(8)")); + exit(EXIT_SUCCESS); +} + +static void __attribute__ ((__noreturn__)) print_version(void) +{ + printf(UTIL_LINUX_VERSION); + exit(EXIT_SUCCESS); +} + +int main(int argc, char **argv) +{ + char *progname; /* name of executable to be called */ + char *fstype = NULL; + int i, more = 0, verbose = 0; + + enum { VERSION_OPTION = CHAR_MAX + 1 }; + + static const struct option longopts[] = { + {"type", required_argument, NULL, 't'}, + {"version", no_argument, NULL, VERSION_OPTION}, + {"verbose", no_argument, NULL, 'V'}, + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + if (argc == 2 && !strcmp(argv[1], "-V")) + print_version(); + + /* Check commandline options. */ + opterr = 0; + while ((more == 0) + && ((i = getopt_long(argc, argv, "Vt:h", longopts, NULL)) + != -1)) + switch (i) { + case 'V': + verbose++; + break; + case 't': + fstype = optarg; + break; + case 'h': + usage(); + case VERSION_OPTION: + print_version(); + default: + optind--; + more = 1; + break; /* start of specific arguments */ + } + if (optind == argc) { + warnx(_("no device specified")); + errtryhelp(EXIT_FAILURE); + } + + /* If -t wasn't specified, use the default */ + if (fstype == NULL) + fstype = DEFAULT_FSTYPE; + + xasprintf(&progname, "mkfs.%s", fstype); + argv[--optind] = progname; + + if (verbose) { + printf(UTIL_LINUX_VERSION); + i = optind; + while (argv[i]) + printf("%s ", argv[i++]); + printf("\n"); + if (verbose > 1) + return EXIT_SUCCESS; + } + + /* Execute the program */ + execvp(progname, argv + optind); + err(EXIT_FAILURE, _("failed to execute %s"), progname); +} diff --git a/disk-utils/mkfs.cramfs.8 b/disk-utils/mkfs.cramfs.8 new file mode 100644 index 0000000..31cf84b --- /dev/null +++ b/disk-utils/mkfs.cramfs.8 @@ -0,0 +1,90 @@ +.TH MKFS.CRAMFS 8 "April 2013" "util-linux" "System Administration" +.SH NAME +mkfs.cramfs \- make compressed ROM file system +.SH SYNOPSIS +.B mkfs.cramfs +[options] +.I directory file +.SH DESCRIPTION +Files on cramfs file systems are zlib-compressed one page at a time to +allow random read access. The metadata is not compressed, but is +expressed in a terse representation that is more space-efficient than +conventional file systems. +.PP +The file system is intentionally read-only to simplify its design; random +write access for compressed files is difficult to implement. cramfs +ships with a utility (mkcramfs) to pack files into new cramfs images. +.PP +File sizes are limited to less than 16\ MB. +.PP +Maximum file system size is a little under 272\ MB. (The last file on the +file system must begin before the 256\ MB block, but can extend past it.) +.SH ARGUMENTS +The +.I directory +is simply the root of the directory tree that we want to generate a +compressed filesystem out of. +.PP +The +.I file +will contain the cram file system, which later can be mounted. +.SH OPTIONS +.TP +\fB\-v\fR +Enable verbose messaging. +.TP +\fB\-E\fR +Treat all warnings as errors, which are reflected as command return value. +.TP +\fB\-b\fR \fIblocksize\fR +Use defined block size, which has to be divisible by page size. +.TP +\fB\-e\fR \fIedition\fR +Use defined file system edition number in superblock. +.TP +\fB\-N\fR \fIbig, little, host\fR +Use defined endianness. Value defaults to +.IR host . +.TP +\fB\-i\fR \fIfile\fR +Insert a +.I file +to cramfs file system. +.TP +\fB\-n\fR \fIname\fR +Set name of the cramfs file system. +.TP +\fB\-p\fR +Pad by 512 bytes for boot code. +.TP +\fB\-s\fR +This option is ignored. Originally the \-s turned on directory entry +sorting. +.TP +\fB\-z\fR +Make explicit holes. +.TP +\fB\-h\fR, \fB\-\-help\fR +Display help text and exit. +.TP +\fB\-V\fR, \fB\-\-version\fR +Display version information and exit. +.SH "EXIT STATUS" +.RS +.PD 0 +.TP +.B 0 +success +.TP +.B 8 +operation error, such as unable to allocate memory +.PD +.RE +.SH "SEE ALSO" +.BR fsck.cramfs (8), +.BR mount (8) +.SH AVAILABILITY +The example command is part of the util-linux package and is available from +.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/ +Linux Kernel Archive +.UE . diff --git a/disk-utils/mkfs.cramfs.c b/disk-utils/mkfs.cramfs.c new file mode 100644 index 0000000..bf07f8c --- /dev/null +++ b/disk-utils/mkfs.cramfs.c @@ -0,0 +1,926 @@ +/* + * mkcramfs - make a cramfs file system + * + * Copyright (C) 1999-2002 Transmeta Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Old version would die on largish filesystems. Change to mmap the + * files one by one instead of all simultaneously. - aeb, 2002-11-01 + */ + +#include <sys/types.h> +#include <stdio.h> +#include <sys/stat.h> +#include <unistd.h> +#include <sys/mman.h> +#include <fcntl.h> +#include <dirent.h> +#include <stddef.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> +#include <getopt.h> +#include <zconf.h> + +/* We don't use our include/crc32.h, but crc32 from zlib! + * + * The zlib implementation performs pre/post-conditioning. The util-linux + * imlemenation requires post-conditioning (xor) in the applications. + */ +#include <zlib.h> + +#include "c.h" +#include "cramfs.h" +#include "md5.h" +#include "nls.h" +#include "exitcodes.h" +#include "strutils.h" + +#define CLOSE_EXIT_CODE MKFS_EX_ERROR +#include "closestream.h" + +#define XALLOC_EXIT_CODE MKFS_EX_ERROR +#include "xalloc.h" + +/* The kernel only supports PAD_SIZE of 0 and 512. */ +#define PAD_SIZE 512 + +static int verbose = 0; + +static unsigned int blksize = 0; /* settable via -b option, default page size */ +static long total_blocks = 0, total_nodes = 1; /* pre-count the root node */ +static int image_length = 0; +static int cramfs_is_big_endian = 0; /* target is big endian */ + +/* + * If opt_holes is set, then mkcramfs can create explicit holes in the + * data, which saves 26 bytes per hole (which is a lot smaller a + * saving than for most filesystems). + * + * Note that kernels up to at least 2.3.39 don't support cramfs holes, + * which is why this is turned off by default. + */ +static unsigned int opt_edition = 0; +static int opt_errors = 0; +static int opt_holes = 0; +static int opt_pad = 0; +static char *opt_image = NULL; +static char *opt_name = NULL; + +static int warn_dev = 0; +static int warn_gid = 0; +static int warn_namelen = 0; +static int warn_skip = 0; +static int warn_size = 0; +static int warn_uid = 0; + +/* entry.flags */ +#define CRAMFS_EFLAG_MD5 1 +#define CRAMFS_EFLAG_INVALID 2 + +/* In-core version of inode / directory entry. */ +struct entry { + /* stats */ + unsigned char *name; + unsigned int mode, size, uid, gid; + unsigned char md5sum[UL_MD5LENGTH]; + unsigned char flags; /* CRAMFS_EFLAG_* */ + + /* FS data */ + char *path; + int fd; /* temporarily open files while mmapped */ + struct entry *same; /* points to other identical file */ + unsigned int offset; /* pointer to compressed data in archive */ + unsigned int dir_offset; /* offset of directory entry in archive */ + + /* organization */ + struct entry *child; /* NULL for non-directory and empty dir */ + struct entry *next; +}; + +/* + * Width of various bitfields in struct cramfs_inode. + * Used only to generate warnings. + */ +#define CRAMFS_SIZE_WIDTH 24 +#define CRAMFS_UID_WIDTH 16 +#define CRAMFS_GID_WIDTH 8 +#define CRAMFS_OFFSET_WIDTH 26 + +static void __attribute__((__noreturn__)) usage(void) +{ + printf( + _("usage: %s [-h] [-v] [-b blksize] [-e edition] [-N endian] [-i file] " + "[-n name] dirname outfile\n" + " -v be verbose\n" + " -E make all warnings errors " + "(non-zero exit status)\n" + " -b blksize use this blocksize, must equal page size\n" + " -e edition set edition number (part of fsid)\n" + " -N endian set cramfs endianness (big|little|host), default host\n" + " -i file insert a file image into the filesystem\n" + " -n name set name of cramfs filesystem\n" + " -p pad by %d bytes for boot code\n" + " -s sort directory entries (old option, ignored)\n" + " -z make explicit holes\n" + " dirname root of the filesystem to be compressed\n" + " outfile output file\n"), + program_invocation_short_name, PAD_SIZE); + + fputs(USAGE_SEPARATOR, stdout); + printf(USAGE_HELP_OPTIONS(16)); + printf(USAGE_MAN_TAIL("mkfs.cramfs(8)")); + exit(MKFS_EX_OK); +} + +static char * +do_mmap(char *path, unsigned int size, unsigned int mode){ + int fd; + char *start = NULL; + + if (!size) + return NULL; + + if (S_ISLNK(mode)) { + start = xmalloc(size); + if (readlink(path, start, size) < 0) { + warn(_("readlink failed: %s"), path); + warn_skip = 1; + goto err; + } + return start; + } + + fd = open(path, O_RDONLY); + if (fd < 0) { + warn(_("cannot open %s"), path); + warn_skip = 1; + goto err; + } + + start = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); + close(fd); + if (start == MAP_FAILED) + err(MKFS_EX_ERROR, "mmap"); + return start; +err: + free(start); + return NULL; +} + +static void +do_munmap(char *start, unsigned int size, unsigned int mode){ + if (S_ISLNK(mode)) + free(start); + else + munmap(start, size); +} + +/* compute md5sums, so that we do not have to compare every pair of files */ +static void +mdfile(struct entry *e) { + char *start; + + start = do_mmap(e->path, e->size, e->mode); + if (start == NULL) { + e->flags |= CRAMFS_EFLAG_INVALID; + } else { + UL_MD5_CTX ctx; + + ul_MD5Init(&ctx); + ul_MD5Update(&ctx, (unsigned char *) start, e->size); + ul_MD5Final(e->md5sum, &ctx); + + do_munmap(start, e->size, e->mode); + + e->flags |= CRAMFS_EFLAG_MD5; + } +} + +/* md5 digests are equal; files are almost certainly the same, + but just to be sure, do the comparison */ +static int +identical_file(struct entry *e1, struct entry *e2){ + char *start1, *start2; + int equal; + + start1 = do_mmap(e1->path, e1->size, e1->mode); + if (!start1) + return 0; + start2 = do_mmap(e2->path, e2->size, e2->mode); + if (!start2) { + do_munmap(start1, e1->size, e1->mode); + return 0; + } + equal = !memcmp(start1, start2, e1->size); + do_munmap(start1, e1->size, e1->mode); + do_munmap(start2, e2->size, e2->mode); + return equal; +} + +/* + * The longest file name component to allow for in the input directory tree. + * Ext2fs (and many others) allow up to 255 bytes. A couple of filesystems + * allow longer (e.g. smbfs 1024), but there isn't much use in supporting + * >255-byte names in the input directory tree given that such names get + * truncated to 255 bytes when written to cramfs. + */ +#define MAX_INPUT_NAMELEN 255 + +static int find_identical_file(struct entry *orig, struct entry *new, loff_t *fslen_ub) +{ + if (orig == new) + return 1; + if (!orig) + return 0; + if (orig->size == new->size && orig->path) { + if (!orig->flags) + mdfile(orig); + if (!new->flags) + mdfile(new); + + if ((orig->flags & CRAMFS_EFLAG_MD5) && + (new->flags & CRAMFS_EFLAG_MD5) && + !memcmp(orig->md5sum, new->md5sum, UL_MD5LENGTH) && + identical_file(orig, new)) { + new->same = orig; + *fslen_ub -= new->size; + return 1; + } + } + return find_identical_file(orig->child, new, fslen_ub) || + find_identical_file(orig->next, new, fslen_ub); +} + +static void eliminate_doubles(struct entry *root, struct entry *orig, loff_t *fslen_ub) { + if (orig) { + if (orig->size && orig->path) + find_identical_file(root,orig, fslen_ub); + eliminate_doubles(root,orig->child, fslen_ub); + eliminate_doubles(root,orig->next, fslen_ub); + } +} + +/* + * We define our own sorting function instead of using alphasort which + * uses strcoll and changes ordering based on locale information. + */ +static int cramsort (const struct dirent **a, const struct dirent **b) +{ + return strcmp((*a)->d_name, (*b)->d_name); +} + +static unsigned int parse_directory(struct entry *root_entry, const char *name, struct entry **prev, loff_t *fslen_ub) +{ + struct dirent **dirlist; + int totalsize = 0, dircount, dirindex; + char *path, *endpath; + size_t len = strlen(name); + + /* Set up the path. */ + /* TODO: Reuse the parent's buffer to save memcpy'ing and duplication. */ + path = xmalloc(len + 1 + MAX_INPUT_NAMELEN + 1); + memcpy(path, name, len); + endpath = path + len; + *endpath = '/'; + endpath++; + + /* read in the directory and sort */ + dircount = scandir(name, &dirlist, NULL, cramsort); + + if (dircount < 0) + err(MKFS_EX_ERROR, _("could not read directory %s"), name); + + /* process directory */ + for (dirindex = 0; dirindex < dircount; dirindex++) { + struct dirent *dirent; + struct entry *entry; + struct stat st; + int size; + size_t namelen; + + dirent = dirlist[dirindex]; + + /* Ignore "." and ".." - we won't be adding them + to the archive */ + if (dirent->d_name[0] == '.') { + if (dirent->d_name[1] == '\0') + continue; + if (dirent->d_name[1] == '.' && + dirent->d_name[2] == '\0') + continue; + } + namelen = strlen(dirent->d_name); + if (namelen > MAX_INPUT_NAMELEN) { + namelen = MAX_INPUT_NAMELEN; + warn_namelen = 1; + } + + memcpy(endpath, dirent->d_name, namelen + 1); + + if (lstat(path, &st) < 0) { + warn(_("stat of %s failed"), endpath); + warn_skip = 1; + continue; + } + entry = xcalloc(1, sizeof(struct entry)); + entry->name = (unsigned char *)xstrndup(dirent->d_name, namelen); + entry->mode = st.st_mode; + entry->size = st.st_size; + entry->uid = st.st_uid; + if (entry->uid >= 1 << CRAMFS_UID_WIDTH) + warn_uid = 1; + entry->gid = st.st_gid; + if (entry->gid >= 1 << CRAMFS_GID_WIDTH) + /* TODO: We ought to replace with a default + gid instead of truncating; otherwise there + are security problems. Maybe mode should + be &= ~070. Same goes for uid once Linux + supports >16-bit uids. */ + warn_gid = 1; + size = sizeof(struct cramfs_inode) + ((namelen + 3) & ~3); + *fslen_ub += size; + if (S_ISDIR(st.st_mode)) { + entry->size = parse_directory(root_entry, path, &entry->child, fslen_ub); + } else if (S_ISREG(st.st_mode)) { + entry->path = xstrdup(path); + if (entry->size >= (1 << CRAMFS_SIZE_WIDTH)) { + warn_size = 1; + entry->size = (1 << CRAMFS_SIZE_WIDTH) - 1; + } + } else if (S_ISLNK(st.st_mode)) { + entry->path = xstrdup(path); + } else if (S_ISFIFO(st.st_mode) || S_ISSOCK(st.st_mode)) { + /* maybe we should skip sockets */ + entry->size = 0; + } else { + entry->size = st.st_rdev; + if (entry->size & -(1<<CRAMFS_SIZE_WIDTH)) + warn_dev = 1; + } + + if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) { + int blocks = ((entry->size - 1) / blksize + 1); + + /* block pointers & data expansion allowance + data */ + if (entry->size) + *fslen_ub += (4+26)*blocks + entry->size + 3; + } + + /* Link it into the list */ + *prev = entry; + prev = &entry->next; + totalsize += size; + } + free(path); + free(dirlist); /* allocated by scandir() with malloc() */ + return totalsize; +} + +/* Returns sizeof(struct cramfs_super), which includes the root inode. */ +static unsigned int write_superblock(struct entry *root, char *base, int size) +{ + struct cramfs_super *super = (struct cramfs_super *) base; + unsigned int offset = sizeof(struct cramfs_super) + image_length; + + if (opt_pad) { + offset += opt_pad; + } + + super->magic = CRAMFS_MAGIC; + super->flags = CRAMFS_FLAG_FSID_VERSION_2 | CRAMFS_FLAG_SORTED_DIRS; + if (opt_holes) + super->flags |= CRAMFS_FLAG_HOLES; + if (image_length > 0) + super->flags |= CRAMFS_FLAG_SHIFTED_ROOT_OFFSET; + super->size = size; + memcpy(super->signature, CRAMFS_SIGNATURE, sizeof(super->signature)); + + super->fsid.crc = crc32(0L, NULL, 0); + super->fsid.edition = opt_edition; + super->fsid.blocks = total_blocks; + super->fsid.files = total_nodes; + + memset(super->name, 0x00, sizeof(super->name)); + if (opt_name) + str2memcpy((char *)super->name, opt_name, sizeof(super->name)); + else + str2memcpy((char *)super->name, "Compressed", sizeof(super->name)); + + super->root.mode = root->mode; + super->root.uid = root->uid; + super->root.gid = root->gid; + super->root.size = root->size; + super->root.offset = offset >> 2; + + super_toggle_endianness(cramfs_is_big_endian, super); + inode_from_host(cramfs_is_big_endian, &super->root, &super->root); + + return offset; +} + +static void set_data_offset(struct entry *entry, char *base, unsigned long offset) +{ + struct cramfs_inode *inode = (struct cramfs_inode *) (base + entry->dir_offset); + inode_to_host(cramfs_is_big_endian, inode, inode); + if (offset >= (1 << (2 + CRAMFS_OFFSET_WIDTH))) + errx(MKFS_EX_ERROR, _("filesystem too big. Exiting.")); + inode->offset = (offset >> 2); + inode_from_host(cramfs_is_big_endian, inode, inode); +} + + +/* + * We do a width-first printout of the directory + * entries, using a stack to remember the directories + * we've seen. + */ +static unsigned int write_directory_structure(struct entry *entry, char *base, unsigned int offset) +{ + int stack_entries = 0; + int stack_size = 64; + struct entry **entry_stack; + + entry_stack = xmalloc(stack_size * sizeof(struct entry *)); + + for (;;) { + int dir_start = stack_entries; + while (entry) { + struct cramfs_inode *inode = + (struct cramfs_inode *) (base + offset); + size_t len = strlen((const char *)entry->name); + + entry->dir_offset = offset; + + inode->mode = entry->mode; + inode->uid = entry->uid; + inode->gid = entry->gid; + inode->size = entry->size; + inode->offset = 0; + /* Non-empty directories, regfiles and symlinks will + write over inode->offset later. */ + + offset += sizeof(struct cramfs_inode); + total_nodes++; /* another node */ + memcpy(base + offset, entry->name, len); + /* Pad up the name to a 4-byte boundary */ + while (len & 3) { + *(base + offset + len) = '\0'; + len++; + } + inode->namelen = len >> 2; + offset += len; + + if (verbose) + printf(" %s\n", entry->name); + if (entry->child) { + if (stack_entries >= stack_size) { + stack_size *= 2; + entry_stack = xrealloc(entry_stack, stack_size * sizeof(struct entry *)); + } + entry_stack[stack_entries] = entry; + stack_entries++; + } + inode_from_host(cramfs_is_big_endian, inode, inode); + entry = entry->next; + } + + /* + * Reverse the order the stack entries pushed during + * this directory, for a small optimization of disk + * access in the created fs. This change makes things + * `ls -UR' order. + */ + { + struct entry **lo = entry_stack + dir_start; + struct entry **hi = entry_stack + stack_entries; + struct entry *tmp; + + while (lo < --hi) { + tmp = *lo; + *lo++ = *hi; + *hi = tmp; + } + } + + /* Pop a subdirectory entry from the stack, and recurse. */ + if (!stack_entries) + break; + stack_entries--; + entry = entry_stack[stack_entries]; + + set_data_offset(entry, base, offset); + if (verbose) + printf("'%s':\n", entry->name); + entry = entry->child; + } + free(entry_stack); + return offset; +} + +static int is_zero(unsigned char const *begin, unsigned len) +{ + if (opt_holes) + /* Returns non-zero iff the first LEN bytes from BEGIN are + all NULs. */ + return (len-- == 0 || + (begin[0] == '\0' && + (len-- == 0 || + (begin[1] == '\0' && + (len-- == 0 || + (begin[2] == '\0' && + (len-- == 0 || + (begin[3] == '\0' && + memcmp(begin, begin + 4, len) == 0)))))))); + else + /* Never create holes. */ + return 0; +} + +/* + * One 4-byte pointer per block and then the actual blocked + * output. The first block does not need an offset pointer, + * as it will start immediately after the pointer block; + * so the i'th pointer points to the end of the i'th block + * (i.e. the start of the (i+1)'th block or past EOF). + * + * Note that size > 0, as a zero-sized file wouldn't ever + * have gotten here in the first place. + */ +static unsigned int +do_compress(char *base, unsigned int offset, unsigned char const *name, + char *path, unsigned int size, unsigned int mode) +{ + unsigned long original_size, original_offset, new_size, blocks, curr; + long change; + char *start; + Bytef *p; + + /* get uncompressed data */ + start = do_mmap(path, size, mode); + if (start == NULL) + return offset; + p = (Bytef *) start; + + original_size = size; + original_offset = offset; + blocks = (size - 1) / blksize + 1; + curr = offset + 4 * blocks; + + total_blocks += blocks; + + do { + uLongf len = 2 * blksize; + uLongf input = size; + if (input > blksize) + input = blksize; + size -= input; + if (!is_zero (p, input)) { + compress((Bytef *)(base + curr), &len, p, input); + curr += len; + } + p += input; + + if (len > blksize*2) { + /* (I don't think this can happen with zlib.) */ + printf(_("AIEEE: block \"compressed\" to > " + "2*blocklength (%ld)\n"), + len); + exit(MKFS_EX_ERROR); + } + + *(uint32_t *) (base + offset) = u32_toggle_endianness(cramfs_is_big_endian, curr); + offset += 4; + } while (size); + + do_munmap(start, original_size, mode); + + curr = (curr + 3) & ~3; + new_size = curr - original_offset; + /* TODO: Arguably, original_size in these 2 lines should be + st_blocks * 512. But if you say that, then perhaps + administrative data should also be included in both. */ + change = new_size - original_size; + if (verbose) + printf(_("%6.2f%% (%+ld bytes)\t%s\n"), + (change * 100) / (double) original_size, change, name); + + return curr; +} + + +/* + * Traverse the entry tree, writing data for every item that has + * non-null entry->path (i.e. every symlink and non-empty + * regfile). + */ +static unsigned int +write_data(struct entry *entry, char *base, unsigned int offset) { + struct entry *e; + + for (e = entry; e; e = e->next) { + if (e->path) { + if (e->same) { + set_data_offset(e, base, e->same->offset); + e->offset = e->same->offset; + } else if (e->size) { + set_data_offset(e, base, offset); + e->offset = offset; + offset = do_compress(base, offset, e->name, + e->path, e->size,e->mode); + } + } else if (e->child) + offset = write_data(e->child, base, offset); + } + return offset; +} + +static unsigned int write_file(char *file, char *base, unsigned int offset) +{ + int fd; + char *buf; + + fd = open(file, O_RDONLY); + if (fd < 0) + err(MKFS_EX_ERROR, _("cannot open %s"), file); + buf = mmap(NULL, image_length, PROT_READ, MAP_PRIVATE, fd, 0); + memcpy(base + offset, buf, image_length); + munmap(buf, image_length); + if (close (fd) < 0) + err(MKFS_EX_ERROR, _("cannot close file %s"), file); + /* Pad up the image_length to a 4-byte boundary */ + while (image_length & 3) { + *(base + offset + image_length) = '\0'; + image_length++; + } + return (offset + image_length); +} + +/* + * Maximum size fs you can create is roughly 256MB. (The last file's + * data must begin within 256MB boundary but can extend beyond that.) + * + * Note that if you want it to fit in a ROM then you're limited to what the + * hardware and kernel can support (64MB?). + */ +static unsigned int +maxfslen(void) { + return (((1 << CRAMFS_OFFSET_WIDTH) - 1) << 2) /* offset */ + + (1 << CRAMFS_SIZE_WIDTH) - 1 /* filesize */ + + (1 << CRAMFS_SIZE_WIDTH) * 4 / blksize; /* block pointers */ +} + +/* + * Usage: + * + * mkcramfs directory-name outfile + * + * where "directory-name" is simply the root of the directory + * tree that we want to generate a compressed filesystem out + * of. + */ +int main(int argc, char **argv) +{ + struct stat st; /* used twice... */ + struct entry *root_entry; + char *rom_image; + ssize_t offset, written; + int fd; + /* initial guess (upper-bound) of required filesystem size */ + loff_t fslen_ub = sizeof(struct cramfs_super); + unsigned int fslen_max; + char const *dirname, *outfile; + uint32_t crc = crc32(0L, NULL, 0); + int c; + cramfs_is_big_endian = HOST_IS_BIG_ENDIAN; /* default is to use host order */ + + total_blocks = 0; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + if (argc > 1) { + /* first arg may be one of our standard longopts */ + if (!strcmp(argv[1], "--help")) + usage(); + if (!strcmp(argv[1], "--version")) { + printf(UTIL_LINUX_VERSION); + exit(MKFS_EX_OK); + } + } + strutils_set_exitcode(MKFS_EX_USAGE); + + /* command line options */ + while ((c = getopt(argc, argv, "hb:Ee:i:n:N:psVvz")) != EOF) { + switch (c) { + case 'h': + usage(); + case 'b': + blksize = strtou32_or_err(optarg, _("invalid blocksize argument")); + break; + case 'E': + opt_errors = 1; + break; + case 'e': + opt_edition = strtou32_or_err(optarg, _("invalid edition number argument")); + break; + case 'N': + if (strcmp(optarg, "big") == 0) + cramfs_is_big_endian = 1; + else if (strcmp(optarg, "little") == 0) + cramfs_is_big_endian = 0; + else if (strcmp(optarg, "host") == 0) + /* default */ ; + else + errx(MKFS_EX_USAGE, _("invalid endianness given;" + " must be 'big', 'little', or 'host'")); + break; + case 'i': + opt_image = optarg; + if (lstat(opt_image, &st) < 0) + err(MKFS_EX_USAGE, _("stat of %s failed"), opt_image); + image_length = st.st_size; /* may be padded later */ + fslen_ub += (image_length + 3); /* 3 is for padding */ + break; + case 'n': + opt_name = optarg; + break; + case 'p': + opt_pad = PAD_SIZE; + fslen_ub += PAD_SIZE; + break; + case 's': + /* old option, ignored */ + break; + case 'V': + printf(UTIL_LINUX_VERSION); + exit(MKFS_EX_OK); + case 'v': + verbose = 1; + break; + case 'z': + opt_holes = 1; + break; + default: + errtryhelp(MKFS_EX_USAGE); + } + } + + if ((argc - optind) != 2) { + warnx(_("bad usage")); + errtryhelp(MKFS_EX_USAGE); + } + dirname = argv[optind]; + outfile = argv[optind + 1]; + + if (blksize == 0) + blksize = getpagesize(); + + if (stat(dirname, &st) < 0) + err(MKFS_EX_USAGE, _("stat of %s failed"), dirname); + fd = open(outfile, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (fd < 0) + err(MKFS_EX_USAGE, _("cannot open %s"), outfile); + + root_entry = xcalloc(1, sizeof(struct entry)); + root_entry->mode = st.st_mode; + root_entry->uid = st.st_uid; + root_entry->gid = st.st_gid; + + root_entry->size = parse_directory(root_entry, dirname, &root_entry->child, &fslen_ub); + + /* find duplicate files */ + eliminate_doubles(root_entry,root_entry, &fslen_ub); + + /* always allocate a multiple of blksize bytes because that's + what we're going to write later on */ + fslen_ub = ((fslen_ub - 1) | (blksize - 1)) + 1; + fslen_max = maxfslen(); + + if (fslen_ub > fslen_max) { + warnx( _("warning: guestimate of required size (upper bound) " + "is %lldMB, but maximum image size is %uMB. " + "We might die prematurely."), + (long long)fslen_ub >> 20, + fslen_max >> 20); + fslen_ub = fslen_max; + } + + /* TODO: Why do we use a private/anonymous mapping here + followed by a write below, instead of just a shared mapping + and a couple of ftruncate calls? Is it just to save us + having to deal with removing the file afterwards? If we + really need this huge anonymous mapping, we ought to mmap + in smaller chunks, so that the user doesn't need nn MB of + RAM free. If the reason is to be able to write to + un-mmappable block devices, then we could try shared mmap + and revert to anonymous mmap if the shared mmap fails. */ + rom_image = mmap(NULL, + fslen_ub?fslen_ub:1, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, + -1, 0); + + if (-1 == (int) (long) rom_image) + err(MKFS_EX_ERROR, _("ROM image map")); + + /* Skip the first opt_pad bytes for boot loader code */ + offset = opt_pad; + memset(rom_image, 0x00, opt_pad); + + /* Skip the superblock and come back to write it later. */ + offset += sizeof(struct cramfs_super); + + /* Insert a file image. */ + if (opt_image) { + if (verbose) + printf(_("Including: %s\n"), opt_image); + offset = write_file(opt_image, rom_image, offset); + } + + offset = write_directory_structure(root_entry->child, rom_image, offset); + if (verbose) + printf(_("Directory data: %zd bytes\n"), offset); + + offset = write_data(root_entry, rom_image, offset); + + /* We always write a multiple of blksize bytes, so that + losetup works. */ + offset = ((offset - 1) | (blksize - 1)) + 1; + if (verbose) + printf(_("Everything: %zd kilobytes\n"), offset >> 10); + + /* Write the superblock now that we can fill in all of the fields. */ + write_superblock(root_entry, rom_image+opt_pad, offset); + if (verbose) + printf(_("Super block: %zd bytes\n"), + sizeof(struct cramfs_super)); + + /* Put the checksum in. */ + crc = crc32(crc, (unsigned char *) (rom_image+opt_pad), (offset-opt_pad)); + ((struct cramfs_super *) (rom_image+opt_pad))->fsid.crc = u32_toggle_endianness(cramfs_is_big_endian, crc); + if (verbose) + printf(_("CRC: %x\n"), crc); + + /* Check to make sure we allocated enough space. */ + if (fslen_ub < offset) + errx(MKFS_EX_ERROR, + _("not enough space allocated for ROM image " + "(%lld allocated, %zu used)"), + (long long) fslen_ub, offset); + + written = write(fd, rom_image, offset); + if (offset != written) + errx(MKFS_EX_ERROR, _("ROM image write failed (%zd %zd)"), + written, offset); + if (close_fd(fd) != 0) + err(MKFS_EX_ERROR, _("ROM image")); + + /* + * (These warnings used to come at the start, but they scroll off + * the screen too quickly.) + */ + if (warn_namelen) + /* Can't happen when reading from ext2fs. */ + /* Bytes, not chars: think UTF8. */ + warnx(_("warning: filenames truncated to %u bytes."), MAX_INPUT_NAMELEN); + if (warn_skip) + warnx(_("warning: files were skipped due to errors.")); + if (warn_size) + warnx(_("warning: file sizes truncated to %luMB " + "(minus 1 byte)."), 1L << (CRAMFS_SIZE_WIDTH - 20)); + if (warn_uid) + /* (not possible with current Linux versions) */ + warnx(_("warning: uids truncated to %u bits. " + "(This may be a security concern.)"), CRAMFS_UID_WIDTH); + if (warn_gid) + warnx(_("warning: gids truncated to %u bits. " + "(This may be a security concern.)"), CRAMFS_GID_WIDTH); + if (warn_dev) + warnx(_("WARNING: device numbers truncated to %u bits. " + "This almost certainly means\n" + "that some device files will be wrong."), + CRAMFS_OFFSET_WIDTH); + if (opt_errors && + (warn_namelen|warn_skip|warn_size|warn_uid|warn_gid|warn_dev)) + exit(MKFS_EX_ERROR); + + return MKFS_EX_OK; +} diff --git a/disk-utils/mkfs.minix.8 b/disk-utils/mkfs.minix.8 new file mode 100644 index 0000000..a640deb --- /dev/null +++ b/disk-utils/mkfs.minix.8 @@ -0,0 +1,89 @@ +.\" Copyright 1992, 1993, 1994 Rickard E. Faith (faith@cs.unc.edu) +.\" May be freely distributed. +.TH MKFS.MINIX 8 "June 2015" "util-linux" "System Administration" +.SH NAME +mkfs.minix \- make a Minix filesystem +.SH SYNOPSIS +.B mkfs.minix +[options] +.I device +.RI [ size-in-blocks ] +.SH DESCRIPTION +.B mkfs.minix +creates a Linux MINIX filesystem on a device (usually a disk partition). + +The +.I device +is usually of the following form: + +.nf +.RS +/dev/hda[1\(en8] (IDE disk 1) +/dev/hdb[1\(en8] (IDE disk 2) +/dev/sda[1\(en8] (SCSI disk 1) +/dev/sdb[1\(en8] (SCSI disk 2) +.RE +.fi + +The device may be a block device or an image file of one, but this is not +enforced. Expect not much fun on a character device :-). +.PP +The +.I size-in-blocks +parameter is the desired size of the file system, in blocks. +It is present only for backwards compatibility. +If omitted the size will be determined automatically. +Only block counts strictly greater than 10 and strictly less than +65536 are allowed. +.SH OPTIONS +.TP +\fB\-c\fR, \fB\-\-check\fR +Check the device for bad blocks before creating the filesystem. If any +are found, the count is printed. +.TP +\fB\-n\fR, \fB\-\-namelength\fR \fIlength\fR +Specify the maximum length of filenames. Currently, the only allowable +values are 14 and 30 for file system versions 1 and 2. Version 3 allows +only value 60. The default is 30. +.TP +\fB\-i\fR, \fB\-\-inodes\fR \fInumber\fR +Specify the number of inodes for the filesystem. +.TP +\fB\-l\fR, \fB\-\-badblocks\fR \fIfilename\fR +Read the list of bad blocks from +.IR filename . +The file has one bad-block number per line. The count of bad blocks read +is printed. +.TP +.B \-1 +Make a Minix version 1 filesystem. This is the default. +.TP +.BR \-2 , " \-v" +Make a Minix version 2 filesystem. +.TP +.B \-3 +Make a Minix version 3 filesystem. +.TP +\fB\-V\fR, \fB\-\-version\fR +Display version information and exit. The long option cannot be combined +with other options. +.TP +\fB\-h\fR, \fB\-\-help\fR +Display help text and exit. +.SH "EXIT CODES" +The exit code returned by +.B mkfs.minix +is one of the following: +.IP 0 +No errors +.IP 8 +Operational error +.IP 16 +Usage or syntax error +.SH "SEE ALSO" +.BR fsck (8), +.BR mkfs (8), +.BR reboot (8) +.SH AVAILABILITY +The mkfs.minix command is part of the util-linux package and is available from +https://www.kernel.org/pub/linux/utils/util-linux/. diff --git a/disk-utils/mkfs.minix.c b/disk-utils/mkfs.minix.c new file mode 100644 index 0000000..54c47da --- /dev/null +++ b/disk-utils/mkfs.minix.c @@ -0,0 +1,841 @@ +/* + * mkfs.minix.c - make a linux (minix) file-system. + * + * (C) 1991 Linus Torvalds. This file may be redistributed as per + * the Linux copyright. + */ + +/* + * DD.MM.YY + * + * 24.11.91 - Time began. Used the fsck sources to get started. + * + * 25.11.91 - Corrected some bugs. Added support for ".badblocks" + * The algorithm for ".badblocks" is a bit weird, but + * it should work. Oh, well. + * + * 25.01.92 - Added the -l option for getting the list of bad blocks + * out of a named file. (Dave Rivers, rivers@ponds.uucp) + * + * 28.02.92 - Added %-information when using -c. + * + * 28.02.93 - Added support for other namelengths than the original + * 14 characters so that I can test the new kernel routines.. + * + * 09.10.93 - Make exit status conform to that required by fsutil + * (Rik Faith, faith@cs.unc.edu) + * + * 31.10.93 - Added inode request feature, for backup floppies: use + * 32 inodes, for a news partition use more. + * (Scott Heavner, sdh@po.cwru.edu) + * + * 03.01.94 - Added support for file system valid flag. + * (Dr. Wettstein, greg%wind.uucp@plains.nodak.edu) + * + * 30.10.94 - Added support for v2 filesystem + * (Andreas Schwab, schwab@issan.informatik.uni-dortmund.de) + * + * 09.11.94 - Added test to prevent overwrite of mounted fs adapted + * from Theodore Ts'o's (tytso@athena.mit.edu) mke2fs + * program. (Daniel Quinlan, quinlan@yggdrasil.com) + * + * 03.20.95 - Clear first 512 bytes of filesystem to make certain that + * the filesystem is not misidentified as a MS-DOS FAT filesystem. + * (Daniel Quinlan, quinlan@yggdrasil.com) + * + * 02.07.96 - Added small patch from Russell King to make the program a + * good deal more portable (janl@math.uio.no) + * + * 06.29.11 - Overall cleanups for util-linux and v3 support + * Davidlohr Bueso <dave@gnu.org> + * + * 06.20.15 - Do not infinite loop or crash on large devices + * Joshua Hudson <joshudson@gmail.com> + * + */ + +#include <stdio.h> +#include <time.h> +#include <unistd.h> +#include <string.h> +#include <signal.h> +#include <fcntl.h> +#include <ctype.h> +#include <stdlib.h> +#include <termios.h> +#include <sys/stat.h> +#include <getopt.h> +#include <err.h> + +#include "blkdev.h" +#include "minix_programs.h" +#include "nls.h" +#include "pathnames.h" +#include "bitops.h" +#include "exitcodes.h" +#include "strutils.h" +#include "all-io.h" +#include "closestream.h" +#include "ismounted.h" + +#define XALLOC_EXIT_CODE MKFS_EX_ERROR +#include "xalloc.h" + +#define MINIX_ROOT_INO 1 +#define MINIX_BAD_INO 2 + +#define TEST_BUFFER_BLOCKS 16 +#define MAX_GOOD_BLOCKS 512 + +#define MINIX_MAX_INODES 65535 + +#define DEFAULT_FS_VERSION 1 + +/* + * Global variables used in minix_programs.h inline functions + */ +int fs_version = DEFAULT_FS_VERSION; +char *super_block_buffer; + +static char *inode_buffer = NULL; + +#define Inode (((struct minix_inode *) inode_buffer) - 1) +#define Inode2 (((struct minix2_inode *) inode_buffer) - 1) + +struct fs_control { + char *device_name; /* device on a Minix file system is created */ + int device_fd; /* open file descriptor of the device */ + unsigned long long fs_blocks; /* device block count for the file system */ + int fs_used_blocks; /* used blocks on a device */ + int fs_bad_blocks; /* number of bad blocks found from device */ + uint16_t fs_namelen; /* maximum length of filenames */ + size_t fs_dirsize; /* maximum size of directory */ + unsigned long fs_inodes; /* number of inodes */ + int fs_magic; /* file system magic number */ + unsigned int + check_blocks:1; /* check for bad blocks */ +}; + +static char root_block[MINIX_BLOCK_SIZE]; +static char boot_block_buffer[512]; +static unsigned short good_blocks_table[MAX_GOOD_BLOCKS]; + +static char *inode_map; +static char *zone_map; + +#define zone_in_use(x) (isset(zone_map,(x)-get_first_zone()+1) != 0) + +#define mark_inode(x) (setbit(inode_map,(x))) +#define unmark_inode(x) (clrbit(inode_map,(x))) + +#define mark_zone(x) (setbit(zone_map,(x)-get_first_zone()+1)) +#define unmark_zone(x) (clrbit(zone_map,(x)-get_first_zone()+1)) + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *out = stdout; + fputs(USAGE_HEADER, out); + fprintf(out, _(" %s [options] /dev/name [blocks]\n"), program_invocation_short_name); + fputs(USAGE_OPTIONS, out); + fputs(_(" -1 use Minix version 1\n"), out); + fputs(_(" -2, -v use Minix version 2\n"), out); + fputs(_(" -3 use Minix version 3\n"), out); + fputs(_(" -n, --namelength <num> maximum length of filenames\n"), out); + fputs(_(" -i, --inodes <num> number of inodes for the filesystem\n"), out); + fputs(_(" -c, --check check the device for bad blocks\n"), out); + fputs(_(" -l, --badblocks <file> list of bad blocks from file\n"), out); + fputs(USAGE_SEPARATOR, out); + printf(USAGE_HELP_OPTIONS(25)); + printf(USAGE_MAN_TAIL("mkfs.minix(8)")); + exit(MKFS_EX_OK); +} + +#ifdef TEST_SCRIPT +static inline time_t mkfs_minix_time(time_t *t) +{ + const char *str = getenv("MKFS_MINIX_TEST_SECOND_SINCE_EPOCH"); + time_t sec; + + if (str && sscanf(str, "%ld", &sec) == 1) + return sec; + return time(t); +} +#else /* !TEST_SCRIPT */ +# define mkfs_minix_time(x) time(x) +#endif + +static void super_set_state(void) +{ + switch (fs_version) { + case 1: + case 2: + Super.s_state |= MINIX_VALID_FS; + Super.s_state &= ~MINIX_ERROR_FS; + break; + default: /* v3 */ + break; + } +} + +static void write_tables(const struct fs_control *ctl) { + unsigned long imaps = get_nimaps(); + unsigned long zmaps = get_nzmaps(); + size_t buffsz = get_inode_buffer_size(); + + /* Mark the super block valid. */ + super_set_state(); + + if (lseek(ctl->device_fd, 0, SEEK_SET)) + err(MKFS_EX_ERROR, _("%s: seek to boot block failed " + " in write_tables"), ctl->device_name); + if (write_all(ctl->device_fd, boot_block_buffer, 512)) + err(MKFS_EX_ERROR, _("%s: unable to clear boot sector"), ctl->device_name); + if (MINIX_BLOCK_SIZE != lseek(ctl->device_fd, MINIX_BLOCK_SIZE, SEEK_SET)) + err(MKFS_EX_ERROR, _("%s: seek failed in write_tables"), ctl->device_name); + + if (write_all(ctl->device_fd, super_block_buffer, MINIX_BLOCK_SIZE)) + err(MKFS_EX_ERROR, _("%s: unable to write super-block"), ctl->device_name); + + if (write_all(ctl->device_fd, inode_map, imaps * MINIX_BLOCK_SIZE)) + err(MKFS_EX_ERROR, _("%s: unable to write inode map"), ctl->device_name); + + if (write_all(ctl->device_fd, zone_map, zmaps * MINIX_BLOCK_SIZE)) + err(MKFS_EX_ERROR, _("%s: unable to write zone map"), ctl->device_name); + + if (write_all(ctl->device_fd, inode_buffer, buffsz)) + err(MKFS_EX_ERROR, _("%s: unable to write inodes"), ctl->device_name); +} + +static void write_block(const struct fs_control *ctl, int blk, char * buffer) { + if (blk * MINIX_BLOCK_SIZE != lseek(ctl->device_fd, blk * MINIX_BLOCK_SIZE, SEEK_SET)) + errx(MKFS_EX_ERROR, _("%s: seek failed in write_block"), ctl->device_name); + + if (write_all(ctl->device_fd, buffer, MINIX_BLOCK_SIZE)) + errx(MKFS_EX_ERROR, _("%s: write failed in write_block"), ctl->device_name); +} + +static int get_free_block(struct fs_control *ctl) { + unsigned int blk; + unsigned int zones = get_nzones(); + unsigned int first_zone = get_first_zone(); + + if (ctl->fs_used_blocks + 1 >= MAX_GOOD_BLOCKS) + errx(MKFS_EX_ERROR, _("%s: too many bad blocks"), ctl->device_name); + if (ctl->fs_used_blocks) + blk = good_blocks_table[ctl->fs_used_blocks - 1] + 1; + else + blk = first_zone; + while (blk < zones && zone_in_use(blk)) + blk++; + if (blk >= zones) + errx(MKFS_EX_ERROR, _("%s: not enough good blocks"), ctl->device_name); + good_blocks_table[ctl->fs_used_blocks] = blk; + ctl->fs_used_blocks++; + return blk; +} + +static void mark_good_blocks(const struct fs_control *ctl) { + int blk; + + for (blk=0 ; blk < ctl->fs_used_blocks ; blk++) + mark_zone(good_blocks_table[blk]); +} + +static inline int next(unsigned long zone) { + unsigned long zones = get_nzones(); + unsigned long first_zone = get_first_zone(); + + if (!zone) + zone = first_zone-1; + while (++zone < zones) + if (zone_in_use(zone)) + return zone; + return 0; +} + +static void make_bad_inode_v1(struct fs_control *ctl) +{ + struct minix_inode * inode = &Inode[MINIX_BAD_INO]; + int i,j,zone; + int ind=0,dind=0; + unsigned short ind_block[MINIX_BLOCK_SIZE>>1]; + unsigned short dind_block[MINIX_BLOCK_SIZE>>1]; + +#define NEXT_BAD (zone = next(zone)) + + if (!ctl->fs_bad_blocks) + return; + mark_inode(MINIX_BAD_INO); + inode->i_nlinks = 1; + inode->i_time = mkfs_minix_time(NULL); + inode->i_mode = S_IFREG + 0000; + inode->i_size = ctl->fs_bad_blocks * MINIX_BLOCK_SIZE; + zone = next(0); + for (i=0 ; i<7 ; i++) { + inode->i_zone[i] = zone; + if (!NEXT_BAD) + goto end_bad; + } + inode->i_zone[7] = ind = get_free_block(ctl); + memset(ind_block,0,MINIX_BLOCK_SIZE); + for (i=0 ; i<512 ; i++) { + ind_block[i] = zone; + if (!NEXT_BAD) + goto end_bad; + } + inode->i_zone[8] = dind = get_free_block(ctl); + memset(dind_block,0,MINIX_BLOCK_SIZE); + for (i=0 ; i<512 ; i++) { + write_block(ctl, ind,(char *) ind_block); + dind_block[i] = ind = get_free_block(ctl); + memset(ind_block,0,MINIX_BLOCK_SIZE); + for (j=0 ; j<512 ; j++) { + ind_block[j] = zone; + if (!NEXT_BAD) + goto end_bad; + } + } + errx(MKFS_EX_ERROR, _("%s: too many bad blocks"), ctl->device_name); +end_bad: + if (ind) + write_block(ctl, ind, (char *) ind_block); + if (dind) + write_block(ctl, dind, (char *) dind_block); +} + +static void make_bad_inode_v2_v3 (struct fs_control *ctl) +{ + struct minix2_inode *inode = &Inode2[MINIX_BAD_INO]; + int i, j, zone; + int ind = 0, dind = 0; + unsigned long ind_block[MINIX_BLOCK_SIZE >> 2]; + unsigned long dind_block[MINIX_BLOCK_SIZE >> 2]; + + if (!ctl->fs_bad_blocks) + return; + mark_inode (MINIX_BAD_INO); + inode->i_nlinks = 1; + inode->i_atime = inode->i_mtime = inode->i_ctime = mkfs_minix_time(NULL); + inode->i_mode = S_IFREG + 0000; + inode->i_size = ctl->fs_bad_blocks * MINIX_BLOCK_SIZE; + zone = next (0); + for (i = 0; i < 7; i++) { + inode->i_zone[i] = zone; + if (!NEXT_BAD) + goto end_bad; + } + inode->i_zone[7] = ind = get_free_block (ctl); + memset (ind_block, 0, MINIX_BLOCK_SIZE); + for (i = 0; i < 256; i++) { + ind_block[i] = zone; + if (!NEXT_BAD) + goto end_bad; + } + inode->i_zone[8] = dind = get_free_block (ctl); + memset (dind_block, 0, MINIX_BLOCK_SIZE); + for (i = 0; i < 256; i++) { + write_block (ctl, ind, (char *) ind_block); + dind_block[i] = ind = get_free_block (ctl); + memset (ind_block, 0, MINIX_BLOCK_SIZE); + for (j = 0; j < 256; j++) { + ind_block[j] = zone; + if (!NEXT_BAD) + goto end_bad; + } + } + /* Could make triple indirect block here */ + errx(MKFS_EX_ERROR, _("%s: too many bad blocks"), ctl->device_name); + end_bad: + if (ind) + write_block (ctl, ind, (char *) ind_block); + if (dind) + write_block (ctl, dind, (char *) dind_block); +} + +static void make_bad_inode(struct fs_control *ctl) +{ + if (fs_version < 2) { + make_bad_inode_v1(ctl); + return; + } + make_bad_inode_v2_v3(ctl); +} + +static void make_root_inode_v1(struct fs_control *ctl) { + struct minix_inode * inode = &Inode[MINIX_ROOT_INO]; + + mark_inode(MINIX_ROOT_INO); + inode->i_zone[0] = get_free_block(ctl); + inode->i_nlinks = 2; + inode->i_time = mkfs_minix_time(NULL); + if (ctl->fs_bad_blocks) + inode->i_size = 3 * ctl->fs_dirsize; + else { + memset(&root_block[2 * ctl->fs_dirsize], 0, ctl->fs_dirsize); + inode->i_size = 2 * ctl->fs_dirsize; + } + inode->i_mode = S_IFDIR + 0755; + inode->i_uid = getuid(); + if (inode->i_uid) + inode->i_gid = getgid(); + write_block(ctl, inode->i_zone[0],root_block); +} + +static void make_root_inode_v2_v3 (struct fs_control *ctl) { + struct minix2_inode *inode = &Inode2[MINIX_ROOT_INO]; + + mark_inode (MINIX_ROOT_INO); + inode->i_zone[0] = get_free_block (ctl); + inode->i_nlinks = 2; + inode->i_atime = inode->i_mtime = inode->i_ctime = mkfs_minix_time(NULL); + + if (ctl->fs_bad_blocks) + inode->i_size = 3 * ctl->fs_dirsize; + else { + memset(&root_block[2 * ctl->fs_dirsize], 0, ctl->fs_dirsize); + inode->i_size = 2 * ctl->fs_dirsize; + } + + inode->i_mode = S_IFDIR + 0755; + inode->i_uid = getuid(); + if (inode->i_uid) + inode->i_gid = getgid(); + write_block (ctl, inode->i_zone[0], root_block); +} + +static void make_root_inode(struct fs_control *ctl) +{ + char *tmp = root_block; + + if (fs_version == 3) { + *(uint32_t *) tmp = 1; + strcpy(tmp + 4, "."); + tmp += ctl->fs_dirsize; + *(uint32_t *) tmp = 1; + strcpy(tmp + 4, ".."); + tmp += ctl->fs_dirsize; + *(uint32_t *) tmp = 2; + strcpy(tmp + 4, ".badblocks"); + } else { + *(uint16_t *) tmp = 1; + strcpy(tmp + 2, "."); + tmp += ctl->fs_dirsize; + *(uint16_t *) tmp = 1; + strcpy(tmp + 2, ".."); + tmp += ctl->fs_dirsize; + *(uint16_t *) tmp = 2; + strcpy(tmp + 2, ".badblocks"); + } + if (fs_version < 2) { + make_root_inode_v1(ctl); + return; + } + make_root_inode_v2_v3(ctl); +} + +static void super_set_nzones(const struct fs_control *ctl) +{ + switch (fs_version) { + case 3: + Super3.s_zones = ctl->fs_blocks; + break; + case 2: + Super.s_zones = ctl->fs_blocks; + break; + default: /* v1 */ + Super.s_nzones = ctl->fs_blocks; + break; + } +} + +static void super_init_maxsize(void) +{ + switch (fs_version) { + case 3: + Super3.s_max_size = 2147483647L; + break; + case 2: + Super.s_max_size = 0x7fffffff; + break; + default: /* v1 */ + Super.s_max_size = (7+512+512*512)*1024; + break; + } +} + +static void super_set_map_blocks(const struct fs_control *ctl, unsigned long inodes) +{ + switch (fs_version) { + case 3: + Super3.s_imap_blocks = UPPER(inodes + 1, BITS_PER_BLOCK); + Super3.s_zmap_blocks = UPPER(ctl->fs_blocks - (1 + get_nimaps() + inode_blocks()), + BITS_PER_BLOCK + 1); + Super3.s_firstdatazone = first_zone_data(); + break; + default: + Super.s_imap_blocks = UPPER(inodes + 1, BITS_PER_BLOCK); + Super.s_zmap_blocks = UPPER(ctl->fs_blocks - (1 + get_nimaps() + inode_blocks()), + BITS_PER_BLOCK + 1); + Super.s_firstdatazone = first_zone_data(); + break; + } +} + +static void super_set_magic(const struct fs_control *ctl) +{ + switch (fs_version) { + case 3: + Super3.s_magic = ctl->fs_magic; + break; + default: + Super.s_magic = ctl->fs_magic; + break; + } +} + +static void setup_tables(const struct fs_control *ctl) { + unsigned long inodes, zmaps, imaps, zones, i; + + super_block_buffer = xcalloc(1, MINIX_BLOCK_SIZE); + + memset(boot_block_buffer,0,512); + super_set_magic(ctl); + + if (fs_version == 3) { + Super3.s_log_zone_size = 0; + Super3.s_blocksize = MINIX_BLOCK_SIZE; + } + else { + Super.s_log_zone_size = 0; + } + + super_init_maxsize(); + super_set_nzones(ctl); + zones = get_nzones(); + + /* some magic nrs: 1 inode / 3 blocks for smaller filesystems, + * for one inode / 16 blocks for large ones. mkfs will eventually + * crab about too far when getting close to the maximum size. */ + if (ctl->fs_inodes == 0) + if (2048 * 1024 < ctl->fs_blocks) /* 2GB */ + inodes = ctl->fs_blocks / 16; + else if (512 * 1024 < ctl->fs_blocks) /* 0.5GB */ + inodes = ctl->fs_blocks / 8; + else + inodes = ctl->fs_blocks / 3; + else + inodes = ctl->fs_inodes; + /* Round up inode count to fill block size */ + if (fs_version == 2 || fs_version == 3) + inodes = ((inodes + MINIX2_INODES_PER_BLOCK - 1) & + ~(MINIX2_INODES_PER_BLOCK - 1)); + else + inodes = ((inodes + MINIX_INODES_PER_BLOCK - 1) & + ~(MINIX_INODES_PER_BLOCK - 1)); + + if (fs_version == 3) + Super3.s_ninodes = inodes; + else { + if (inodes > MINIX_MAX_INODES) + inodes = MINIX_MAX_INODES; + Super.s_ninodes = inodes; + } + super_set_map_blocks(ctl, inodes); + if (MINIX_MAX_INODES < first_zone_data()) + errx(MKFS_EX_ERROR, + _("First data block at %jd, which is too far (max %d).\n" + "Try specifying fewer inodes by passing --inodes <num>"), + (intmax_t)first_zone_data(), + MINIX_MAX_INODES); + imaps = get_nimaps(); + zmaps = get_nzmaps(); + + inode_map = xmalloc(imaps * MINIX_BLOCK_SIZE); + zone_map = xmalloc(zmaps * MINIX_BLOCK_SIZE); + memset(inode_map,0xff,imaps * MINIX_BLOCK_SIZE); + memset(zone_map,0xff,zmaps * MINIX_BLOCK_SIZE); + + for (i = get_first_zone() ; i<zones ; i++) + unmark_zone(i); + for (i = MINIX_ROOT_INO ; i<=inodes; i++) + unmark_inode(i); + + inode_buffer = xmalloc(get_inode_buffer_size()); + memset(inode_buffer,0, get_inode_buffer_size()); + + printf(P_("%lu inode\n", "%lu inodes\n", inodes), inodes); + printf(P_("%lu block\n", "%lu blocks\n", zones), zones); + printf(_("Firstdatazone=%jd (%jd)\n"), + (intmax_t)get_first_zone(), (intmax_t)first_zone_data()); + printf(_("Zonesize=%zu\n"), (size_t) MINIX_BLOCK_SIZE << get_zone_size()); + printf(_("Maxsize=%zu\n\n"),get_max_size()); +} + +/* + * Perform a test of a block; return the number of + * blocks readable/writable. + */ +static size_t do_check(const struct fs_control *ctl, char * buffer, int try, unsigned int current_block) { + ssize_t got; + + /* Seek to the correct loc. */ + if (lseek(ctl->device_fd, current_block * MINIX_BLOCK_SIZE, SEEK_SET) != + current_block * MINIX_BLOCK_SIZE ) + err(MKFS_EX_ERROR, _("%s: seek failed during testing of blocks"), + ctl->device_name); + + /* Try the read */ + got = read(ctl->device_fd, buffer, try * MINIX_BLOCK_SIZE); + if (got < 0) got = 0; + if (got & (MINIX_BLOCK_SIZE - 1 )) { + printf(_("Weird values in do_check: probably bugs\n")); + } + got /= MINIX_BLOCK_SIZE; + return got; +} + +static unsigned int currently_testing = 0; + +static void alarm_intr(int alnum __attribute__ ((__unused__))) { + unsigned long zones = get_nzones(); + + if (currently_testing >= zones) + return; + signal(SIGALRM,alarm_intr); + alarm(5); + if (!currently_testing) + return; + printf("%d ...", currently_testing); + fflush(stdout); +} + +static void check_blocks(struct fs_control *ctl) { + size_t try, got; + static char buffer[MINIX_BLOCK_SIZE * TEST_BUFFER_BLOCKS]; + unsigned long zones = get_nzones(); + unsigned long first_zone = get_first_zone(); + + currently_testing=0; + signal(SIGALRM,alarm_intr); + alarm(5); + while (currently_testing < zones) { + if (lseek(ctl->device_fd, currently_testing * MINIX_BLOCK_SIZE,SEEK_SET) != + currently_testing*MINIX_BLOCK_SIZE) + errx(MKFS_EX_ERROR, _("%s: seek failed in check_blocks"), + ctl->device_name); + try = TEST_BUFFER_BLOCKS; + if (currently_testing + try > zones) + try = zones-currently_testing; + got = do_check(ctl, buffer, try, currently_testing); + currently_testing += got; + if (got == try) + continue; + if (currently_testing < first_zone) + errx(MKFS_EX_ERROR, _("%s: bad blocks before data-area: " + "cannot make fs"), ctl->device_name); + mark_zone(currently_testing); + ctl->fs_bad_blocks++; + currently_testing++; + } + if (ctl->fs_bad_blocks > 0) + printf(P_("%d bad block\n", "%d bad blocks\n", ctl->fs_bad_blocks), ctl->fs_bad_blocks); +} + +static void get_list_blocks(struct fs_control *ctl, char *filename) { + FILE *listfile; + unsigned long blockno; + + listfile = fopen(filename,"r"); + if (listfile == NULL) + err(MKFS_EX_ERROR, _("%s: can't open file of bad blocks"), + ctl->device_name); + + while (!feof(listfile)) { + if (fscanf(listfile,"%lu\n", &blockno) != 1) { + printf(_("badblock number input error on line %d\n"), ctl->fs_bad_blocks + 1); + errx(MKFS_EX_ERROR, _("%s: cannot read badblocks file"), + ctl->device_name); + } + mark_zone(blockno); + ctl->fs_bad_blocks++; + } + fclose(listfile); + + if (ctl->fs_bad_blocks > 0) + printf(P_("%d bad block\n", "%d bad blocks\n", ctl->fs_bad_blocks), ctl->fs_bad_blocks); +} + +static int find_super_magic(const struct fs_control *ctl) +{ + switch (fs_version) { + case 1: + if (ctl->fs_namelen == 14) + return MINIX_SUPER_MAGIC; + return MINIX_SUPER_MAGIC2; + case 2: + if (ctl->fs_namelen == 14) + return MINIX2_SUPER_MAGIC; + return MINIX2_SUPER_MAGIC2; + case 3: + return MINIX3_SUPER_MAGIC; + default: + abort(); + } +} + +static void determine_device_blocks(struct fs_control *ctl, const struct stat *statbuf) +{ + unsigned long long dev_blocks; + + if (S_ISBLK(statbuf->st_mode)) { + int sectorsize; + + if (blkdev_get_sector_size(ctl->device_fd, §orsize) == -1) + sectorsize = DEFAULT_SECTOR_SIZE; /* kernel < 2.3.3 */ + if (MINIX_BLOCK_SIZE < sectorsize) + errx(MKFS_EX_ERROR, _("block size smaller than physical " + "sector size of %s"), ctl->device_name); + if (blkdev_get_size(ctl->device_fd, &dev_blocks) == -1) + errx(MKFS_EX_ERROR, _("cannot determine size of %s"), ctl->device_name); + dev_blocks /= MINIX_BLOCK_SIZE; + } else if (!S_ISBLK(statbuf->st_mode)) + dev_blocks = statbuf->st_size / MINIX_BLOCK_SIZE; + if (!ctl->fs_blocks) + ctl->fs_blocks = dev_blocks; + else if (dev_blocks < ctl->fs_blocks) + errx(MKFS_EX_ERROR, + _("%s: requested blocks (%llu) exceeds available (%llu) blocks\n"), + ctl->device_name, ctl->fs_blocks, dev_blocks); + if (ctl->fs_blocks < 10) + errx(MKFS_EX_ERROR, _("%s: number of blocks too small"), ctl->device_name); + if (fs_version == 1 && ctl->fs_blocks > MINIX_MAX_INODES) + ctl->fs_blocks = MINIX_MAX_INODES; + if (ctl->fs_blocks > (4 + ((MINIX_MAX_INODES - 4) * BITS_PER_BLOCK))) + ctl->fs_blocks = 4 + ((MINIX_MAX_INODES - 4) * BITS_PER_BLOCK); /* Utter maximum: Clip. */ +} + +static void check_user_instructions(struct fs_control *ctl) +{ + switch (fs_version) { + case 1: + case 2: + if (ctl->fs_namelen == 14 || ctl->fs_namelen == 30) + ctl->fs_dirsize = ctl->fs_namelen + 2; + else + errx(MKFS_EX_ERROR, _("unsupported name length: %d"), ctl->fs_namelen); + break; + case 3: + if (ctl->fs_namelen == 60) + ctl->fs_dirsize = ctl->fs_namelen + 4; + else + errx(MKFS_EX_ERROR, _("unsupported name length: %d"), ctl->fs_namelen); + break; + default: + errx(MKFS_EX_ERROR, _("unsupported minix file system version: %d"), fs_version); + } + ctl->fs_magic = find_super_magic(ctl); +} + +int main(int argc, char ** argv) +{ + struct fs_control ctl = { + .fs_namelen = 30, /* keep in sync with DEFAULT_FS_VERSION */ + 0 + }; + int i; + struct stat statbuf; + char * listfile = NULL; + static const struct option longopts[] = { + {"namelength", required_argument, NULL, 'n'}, + {"inodes", required_argument, NULL, 'i'}, + {"check", no_argument, NULL, 'c'}, + {"badblocks", required_argument, NULL, 'l'}, + {"version", no_argument, NULL, 'V'}, + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + strutils_set_exitcode(MKFS_EX_USAGE); + + while ((i = getopt_long(argc, argv, "1v23n:i:cl:Vh", longopts, NULL)) != -1) + switch (i) { + case '1': + fs_version = 1; + break; + case 'v': /* kept for backwards compatibility */ + warnx(_("-v is ambiguous, use '-2' instead")); + /* fallthrough */ + case '2': + fs_version = 2; + break; + case '3': + fs_version = 3; + ctl.fs_namelen = 60; + break; + case 'n': + ctl.fs_namelen = strtou16_or_err(optarg, + _("failed to parse maximum length of filenames")); + break; + case 'i': + ctl.fs_inodes = strtoul_or_err(optarg, + _("failed to parse number of inodes")); + break; + case 'c': + ctl.check_blocks = 1; + break; + case 'l': + listfile = optarg; + break; + case 'V': + printf(UTIL_LINUX_VERSION); + return MKFS_EX_OK; + case 'h': + usage(); + default: + errtryhelp(MKFS_EX_USAGE); + } + argc -= optind; + argv += optind; + if (argc > 0) { + ctl.device_name = argv[0]; + argc--; + argv++; + } + if (argc > 0) + ctl.fs_blocks = strtoul_or_err(argv[0], _("failed to parse number of blocks")); + + if (!ctl.device_name) { + warnx(_("no device specified")); + errtryhelp(MKFS_EX_USAGE); + } + check_user_instructions(&ctl); + if (is_mounted(ctl.device_name)) + errx(MKFS_EX_ERROR, _("%s is mounted; will not make a filesystem here!"), + ctl.device_name); + if (stat(ctl.device_name, &statbuf) < 0) + err(MKFS_EX_ERROR, _("stat of %s failed"), ctl.device_name); + ctl.device_fd = open_blkdev_or_file(&statbuf, ctl.device_name, O_RDWR); + if (ctl.device_fd < 0) + err(MKFS_EX_ERROR, _("cannot open %s"), ctl.device_name); + determine_device_blocks(&ctl, &statbuf); + setup_tables(&ctl); + if (ctl.check_blocks) + check_blocks(&ctl); + else if (listfile) + get_list_blocks(&ctl, listfile); + + make_root_inode(&ctl); + make_bad_inode(&ctl); + + mark_good_blocks(&ctl); + write_tables(&ctl); + if (close_fd(ctl.device_fd) != 0) + err(MKFS_EX_ERROR, _("write failed")); + + return MKFS_EX_OK; +} diff --git a/disk-utils/mkswap.8 b/disk-utils/mkswap.8 new file mode 100644 index 0000000..27f9221 --- /dev/null +++ b/disk-utils/mkswap.8 @@ -0,0 +1,156 @@ +.\" Copyright 1998 Andries E. Brouwer (aeb@cwi.nl) +.\" +.\" May be distributed under the GNU General Public License +.\" +.TH MKSWAP 8 "March 2009" "util-linux" "System Administration" +.SH NAME +mkswap \- set up a Linux swap area +.SH SYNOPSIS +.B mkswap +[options] +.I device +.RI [ size ] +.SH DESCRIPTION +.B mkswap +sets up a Linux swap area on a device or in a file. + +The +.I device +argument will usually be a disk partition (something like +.IR /dev/sdb7 ) +but can also be a file. +The Linux kernel does not look at partition IDs, but +many installation scripts will assume that partitions +of hex type 82 (LINUX_SWAP) are meant to be swap partitions. +(\fBWarning: Solaris also uses this type. Be careful not to kill +your Solaris partitions.\fP) + +The +.I size +parameter is superfluous but retained for backwards compatibility. +(It specifies the desired size of the swap area in 1024-byte blocks. +.B mkswap +will use the entire partition or file if it is omitted. +Specifying it is unwise \(en a typo may destroy your disk.) + +After creating the swap area, you need the +.B swapon +command to start using it. Usually swap areas are listed in +.I /etc/fstab +so that they can be taken into use at boot time by a +.B swapon \-a +command in some boot script. + +.SH WARNING +The swap header does not touch the first block. A boot loader or disk label +can be there, but it is not a recommended setup. The recommended setup is to +use a separate partition for a Linux swap area. + +.BR mkswap , +like many others mkfs-like utils, +.B erases the first partition block to make any previous filesystem invisible. + +However, +.B mkswap +refuses to erase the first block on a device with a disk +label (SUN, BSD, \&...\&). + +.SH OPTIONS +.TP +.BR \-c , " \-\-check" +Check the device (if it is a block device) for bad blocks +before creating the swap area. +If any bad blocks are found, the count is printed. +.TP +.BR \-f , " \-\-force" +Go ahead even if the command is stupid. +This allows the creation of a swap area larger than the file +or partition it resides on. + +Also, without this option, +.B mkswap +will refuse to erase the first block on a device with a partition table. +.TP +.BR \-L , " \-\-label " \fIlabel\fR +Specify a \fIlabel\fR for the device, to allow +.B swapon +by label. +.TP +.BR \-p , " \-\-pagesize " \fIsize\fR +Specify the page \fIsize\fR (in bytes) to use. This option is usually unnecessary; +.B mkswap +reads the size from the kernel. +.TP +.BR \-U , " \-\-uuid " \fIUUID\fR +Specify the \fIUUID\fR to use. The default is to generate a UUID. +.TP +.BR \-v , " \-\-swapversion 1" +Specify the swap-space version. (This option is currently pointless, as the old +.B \-v 0 +option has become obsolete and now only +.B \-v 1 +is supported. +The kernel has not supported v0 swap-space format since 2.5.22 (June 2002). +The new version v1 is supported since 2.1.117 (August 1998).) +.TP +.BR \-h , " \-\-help" +Display help text and exit. +.TP +.BR \-V , " \-\-version" +Display version information and exit. + +.SH NOTES +The maximum useful size of a swap area depends on the architecture and +the kernel version. + +The maximum number of the pages that is possible to address by swap area header +is 4294967295 (UINT_MAX). The remaining space on the swap device is ignored. + +Presently, Linux allows 32 swap areas. +The areas in use can be seen in the file +.I /proc/swaps + +.B mkswap +refuses areas smaller than 10 pages. + +If you don't know the page size that your machine uses, you may be +able to look it up with "cat /proc/cpuinfo" (or you may not \(en +the contents of this file depend on architecture and kernel version). + +To set up a swap file, it is necessary to create that file before +initializing it with +.BR mkswap , +e.g.\& using a command like + +.nf +.RS +# dd if=/dev/zero of=swapfile bs=1MiB count=$((8*1024)) +.RE +.fi + +to create 8GiB swapfile. + +Note that a swap file must not contain any holes. Using +.BR cp (1) +to create the file is not acceptable. Neither is use of +.BR fallocate (1) +on file systems that support preallocated files, such as +.BR XFS " or " ext4 , +or on copy-on-write filesystems like +.BR btrfs . +It is recommended to use +.BR dd (1) +and /dev/zero in these cases. Please read notes from +.BR swapon (8) +before adding a swap file to copy-on-write filesystems. + +.SH ENVIRONMENT +.IP LIBBLKID_DEBUG=all +enables libblkid debug output. + +.SH "SEE ALSO" +.BR fdisk (8), +.BR swapon (8) +.SH AVAILABILITY +The mkswap command is part of the util-linux package and is available from +https://www.kernel.org/pub/linux/utils/util-linux/. diff --git a/disk-utils/mkswap.c b/disk-utils/mkswap.c new file mode 100644 index 0000000..28a139d --- /dev/null +++ b/disk-utils/mkswap.c @@ -0,0 +1,544 @@ +/* + * mkswap.c - set up a linux swap device + * + * Copyright (C) 1991 Linus Torvalds + * 20.12.91 - time began. Got VM working yesterday by doing this by hand. + * + * Copyright (C) 1999 Jakub Jelinek <jj@ultra.linux.cz> + * Copyright (C) 2007-2014 Karel Zak <kzak@redhat.com> + */ + +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <fcntl.h> +#include <stdlib.h> +#include <limits.h> +#include <sys/utsname.h> +#include <sys/stat.h> +#include <errno.h> +#include <getopt.h> +#include <assert.h> +#ifdef HAVE_LIBSELINUX +#include <selinux/selinux.h> +#include <selinux/context.h> +#endif + +#include "linux_version.h" +#include "swapheader.h" +#include "strutils.h" +#include "nls.h" +#include "blkdev.h" +#include "pathnames.h" +#include "all-io.h" +#include "xalloc.h" +#include "c.h" +#include "closestream.h" +#include "ismounted.h" + +#ifdef HAVE_LIBUUID +# include <uuid.h> +#endif + +#ifdef HAVE_LIBBLKID +# include <blkid.h> +#endif + +#define MIN_GOODPAGES 10 + +#define SELINUX_SWAPFILE_TYPE "swapfile_t" + +struct mkswap_control { + struct swap_header_v1_2 *hdr; /* swap header */ + void *signature_page;/* buffer with swap header */ + + char *devname; /* device or file name */ + struct stat devstat; /* stat() result */ + int fd; /* swap file descriptor */ + + unsigned long long npages; /* number of pages */ + unsigned long nbadpages; /* number of bad pages */ + + int user_pagesize; /* --pagesize */ + int pagesize; /* final pagesize used for the header */ + + char *opt_label; /* LABEL as specified on command line */ + unsigned char *uuid; /* UUID parsed by libbuuid */ + + unsigned int check:1, /* --check */ + force:1; /* --force */ +}; + +static void init_signature_page(struct mkswap_control *ctl) +{ + const int kernel_pagesize = getpagesize(); + + if (ctl->user_pagesize) { + if (ctl->user_pagesize < 0 || !is_power_of_2(ctl->user_pagesize) || + (size_t) ctl->user_pagesize < sizeof(struct swap_header_v1_2) + 10) + errx(EXIT_FAILURE, + _("Bad user-specified page size %u"), + ctl->user_pagesize); + if (ctl->user_pagesize != kernel_pagesize) + warnx(_("Using user-specified page size %d, " + "instead of the system value %d"), + ctl->user_pagesize, kernel_pagesize); + ctl->pagesize = ctl->user_pagesize; + } else + ctl->pagesize = kernel_pagesize; + + ctl->signature_page = xcalloc(1, ctl->pagesize); + ctl->hdr = (struct swap_header_v1_2 *) ctl->signature_page; +} + +static void deinit_signature_page(struct mkswap_control *ctl) +{ + free(ctl->signature_page); + + ctl->hdr = NULL; + ctl->signature_page = NULL; +} + +static void set_signature(const struct mkswap_control *ctl) +{ + char *sp = (char *) ctl->signature_page; + + assert(sp); + memcpy(sp + ctl->pagesize - SWAP_SIGNATURE_SZ, SWAP_SIGNATURE, SWAP_SIGNATURE_SZ); +} + +static void set_uuid_and_label(const struct mkswap_control *ctl) +{ + assert(ctl); + assert(ctl->hdr); + + /* set UUID */ + if (ctl->uuid) + memcpy(ctl->hdr->uuid, ctl->uuid, sizeof(ctl->hdr->uuid)); + + /* set LABEL */ + if (ctl->opt_label) { + xstrncpy(ctl->hdr->volume_name, + ctl->opt_label, sizeof(ctl->hdr->volume_name)); + if (strlen(ctl->opt_label) > strlen(ctl->hdr->volume_name)) + warnx(_("Label was truncated.")); + } + + /* report results */ + if (ctl->uuid || ctl->opt_label) { + if (ctl->opt_label) + printf("LABEL=%s, ", ctl->hdr->volume_name); + else + printf(_("no label, ")); +#ifdef HAVE_LIBUUID + if (ctl->uuid) { + char uuid_string[UUID_STR_LEN]; + uuid_unparse(ctl->uuid, uuid_string); + printf("UUID=%s\n", uuid_string); + } else +#endif + printf(_("no uuid\n")); + } +} + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *out = stdout; + fprintf(out, + _("\nUsage:\n" + " %s [options] device [size]\n"), + program_invocation_short_name); + + fputs(USAGE_SEPARATOR, out); + fputs(_("Set up a Linux swap area.\n"), out); + + fprintf(out, _( + "\nOptions:\n" + " -c, --check check bad blocks before creating the swap area\n" + " -f, --force allow swap size area be larger than device\n" + " -p, --pagesize SIZE specify page size in bytes\n" + " -L, --label LABEL specify label\n" + " -v, --swapversion NUM specify swap-space version number\n" + " -U, --uuid UUID specify the uuid to use\n" + )); + printf(USAGE_HELP_OPTIONS(27)); + + printf(USAGE_MAN_TAIL("mkswap(8)")); + exit(EXIT_SUCCESS); +} + +static void page_bad(struct mkswap_control *ctl, unsigned int page) +{ + const unsigned long max_badpages = + (ctl->pagesize - 1024 - 128 * sizeof(int) - 10) / sizeof(int); + + if (ctl->nbadpages == max_badpages) + errx(EXIT_FAILURE, _("too many bad pages: %lu"), max_badpages); + + ctl->hdr->badpages[ctl->nbadpages] = page; + ctl->nbadpages++; +} + +static void check_blocks(struct mkswap_control *ctl) +{ + unsigned int current_page = 0; + int do_seek = 1; + char *buffer; + + assert(ctl); + assert(ctl->fd > -1); + + buffer = xmalloc(ctl->pagesize); + while (current_page < ctl->npages) { + ssize_t rc; + + if (do_seek && lseek(ctl->fd, current_page * ctl->pagesize, SEEK_SET) != + current_page * ctl->pagesize) + errx(EXIT_FAILURE, _("seek failed in check_blocks")); + + rc = read(ctl->fd, buffer, ctl->pagesize); + do_seek = (rc < 0 || rc != ctl->pagesize); + if (do_seek) + page_bad(ctl, current_page); + current_page++; + } + printf(P_("%lu bad page\n", "%lu bad pages\n", ctl->nbadpages), ctl->nbadpages); + free(buffer); +} + +/* return size in pages */ +static unsigned long long get_size(const struct mkswap_control *ctl) +{ + int fd; + unsigned long long size; + + fd = open(ctl->devname, O_RDONLY); + if (fd < 0) + err(EXIT_FAILURE, _("cannot open %s"), ctl->devname); + if (blkdev_get_size(fd, &size) == 0) + size /= ctl->pagesize; + + close(fd); + return size; +} + +#ifdef HAVE_LIBBLKID +static blkid_probe new_prober(const struct mkswap_control *ctl) +{ + blkid_probe pr = blkid_new_probe(); + if (!pr) + errx(EXIT_FAILURE, _("unable to alloc new libblkid probe")); + if (blkid_probe_set_device(pr, ctl->fd, 0, 0)) + errx(EXIT_FAILURE, _("unable to assign device to libblkid probe")); + return pr; +} +#endif + +static void open_device(struct mkswap_control *ctl) +{ + assert(ctl); + assert(ctl->devname); + + if (stat(ctl->devname, &ctl->devstat) < 0) + err(EXIT_FAILURE, _("stat of %s failed"), ctl->devname); + ctl->fd = open_blkdev_or_file(&ctl->devstat, ctl->devname, O_RDWR); + if (ctl->fd < 0) + err(EXIT_FAILURE, _("cannot open %s"), ctl->devname); + if (ctl->check && S_ISREG(ctl->devstat.st_mode)) { + ctl->check = 0; + warnx(_("warning: checking bad blocks from swap file is not supported: %s"), + ctl->devname); + } +} + +static void wipe_device(struct mkswap_control *ctl) +{ + char *type = NULL; + int zap = 1; +#ifdef HAVE_LIBBLKID + blkid_probe pr = NULL; +#endif + if (!ctl->force) { + const char *v = NULL; + + if (lseek(ctl->fd, 0, SEEK_SET) != 0) + errx(EXIT_FAILURE, _("unable to rewind swap-device")); + +#ifdef HAVE_LIBBLKID + pr = new_prober(ctl); + blkid_probe_enable_partitions(pr, 1); + blkid_probe_enable_superblocks(pr, 0); + + if (blkid_do_fullprobe(pr) == 0 && + blkid_probe_lookup_value(pr, "PTTYPE", &v, NULL) == 0 && v) { + type = xstrdup(v); + zap = 0; + } +#else + /* don't zap if compiled without libblkid */ + zap = 0; +#endif + } + + if (zap) { + /* + * Wipe bootbits + */ + char buf[1024] = { '\0' }; + + if (lseek(ctl->fd, 0, SEEK_SET) != 0) + errx(EXIT_FAILURE, _("unable to rewind swap-device")); + + if (write_all(ctl->fd, buf, sizeof(buf))) + errx(EXIT_FAILURE, _("unable to erase bootbits sectors")); +#ifdef HAVE_LIBBLKID + /* + * Wipe rest of the device + */ + if (!pr) + pr = new_prober(ctl); + + blkid_probe_enable_superblocks(pr, 1); + blkid_probe_enable_partitions(pr, 0); + blkid_probe_set_superblocks_flags(pr, BLKID_SUBLKS_MAGIC|BLKID_SUBLKS_TYPE); + + while (blkid_do_probe(pr) == 0) { + const char *data = NULL; + + if (blkid_probe_lookup_value(pr, "TYPE", &data, NULL) == 0 && data) + warnx(_("%s: warning: wiping old %s signature."), ctl->devname, data); + blkid_do_wipe(pr, 0); + } +#endif + } else { + warnx(_("%s: warning: don't erase bootbits sectors"), + ctl->devname); + if (type) + fprintf(stderr, _(" (%s partition table detected). "), type); + else + fprintf(stderr, _(" (compiled without libblkid). ")); + fprintf(stderr, _("Use -f to force.\n")); + } + free(type); +#ifdef HAVE_LIBBLKID + blkid_free_probe(pr); +#endif +} + +#define SIGNATURE_OFFSET 1024 + +static void write_header_to_device(struct mkswap_control *ctl) +{ + assert(ctl); + assert(ctl->fd > -1); + assert(ctl->signature_page); + + if (lseek(ctl->fd, SIGNATURE_OFFSET, SEEK_SET) != SIGNATURE_OFFSET) + errx(EXIT_FAILURE, _("unable to rewind swap-device")); + + if (write_all(ctl->fd, (char *) ctl->signature_page + SIGNATURE_OFFSET, + ctl->pagesize - SIGNATURE_OFFSET) == -1) + err(EXIT_FAILURE, + _("%s: unable to write signature page"), + ctl->devname); +} + +int main(int argc, char **argv) +{ + struct mkswap_control ctl = { .fd = -1 }; + int c, permMask; + uint64_t sz; + int version = SWAP_VERSION; + char *block_count = NULL, *strsz = NULL; +#ifdef HAVE_LIBUUID + const char *opt_uuid = NULL; + uuid_t uuid_dat; +#endif + static const struct option longopts[] = { + { "check", no_argument, NULL, 'c' }, + { "force", no_argument, NULL, 'f' }, + { "pagesize", required_argument, NULL, 'p' }, + { "label", required_argument, NULL, 'L' }, + { "swapversion", required_argument, NULL, 'v' }, + { "uuid", required_argument, NULL, 'U' }, + { "version", no_argument, NULL, 'V' }, + { "help", no_argument, NULL, 'h' }, + { NULL, 0, NULL, 0 } + }; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + while((c = getopt_long(argc, argv, "cfp:L:v:U:Vh", longopts, NULL)) != -1) { + switch (c) { + case 'c': + ctl.check = 1; + break; + case 'f': + ctl.force = 1; + break; + case 'p': + ctl.user_pagesize = strtou32_or_err(optarg, _("parsing page size failed")); + break; + case 'L': + ctl.opt_label = optarg; + break; + case 'v': + version = strtos32_or_err(optarg, _("parsing version number failed")); + if (version != SWAP_VERSION) + errx(EXIT_FAILURE, + _("swapspace version %d is not supported"), version); + break; + case 'U': +#ifdef HAVE_LIBUUID + opt_uuid = optarg; +#else + warnx(_("warning: ignoring -U (UUIDs are unsupported by %s)"), + program_invocation_short_name); +#endif + break; + case 'V': + printf(UTIL_LINUX_VERSION); + exit(EXIT_SUCCESS); + case 'h': + usage(); + default: + errtryhelp(EXIT_FAILURE); + } + } + + if (optind < argc) + ctl.devname = argv[optind++]; + if (optind < argc) + block_count = argv[optind++]; + if (optind != argc) { + warnx(_("only one device argument is currently supported")); + errtryhelp(EXIT_FAILURE); + } + +#ifdef HAVE_LIBUUID + if(opt_uuid) { + if (uuid_parse(opt_uuid, uuid_dat) != 0) + errx(EXIT_FAILURE, _("error: parsing UUID failed")); + } else + uuid_generate(uuid_dat); + ctl.uuid = uuid_dat; +#endif + + init_signature_page(&ctl); /* get pagesize and allocate signature page */ + + if (!ctl.devname) { + warnx(_("error: Nowhere to set up swap on?")); + errtryhelp(EXIT_FAILURE); + } + if (block_count) { + /* this silly user specified the number of blocks explicitly */ + uint64_t blks = strtou64_or_err(block_count, + _("invalid block count argument")); + ctl.npages = blks / (ctl.pagesize / 1024); + } + + sz = get_size(&ctl); + if (!ctl.npages) + ctl.npages = sz; + else if (ctl.npages > sz && !ctl.force) + errx(EXIT_FAILURE, + _("error: " + "size %llu KiB is larger than device size %"PRIu64" KiB"), + ctl.npages * (ctl.pagesize / 1024), sz * (ctl.pagesize / 1024)); + + if (ctl.npages < MIN_GOODPAGES) + errx(EXIT_FAILURE, + _("error: swap area needs to be at least %ld KiB"), + (long)(MIN_GOODPAGES * ctl.pagesize / 1024)); + if (ctl.npages > UINT32_MAX) { + /* true when swap is bigger than 17.59 terabytes */ + ctl.npages = UINT32_MAX; + warnx(_("warning: truncating swap area to %llu KiB"), + ctl.npages * ctl.pagesize / 1024); + } + + if (is_mounted(ctl.devname)) + errx(EXIT_FAILURE, _("error: " + "%s is mounted; will not make swapspace"), + ctl.devname); + + open_device(&ctl); + permMask = S_ISBLK(ctl.devstat.st_mode) ? 07007 : 07077; + if ((ctl.devstat.st_mode & permMask) != 0) + warnx(_("%s: insecure permissions %04o, %04o suggested."), + ctl.devname, ctl.devstat.st_mode & 07777, + ~permMask & 0666); + if (getuid() == 0 && S_ISREG(ctl.devstat.st_mode) && ctl.devstat.st_uid != 0) + warnx(_("%s: insecure file owner %d, 0 (root) suggested."), + ctl.devname, ctl.devstat.st_uid); + + + if (ctl.check) + check_blocks(&ctl); + + wipe_device(&ctl); + + assert(ctl.hdr); + ctl.hdr->version = version; + ctl.hdr->last_page = ctl.npages - 1; + ctl.hdr->nr_badpages = ctl.nbadpages; + + if ((ctl.npages - MIN_GOODPAGES) < ctl.nbadpages) + errx(EXIT_FAILURE, _("Unable to set up swap-space: unreadable")); + + sz = (ctl.npages - ctl.nbadpages - 1) * ctl.pagesize; + strsz = size_to_human_string(SIZE_SUFFIX_SPACE | SIZE_SUFFIX_3LETTER, sz); + + printf(_("Setting up swapspace version %d, size = %s (%"PRIu64" bytes)\n"), + version, strsz, sz); + free(strsz); + + set_signature(&ctl); + set_uuid_and_label(&ctl); + + write_header_to_device(&ctl); + + deinit_signature_page(&ctl); + +#ifdef HAVE_LIBSELINUX + if (S_ISREG(ctl.devstat.st_mode) && is_selinux_enabled() > 0) { + security_context_t context_string; + security_context_t oldcontext; + context_t newcontext; + + if (fgetfilecon(ctl.fd, &oldcontext) < 0) { + if (errno != ENODATA) + err(EXIT_FAILURE, + _("%s: unable to obtain selinux file label"), + ctl.devname); + if (matchpathcon(ctl.devname, ctl.devstat.st_mode, &oldcontext)) + errx(EXIT_FAILURE, _("unable to matchpathcon()")); + } + if (!(newcontext = context_new(oldcontext))) + errx(EXIT_FAILURE, _("unable to create new selinux context")); + if (context_type_set(newcontext, SELINUX_SWAPFILE_TYPE)) + errx(EXIT_FAILURE, _("couldn't compute selinux context")); + + context_string = context_str(newcontext); + + if (strcmp(context_string, oldcontext)!=0) { + if (fsetfilecon(ctl.fd, context_string) && errno != ENOTSUP) + err(EXIT_FAILURE, _("unable to relabel %s to %s"), + ctl.devname, context_string); + } + context_free(newcontext); + freecon(oldcontext); + } +#endif + /* + * A subsequent swapon() will fail if the signature + * is not actually on disk. (This is a kernel bug.) + * The fsync() in close_fd() will take care of writing. + */ + if (close_fd(ctl.fd) != 0) + err(EXIT_FAILURE, _("write failed")); + return EXIT_SUCCESS; +} diff --git a/disk-utils/partx.8 b/disk-utils/partx.8 new file mode 100644 index 0000000..223f209 --- /dev/null +++ b/disk-utils/partx.8 @@ -0,0 +1,195 @@ +.\" partx.8 -- man page for partx +.\" Copyright 2007 Karel Zak <kzak@redhat.com> +.\" Copyright 2007 Red Hat, Inc. +.\" Copyright 2010 Davidlohr Bueso <dave@gnu.org> +.\" May be distributed under the GNU General Public License +.\" +.TH PARTX "8" "December 2014" "util-linux" "System Administration" +.SH NAME +partx \- tell the kernel about the presence and numbering of on-disk partitions +.SH SYNOPSIS +.B partx +.RB [ \-a | \-d | \-P | \-r | \-s | \-u ] +.RB [ \-t " \fItype\fR]" +.RB [ \-n " \fIM" : \fIN\fR] +.RB [ \- "] " \fIdisk +.br +.B partx +.RB [ \-a | \-d | \-P | \-r | \-s | \-u ] +.RB [ \-t " \fItype\fR]" +.IR partition " [" disk ] +.SH DESCRIPTION +Given a device or disk-image, +.B partx +tries to parse the partition table and list its contents. It +can also tell the kernel to add or remove partitions from its +bookkeeping. +.PP +The +.I disk +argument is optional when a +.I partition +argument is provided. To force scanning a partition as if it were a whole disk +(for example to list nested subpartitions), use the argument "\-" (hyphen-minus). +For example: + +.RS 7 +.TP +partx \-\-show \- /dev/sda3 +.RE +.PP +This will see sda3 as a whole-disk rather than as a partition. +.PP +.B partx is not an fdisk program +\(en adding and removing partitions does not change the disk, it just +tells the kernel about the presence and numbering of on-disk +partitions. +.SH OPTIONS +.TP +.BR \-a , " \-\-add" +Add the specified partitions, or read the disk and add all partitions. +.TP +.BR \-b , " \-\-bytes" +Print the SIZE column in bytes rather than in human-readable format. +.TP +.BR \-d , " \-\-delete" +Delete the specified partitions or all partitions. +.TP +.BR \-g , " \-\-noheadings" +Do not print a header line with \fB\-\-show\fR or \fB\-\-raw\fR. +.TP +.BR \-l , " \-\-list" +List the partitions. Note that all numbers are in 512-byte sectors. +This output format is DEPRECATED in favour of +.BR \-\-show . +Do not use it in newly written scripts. +.TP +.BR \-n , " \-\-nr " \fIM : \fIN +Specify the range of partitions. For backward compatibility also the +format \fIM\fB\-\fIN\fR is supported. +The range may contain negative numbers, for example +.B \-\-nr \-1:\-1 +means the last partition, and +.B \-\-nr \-2:\-1 +means the last two partitions. Supported range specifications are: +.RS 14 +.TP +.I M +Specifies just one partition (e.g.\& \fB\-\-nr 3\fR). +.TP +.IB M : +Specifies the lower limit only (e.g.\& \fB\-\-nr 2:\fR). +.TP +.BI : N +Specifies the upper limit only (e.g.\& \fB\-\-nr :4\fR). +.TP +.IB M : N +Specifies the lower and upper limits (e.g.\& \fB\-\-nr 2:4\fR). +.RE +.TP +.BR \-o , " \-\-output " \fIlist +Define the output columns to use for +.BR \-\-show , +.B \-\-pairs +and +.B \-\-raw +output. If no output arrangement is specified, then a default set is +used. Use +.B \-\-help +to get +.I list +of all supported columns. This option cannot be combined with the +.BR \-\-add , +.BR \-\-delete , +.B \-\-update +or +.B \-\-list +options. +.TP +.B \-\-output\-all +Output all available columns. +.TP +.BR \-P , " \-\-pairs" +List the partitions using the KEY="value" format. +.TP +.BR \-r , " \-\-raw" +List the partitions using the raw output format. +.TP +.BR \-s , " \-\-show" +List the partitions. +The output columns can be selected and rearranged with the +\fB\-\-output\fR option. +All numbers (except SIZE) are in 512-byte sectors. +.TP +.BR \-t , " \-\-type " \fItype +Specify the partition table type. +.TP +.B \-\-list\-types +List supported partition types and exit. +.TP +.BR \-u , " \-\-update" +Update the specified partitions. +.TP +.BR \-S , " \-\-sector\-size " \fIsize +Overwrite default sector size. +.TP +.BR \-v , " \-\-verbose" +Verbose mode. +.TP +.BR \-V , " \-\-version" +Display version information and exit. +.TP +.BR \-h , " \-\-help" +Display help text and exit. +.SH EXAMPLES +.TP +partx \-\-show /dev/sdb3 +.TQ +partx \-\-show \-\-nr 3 /dev/sdb +.TQ +partx \-\-show /dev/sdb3 /dev/sdb +All three commands list partition 3 of /dev/sdb. +.TP +partx \-\-show \- /dev/sdb3 +Lists all subpartitions on /dev/sdb3 (the device is used as +whole-disk). +.TP +partx \-o START \-g \-\-nr 5 /dev/sdb +Prints the start sector of partition 5 on /dev/sdb without header. +.TP +partx \-o SECTORS,SIZE /dev/sda5 /dev/sda +Lists the length in sectors and human-readable size of partition 5 on +/dev/sda. +.TP +partx \-\-add \-\-nr 3:5 /dev/sdd +Adds all available partitions from 3 to 5 (inclusive) on /dev/sdd. +.TP +partx \-d \-\-nr :\-1 /dev/sdd +Removes the last partition on /dev/sdd. +.SH SEE ALSO +.BR addpart (8), +.BR delpart (8), +.BR fdisk (8), +.BR parted (8), +.BR partprobe (8) +.SH AUTHORS +.MT dave@gnu.org +Davidlohr Bueso +.ME +.br +.MT kzak@redhat.com +Karel Zak +.ME +.PP +The original version was written by +.MT aeb@cwi.nl +Andries E.\& Brouwer +.ME . +.SH ENVIRONMENT +.IP LIBBLKID_DEBUG=all +enables libblkid debug output. +.SH AVAILABILITY +The partx command is part of the util-linux package and is available from +.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/ +Linux Kernel Archive +.UE . diff --git a/disk-utils/partx.c b/disk-utils/partx.c new file mode 100644 index 0000000..e10efa9 --- /dev/null +++ b/disk-utils/partx.c @@ -0,0 +1,1071 @@ +/* + * partx: tell the kernel about your disk's partitions + * [This is not an fdisk - adding and removing partitions + * is not a change of the disk, but just telling the kernel + * about presence and numbering of on-disk partitions.] + * + * aeb, 2000-03-21 -- sah is 42 now + * + * Copyright (C) 2010 Davidlohr Bueso <dave@gnu.org> + * Rewritten to use libblkid for util-linux + * based on ideas from Karel Zak <kzak@redhat.com> + */ + +#include <stdio.h> +#include <errno.h> +#include <stdlib.h> +#include <ctype.h> +#include <getopt.h> +#include <unistd.h> +#include <assert.h> +#include <dirent.h> + +#include <blkid.h> +#include <libsmartcols.h> + +#include "c.h" +#include "pathnames.h" +#include "nls.h" +#include "blkdev.h" +#include "strutils.h" +#include "xalloc.h" +#include "partx.h" +#include "sysfs.h" +#include "loopdev.h" +#include "closestream.h" +#include "optutils.h" + +/* this is the default upper limit, could be modified by --nr */ +#define SLICES_MAX 256 + +/* basic table settings */ +enum { + PARTX_RAW = (1 << 0), + PARTX_NOHEADINGS = (1 << 1), + PARTX_EXPORT = (1 << 2), +}; + +/* all the columns (-o option) */ +enum { + COL_PARTNO, + COL_START, + COL_END, + COL_SECTORS, + COL_SIZE, + COL_NAME, + COL_UUID, + COL_TYPE, + COL_FLAGS, + COL_SCHEME, +}; + +#define ACT_ERROR "--{add,delete,show,list,raw,pairs}" +enum { + ACT_NONE, + ACT_LIST, + ACT_SHOW, + ACT_ADD, + ACT_UPD, + ACT_DELETE +}; + +enum { + FL_BYTES = (1 << 1) +}; + +/* column names */ +struct colinfo { + const char *name; /* header */ + double whint; /* width hint (N < 1 is in percent of termwidth) */ + int flags; /* SCOLS_FL_* */ + const char *help; +}; + +/* columns descriptions */ +static struct colinfo infos[] = { + [COL_PARTNO] = { "NR", 0.25, SCOLS_FL_RIGHT, N_("partition number") }, + [COL_START] = { "START", 0.30, SCOLS_FL_RIGHT, N_("start of the partition in sectors") }, + [COL_END] = { "END", 0.30, SCOLS_FL_RIGHT, N_("end of the partition in sectors") }, + [COL_SECTORS] = { "SECTORS", 0.30, SCOLS_FL_RIGHT, N_("number of sectors") }, + [COL_SIZE] = { "SIZE", 0.30, SCOLS_FL_RIGHT, N_("human readable size") }, + [COL_NAME] = { "NAME", 0.30, SCOLS_FL_TRUNC, N_("partition name") }, + [COL_UUID] = { "UUID", 36, 0, N_("partition UUID")}, + [COL_SCHEME] = { "SCHEME", 0.1, SCOLS_FL_TRUNC, N_("partition table type (dos, gpt, ...)")}, + [COL_FLAGS] = { "FLAGS", 0.1, SCOLS_FL_TRUNC, N_("partition flags")}, + [COL_TYPE] = { "TYPE", 1, SCOLS_FL_RIGHT, N_("partition type (a string, a UUID, or hex)")}, +}; + +#define NCOLS ARRAY_SIZE(infos) + +/* array with IDs of enabled columns */ +static int columns[NCOLS]; +static size_t ncolumns; + +static int verbose; +static int partx_flags; +static struct loopdev_cxt lc; +static int loopdev; + +static void assoc_loopdev(const char *fname) +{ + int rc; + + if (loopcxt_init(&lc, 0)) + err(EXIT_FAILURE, _("failed to initialize loopcxt")); + + rc = loopcxt_find_unused(&lc); + if (rc) + err(EXIT_FAILURE, _("%s: failed to find unused loop device"), + fname); + + if (verbose) + printf(_("Trying to use '%s' for the loop device\n"), + loopcxt_get_device(&lc)); + + if (loopcxt_set_backing_file(&lc, fname)) + err(EXIT_FAILURE, _("%s: failed to set backing file"), fname); + + rc = loopcxt_setup_device(&lc); + + if (rc == -EBUSY) + err(EXIT_FAILURE, _("%s: failed to set up loop device"), fname); + + loopdev = 1; +} + +static inline int get_column_id(int num) +{ + assert(ARRAY_SIZE(columns) == NCOLS); + assert((size_t)num < ncolumns); + assert(columns[num] < (int) NCOLS); + return columns[num]; +} + +static inline struct colinfo *get_column_info(int num) +{ + return &infos[ get_column_id(num) ]; +} + +static int column_name_to_id(const char *name, size_t namesz) +{ + size_t i; + + assert(name); + + for (i = 0; i < NCOLS; i++) { + const char *cn = infos[i].name; + + if (!strncasecmp(name, cn, namesz) && !*(cn + namesz)) + return i; + } + warnx(_("unknown column: %s"), name); + return -1; +} + +/* + * Given a partition return the corresponding partition number. + * + * Note that this function tries to use sysfs, otherwise it assumes that the + * last characters are always numeric (sda1, sdc20, etc). + */ +static int get_partno_from_device(char *partition, dev_t devno) +{ + int partno = 0; + size_t sz; + char *p, *end = NULL; + + assert(partition); + + if (devno) { + struct path_cxt *pc; + int rc; + + pc = ul_new_sysfs_path(devno, NULL, NULL); + if (!pc) + goto err; + + rc = ul_path_read_s32(pc, &partno, "partition"); + ul_unref_path(pc); + + if (rc == 0) + return partno; + } + + sz = strlen(partition); + p = partition + sz - 1; + + if (!isdigit((unsigned char) *p)) + goto err; + + while (isdigit((unsigned char) *(p - 1))) p--; + + errno = 0; + partno = strtol(p, &end, 10); + if (errno || !end || *end || p == end) + goto err; + + return partno; +err: + errx(EXIT_FAILURE, _("%s: failed to get partition number"), partition); +} + +static int get_max_partno(const char *disk, dev_t devno) +{ + char path[PATH_MAX], *parent, *dirname = NULL; + struct stat st; + DIR *dir; + struct dirent *d; + int partno = 0; + + if (!devno && !stat(disk, &st)) + devno = st.st_rdev; + if (!devno) + goto dflt; + parent = strrchr(disk, '/'); + if (!parent) + goto dflt; + parent++; + + snprintf(path, sizeof(path), _PATH_SYS_DEVBLOCK "/%d:%d/", + major(devno), minor(devno)); + + dir = opendir(path); + if (!dir) + goto dflt; + + dirname = xstrdup(path); + + while ((d = readdir(dir))) { + int fd; + + if (!strcmp(d->d_name, ".") || + !strcmp(d->d_name, "..")) + continue; +#ifdef _DIRENT_HAVE_D_TYPE + if (d->d_type != DT_DIR && d->d_type != DT_UNKNOWN) + continue; +#endif + if (strncmp(parent, d->d_name, strlen(parent))) + continue; + snprintf(path, sizeof(path), "%s/partition", d->d_name); + + fd = openat(dirfd(dir), path, O_RDONLY); + if (fd) { + int x = 0; + FILE *f = fdopen(fd, "r"); + if (f) { + if (fscanf(f, "%d", &x) == 1 && x > partno) + partno = x; + fclose(f); + } + } + } + + free(dirname); + closedir(dir); + return partno; +dflt: + return SLICES_MAX; +} + +static int recount_range_by_pt(blkid_partlist ls, int *lower, int *upper) +{ + int n = 0, i, nparts = blkid_partlist_numof_partitions(ls); + + for (i = 0; i < nparts; i++) { + blkid_partition par = blkid_partlist_get_partition(ls, i); + int partno = blkid_partition_get_partno(par); + n = max(partno, n); + } + + if (*lower < 0) + *lower = n + *lower + 1; + if (*upper < 0) + *upper = n + *upper + 1; + + if (*lower > *upper && *upper != 0) { + warnx(_("specified range <%d:%d> does not make sense"), *lower, *upper); + return -EINVAL; + } + if (verbose) + printf(_("range recount: max partno=%d, lower=%d, upper=%d\n"), n, *lower, *upper); + return 0; +} + +static void del_parts_warnx(const char *device, int first, int last) +{ + if (first == last) + warnx(_("%s: error deleting partition %d"), device, first); + else + warnx(_("%s: error deleting partitions %d-%d"), + device, first, last); +} + +static int del_parts(int fd, const char *device, dev_t devno, + int lower, int upper) +{ + int rc = 0, i, errfirst = 0, errlast = 0; + + assert(fd >= 0); + assert(device); + + /* recount range by information in /sys */ + if (!lower) + lower = 1; + if (!upper || lower < 0 || upper < 0) { + int n = get_max_partno(device, devno); + if (!upper) + upper = n; + else if (upper < 0) + upper = n + upper + 1; + if (lower < 0) + lower = n + lower + 1; + } + if (lower > upper) { + warnx(_("specified range <%d:%d> " + "does not make sense"), lower, upper); + return -1; + } + + for (i = lower; i <= upper; i++) { + rc = partx_del_partition(fd, i); + if (rc == 0) { + if (verbose) + printf(_("%s: partition #%d removed\n"), device, i); + continue; + } else if (errno == ENXIO) { + if (verbose) + printf(_("%s: partition #%d doesn't exist\n"), device, i); + continue; + } + rc = -1; + if (verbose) + warn(_("%s: deleting partition #%d failed"), device, i); + if (!errfirst) + errlast = errfirst = i; + else if (errlast + 1 == i) + errlast++; + else { + del_parts_warnx(device, errfirst, errlast); + errlast = errfirst = i; + } + } + + if (errfirst) + del_parts_warnx(device, errfirst, errlast); + return rc; +} + + +static void add_parts_warnx(const char *device, int first, int last) +{ + if (first == last) + warnx(_("%s: error adding partition %d"), device, first); + else + warnx(_("%s: error adding partitions %d-%d"), + device, first, last); +} + +static int add_parts(int fd, const char *device, + blkid_partlist ls, int lower, int upper) +{ + int i, nparts, rc, errfirst = 0, errlast = 0; + + assert(fd >= 0); + assert(device); + assert(ls); + + rc = recount_range_by_pt(ls, &lower, &upper); + if (rc) + return rc; + + nparts = blkid_partlist_numof_partitions(ls); + + for (i = 0; i < nparts; i++) { + blkid_partition par = blkid_partlist_get_partition(ls, i); + int n = blkid_partition_get_partno(par); + uintmax_t start, size; + + if (lower && n < lower) + continue; + if (upper && n > upper) + continue; + + start = blkid_partition_get_start(par); + size = blkid_partition_get_size(par); + + if (blkid_partition_is_extended(par)) + /* + * Let's follow the Linux kernel and reduce + * DOS extended partition to 1 or 2 sectors. + */ + size = min(size, (uintmax_t) 2); + + if (partx_add_partition(fd, n, start, size) == 0) { + if (verbose) + printf(_("%s: partition #%d added\n"), device, n); + continue; + } + rc = -1; + if (verbose) + warn(_("%s: adding partition #%d failed"), device, n); + if (!errfirst) + errlast = errfirst = n; + else if (errlast + 1 == n) + errlast++; + else { + add_parts_warnx(device, errfirst, errlast); + errlast = errfirst = n; + } + } + + if (errfirst) + add_parts_warnx(device, errfirst, errlast); + + /* + * The kernel with enabled partitions scanner for loop devices add *all* + * partitions, so we should delete any extra, unwanted ones, when the -n + * option is passed. + */ + if (loopdev && loopcxt_is_partscan(&lc) && (lower || upper)) { + for (i = 0; i < nparts; i++) { + blkid_partition par = blkid_partlist_get_partition(ls, i); + int n = blkid_partition_get_partno(par); + + if (n < lower || n > upper) + partx_del_partition(fd, n); + } + } + + return rc; +} + +static void upd_parts_warnx(const char *device, int first, int last) +{ + if (first == last) + warnx(_("%s: error updating partition %d"), device, first); + else + warnx(_("%s: error updating partitions %d-%d"), + device, first, last); +} + +static int upd_parts(int fd, const char *device, dev_t devno, + blkid_partlist ls, int lower, int upper) +{ + int n, nparts, rc = 0, errfirst = 0, errlast = 0, err; + blkid_partition par; + uintmax_t start, size; + + assert(fd >= 0); + assert(device); + assert(ls); + + /* recount range by information in /sys, if on disk number of + * partitions is greater than in /sys the use on-disk limit */ + nparts = blkid_partlist_numof_partitions(ls); + if (!lower) + lower = 1; + if (!upper || lower < 0 || upper < 0) { + n = get_max_partno(device, devno); + if (!upper) + upper = n > nparts ? n : nparts; + else if (upper < 0) + upper = n + upper + 1; + if (lower < 0) + lower = n + lower + 1; + } + if (lower > upper) { + warnx(_("specified range <%d:%d> " + "does not make sense"), lower, upper); + return -1; + } + + for (n = lower; n <= upper; n++) { + par = blkid_partlist_get_partition_by_partno(ls, n); + if (!par) { + if (verbose) + warn(_("%s: no partition #%d"), device, n); + continue; + } + + start = blkid_partition_get_start(par); + size = blkid_partition_get_size(par); + if (blkid_partition_is_extended(par)) + /* + * Let's follow the Linux kernel and reduce + * DOS extended partition to 1 or 2 sectors. + */ + size = min(size, (uintmax_t) 2); + + err = partx_del_partition(fd, n); + if (err == -1 && errno == ENXIO) + err = 0; /* good, it already doesn't exist */ + if (err == -1 && errno == EBUSY) + { + /* try to resize */ + err = partx_resize_partition(fd, n, start, size); + if (verbose) + printf(_("%s: partition #%d resized\n"), device, n); + if (err == 0) + continue; + } + if (err == 0 && partx_add_partition(fd, n, start, size) == 0) { + if (verbose) + printf(_("%s: partition #%d added\n"), device, n); + continue; + } + + if (err == 0) + continue; + rc = -1; + if (verbose) + warn(_("%s: updating partition #%d failed"), device, n); + if (!errfirst) + errlast = errfirst = n; + else if (errlast + 1 == n) + errlast++; + else { + upd_parts_warnx(device, errfirst, errlast); + errlast = errfirst = n; + } + } + + if (errfirst) + upd_parts_warnx(device, errfirst, errlast); + return rc; +} + +static int list_parts(blkid_partlist ls, int lower, int upper) +{ + int i, nparts, rc; + + assert(ls); + + rc = recount_range_by_pt(ls, &lower, &upper); + if (rc) + return rc; + + nparts = blkid_partlist_numof_partitions(ls); + + for (i = 0; i < nparts; i++) { + blkid_partition par = blkid_partlist_get_partition(ls, i); + int n = blkid_partition_get_partno(par); + uintmax_t start, size; + + if (lower && n < lower) + continue; + if (upper && n > upper) + continue; + + start = blkid_partition_get_start(par); + size = blkid_partition_get_size(par); + + printf(P_("#%2d: %9ju-%9ju (%9ju sector, %6ju MB)\n", + "#%2d: %9ju-%9ju (%9ju sectors, %6ju MB)\n", + size), + n, start, start + size -1, + size, (size << 9) / 1000000); + } + return 0; +} + +static int add_scols_line(struct libscols_table *table, blkid_partition par) +{ + struct libscols_line *line; + int i, rc = 0; + + assert(table); + assert(par); + + line = scols_table_new_line(table, NULL); + if (!line) { + warn(_("failed to allocate output line")); + return -ENOMEM; + } + + for (i = 0; (size_t)i < ncolumns; i++) { + char *str = NULL; /* allocated string */ + const char *cstr = NULL; /* foreign string */ + + switch (get_column_id(i)) { + case COL_PARTNO: + xasprintf(&str, "%d", blkid_partition_get_partno(par)); + break; + case COL_START: + xasprintf(&str, "%ju", blkid_partition_get_start(par)); + break; + case COL_END: + xasprintf(&str, "%ju", + blkid_partition_get_start(par) + + blkid_partition_get_size(par) - 1); + break; + case COL_SECTORS: + xasprintf(&str, "%ju", blkid_partition_get_size(par)); + break; + case COL_SIZE: + if (partx_flags & FL_BYTES) + xasprintf(&str, "%ju", (uintmax_t) + blkid_partition_get_size(par) << 9); + else + str = size_to_human_string(SIZE_SUFFIX_1LETTER, + blkid_partition_get_size(par) << 9); + break; + case COL_NAME: + cstr = blkid_partition_get_name(par); + break; + case COL_UUID: + cstr = blkid_partition_get_uuid(par); + break; + case COL_TYPE: + if (blkid_partition_get_type_string(par)) + cstr = blkid_partition_get_type_string(par); + else + xasprintf(&str, "0x%x", + blkid_partition_get_type(par)); + break; + case COL_FLAGS: + xasprintf(&str, "0x%llx", blkid_partition_get_flags(par)); + break; + case COL_SCHEME: + { + blkid_parttable tab = blkid_partition_get_table(par); + if (tab) + cstr = blkid_parttable_get_type(tab); + break; + } + default: + break; + } + + if (cstr) + rc = scols_line_set_data(line, i, cstr); + else if (str) + rc = scols_line_refer_data(line, i, str); + if (rc) { + warn(_("failed to add output data")); + break; + } + } + + return rc; +} + +static int show_parts(blkid_partlist ls, int scols_flags, int lower, int upper) +{ + int i, rc = -1; + struct libscols_table *table; + int nparts; + + assert(ls); + + nparts = blkid_partlist_numof_partitions(ls); + if (!nparts) + return 0; + + scols_init_debug(0); + table = scols_new_table(); + if (!table) { + warn(_("failed to allocate output table")); + return -1; + } + scols_table_enable_raw(table, !!(scols_flags & PARTX_RAW)); + scols_table_enable_export(table, !!(scols_flags & PARTX_EXPORT)); + scols_table_enable_noheadings(table, !!(scols_flags & PARTX_NOHEADINGS)); + + for (i = 0; (size_t)i < ncolumns; i++) { + struct colinfo *col = get_column_info(i); + + if (!scols_table_new_column(table, col->name, col->whint, col->flags)) { + warnx(_("failed to allocate output column")); + goto done; + } + } + + rc = recount_range_by_pt(ls, &lower, &upper); + if (rc) + goto done; + + for (i = 0; i < nparts; i++) { + blkid_partition par = blkid_partlist_get_partition(ls, i); + int n = blkid_partition_get_partno(par); + + if (lower && n < lower) + continue; + if (upper && n > upper) + continue; + + rc = add_scols_line(table, par); + if (rc) + break; + } + + rc = 0; + scols_print_table(table); +done: + scols_unref_table(table); + return rc; +} + +static blkid_partlist get_partlist(blkid_probe pr, + const char *device, char *type) +{ + blkid_partlist ls; + blkid_parttable tab; + + assert(pr); + assert(device); + + if (type) { + char *name[] = { type, NULL }; + + if (blkid_probe_filter_partitions_type(pr, + BLKID_FLTR_ONLYIN, name)) { + warnx(_("failed to initialize blkid " + "filter for '%s'"), type); + return NULL; + } + } + + ls = blkid_probe_get_partitions(pr); + if (!ls) { + warnx(_("%s: failed to read partition table"), device); + return NULL; + } + + tab = blkid_partlist_get_table(ls); + if (verbose && tab) { + printf(_("%s: partition table type '%s' detected\n"), + device, blkid_parttable_get_type(tab)); + + if (!blkid_partlist_numof_partitions(ls)) + printf(_("%s: partition table with no partitions"), device); + } + + return ls; +} + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *out = stdout; + size_t i; + + fputs(USAGE_HEADER, out); + fprintf(out, + _(" %s [-a|-d|-s|-u] [--nr <n:m> | <partition>] <disk>\n"), + program_invocation_short_name); + + fputs(USAGE_SEPARATOR, out); + fputs(_("Tell the kernel about the presence and numbering of partitions.\n"), out); + + fputs(USAGE_OPTIONS, out); + fputs(_(" -a, --add add specified partitions or all of them\n"), out); + fputs(_(" -d, --delete delete specified partitions or all of them\n"), out); + fputs(_(" -u, --update update specified partitions or all of them\n"), out); + fputs(_(" -s, --show list partitions\n\n"), out); + fputs(_(" -b, --bytes print SIZE in bytes rather than in human readable format\n"), out); + fputs(_(" -g, --noheadings don't print headings for --show\n"), out); + fputs(_(" -n, --nr <n:m> specify the range of partitions (e.g. --nr 2:4)\n"), out); + fputs(_(" -o, --output <list> define which output columns to use\n"), out); + fputs(_(" --output-all output all columns\n"), out); + fputs(_(" -P, --pairs use key=\"value\" output format\n"), out); + fputs(_(" -r, --raw use raw output format\n"), out); + fputs(_(" -S, --sector-size <num> overwrite sector size\n"), out); + fputs(_(" -t, --type <type> specify the partition type\n"), out); + fputs(_(" --list-types list supported partition types and exit\n"), out); + fputs(_(" -v, --verbose verbose mode\n"), out); + + fputs(USAGE_SEPARATOR, out); + printf(USAGE_HELP_OPTIONS(22)); + + fputs(USAGE_COLUMNS, out); + for (i = 0; i < NCOLS; i++) + fprintf(out, " %10s %s\n", infos[i].name, _(infos[i].help)); + + printf(USAGE_MAN_TAIL("partx(8)")); + + exit(EXIT_SUCCESS); +} + +int main(int argc, char **argv) +{ + int fd, c, what = ACT_NONE, lower = 0, upper = 0, rc = 0; + int scols_flags = 0; + char *type = NULL; + char *device = NULL; /* pointer to argv[], ie: /dev/sda1 */ + char *wholedisk = NULL; /* allocated, ie: /dev/sda */ + char *outarg = NULL; + dev_t disk_devno = 0, part_devno = 0; + unsigned int sector_size = 0; + + enum { + OPT_LIST_TYPES = CHAR_MAX + 1, + OPT_OUTPUT_ALL + }; + static const struct option long_opts[] = { + { "bytes", no_argument, NULL, 'b' }, + { "noheadings", no_argument, NULL, 'g' }, + { "raw", no_argument, NULL, 'r' }, + { "list", no_argument, NULL, 'l' }, + { "show", no_argument, NULL, 's' }, + { "add", no_argument, NULL, 'a' }, + { "delete", no_argument, NULL, 'd' }, + { "update", no_argument, NULL, 'u' }, + { "type", required_argument, NULL, 't' }, + { "list-types", no_argument, NULL, OPT_LIST_TYPES }, + { "nr", required_argument, NULL, 'n' }, + { "output", required_argument, NULL, 'o' }, + { "output-all", no_argument, NULL, OPT_OUTPUT_ALL }, + { "pairs", no_argument, NULL, 'P' }, + { "sector-size",required_argument, NULL, 'S' }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + { "verbose", no_argument, NULL, 'v' }, + { NULL, 0, NULL, 0 } + }; + + static const ul_excl_t excl[] = { /* rows and cols in ASCII order */ + { 'P','a','d','l','r','s','u' }, + { 0 } + }; + int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + while ((c = getopt_long(argc, argv, + "abdglrsuvn:t:o:PS:hV", long_opts, NULL)) != -1) { + + err_exclusive_options(c, long_opts, excl, excl_st); + + switch(c) { + case 'a': + what = ACT_ADD; + break; + case 'b': + partx_flags |= FL_BYTES; + break; + case 'd': + what = ACT_DELETE; + break; + case 'g': + scols_flags |= PARTX_NOHEADINGS; + break; + case 'l': + what = ACT_LIST; + break; + case 'n': + if (parse_range(optarg, &lower, &upper, 0)) + errx(EXIT_FAILURE, _("failed to parse --nr <M-N> range")); + break; + case 'o': + outarg = optarg; + break; + case OPT_OUTPUT_ALL: + for (ncolumns = 0; ncolumns < ARRAY_SIZE(infos); ncolumns++) + columns[ncolumns] = ncolumns; + break; + case 'P': + scols_flags |= PARTX_EXPORT; + what = ACT_SHOW; + break; + case 'r': + scols_flags |= PARTX_RAW; + what = ACT_SHOW; + break; + case 's': + what = ACT_SHOW; + break; + case 'S': + sector_size = strtou32_or_err(optarg, _("invalid sector size argument")); + break; + case 't': + type = optarg; + break; + case 'u': + what = ACT_UPD; + break; + case 'v': + verbose = 1; + break; + case OPT_LIST_TYPES: + { + size_t idx = 0; + const char *name = NULL; + + while (blkid_partitions_get_name(idx++, &name) == 0) + puts(name); + return EXIT_SUCCESS; + } + case 'h': + usage(); + case 'V': + printf(UTIL_LINUX_VERSION); + return EXIT_SUCCESS; + default: + errtryhelp(EXIT_FAILURE); + } + } + + if (what == ACT_NONE) + what = ACT_SHOW; + + /* --show default, could by modified by -o */ + if (what == ACT_SHOW && !ncolumns) { + columns[ncolumns++] = COL_PARTNO; + columns[ncolumns++] = COL_START; + columns[ncolumns++] = COL_END; + columns[ncolumns++] = COL_SECTORS; + columns[ncolumns++] = COL_SIZE; + columns[ncolumns++] = COL_NAME; + columns[ncolumns++] = COL_UUID; + } + + if (what == ACT_SHOW && outarg && + string_add_to_idarray(outarg, columns, ARRAY_SIZE(columns), + &ncolumns, column_name_to_id) < 0) + return EXIT_FAILURE; + + /* + * Note that 'partx /dev/sda1' == 'partx /dev/sda1 /dev/sda' + * so assume that the device and/or disk are always the last + * arguments to be passed to partx. + */ + if (optind == argc - 2) { + /* passed 2 arguments: + * /dev/sda1 /dev/sda : partition + whole-disk + * -- /dev/sda1 : partition that should be used as a whole-disk + */ + device = argv[optind]; + + if (strcmp(device, "-") == 0) { + device = NULL; + wholedisk = xstrdup(argv[optind + 1]); + } else { + device = argv[optind]; + wholedisk = xstrdup(argv[optind + 1]); + + if (device && wholedisk && !startswith(device, wholedisk)) + errx(EXIT_FAILURE, _("partition and disk name do not match")); + } + } else if (optind == argc - 1) { + /* passed only one arg (ie: /dev/sda3 or /dev/sda) */ + struct stat sb; + + device = argv[optind]; + + if (stat(device, &sb)) + err(EXIT_FAILURE, _("stat of %s failed"), device); + + part_devno = sb.st_rdev; + + if (blkid_devno_to_wholedisk(part_devno, + NULL, 0, &disk_devno) == 0 && + part_devno != disk_devno) + wholedisk = blkid_devno_to_devname(disk_devno); + + if (!wholedisk) { + wholedisk = xstrdup(device); + disk_devno = part_devno; + device = NULL; + part_devno = 0; + } + } else { + warnx(_("bad usage")); + errtryhelp(EXIT_FAILURE); + } + if (device && (upper || lower)) + errx(EXIT_FAILURE, _("--nr and <partition> are mutually exclusive")); + + assert(wholedisk); + + if (device) { + /* use partno from given partition instead of --nr range, e.g: + * partx -d /dev/sda3 + * is the same like: + * partx -d --nr 3 /dev/sda + */ + struct stat sb; + + if (!part_devno && !stat(device, &sb)) + part_devno = sb.st_rdev; + + lower = upper = get_partno_from_device(device, part_devno); + } + + if (verbose) + printf(_("partition: %s, disk: %s, lower: %d, upper: %d\n"), + device ? device : "none", wholedisk, lower, upper); + + if (what == ACT_ADD || what == ACT_DELETE) { + struct stat x; + + if (stat(wholedisk, &x)) + errx(EXIT_FAILURE, "%s", wholedisk); + + if (S_ISREG(x.st_mode)) { + /* not a blkdev, try to associate it to a loop device */ + if (what == ACT_DELETE) + errx(EXIT_FAILURE, _("%s: cannot delete partitions"), + wholedisk); + if (!loopmod_supports_partscan()) + errx(EXIT_FAILURE, _("%s: partitioned loop devices unsupported"), + wholedisk); + assoc_loopdev(wholedisk); + wholedisk = xstrdup(lc.device); + } else if (!S_ISBLK(x.st_mode)) + errx(EXIT_FAILURE, _("%s: not a block device"), wholedisk); + } + if ((fd = open(wholedisk, O_RDONLY)) == -1) + err(EXIT_FAILURE, _("cannot open %s"), wholedisk); + + if (what == ACT_DELETE) + rc = del_parts(fd, wholedisk, disk_devno, lower, upper); + else { + blkid_probe pr = blkid_new_probe(); + blkid_partlist ls = NULL; + + if (!pr || blkid_probe_set_device(pr, fd, 0, 0)) + warnx(_("%s: failed to initialize blkid prober"), + wholedisk); + else { + if (sector_size) + blkid_probe_set_sectorsize(pr, sector_size); + + ls = get_partlist(pr, wholedisk, type); + } + + if (ls) { + switch (what) { + case ACT_SHOW: + rc = show_parts(ls, scols_flags, lower, upper); + break; + case ACT_LIST: + rc = list_parts(ls, lower, upper); + break; + case ACT_ADD: + rc = add_parts(fd, wholedisk, ls, lower, upper); + break; + case ACT_UPD: + rc = upd_parts(fd, wholedisk, disk_devno, ls, lower, upper); + break; + case ACT_NONE: + break; + default: + abort(); + } + } else + rc = 1; + + blkid_free_probe(pr); + } + + if (loopdev) + loopcxt_deinit(&lc); + + if (close_fd(fd) != 0) + err(EXIT_FAILURE, _("write failed")); + + return rc ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/disk-utils/raw.8 b/disk-utils/raw.8 new file mode 100644 index 0000000..5a3b96e --- /dev/null +++ b/disk-utils/raw.8 @@ -0,0 +1,99 @@ +.TH RAW 8 "August 1999" "util-linux" "System Administration" +.SH NAME +raw \- bind a Linux raw character device +.SH SYNOPSIS +.B raw +.I /dev/raw/raw<N> <major> <minor> +.PP +.B raw +.I /dev/raw/raw<N> /dev/<blockdev> +.PP +.B raw \-q +.I /dev/raw/raw<N> +.PP +.B raw \-qa +.SH DESCRIPTION +.B raw +is used to bind a Linux raw character device to a block device. Any +block device may be used: at the time of binding, the device driver does +not even have to be accessible (it may be loaded on demand as a kernel +module later). +.PP +.B raw +is used in two modes: it either sets raw device bindings, or it queries +existing bindings. When setting a raw device, +.I /dev/raw/raw<N> +is the device name of an existing raw device node in the filesystem. +The block device to which it is to be bound can be specified either in +terms of its +.I major +and +.I minor +device numbers, or as a path name +.I /dev/<blockdev> +to an existing block device file. +.PP +The bindings already in existence can be queried with the +.I \-q +option, which is used either with a raw device filename to query that one +device, or with the +.I \-a +option to query all bound raw devices. +.PP +Unbinding can be done by specifying major and minor 0. +.PP +Once bound to a block device, a raw device can be opened, read and +written, just like the block device it is bound to. However, the raw +device does not behave exactly like the block device. In particular, +access to the raw device bypasses the kernel's block buffer cache +entirely: all I/O is done directly to and from the address space of the +process performing the I/O. If the underlying block device driver can +support DMA, then no data copying at all is required to complete the +I/O. +.PP +Because raw I/O involves direct hardware access to a process's memory, a +few extra restrictions must be observed. All I/Os must be correctly +aligned in memory and on disk: they must start at a sector offset on +disk, they must be an exact number of sectors long, and the data buffer +in virtual memory must also be aligned to a multiple of the sector +size. The sector size is 512 bytes for most devices. +.SH OPTIONS +.TP +\fB\-q\fR, \fB\-\-query\fR +Set query mode. +.B raw +will query an existing binding instead of setting a new one. +.TP +\fB\-a\fR, \fB\-\-all\fR +With +.B \-q +, specify that all bound raw devices should be queried. +.TP +\fB\-h\fR, \fB\-\-help\fR +Display help text and exit. +.TP +\fB\-V\fR, \fB\-\-version\fR +Display version information and exit. + +.SH BUGS +The Linux +.BR dd (1) +command should be used without the \fBbs=\fR option, or the blocksize +needs to be a multiple of the sector size of the device (512 bytes usually), +otherwise it will fail with "Invalid Argument" messages (EINVAL). + +.PP +Raw I/O devices do not maintain cache coherency with the Linux block +device buffer cache. If you use raw I/O to overwrite data already in +the buffer cache, the buffer cache will no longer correspond to the +contents of the actual storage device underneath. This is deliberate, +but is regarded either a bug or a feature depending on who you ask! +.SH NOTES +Rather than using raw devices applications should prefer +.BR open (2) +devices, such as /dev/sda1, with the O_DIRECT flag. +.SH AUTHOR +Stephen Tweedie (sct@redhat.com) +.SH AVAILABILITY +The raw command is part of the util-linux package and is available from +https://www.kernel.org/pub/linux/utils/util-linux/. diff --git a/disk-utils/raw.c b/disk-utils/raw.c new file mode 100644 index 0000000..8abcbb7 --- /dev/null +++ b/disk-utils/raw.c @@ -0,0 +1,277 @@ +/* + * raw.c: User mode tool to bind and query raw character devices. + * + * Stephen Tweedie, 1999, 2000 + * + * This file may be redistributed under the terms of the GNU General + * Public License, version 2. + * + * Copyright Red Hat Software, 1999, 2000 + * + */ + +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <linux/major.h> +#include <linux/raw.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "c.h" +#include "closestream.h" +#include "nls.h" +#include "pathnames.h" + +#define EXIT_RAW_ACCESS 3 +#define EXIT_RAW_IOCTL 4 + +#define RAW_NR_MINORS 8192 + +static int do_query; +static int do_query_all; + +static int master_fd; +static int raw_minor; + +void open_raw_ctl(void); +static int query(int minor_raw, const char *raw_name, int quiet); +static int bind(int minor_raw, int block_major, int block_minor); + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *out = stdout; + fputs(USAGE_HEADER, out); + fprintf(out, + _(" %1$s %2$srawN <major> <minor>\n" + " %1$s %2$srawN /dev/<blockdevice>\n" + " %1$s -q %2$srawN\n" + " %1$s -qa\n"), program_invocation_short_name, + _PATH_RAWDEVDIR); + + fputs(USAGE_SEPARATOR, out); + fputs(_("Bind a raw character device to a block device.\n"), out); + + fputs(USAGE_OPTIONS, out); + fputs(_(" -q, --query set query mode\n"), out); + fputs(_(" -a, --all query all raw devices\n"), out); + printf(USAGE_HELP_OPTIONS(16)); + printf(USAGE_MAN_TAIL("raw(8)")); + exit(EXIT_SUCCESS); +} + +static long strtol_octal_or_err(const char *str, const char *errmesg) +{ + long num; + char *end = NULL; + + if (str == NULL || *str == '\0') + goto err; + errno = 0; + num = strtol(str, &end, 0); + + if (errno || str == end || (end && *end)) + goto err; + + return num; + err: + if (errno) + err(EXIT_FAILURE, "%s: '%s'", errmesg, str); + else + errx(EXIT_FAILURE, "%s: '%s'", errmesg, str); + return 0; +} + +int main(int argc, char *argv[]) +{ + int c; + char *raw_name; + char *block_name; + int retval; + int block_major, block_minor; + int i, rc; + + struct stat statbuf; + + static const struct option longopts[] = { + {"query", no_argument, NULL, 'q'}, + {"all", no_argument, NULL, 'a'}, + {"version", no_argument, NULL, 'V'}, + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, '0'}, + }; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + while ((c = getopt_long(argc, argv, "qaVh", longopts, NULL)) != -1) + switch (c) { + case 'q': + do_query = 1; + break; + case 'a': + do_query_all = 1; + break; + case 'V': + printf(UTIL_LINUX_VERSION); + return EXIT_SUCCESS; + case 'h': + usage(); + default: + errtryhelp(EXIT_FAILURE); + } + + /* + * Check for, and open, the master raw device, /dev/raw + */ + open_raw_ctl(); + + if (do_query_all) { + if (optind < argc) { + warnx(_("bad usage")); + errtryhelp(EXIT_FAILURE); + } + for (i = 1; i < RAW_NR_MINORS; i++) + query(i, NULL, 1); + exit(EXIT_SUCCESS); + } + + /* + * It's a bind or a single query. Either way we need a raw device. + */ + + if (optind >= argc) { + warnx(_("bad usage")); + errtryhelp(EXIT_FAILURE); + } + raw_name = argv[optind++]; + + /* + * try to check the device name before stat(), because on systems with + * udev the raw0 causes a create udev event for char 162/0, which + * causes udev to *remove* /dev/rawctl + */ + rc = sscanf(raw_name, _PATH_RAWDEVDIR "raw%d", &raw_minor); + if (rc != 1) { + warnx(_("bad usage")); + errtryhelp(EXIT_FAILURE); + } + if (raw_minor == 0) + errx(EXIT_RAW_ACCESS, + _("Device '%s' is the control raw device " + "(use raw<N> where <N> is greater than zero)"), + raw_name); + + if (do_query) + return query(raw_minor, raw_name, 0); + + /* + * It's not a query, so we still have some parsing to do. Have we been + * given a block device filename or a major/minor pair? + */ + switch (argc - optind) { + case 1: + block_name = argv[optind]; + retval = stat(block_name, &statbuf); + if (retval) + err(EXIT_RAW_ACCESS, + _("Cannot locate block device '%s'"), block_name); + if (!S_ISBLK(statbuf.st_mode)) + errx(EXIT_RAW_ACCESS, + _("Device '%s' is not a block device"), + block_name); + block_major = major(statbuf.st_rdev); + block_minor = minor(statbuf.st_rdev); + break; + + case 2: + block_major = + strtol_octal_or_err(argv[optind], + _("failed to parse argument")); + block_minor = + strtol_octal_or_err(argv[optind + 1], + _("failed to parse argument")); + break; + + default: + warnx(_("bad usage")); + errtryhelp(EXIT_FAILURE); + } + + return bind(raw_minor, block_major, block_minor); +} + +void open_raw_ctl(void) +{ + master_fd = open(_PATH_RAWDEVCTL, O_RDWR, 0); + if (master_fd < 0) { + master_fd = open(_PATH_RAWDEVCTL_OLD, O_RDWR, 0); + if (master_fd < 0) + err(EXIT_RAW_ACCESS, + _("Cannot open master raw device '%s'"), + _PATH_RAWDEVCTL); + } +} + +static int query(int minor_raw, const char *raw_name, int quiet) +{ + struct raw_config_request rq; + static int has_worked = 0; + + if (raw_name) { + struct stat statbuf; + + if (stat(raw_name, &statbuf) != 0) + err(EXIT_RAW_ACCESS, + _("Cannot locate raw device '%s'"), raw_name); + if (!S_ISCHR(statbuf.st_mode)) + errx(EXIT_RAW_ACCESS, + _("Raw device '%s' is not a character dev"), + raw_name); + if (major(statbuf.st_rdev) != RAW_MAJOR) + errx(EXIT_RAW_ACCESS, + _("Device '%s' is not a raw dev"), raw_name); + minor_raw = minor(statbuf.st_rdev); + } + + rq.raw_minor = minor_raw; + if (ioctl(master_fd, RAW_GETBIND, &rq) < 0) { + if (quiet && errno == ENODEV) + return 3; + if (has_worked && errno == EINVAL) + return 0; + err(EXIT_RAW_IOCTL, _("Error querying raw device")); + } + + /* If one query has worked, mark that fact so that we don't report + * spurious fatal errors if raw(8) has been built to support more raw + * minor numbers than the kernel has. */ + has_worked = 1; + if (quiet && !rq.block_major && !rq.block_minor) + return 0; + printf(_("%sraw%d: bound to major %d, minor %d\n"), + _PATH_RAWDEVDIR, minor_raw, (int)rq.block_major, + (int)rq.block_minor); + return 0; +} + +static int bind(int minor_raw, int block_major, int block_minor) +{ + struct raw_config_request rq; + + rq.raw_minor = minor_raw; + rq.block_major = block_major; + rq.block_minor = block_minor; + if (ioctl(master_fd, RAW_SETBIND, &rq) < 0) + err(EXIT_RAW_IOCTL, _("Error setting raw device")); + printf(_("%sraw%d: bound to major %d, minor %d\n"), + _PATH_RAWDEVDIR, raw_minor, (int)rq.block_major, + (int)rq.block_minor); + return 0; +} diff --git a/disk-utils/resizepart.8 b/disk-utils/resizepart.8 new file mode 100644 index 0000000..a3ea0d2 --- /dev/null +++ b/disk-utils/resizepart.8 @@ -0,0 +1,38 @@ +.\" resizepart.8 -- man page for resizepart +.\" Copyright 2012 Vivek Goyal <vgoyal@redhat.com> +.\" Copyright 2012 Red Hat, Inc. +.\" May be distributed under the GNU General Public License +.TH RESIZEPART 8 "January 2015" "util-linux" "System Administration" +.SH NAME +resizepart \- tell the kernel about the new size of a partition +.SH SYNOPSIS +.B resizepart +.I device partition length +.SH DESCRIPTION +.B resizepart +tells the Linux kernel about the new size of the specified partition. +The command is a simple wrapper around the "resize partition" ioctl. + +This command doesn't manipulate partitions on a block device. + +.SH PARAMETERS +.TP +.I device +The disk device. +.TP +.I partition +The partition number. +.TP +.I length +The new length of the partition (in 512-byte sectors). + +.SH SEE ALSO +.BR addpart (8), +.BR delpart (8), +.BR fdisk (8), +.BR parted (8), +.BR partprobe (8), +.BR partx (8) +.SH AVAILABILITY +The resizepart command is part of the util-linux package and is available from +https://www.kernel.org/pub/linux/utils/util-linux/. diff --git a/disk-utils/resizepart.c b/disk-utils/resizepart.c new file mode 100644 index 0000000..527517f --- /dev/null +++ b/disk-utils/resizepart.c @@ -0,0 +1,119 @@ +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "c.h" +#include "nls.h" +#include "partx.h" +#include "sysfs.h" +#include "strutils.h" +#include "closestream.h" + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *out = stdout; + fputs(USAGE_HEADER, out); + fprintf(out, _(" %s <disk device> <partition number> <length>\n"), + program_invocation_short_name); + + fputs(USAGE_SEPARATOR, out); + fputs(_("Tell the kernel about the new size of a partition.\n"), out); + + fputs(USAGE_OPTIONS, out); + printf(USAGE_HELP_OPTIONS(16)); + printf(USAGE_MAN_TAIL("resizepart(8)")); + exit(EXIT_SUCCESS); +} + +static int get_partition_start(int fd, int partno, uint64_t *start) +{ + struct stat st; + struct path_cxt *disk = NULL, *part = NULL; + dev_t devno = 0; + int rc = -1; + + /* + * wholedisk + */ + if (fstat(fd, &st) || !S_ISBLK(st.st_mode)) + goto done; + devno = st.st_rdev; + disk = ul_new_sysfs_path(devno, NULL, NULL); + if (!disk) + goto done; + /* + * partition + */ + devno = sysfs_blkdev_partno_to_devno(disk, partno); + if (!devno) + goto done; + + part = ul_new_sysfs_path(devno, disk, NULL); + if (!part) + goto done; + if (ul_path_read_u64(part, start, "start")) + goto done; + + rc = 0; +done: + ul_unref_path(part); + ul_unref_path(disk); + return rc; +} + +int main(int argc, char **argv) +{ + int c, fd, partno; + const char *wholedisk; + uint64_t start; + + static const struct option longopts[] = { + {"help", no_argument, NULL, 'h'}, + {"version", no_argument, NULL, 'V'}, + {NULL, 0, NULL, '0'}, + }; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + while ((c = getopt_long(argc, argv, "Vh", longopts, NULL)) != -1) + switch (c) { + case 'V': + printf(UTIL_LINUX_VERSION); + return EXIT_SUCCESS; + case 'h': + usage(); + default: + errtryhelp(EXIT_FAILURE); + } + + if (argc != 4) { + warnx(_("not enough arguments")); + errtryhelp(EXIT_FAILURE); + } + + wholedisk = argv[1]; + partno = strtou32_or_err(argv[2], _("invalid partition number argument")); + + if ((fd = open(wholedisk, O_RDONLY)) < 0) + err(EXIT_FAILURE, _("cannot open %s"), wholedisk); + + if (get_partition_start(fd, partno, &start)) + err(EXIT_FAILURE, _("%s: failed to get start of the partition number %s"), + wholedisk, argv[2]); + + if (partx_resize_partition(fd, partno, start, + strtou64_or_err(argv[3], _("invalid length argument")))) + err(EXIT_FAILURE, _("failed to resize partition")); + + if (close_fd(fd) != 0) + err(EXIT_FAILURE, _("write failed")); + + return 0; +} diff --git a/disk-utils/sfdisk.8 b/disk-utils/sfdisk.8 new file mode 100644 index 0000000..ff54760 --- /dev/null +++ b/disk-utils/sfdisk.8 @@ -0,0 +1,583 @@ +.\" sfdisk.8 -- man page for sfdisk +.\" Copyright (C) 2014 Karel Zak <kzak@redhat.com> +.\" +.\" Permission is granted to make and distribute verbatim copies of this +.\" manual provided the copyright notice and this permission notice are +.\" preserved on all copies. +.\" +.\" Permission is granted to copy and distribute modified versions of this +.\" manual under the conditions for verbatim copying, provided that the +.\" entire resulting derived work is distributed under the terms of a +.\" permission notice identical to this one. +.\" +.TH SFDISK 8 "June 2015" "util-linux" "System Administration" +.SH NAME +sfdisk \- display or manipulate a disk partition table +.SH SYNOPSIS +.B sfdisk +[options] +.I device +.RB [ \-N +.IR partition-number ] +.sp +.B sfdisk +[options] +.I command +.SH DESCRIPTION +.B sfdisk +is a script-oriented tool for partitioning any block device. + +Since version 2.26 +.B sfdisk +supports MBR (DOS), GPT, SUN and SGI disk labels, but no longer provides any +functionality for CHS (Cylinder-Head-Sector) addressing. CHS has +never been important for Linux, and this addressing concept does not make any +sense for new devices. +.sp +.B sfdisk +(since version 2.26) +.B aligns the start and end of partitions +to block-device I/O limits when relative sizes are specified, when the default +values are used or when multiplicative suffixes (e.g. MiB) are used for sizes. +It is possible that partition size will be optimized (reduced or enlarged) due +to alignment if the start offset is specified exactly in sectors and partition +size relative or by multiplicative suffixes. + +The recommended way is not to specify start offsets at all and specify +partition size in MiB, GiB (or so). In this case sfdisk align all partitions +to block-device I/O limits (or when I/O limits are too small then to megabyte +boundary to keep disk layout portable). If this default behaviour is unwanted +(usually for very small partitions) then specify offsets and sizes in +sectors. In this case sfdisk entirely follows specified numbers without any +optimization. +.sp +.B sfdisk +does not create the standard system partitions for SGI and SUN disk labels like +.BR fdisk (8) +does. +It is necessary to explicitly create all partitions including whole-disk system +partitions. + +.B sfdisk +uses BLKRRPART (reread partition table) ioctl to make sure that the device is +not used by system or another tools (see also --no-reread). It's possible that +this feature or another sfdisk activity races with \fBudevd\fR. The recommended way +how to avoid possible collisions is to use exclusive flock for the whole-disk +device to serialize device access. The exclusive lock will cause udevd to skip +the event handling on the device. For example: +.RS +.sp +.nf +.B "flock /dev/sdc sfdisk /dev/sdc" +.fi +.sp +.RE +Note, this semantic is not currently supported by udevd for MD and DM devices. + +.SH COMMANDS +The commands are mutually exclusive. +.TP +.RB [ \-N " \fIpartition-number\fR] " \fIdevice\fR +The default \fBsfdisk\fR command is to read the specification for the desired +partitioning of \fIdevice\fR from standard input, and then create a partition +table according to the specification. See below for the description of the +input format. If standard input is a terminal, then \fBsfdisk\fR starts an +interactive session. +.sp +If the option \fB\-N\fR is specified, then the changes are applied to +the partition addressed by \fIpartition-number\fR. The unspecified fields +of the partition are not modified. +.sp +Note that it's possible to address an unused partition with \fB\-N\fR. +For example, an MBR always contains 4 partitions, but the number of used +partitions may be smaller. In this case \fBsfdisk\fR follows the default +values from the partition table and does not use built-in defaults for the +unused partition given with \fB\-N\fR. See also \fB\-\-append\fR. +.TP +.BR \-A , " \-\-activate \fIdevice " [ \fIpartition-number...] +Switch on the bootable flag for the specified partitions and switch off the +bootable flag on all unspecified partitions. The special placeholder '-' +may be used instead of the partition numbers to switch off the bootable flag +on all partitions. + +The activation command is supported for MBR and PMBR only. If GPT label is detected +than sfdisk prints warning and automatically enter PMBR. + +If no \fIpartition-number\fR is specified, then list the partitions with an +enabled flag. +.TP +.BR "\-\-delete \fIdevice " [ \fIpartition-number ...] +Delete all or the specified partitions. +.TP +.BR \-d , " \-\-dump " \fIdevice\fR +Dump the partitions of a device in a format that is usable as input to \fBsfdisk\fR. +See the section \fBBACKING UP THE PARTITION TABLE\fR. +.TP +.BR \-g , " \-\-show\-geometry " [ \fIdevice ...] +List the geometry of all or the specified devices. For backward +compatibility the deprecated option \fB\-\-show\-pt\-geometry\fR have the same +meaning as this one. +.TP +.BR \-J , " \-\-json " \fIdevice\fR +Dump the partitions of a device in JSON format. Note that \fBsfdisk\fR is +not able to use JSON as input format. +.TP +.BR \-l , " \-\-list " [ \fIdevice ...] +List the partitions of all or the specified devices. This command can be used +together with \fB\-\-verify\fR. +.TP +.BR \-F , " \-\-list-free " [ \fIdevice ...] +List the free unpartitioned areas on all or the specified devices. +.TP +.BR "\-\-part\-attrs \fIdevice partition-number " [ \fIattributes ] +Change the GPT partition attribute bits. If \fIattributes\fR is not specified, +then print the current partition settings. The \fIattributes\fR argument is a +comma- or space-delimited list of bits. The currently supported attribute +bits are: RequiredPartition, NoBlockIOProtocol, LegacyBIOSBootable +and GUID-specific bits in the range from 48 to 63. For example, the string +"RequiredPartition,50,51" sets three bits. +.TP +.BR "\-\-part\-label \fIdevice partition-number " [ \fIlabel ] +Change the GPT partition name (label). If \fIlabel\fR is not specified, +then print the current partition label. +.TP +.BR "\-\-part\-type \fIdevice partition-number " [ \fItype ] +Change the partition type. If \fItype\fR is not specified, then print the +current partition type. The \fItype\fR argument is hexadecimal for MBR, +or a GUID for GPT. For backward compatibility the options \fB\-c\fR and +\fB\-\-id\fR have the same meaning as this one. +.TP +.BR "\-\-part\-uuid \fIdevice partition-number " [ \fIuuid ] +Change the GPT partition UUID. If \fIuuid\fR is not specified, +then print the current partition UUID. +.TP +.BR \-r , " \-\-reorder " \fIdevice +Renumber the partitions, ordering them by their start offset. +.TP +.BR \-s , " \-\-show\-size " [ \fIdevice ...] +List the sizes of all or the specified devices in units of 1024 byte size. +This command is DEPRECATED in favour of +.BR blockdev (1). +.TP +.BR \-T , " \-\-list\-types" +Print all supported types for the current disk label or the label specified by +\fB\-\-label\fR. +.TP +.BR \-V , " \-\-verify " [ \fIdevice ...] +Test whether the partition table and partitions seem correct. + +.SH OPTIONS +.TP +.BR \-a , " \-\-append" +Don't create a new partition table, but only append the specified partitions. +.TP +.BR \-b , " \-\-backup" +Back up the current partition table sectors before starting the partitioning. +The default backup file name is ~/sfdisk-<device>-<offset>.bak; to use another +name see option \fB\-O\fR, \fB\-\-backup\-file\fR. +.TP +.BR \-\-color [ =\fIwhen ] +Colorize the output. The optional argument \fIwhen\fP +can be \fBauto\fR, \fBnever\fR or \fBalways\fR. If the \fIwhen\fR argument is omitted, +it defaults to \fBauto\fR. The colors can be disabled; for the current built-in default +see the \fB\-\-help\fR output. See also the \fBCOLORS\fR section. +.TP +.BR \-f , " \-\-force" +Disable all consistency checking. +.TP +.B \-\-Linux +Deprecated and ignored option. Partitioning that is compatible with +Linux (and other modern operating systems) is the default. +.TP +.BR \-n , " \-\-no\-act" +Do everything except writing to the device. +.TP +.B \-\-no\-reread +Do not check through the re-read-partition-table ioctl whether the device is in use. +.TP +.B \-\-no\-tell\-kernel +Don't tell the kernel about partition changes. This option is recommended together +with \fB\-\-no\-reread\fR to modify a partition on used disk. The modified partition +should not be used (e.g. mounted). +.TP +.BR \-O , " \-\-backup\-file " \fIpath +Override the default backup file name. Note that the device name and offset +are always appended to the file name. +.TP +.BR \-\-move-data [ =\fIpath ] +Move data after partition relocation, for example when moving the beginning +of a partition to another place on the disk. The size of the partition has +to remain the same, the new and old location may overlap. This option requires +option \fB\-N\fR in order to be processed on one specific partition only. + +The \fIpath\fR overrides the default log file name +(the default is ~/sfdisk-<devname>.move). The log file contains information +about all read/write operations on the partition data. + +Note that this operation is risky and not atomic. \fBDon't forget to backup your data!\fR + +In the example below, the first command creates a 100MiB free area before +the first partition and moves the data it contains (e.g. a filesystem), +the next command creates a new partition from the free space (at offset 2048), +and the last command reorders partitions to match disk order +(the original sdc1 will become sdc2). +.RS +.sp +.B "echo '+100M,' | sfdisk --move-data /dev/sdc -N 1" +.br +.B "echo '2048,' | sfdisk /dev/sdc --append +.br +.B sfdisk /dev/sdc --reorder +.sp +.RE + +.TP +.BR \-o , " \-\-output " \fIlist +Specify which output columns to print. Use +.B \-\-help +to get a list of all supported columns. +.sp +The default list of columns may be extended if \fIlist\fP is +specified in the format \fI+list\fP (e.g. \fB-o +UUID\fP). +.TP +.BR \-q , " \-\-quiet" +Suppress extra info messages. +.TP +.BR \-u , " \-\-unit S" +Deprecated option. Only the sector unit is supported. This option is not +supported when using the --show-size command. +.TP +.BR \-X , " \-\-label " \fItype +Specify the disk label type (e.g. \fBdos\fR, \fBgpt\fR, ...). If this option +is not given, then \fBsfdisk\fR defaults to the existing label, but if there +is no label on the device yet, then the type defaults to \fBdos\fR. The default +or the current label may be overwritten by the "label: <name>" script header +line. The option \fB\-\-label\fR does not force \fBsfdisk\fR to create empty +disk label (see the \fBEMPTY DISK LABEL\fR section below). +.TP +.BR \-Y , " \-\-label\-nested " \fItype +Force editing of a nested disk label. The primary disk label has to exist already. +This option allows to edit for example a hybrid/protective MBR on devices with GPT. + +.TP +.BR -w , " \-\-wipe "\fIwhen +Wipe filesystem, RAID and partition-table signatures from the device, in order +to avoid possible collisions. The argument \fIwhen\fR can be \fBauto\fR, +\fBnever\fR or \fBalways\fR. When this option is not given, the default is +\fBauto\fR, in which case signatures are wiped only when in interactive mode; +except the old partition-table signatures which are always wiped before create +a new partition-table if the argument \fIwhen\fR is not \fBnever\fR. In all +cases detected signatures are reported by warning messages before a new +partition table is created. See also +.BR wipefs (8) +command. + +.TP +.BR -W , " \-\-wipe-partitions "\fIwhen +Wipe filesystem, RAID and partition-table signatures from a newly created +partitions, in order to avoid possible collisions. The argument \fIwhen\fR can +be \fBauto\fR, \fBnever\fR or \fBalways\fR. When this option is not given, the +default is \fBauto\fR, in which case signatures are wiped only when in +interactive mode and after confirmation by user. In all cases detected +signatures are reported by warning messages after a new partition is created. +See also +.BR wipefs (8) +command. + +.TP +.BR \-v , " \-\-version" +Display version information and exit. +.TP +.BR \-h , " \-\-help" +Display help text and exit. + +.SH "INPUT FORMATS" +.B sfdisk +supports two input formats and generic header lines. + +.B Header lines +.RS +The optional header lines specify generic information that apply to the partition +table. The header-line format is: +.RS +.sp +.B "<name>: <value>" +.sp +.RE +The currently recognized headers are: +.RS +.TP +.B unit +Specify the partitioning unit. The only supported unit is \fBsectors\fR. +.TP +.B label +Specify the partition table type. For example \fBdos\fR or \fBgpt\fR. +.TP +.B label-id +Specify the partition table identifier. It should be a hexadecimal number +(with a 0x prefix) for MBR and a UUID for GPT. +.TP +.B first-lba +Specify the first usable sector for GPT partitions. +.TP +.B last-lba +Specify the last usable sector for GPT partitions. +.TP +.B table-length +Specify the maximal number of GPT partitions. +.TP +.B grain +Specify minimal size in bytes used to calculate partitions alignment. The +default is 1MiB and it's strongly recommended to use the default. Do not +modify this variable if you're not sure. +.RE +.sp +Note that it is only possible to use header lines before the first partition +is specified in the input. +.RE + +.B Unnamed-fields format +.RS +.RS +.sp +.I start size type bootable +.sp +.RE +where each line fills one partition descriptor. +.sp +Fields are separated by whitespace, comma or semicolon possibly +followed by whitespace; initial and trailing whitespace is ignored. +Numbers can be octal, decimal or hexadecimal; decimal is the default. +When a field is absent, empty or specified as '-' a default value is +used. But when the \fB-N\fR option (change a single partition) is +given, the default for each field is its previous value. +.sp +The default value of +.I start +is the first non-assigned sector aligned according to device I/O limits. +The default start offset for the first partition is 1 MiB. The offset may +be followed by the multiplicative suffixes (KiB, MiB, GiB, TiB, PiB, +EiB, ZiB and YiB) then the number is interpreted as offset in bytes. +.sp +The default value of +.I size +indicates "as much as possible"; i.e. until the next partition or +end-of-device. A numerical argument is by default interpreted as a +number of sectors, however if the size is followed by one of the +multiplicative suffixes (KiB, MiB, GiB, TiB, PiB, EiB, ZiB and YiB) +then the number is interpreted as the size of the partition in bytes +and it is then aligned according to the device I/O limits. A '+' can +be used instead of a number to enlarge the partition as much as +possible. Note '+' is equivalent to the default behaviour for a new +partition; existing partitions will be resized as required. +.sp +The partition +.I type +is given in hex for MBR (DOS), without the 0x prefix, a GUID string for GPT, or +a shortcut: +.RS +.TP +.B L +Linux; means 83 for MBR and 0FC63DAF-8483-4772-8E79-3D69D8477DE4 for GPT. +.TP +.B S +swap area; means 82 for MBR and 0657FD6D-A4AB-43C4-84E5-0933C84B4F4F for GPT +.TP +.B E +extended partition; means 5 for MBR +.TP +.B H +home partition; means 933AC7E1-2EB4-4F13-B844-0E14E2AEF915 for GPT +.TP +.B X +linux extended partition; means 85 for MBR. +.TP +.B U +EFI System partition, means EF for MBR and C12A7328-F81F-11D2-BA4B-00A0C93EC93B for GPT +.TP +.B R +Linux RAID; means FD for MBR and A19D880F-05FC-4D3B-A006-743F0F84911E for GPT +.TP +.B V +LVM; means 8E for MBR and E6D6D379-F507-44C2-A23C-238F2A3DF928 for GPT +.RE +.PP +The default +.I type +value is +.I L + +.I bootable +is specified as [\fB*\fR|\fB-\fR], with as default not-bootable. The +value of this field is irrelevant for Linux - when Linux runs it has +been booted already - but ir might play a role for certain boot +loaders and for other operating systems. +.RE + +.B Named-fields format +.RS +This format is more readable, robust, extensible and allows to specify additional +information (e.g. a UUID). It is recommended to use this format to keep your scripts +more readable. +.RS +.sp +.RI [ "device \fB:" ] " name" [\fB= value "], ..." +.sp +.RE +The +.I device +field is optional. \fBsfdisk\fR extracts the partition number from the +device name. It allows to specify the partitions in random order. +This functionality is mostly used by \fB\-\-dump\fR. +Don't use it if you are not sure. + +The +.I value +can be between quotation marks (e.g. name="This is partition name"). +The currently supported fields are: +.RS +.TP +.BI start= number +The first non-assigned sector aligned according to device I/O limits. The default +start offset for the first partition is 1 MiB. The offset may be followed by +the multiplicative suffixes (KiB, MiB, GiB, TiB, PiB, EiB, ZiB and YiB) then +the number is interpreted as offset in bytes. +.TP +.BI size= number +Specify the partition size in sectors. The number may be followed by the multiplicative +suffixes (KiB, MiB, GiB, TiB, PiB, EiB, ZiB and YiB), then it's interpreted as size +in bytes and the size is aligned according to device I/O limits. +.TP +.B bootable +Mark the partition as bootable. +.TP +.BI attrs= string +Partition attributes, usually GPT partition attribute bits. See +\fB\-\-part\-attrs\fR for more details about the GPT-bits string format. +.TP +.BI uuid= string +GPT partition UUID. +.TP +.BI name= string +GPT partition name. +.TP +.BI type= code +A hexadecimal number (without 0x) for an MBR partition, or a GUID for a GPT partition. +For backward compatibility the \fBId=\fR field has the same meaning. +.RE +.RE + +.SH "EMPTY DISK LABEL" +.B sfdisk +does not create partition table without partitions by default. The lines with +partitions are expected in the script by default. The empty partition table has +to be explicitly requested by "label: <name>" script header line without any +partitions lines. For example: +.RS +.sp +.B "echo 'label: gpt' | sfdisk /dev/sdb" +.sp +.RE +creates empty GPT partition table. Note that the \fB\-\-append\fR disables this feature. + +.SH "BACKING UP THE PARTITION TABLE" +It is recommended to save the layout of your devices. +.B sfdisk +supports two ways. +.sp +Use the \fB\-\-dump\fR option to save a description of the device layout +to a text file. The dump format is suitable for later \fBsfdisk\fR input. +For example: +.RS +.sp +.B "sfdisk --dump /dev/sda > sda.dump" +.sp +.RE +This can later be restored by: +.RS +.sp +.B "sfdisk /dev/sda < sda.dump" +.RE + +If you want to do a full (binary) backup of all sectors where the +partition table is stored, +then use the \fB\-\-backup\fR option. It writes the sectors to +~/sfdisk-<device>-<offset>.bak files. The default name of the backup file can +be changed with the \fB\-\-backup\-file\fR option. The backup files +contain only raw data from the \fIdevice\fR. +Note that the same concept of backup files is used by +.BR wipefs (8). +For example: +.RS +.sp +.B "sfdisk --backup /dev/sda" +.sp +.RE +The GPT header can later be restored by: +.RS +.sp +.nf +.B "dd if=~/sfdisk-sda-0x00000200.bak of=/dev/sda \e" +.B " seek=$((0x00000200)) bs=1 conv=notrunc" +.fi +.sp +.RE +Note that \fBsfdisk\fR since version 2.26 no longer provides the \fB\-I\fR option to +restore sectors. +.BR dd (1) +provides all necessary functionality. + +.SH COLORS +Implicit coloring can be disabled by an empty file \fI/etc/terminal-colors.d/sfdisk.disable\fR. + +See +.BR terminal-colors.d (5) +for more details about colorization configuration. The logical color names +supported by +.B sfdisk +are: +.TP +.B header +The header of the output tables. +.TP +.B warn +The warning messages. +.TP +.B welcome +The welcome message. + +.SH NOTES +Since version 2.26 \fBsfdisk\fR no longer provides the \fB\-R\fR or +\fB\-\-re\-read\fR option to force the kernel to reread the partition table. +Use \fBblockdev \-\-rereadpt\fR instead. +.PP +Since version 2.26 \fBsfdisk\fR does not provide the \fB\-\-DOS\fR, \fB\-\-IBM\fR, \fB\-\-DOS\-extended\fR, +\fB\-\-unhide\fR, \fB\-\-show\-extended\fR, \fB\-\-cylinders\fR, \fB\-\-heads\fR, \fB\-\-sectors\fR, +\fB\-\-inside\-outer\fR, \fB\-\-not\-inside\-outer\fR options. + +.SH ENVIRONMENT +.IP SFDISK_DEBUG=all +enables sfdisk debug output. +.IP LIBFDISK_DEBUG=all +enables libfdisk debug output. +.IP LIBBLKID_DEBUG=all +enables libblkid debug output. +.IP LIBSMARTCOLS_DEBUG=all +enables libsmartcols debug output. + +.SH "SEE ALSO" +.BR fdisk (8), +.BR cfdisk (8), +.BR parted (8), +.BR partprobe (8), +.BR partx (8) + +.SH AUTHOR +Karel Zak <kzak@redhat.com> +.PP +The current sfdisk implementation is based on the original sfdisk +from Andries E. Brouwer. + +.SH AVAILABILITY +The sfdisk command is part of the util-linux package and is available from +https://www.kernel.org/pub/linux/utils/util-linux/. diff --git a/disk-utils/sfdisk.c b/disk-utils/sfdisk.c new file mode 100644 index 0000000..3911dda --- /dev/null +++ b/disk-utils/sfdisk.c @@ -0,0 +1,2226 @@ +/* + * Copyright (C) 1995 Andries E. Brouwer (aeb@cwi.nl) + * Copyright (C) 2014 Karel Zak <kzak@redhat.com> + * + * This program is free software. You can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation: either Version 1 + * or (at your option) any later version. + * + * A.V. Le Blanc (LeBlanc@mcc.ac.uk) wrote Linux fdisk 1992-1994, + * patched by various people (faith@cs.unc.edu, martin@cs.unc.edu, + * leisner@sdsp.mc.xerox.com, esr@snark.thyrsus.com, aeb@cwi.nl) + * 1993-1995, with version numbers (as far as I have seen) 0.93 - 2.0e. + * This program had (head,sector,cylinder) as basic unit, and was + * (therefore) broken in several ways for the use on larger disks - + * for example, my last patch (from 2.0d to 2.0e) was required + * to allow a partition to cross cylinder 8064, and to write an + * extended partition past the 4GB mark. + * + * Karel Zak wrote new sfdisk based on libfdisk from util-linux + * in 2014. + */ + +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include <getopt.h> +#include <sys/stat.h> +#include <assert.h> +#include <fcntl.h> +#include <libsmartcols.h> +#ifdef HAVE_LIBREADLINE +# define _FUNCTION_DEF +# include <readline/readline.h> +#endif +#include <libgen.h> + +#include "c.h" +#include "xalloc.h" +#include "nls.h" +#include "debug.h" +#include "strutils.h" +#include "closestream.h" +#include "colors.h" +#include "blkdev.h" +#include "all-io.h" +#include "rpmatch.h" +#include "optutils.h" + +#include "libfdisk.h" +#include "fdisk-list.h" + +/* + * sfdisk debug stuff (see fdisk.h and include/debug.h) + */ +static UL_DEBUG_DEFINE_MASK(sfdisk); +UL_DEBUG_DEFINE_MASKNAMES(sfdisk) = UL_DEBUG_EMPTY_MASKNAMES; + +#define SFDISKPROG_DEBUG_INIT (1 << 1) +#define SFDISKPROG_DEBUG_PARSE (1 << 2) +#define SFDISKPROG_DEBUG_MISC (1 << 3) +#define SFDISKPROG_DEBUG_ASK (1 << 4) +#define SFDISKPROG_DEBUG_ALL 0xFFFF + +#define DBG(m, x) __UL_DBG(sfdisk, SFDISKPROG_DEBUG_, m, x) +#define ON_DBG(m, x) __UL_DBG_CALL(sfdisk, SFDISKPROG_DEBUG_, m, x) + +enum { + ACT_FDISK = 1, + ACT_ACTIVATE, + ACT_CHANGE_ID, + ACT_DUMP, + ACT_LIST, + ACT_LIST_FREE, + ACT_LIST_TYPES, + ACT_REORDER, + ACT_SHOW_SIZE, + ACT_SHOW_GEOM, + ACT_VERIFY, + ACT_PARTTYPE, + ACT_PARTUUID, + ACT_PARTLABEL, + ACT_PARTATTRS, + ACT_DELETE +}; + +struct sfdisk { + int act; /* ACT_* */ + int partno; /* -N <partno>, default -1 */ + int wipemode; /* remove foreign signatures from disk */ + int pwipemode; /* remove foreign signatures from partitions */ + const char *label; /* --label <label> */ + const char *label_nested; /* --label-nested <label> */ + const char *backup_file; /* -O <path> */ + const char *move_typescript; /* --movedata <typescript> */ + char *prompt; + + struct fdisk_context *cxt; /* libfdisk context */ + struct fdisk_partition *orig_pa; /* -N <partno> before the change */ + + unsigned int verify : 1, /* call fdisk_verify_disklabel() */ + quiet : 1, /* suppress extra messages */ + interactive : 1, /* running on tty */ + noreread : 1, /* don't check device is in use */ + force : 1, /* do also stupid things */ + backup : 1, /* backup sectors before write PT */ + container : 1, /* PT contains container (MBR extended) partitions */ + append : 1, /* don't create new PT, append partitions only */ + json : 1, /* JSON dump */ + movedata: 1, /* move data after resize */ + notell : 1, /* don't tell kernel aout new PT */ + noact : 1; /* do not write to device */ +}; + +#define SFDISK_PROMPT ">>> " + +static void sfdiskprog_init_debug(void) +{ + __UL_INIT_DEBUG_FROM_ENV(sfdisk, SFDISKPROG_DEBUG_, 0, SFDISK_DEBUG); +} + + +static int get_user_reply(const char *prompt, char *buf, size_t bufsz) +{ + char *p; + size_t sz; + +#ifdef HAVE_LIBREADLINE + if (isatty(STDIN_FILENO)) { + p = readline(prompt); + if (!p) + return 1; + memcpy(buf, p, bufsz); + free(p); + } else +#endif + { + fputs(prompt, stdout); + fflush(stdout); + + if (!fgets(buf, bufsz, stdin)) + return 1; + } + + for (p = buf; *p && !isgraph(*p); p++); /* get first non-blank */ + + if (p > buf) + memmove(buf, p, p - buf); /* remove blank space */ + sz = strlen(buf); + if (sz && *(buf + sz - 1) == '\n') + *(buf + sz - 1) = '\0'; + + DBG(ASK, ul_debug("user's reply: >>>%s<<<", buf)); + return 0; +} + +static int ask_callback(struct fdisk_context *cxt __attribute__((__unused__)), + struct fdisk_ask *ask, + void *data) +{ + struct sfdisk *sf = (struct sfdisk *) data; + int rc = 0; + + assert(ask); + + switch(fdisk_ask_get_type(ask)) { + case FDISK_ASKTYPE_INFO: + if (sf->quiet) + break; + fputs(fdisk_ask_print_get_mesg(ask), stdout); + fputc('\n', stdout); + break; + case FDISK_ASKTYPE_WARNX: + fflush(stdout); + color_scheme_fenable("warn", UL_COLOR_RED, stderr); + fputs(fdisk_ask_print_get_mesg(ask), stderr); + color_fdisable(stderr); + fputc('\n', stderr); + break; + case FDISK_ASKTYPE_WARN: + fflush(stdout); + color_scheme_fenable("warn", UL_COLOR_RED, stderr); + fputs(fdisk_ask_print_get_mesg(ask), stderr); + errno = fdisk_ask_print_get_errno(ask); + fprintf(stderr, ": %m\n"); + color_fdisable(stderr); + break; + case FDISK_ASKTYPE_YESNO: + { + char buf[BUFSIZ]; + fputc('\n', stdout); + do { + int x; + fputs(fdisk_ask_get_query(ask), stdout); + rc = get_user_reply(_(" [Y]es/[N]o: "), buf, sizeof(buf)); + if (rc) + break; + x = rpmatch(buf); + if (x == RPMATCH_YES || x == RPMATCH_NO) { + fdisk_ask_yesno_set_result(ask, x); + break; + } + } while(1); + DBG(ASK, ul_debug("yes-no ask: reply '%s' [rc=%d]", buf, rc)); + break; + } + default: + break; + } + return rc; +} + +static void sfdisk_init(struct sfdisk *sf) +{ + fdisk_init_debug(0); + scols_init_debug(0); + sfdiskprog_init_debug(); + + sf->cxt = fdisk_new_context(); + if (!sf->cxt) + err(EXIT_FAILURE, _("failed to allocate libfdisk context")); + fdisk_set_ask(sf->cxt, ask_callback, (void *) sf); + fdisk_enable_bootbits_protection(sf->cxt, 1); + + if (sf->label_nested) { + struct fdisk_context *x = fdisk_new_nested_context(sf->cxt, + sf->label_nested); + if (!x) + err(EXIT_FAILURE, _("failed to allocate nested libfdisk context")); + /* the original context is available by fdisk_get_parent() */ + sf->cxt = x; + } +} + +static int sfdisk_deinit(struct sfdisk *sf) +{ + struct fdisk_context *parent; + + assert(sf); + assert(sf->cxt); + + parent = fdisk_get_parent(sf->cxt); + if (parent) { + fdisk_unref_context(sf->cxt); + sf->cxt = parent; + } + + fdisk_unref_context(sf->cxt); + free(sf->prompt); + + memset(sf, 0, sizeof(*sf)); + return 0; +} + +static struct fdisk_partition *get_partition(struct fdisk_context *cxt, size_t partno) +{ + struct fdisk_table *tb = NULL; + struct fdisk_partition *pa; + + if (fdisk_get_partitions(cxt, &tb) != 0) + return NULL; + + pa = fdisk_table_get_partition_by_partno(tb, partno); + if (pa) + fdisk_ref_partition(pa); + fdisk_unref_table(tb); + return pa; +} + +static void backup_sectors(struct sfdisk *sf, + const char *tpl, + const char *name, + const char *devname, + uint64_t offset, size_t size) +{ + char *fname; + int fd, devfd; + + devfd = fdisk_get_devfd(sf->cxt); + assert(devfd >= 0); + + xasprintf(&fname, "%s0x%08"PRIx64".bak", tpl, offset); + + fd = open(fname, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR); + if (fd < 0) + goto fail; + + if (lseek(devfd, (off_t) offset, SEEK_SET) == (off_t) -1) { + fdisk_warn(sf->cxt, _("cannot seek %s"), devname); + goto fail; + } else { + unsigned char *buf = xmalloc(size); + + if (read_all(devfd, (char *) buf, size) != (ssize_t) size) { + fdisk_warn(sf->cxt, _("cannot read %s"), devname); + free(buf); + goto fail; + } + if (write_all(fd, buf, size) != 0) { + fdisk_warn(sf->cxt, _("cannot write %s"), fname); + free(buf); + goto fail; + } + free(buf); + } + + fdisk_info(sf->cxt, _("%12s (offset %5ju, size %5ju): %s"), + name, (uintmax_t) offset, (uintmax_t) size, fname); + close(fd); + free(fname); + return; +fail: + errx(EXIT_FAILURE, _("%s: failed to create a backup"), devname); +} + +static char *mk_backup_filename_tpl(const char *filename, const char *devname, const char *suffix) +{ + char *tpl = NULL; + char *name, *buf = xstrdup(devname); + + name = basename(buf); + + if (!filename) { + const char *home = getenv ("HOME"); + if (!home) + errx(EXIT_FAILURE, _("failed to create a backup file, $HOME undefined")); + xasprintf(&tpl, "%s/sfdisk-%s%s", home, name, suffix); + } else + xasprintf(&tpl, "%s-%s%s", filename, name, suffix); + + free(buf); + return tpl; +} + + +static void backup_partition_table(struct sfdisk *sf, const char *devname) +{ + const char *name; + char *tpl; + uint64_t offset = 0; + size_t size = 0; + int i = 0; + + assert(sf); + + if (!fdisk_has_label(sf->cxt)) + return; + + tpl = mk_backup_filename_tpl(sf->backup_file, devname, "-"); + + color_scheme_enable("header", UL_COLOR_BOLD); + fdisk_info(sf->cxt, _("Backup files:")); + color_disable(); + + while (fdisk_locate_disklabel(sf->cxt, i++, &name, &offset, &size) == 0 && size) + backup_sectors(sf, tpl, name, devname, offset, size); + + if (!sf->quiet) + fputc('\n', stdout); + free(tpl); +} + +static int move_partition_data(struct sfdisk *sf, size_t partno, struct fdisk_partition *orig_pa) +{ + struct fdisk_partition *pa = get_partition(sf->cxt, partno); + char *devname = NULL, *typescript = NULL, *buf = NULL; + FILE *f = NULL; + int ok = 0, fd, backward = 0; + fdisk_sector_t nsectors, from, to, step, i; + size_t ss, step_bytes, cc; + uintmax_t src, dst; + int errsv; + + assert(sf->movedata); + + if (!pa) + warnx(_("failed to read new partition from device; ignoring --move-data")); + else if (!fdisk_partition_has_size(pa)) + warnx(_("failed to get size of the new partition; ignoring --move-data")); + else if (!fdisk_partition_has_start(pa)) + warnx(_("failed to get start of the new partition; ignoring --move-data")); + else if (!fdisk_partition_has_size(orig_pa)) + warnx(_("failed to get size of the old partition; ignoring --move-data")); + else if (!fdisk_partition_has_start(orig_pa)) + warnx(_("failed to get start of the old partition; ignoring --move-data")); + else if (fdisk_partition_get_start(pa) == fdisk_partition_get_start(orig_pa)) + warnx(_("start of the partition has not been moved; ignoring --move-data")); + else if (fdisk_partition_get_size(orig_pa) < fdisk_partition_get_size(pa)) + warnx(_("new partition is smaller than original; ignoring --move-data")); + else + ok = 1; + if (!ok) + return -EINVAL; + + DBG(MISC, ul_debug("moving data")); + + fd = fdisk_get_devfd(sf->cxt); + + ss = fdisk_get_sector_size(sf->cxt); + nsectors = fdisk_partition_get_size(orig_pa); + from = fdisk_partition_get_start(orig_pa); + to = fdisk_partition_get_start(pa); + + if ((to >= from && from + nsectors >= to) || + (from >= to && to + nsectors >= from)) { + /* source and target overlay, check if we need to copy + * backwardly from end of the source */ + DBG(MISC, ul_debug("overlay between source and target")); + backward = from < to; + DBG(MISC, ul_debug(" copy order: %s", backward ? "backward" : "forward")); + + step = from > to ? from - to : to - from; + if (step > nsectors) + step = nsectors; + } else + step = nsectors; + + /* make step usable for malloc() */ + if (step * ss > (getpagesize() * 256U)) + step = (getpagesize() * 256) / ss; + + /* align the step (note that nsectors does not have to be power of 2) */ + while (nsectors % step) + step--; + + step_bytes = step * ss; + DBG(MISC, ul_debug(" step: %ju (%zu bytes)", (uintmax_t)step, step_bytes)); + +#if defined(POSIX_FADV_SEQUENTIAL) && defined(HAVE_POSIX_FADVISE) + if (!backward) + posix_fadvise(fd, from * ss, nsectors * ss, POSIX_FADV_SEQUENTIAL); +#endif + devname = fdisk_partname(fdisk_get_devname(sf->cxt), partno+1); + typescript = mk_backup_filename_tpl(sf->move_typescript, devname, ".move"); + + if (!sf->quiet) { + fdisk_info(sf->cxt,""); + color_scheme_enable("header", UL_COLOR_BOLD); + fdisk_info(sf->cxt, _("Data move:")); + color_disable(); + fdisk_info(sf->cxt, _(" typescript file: %s"), typescript); + printf(_(" old start: %ju, new start: %ju (move %ju sectors)\n"), + (uintmax_t) from, (uintmax_t) to, (uintmax_t) nsectors); + fflush(stdout); + } + + if (sf->interactive) { + int yes = 0; + fdisk_ask_yesno(sf->cxt, _("Do you want to move partition data?"), &yes); + if (!yes) { + fdisk_info(sf->cxt, _("Leaving.")); + return 0; + } + } + + f = fopen(typescript, "w"); + if (!f) + goto fail; + + /* don't translate */ + fprintf(f, "# sfdisk: " PACKAGE_STRING "\n"); + fprintf(f, "# Disk: %s\n", devname); + fprintf(f, "# Partition: %zu\n", partno + 1); + fprintf(f, "# Operation: move data\n"); + fprintf(f, "# Original start offset (sectors/bytes): %ju/%ju\n", + (uintmax_t)from, (uintmax_t)from * ss); + fprintf(f, "# New start offset (sectors/bytes): %ju/%ju\n", + (uintmax_t)to, (uintmax_t)to * ss); + fprintf(f, "# Area size (sectors/bytes): %ju/%ju\n", + (uintmax_t)nsectors, (uintmax_t)nsectors * ss); + fprintf(f, "# Sector size: %zu\n", ss); + fprintf(f, "# Step size (in bytes): %zu\n", step_bytes); + fprintf(f, "# Steps: %ju\n", (uintmax_t)(nsectors / step)); + fprintf(f, "#\n"); + fprintf(f, "# <step>: <from> <to> (step offsets in bytes)\n"); + + src = (backward ? from + nsectors : from) * ss; + dst = (backward ? to + nsectors : to) * ss; + buf = xmalloc(step_bytes); + + DBG(MISC, ul_debug(" initial: src=%ju dst=%ju", src, dst)); + + for (cc = 1, i = 0; i < nsectors; i += step, cc++) { + ssize_t rc; + + if (backward) + src -= step_bytes, dst -= step_bytes; + + DBG(MISC, ul_debug("#%05zu: src=%ju dst=%ju", cc, src, dst)); + + /* read source */ + if (lseek(fd, src, SEEK_SET) == (off_t) -1) + goto fail; + rc = read(fd, buf, step_bytes); + if (rc < 0 || rc != (ssize_t) step_bytes) + goto fail; + + /* write target */ + if (lseek(fd, dst, SEEK_SET) == (off_t) -1) + goto fail; + rc = write(fd, buf, step_bytes); + if (rc < 0 || rc != (ssize_t) step_bytes) + goto fail; + fsync(fd); + + /* write log */ + fprintf(f, "%05zu: %12ju %12ju\n", cc, src, dst); + +#if defined(POSIX_FADV_DONTNEED) && defined(HAVE_POSIX_FADVISE) + posix_fadvise(fd, src, step_bytes, POSIX_FADV_DONTNEED); +#endif + if (!backward) + src += step_bytes, dst += step_bytes; + } + + fclose(f); + free(buf); + free(devname); + free(typescript); + + return 0; +fail: + errsv = -errno; + warn(_("%s: failed to move data"), devname); + if (f) + fclose(f); + free(buf); + free(devname); + free(typescript); + + return errsv; +} + +static int write_changes(struct sfdisk *sf) +{ + int rc = 0; + + if (sf->noact) + fdisk_info(sf->cxt, _("The partition table is unchanged (--no-act).")); + else { + rc = fdisk_write_disklabel(sf->cxt); + if (rc == 0 && sf->movedata && sf->orig_pa) + rc = move_partition_data(sf, sf->partno, sf->orig_pa); + if (!rc) { + fdisk_info(sf->cxt, _("\nThe partition table has been altered.")); + if (!sf->notell) { + /* Let's wait a little bit. It's possible that our + * system is still busy with a previous re-read + * ioctl (on sfdisk start) or with another task + * related to the write to the device. + */ + xusleep(250000); + fdisk_reread_partition_table(sf->cxt); + } + } + } + if (!rc) + rc = fdisk_deassign_device(sf->cxt, + sf->noact || sf->notell); /* no-sync */ + return rc; +} + +/* + * sfdisk --list [<device ..] + */ +static int command_list_partitions(struct sfdisk *sf, int argc, char **argv) +{ + int fail = 0; + fdisk_enable_listonly(sf->cxt, 1); + + if (argc) { + int i, ct = 0; + + for (i = 0; i < argc; i++) { + if (ct) + fputs("\n\n", stdout); + if (print_device_pt(sf->cxt, argv[i], 1, sf->verify) != 0) + fail++; + ct++; + } + } else + print_all_devices_pt(sf->cxt, sf->verify); + + return fail; +} + +/* + * sfdisk --list-free [<device ..] + */ +static int command_list_freespace(struct sfdisk *sf, int argc, char **argv) +{ + int fail = 0; + fdisk_enable_listonly(sf->cxt, 1); + + if (argc) { + int i, ct = 0; + + for (i = 0; i < argc; i++) { + if (ct) + fputs("\n\n", stdout); + if (print_device_freespace(sf->cxt, argv[i], 1) != 0) + fail++; + ct++; + } + } else + print_all_devices_freespace(sf->cxt); + + return fail; +} + +/* + * sfdisk --list-types + */ +static int command_list_types(struct sfdisk *sf) +{ + const struct fdisk_parttype *t; + struct fdisk_label *lb; + const char *name; + size_t i = 0; + int codes; + + assert(sf); + assert(sf->cxt); + + name = sf->label ? sf->label : "dos"; + lb = fdisk_get_label(sf->cxt, name); + if (!lb) + errx(EXIT_FAILURE, _("unsupported label '%s'"), name); + + codes = fdisk_label_has_code_parttypes(lb); + fputs(_("Id Name\n\n"), stdout); + + while ((t = fdisk_label_get_parttype(lb, i++))) { + if (codes) + printf("%2x %s\n", fdisk_parttype_get_code(t), + fdisk_parttype_get_name(t)); + else + printf("%s %s\n", fdisk_parttype_get_string(t), + fdisk_parttype_get_name(t)); + } + + return 0; +} + +static int verify_device(struct sfdisk *sf, const char *devname) +{ + int rc = 1; + + fdisk_enable_listonly(sf->cxt, 1); + + if (fdisk_assign_device(sf->cxt, devname, 1)) { + warn(_("cannot open %s"), devname); + return 1; + } + + color_scheme_enable("header", UL_COLOR_BOLD); + fdisk_info(sf->cxt, "%s:", devname); + color_disable(); + + if (!fdisk_has_label(sf->cxt)) + fdisk_info(sf->cxt, _("unrecognized partition table type")); + else + rc = fdisk_verify_disklabel(sf->cxt); + + fdisk_deassign_device(sf->cxt, 1); + return rc; +} + +/* + * sfdisk --verify [<device ..] + */ +static int command_verify(struct sfdisk *sf, int argc, char **argv) +{ + int nfails = 0, ct = 0; + + if (argc) { + int i; + for (i = 0; i < argc; i++) { + if (i) + fdisk_info(sf->cxt, " "); + if (verify_device(sf, argv[i]) < 0) + nfails++; + } + } else { + FILE *f = NULL; + char *dev; + + while ((dev = next_proc_partition(&f))) { + if (ct) + fdisk_info(sf->cxt, " "); + if (verify_device(sf, dev) < 0) + nfails++; + free(dev); + ct++; + } + } + + return nfails; +} + +static int get_size(const char *dev, int silent, uintmax_t *sz) +{ + int fd, rc = 0; + + fd = open(dev, O_RDONLY); + if (fd < 0) { + if (!silent) + warn(_("cannot open %s"), dev); + return -errno; + } + + if (blkdev_get_sectors(fd, (unsigned long long *) sz) == -1) { + if (!silent) + warn(_("Cannot get size of %s"), dev); + rc = -errno; + } + + close(fd); + return rc; +} + +/* + * sfdisk --show-size [<device ..] + * + * (silly, but just for backward compatibility) + */ +static int command_show_size(struct sfdisk *sf __attribute__((__unused__)), + int argc, char **argv) +{ + uintmax_t sz; + + if (argc) { + int i; + for (i = 0; i < argc; i++) { + if (get_size(argv[i], 0, &sz) == 0) + printf("%ju\n", sz / 2); + } + } else { + FILE *f = NULL; + uintmax_t total = 0; + char *dev; + + while ((dev = next_proc_partition(&f))) { + if (get_size(dev, 1, &sz) == 0) { + printf("%s: %9ju\n", dev, sz / 2); + total += sz / 2; + } + free(dev); + } + if (total) + printf(_("total: %ju blocks\n"), total); + } + + return 0; +} + +static int print_geom(struct sfdisk *sf, const char *devname) +{ + fdisk_enable_listonly(sf->cxt, 1); + + if (fdisk_assign_device(sf->cxt, devname, 1)) { + warn(_("cannot open %s"), devname); + return 1; + } + + fdisk_info(sf->cxt, "%s: %ju cylinders, %ju heads, %ju sectors/track", + devname, + (uintmax_t) fdisk_get_geom_cylinders(sf->cxt), + (uintmax_t) fdisk_get_geom_heads(sf->cxt), + (uintmax_t) fdisk_get_geom_sectors(sf->cxt)); + + fdisk_deassign_device(sf->cxt, 1); + return 0; +} + +/* + * sfdisk --show-geometry [<device ..] + */ +static int command_show_geometry(struct sfdisk *sf, int argc, char **argv) +{ + int nfails = 0; + + if (argc) { + int i; + for (i = 0; i < argc; i++) { + if (print_geom(sf, argv[i]) < 0) + nfails++; + } + } else { + FILE *f = NULL; + char *dev; + + while ((dev = next_proc_partition(&f))) { + if (print_geom(sf, dev) < 0) + nfails++; + free(dev); + } + } + + return nfails; +} + +/* + * sfdisk --activate <device> [<partno> ...] + */ +static int command_activate(struct sfdisk *sf, int argc, char **argv) +{ + int rc, nparts, i, listonly; + struct fdisk_partition *pa = NULL; + const char *devname = NULL; + + if (argc < 1) + errx(EXIT_FAILURE, _("no disk device specified")); + devname = argv[0]; + + /* --activate <device> */ + listonly = argc == 1; + + rc = fdisk_assign_device(sf->cxt, devname, listonly); + if (rc) + err(EXIT_FAILURE, _("cannot open %s"), devname); + + if (fdisk_is_label(sf->cxt, GPT)) { + if (fdisk_gpt_is_hybrid(sf->cxt)) + errx(EXIT_FAILURE, _("toggle boot flags is unsupported for Hybrid GPT/MBR")); + + /* Switch from GPT to PMBR */ + sf->cxt = fdisk_new_nested_context(sf->cxt, "dos"); + if (!sf->cxt) + err(EXIT_FAILURE, _("cannot switch to PMBR")); + fdisk_info(sf->cxt, _("Activation is unsupported for GPT -- entering nested PMBR.")); + + } else if (!fdisk_is_label(sf->cxt, DOS)) + errx(EXIT_FAILURE, _("toggle boot flags is supported for MBR or PMBR only")); + + if (!listonly && sf->backup) + backup_partition_table(sf, devname); + + nparts = fdisk_get_npartitions(sf->cxt); + for (i = 0; i < nparts; i++) { + char *data = NULL; + + /* note that fdisk_get_partition() reuses the @pa pointer, you + * don't have to (re)allocate it */ + if (fdisk_get_partition(sf->cxt, i, &pa) != 0) + continue; + + /* sfdisk --activate list bootable partitions */ + if (listonly) { + if (!fdisk_partition_is_bootable(pa)) + continue; + if (fdisk_partition_to_string(pa, sf->cxt, + FDISK_FIELD_DEVICE, &data) == 0) { + printf("%s\n", data); + free(data); + } + + /* deactivate all active partitions */ + } else if (fdisk_partition_is_bootable(pa)) + fdisk_toggle_partition_flag(sf->cxt, i, DOS_FLAG_ACTIVE); + } + + /* sfdisk --activate <partno> [..] */ + for (i = 1; i < argc; i++) { + int n; + + if (i == 1 && strcmp(argv[1], "-") == 0) + break; + n = strtou32_or_err(argv[i], _("failed to parse partition number")); + + rc = fdisk_toggle_partition_flag(sf->cxt, n - 1, DOS_FLAG_ACTIVE); + if (rc) + errx(EXIT_FAILURE, + _("%s: partition %d: failed to toggle bootable flag"), + devname, i + 1); + } + + fdisk_unref_partition(pa); + + if (listonly) + rc = fdisk_deassign_device(sf->cxt, 1); + else + rc = write_changes(sf); + return rc; +} + +/* + * sfdisk --delete <device> [<partno> ...] + */ +static int command_delete(struct sfdisk *sf, int argc, char **argv) +{ + size_t i; + const char *devname = NULL; + + if (argc < 1) + errx(EXIT_FAILURE, _("no disk device specified")); + devname = argv[0]; + + if (fdisk_assign_device(sf->cxt, devname, 0) != 0) + err(EXIT_FAILURE, _("cannot open %s"), devname); + + if (sf->backup) + backup_partition_table(sf, devname); + + /* delete all */ + if (argc == 1) { + size_t nparts = fdisk_get_npartitions(sf->cxt); + for (i = 0; i < nparts; i++) { + if (fdisk_is_partition_used(sf->cxt, i) && + fdisk_delete_partition(sf->cxt, i) != 0) + errx(EXIT_FAILURE, _("%s: partition %zu: failed to delete"), devname, i + 1); + } + /* delete specified */ + } else { + for (i = 1; i < (size_t) argc; i++) { + size_t n = strtou32_or_err(argv[i], _("failed to parse partition number")); + + if (fdisk_delete_partition(sf->cxt, n - 1) != 0) + errx(EXIT_FAILURE, _("%s: partition %zu: failed to delete"), devname, n); + } + } + + return write_changes(sf); +} + +/* + * sfdisk --reorder <device> + */ +static int command_reorder(struct sfdisk *sf, int argc, char **argv) +{ + const char *devname = NULL; + int rc; + + if (argc) + devname = argv[0]; + if (!devname) + errx(EXIT_FAILURE, _("no disk device specified")); + + rc = fdisk_assign_device(sf->cxt, devname, 0); /* read-write */ + if (rc) + err(EXIT_FAILURE, _("cannot open %s"), devname); + + if (sf->backup) + backup_partition_table(sf, devname); + + if (fdisk_reorder_partitions(sf->cxt) == 1) /* unchanged */ + rc = fdisk_deassign_device(sf->cxt, 1); + else + rc = write_changes(sf); + + return rc; +} + + +/* + * sfdisk --dump <device> + */ +static int command_dump(struct sfdisk *sf, int argc, char **argv) +{ + const char *devname = NULL; + struct fdisk_script *dp; + int rc; + + if (argc) + devname = argv[0]; + if (!devname) + errx(EXIT_FAILURE, _("no disk device specified")); + + rc = fdisk_assign_device(sf->cxt, devname, 1); /* read-only */ + if (rc) + err(EXIT_FAILURE, _("cannot open %s"), devname); + + if (!fdisk_has_label(sf->cxt)) + errx(EXIT_FAILURE, _("%s: does not contain a recognized partition table"), devname); + + dp = fdisk_new_script(sf->cxt); + if (!dp) + err(EXIT_FAILURE, _("failed to allocate dump struct")); + + rc = fdisk_script_read_context(dp, NULL); + if (rc) + errx(EXIT_FAILURE, _("%s: failed to dump partition table"), devname); + + if (sf->json) + fdisk_script_enable_json(dp, 1); + fdisk_script_write_file(dp, stdout); + + fdisk_unref_script(dp); + fdisk_deassign_device(sf->cxt, 1); /* no-sync() */ + return 0; +} + +static void assign_device_partition(struct sfdisk *sf, + const char *devname, + size_t partno, + int rdonly) +{ + int rc; + size_t n; + struct fdisk_label *lb = NULL; + + assert(sf); + assert(devname); + + /* read-only when a new <type> undefined */ + rc = fdisk_assign_device(sf->cxt, devname, rdonly); + if (rc) + err(EXIT_FAILURE, _("cannot open %s"), devname); + + lb = fdisk_get_label(sf->cxt, NULL); + if (!lb) + errx(EXIT_FAILURE, _("%s: no partition table found"), devname); + + n = fdisk_get_npartitions(sf->cxt); + if (partno > n) + errx(EXIT_FAILURE, _("%s: partition %zu: partition table contains " + "only %zu partitions"), devname, partno, n); + if (!fdisk_is_partition_used(sf->cxt, partno - 1)) + errx(EXIT_FAILURE, _("%s: partition %zu: partition is unused"), + devname, partno); +} + +/* + * sfdisk --part-type <device> <partno> [<type>] + */ +static int command_parttype(struct sfdisk *sf, int argc, char **argv) +{ + size_t partno; + struct fdisk_parttype *type = NULL; + struct fdisk_label *lb; + const char *devname = NULL, *typestr = NULL; + + if (!argc) + errx(EXIT_FAILURE, _("no disk device specified")); + devname = argv[0]; + + if (argc < 2) + errx(EXIT_FAILURE, _("no partition number specified")); + partno = strtou32_or_err(argv[1], _("failed to parse partition number")); + + if (argc == 3) + typestr = argv[2]; + else if (argc > 3) + errx(EXIT_FAILURE, _("unexpected arguments")); + + /* read-only when a new <type> undefined */ + assign_device_partition(sf, devname, partno, !typestr); + + lb = fdisk_get_label(sf->cxt, NULL); + + /* print partition type */ + if (!typestr) { + const struct fdisk_parttype *t = NULL; + struct fdisk_partition *pa = NULL; + + if (fdisk_get_partition(sf->cxt, partno - 1, &pa) == 0) + t = fdisk_partition_get_type(pa); + if (!t) + errx(EXIT_FAILURE, _("%s: partition %zu: failed to get partition type"), + devname, partno); + + if (fdisk_label_has_code_parttypes(lb)) + printf("%2x\n", fdisk_parttype_get_code(t)); + else + printf("%s\n", fdisk_parttype_get_string(t)); + + fdisk_unref_partition(pa); + fdisk_deassign_device(sf->cxt, 1); + return 0; + } + + if (sf->backup) + backup_partition_table(sf, devname); + + /* parse <type> and apply to PT */ + type = fdisk_label_parse_parttype(lb, typestr); + if (!type) + errx(EXIT_FAILURE, _("failed to parse %s partition type '%s'"), + fdisk_label_get_name(lb), typestr); + + else if (fdisk_set_partition_type(sf->cxt, partno - 1, type) != 0) + errx(EXIT_FAILURE, _("%s: partition %zu: failed to set partition type"), + devname, partno); + fdisk_unref_parttype(type); + return write_changes(sf); +} + +/* + * sfdisk --part-uuid <device> <partno> [<uuid>] + */ +static int command_partuuid(struct sfdisk *sf, int argc, char **argv) +{ + size_t partno; + struct fdisk_partition *pa = NULL; + const char *devname = NULL, *uuid = NULL; + + if (!argc) + errx(EXIT_FAILURE, _("no disk device specified")); + devname = argv[0]; + + if (argc < 2) + errx(EXIT_FAILURE, _("no partition number specified")); + partno = strtou32_or_err(argv[1], _("failed to parse partition number")); + + if (argc == 3) + uuid = argv[2]; + else if (argc > 3) + errx(EXIT_FAILURE, _("unexpected arguments")); + + /* read-only if uuid not given */ + assign_device_partition(sf, devname, partno, !uuid); + + /* print partition uuid */ + if (!uuid) { + const char *str = NULL; + + if (fdisk_get_partition(sf->cxt, partno - 1, &pa) == 0) + str = fdisk_partition_get_uuid(pa); + if (!str) + errx(EXIT_FAILURE, _("%s: partition %zu: failed to get partition UUID"), + devname, partno); + printf("%s\n", str); + fdisk_unref_partition(pa); + fdisk_deassign_device(sf->cxt, 1); + return 0; + } + + if (sf->backup) + backup_partition_table(sf, devname); + + pa = fdisk_new_partition(); + if (!pa) + err(EXIT_FAILURE, _("failed to allocate partition object")); + + if (fdisk_partition_set_uuid(pa, uuid) != 0 || + fdisk_set_partition(sf->cxt, partno - 1, pa) != 0) + errx(EXIT_FAILURE, _("%s: partition %zu: failed to set partition UUID"), + devname, partno); + fdisk_unref_partition(pa); + return write_changes(sf); +} + +/* + * sfdisk --part-label <device> <partno> [<label>] + */ +static int command_partlabel(struct sfdisk *sf, int argc, char **argv) +{ + size_t partno; + struct fdisk_partition *pa = NULL; + const char *devname = NULL, *name = NULL; + + if (!argc) + errx(EXIT_FAILURE, _("no disk device specified")); + devname = argv[0]; + + if (argc < 2) + errx(EXIT_FAILURE, _("no partition number specified")); + partno = strtou32_or_err(argv[1], _("failed to parse partition number")); + + if (argc == 3) + name = argv[2]; + else if (argc > 3) + errx(EXIT_FAILURE, _("unexpected arguments")); + + /* read-only if name not given */ + assign_device_partition(sf, devname, partno, !name); + + /* print partition name */ + if (!name) { + const char *str = NULL; + + if (fdisk_get_partition(sf->cxt, partno - 1, &pa) == 0) + str = fdisk_partition_get_name(pa); + if (!str) + errx(EXIT_FAILURE, _("%s: partition %zu: failed to get partition name"), + devname, partno); + printf("%s\n", str); + fdisk_unref_partition(pa); + fdisk_deassign_device(sf->cxt, 1); + return 0; + } + + if (sf->backup) + backup_partition_table(sf, devname); + + pa = fdisk_new_partition(); + if (!pa) + err(EXIT_FAILURE, _("failed to allocate partition object")); + + if (fdisk_partition_set_name(pa, name) != 0 || + fdisk_set_partition(sf->cxt, partno - 1, pa) != 0) + errx(EXIT_FAILURE, _("%s: partition %zu: failed to set partition name"), + devname, partno); + + fdisk_unref_partition(pa); + return write_changes(sf); +} + +/* + * sfdisk --part-attrs <device> <partno> [<attrs>] + */ +static int command_partattrs(struct sfdisk *sf, int argc, char **argv) +{ + size_t partno; + struct fdisk_partition *pa = NULL; + const char *devname = NULL, *attrs = NULL; + + if (!argc) + errx(EXIT_FAILURE, _("no disk device specified")); + devname = argv[0]; + + if (argc < 2) + errx(EXIT_FAILURE, _("no partition number specified")); + partno = strtou32_or_err(argv[1], _("failed to parse partition number")); + + if (argc == 3) + attrs = argv[2]; + else if (argc > 3) + errx(EXIT_FAILURE, _("unexpected arguments")); + + /* read-only if name not given */ + assign_device_partition(sf, devname, partno, !attrs); + + /* print partition name */ + if (!attrs) { + const char *str = NULL; + + if (fdisk_get_partition(sf->cxt, partno - 1, &pa) == 0) + str = fdisk_partition_get_attrs(pa); + if (str) + printf("%s\n", str); + fdisk_unref_partition(pa); + fdisk_deassign_device(sf->cxt, 1); + return 0; + } + + if (sf->backup) + backup_partition_table(sf, devname); + + pa = fdisk_new_partition(); + if (!pa) + err(EXIT_FAILURE, _("failed to allocate partition object")); + + if (fdisk_partition_set_attrs(pa, attrs) != 0 || + fdisk_set_partition(sf->cxt, partno - 1, pa) != 0) + errx(EXIT_FAILURE, _("%s: partition %zu: failed to set partition attributes"), + devname, partno); + + fdisk_unref_partition(pa); + return write_changes(sf); +} + +static void sfdisk_print_partition(struct sfdisk *sf, size_t n) +{ + struct fdisk_partition *pa = NULL; + char *data; + + assert(sf); + + if (sf->quiet) + return; + if (fdisk_get_partition(sf->cxt, n, &pa) != 0) + return; + + fdisk_partition_to_string(pa, sf->cxt, FDISK_FIELD_DEVICE, &data); + printf("%12s : ", data); + + fdisk_partition_to_string(pa, sf->cxt, FDISK_FIELD_START, &data); + printf("%12s ", data); + + fdisk_partition_to_string(pa, sf->cxt, FDISK_FIELD_END, &data); + printf("%12s ", data); + + fdisk_partition_to_string(pa, sf->cxt, FDISK_FIELD_SIZE, &data); + printf("(%s) ", data); + + fdisk_partition_to_string(pa, sf->cxt, FDISK_FIELD_TYPE, &data); + printf("%s\n", data); + + fdisk_unref_partition(pa); +} + +static void command_fdisk_help(void) +{ + fputs(_("\nHelp:\n"), stdout); + + fputc('\n', stdout); + color_scheme_enable("help-title", UL_COLOR_BOLD); + fputs(_(" Commands:\n"), stdout); + color_disable(); + fputs(_(" write write table to disk and exit\n"), stdout); + fputs(_(" quit show new situation and wait for user's feedback before write\n"), stdout); + fputs(_(" abort exit sfdisk shell\n"), stdout); + fputs(_(" print display the partition table\n"), stdout); + fputs(_(" help show this help text\n"), stdout); + fputc('\n', stdout); + fputs(_(" Ctrl-D the same as 'quit'\n"), stdout); + + fputc('\n', stdout); + color_scheme_enable("help-title", UL_COLOR_BOLD); + fputs(_(" Input format:\n"), stdout); + color_disable(); + fputs(_(" <start>, <size>, <type>, <bootable>\n"), stdout); + + fputc('\n', stdout); + fputs(_(" <start> Beginning of the partition in sectors, or bytes if\n" + " specified in the format <number>{K,M,G,T,P,E,Z,Y}.\n" + " The default is the first free space.\n"), stdout); + + fputc('\n', stdout); + fputs(_(" <size> Size of the partition in sectors, or bytes if\n" + " specified in the format <number>{K,M,G,T,P,E,Z,Y}.\n" + " The default is all available space.\n"), stdout); + + fputc('\n', stdout); + fputs(_(" <type> The partition type. Default is a Linux data partition.\n"), stdout); + fputs(_(" MBR: hex or L,S,E,X,U,R,V shortcuts.\n"), stdout); + fputs(_(" GPT: UUID or L,S,H,U,R,V shortcuts.\n"), stdout); + + fputc('\n', stdout); + fputs(_(" <bootable> Use '*' to mark an MBR partition as bootable.\n"), stdout); + + fputc('\n', stdout); + color_scheme_enable("help-title", UL_COLOR_BOLD); + fputs(_(" Example:\n"), stdout); + color_disable(); + fputs(_(" , 4G Creates a 4GiB partition at default start offset.\n"), stdout); + fputc('\n', stdout); +} + +enum { + SFDISK_DONE_NONE = 0, + SFDISK_DONE_EOF, + SFDISK_DONE_ABORT, + SFDISK_DONE_WRITE, + SFDISK_DONE_ASK +}; + +/* returns: 0 on success, <0 on error, 1 successfully stop sfdisk */ +static int loop_control_commands(struct sfdisk *sf, + struct fdisk_script *dp, + char *buf) +{ + const char *p = skip_blank(buf); + int rc = SFDISK_DONE_NONE; + + if (strcmp(p, "print") == 0) + list_disklabel(sf->cxt); + else if (strcmp(p, "help") == 0) + command_fdisk_help(); + else if (strcmp(p, "quit") == 0) + rc = SFDISK_DONE_ASK; + else if (strcmp(p, "write") == 0) + rc = SFDISK_DONE_WRITE; + else if (strcmp(p, "abort") == 0) + rc = SFDISK_DONE_ABORT; + else { + if (sf->interactive) + fdisk_warnx(sf->cxt, _("unsupported command")); + else { + fdisk_warnx(sf->cxt, _("line %d: unsupported command"), + fdisk_script_get_nlines(dp)); + rc = -EINVAL; + } + } + return rc; +} + +static int has_container(struct sfdisk *sf) +{ + size_t i, nparts; + struct fdisk_partition *pa = NULL; + + if (sf->container) + return sf->container; + + nparts = fdisk_get_npartitions(sf->cxt); + for (i = 0; i < nparts; i++) { + if (fdisk_get_partition(sf->cxt, i, &pa) != 0) + continue; + if (fdisk_partition_is_container(pa)) { + sf->container = 1; + break; + } + } + + fdisk_unref_partition(pa); + return sf->container; +} + +static size_t last_pt_partno(struct sfdisk *sf) +{ + size_t i, nparts, partno = 0; + struct fdisk_partition *pa = NULL; + + + nparts = fdisk_get_npartitions(sf->cxt); + for (i = 0; i < nparts; i++) { + size_t x; + + if (fdisk_get_partition(sf->cxt, i, &pa) != 0 || + !fdisk_partition_is_used(pa)) + continue; + x = fdisk_partition_get_partno(pa); + if (x > partno) + partno = x; + } + + fdisk_unref_partition(pa); + return partno; +} + +#ifdef HAVE_LIBREADLINE +static char *sfdisk_fgets(struct fdisk_script *dp, + char *buf, size_t bufsz, FILE *f) +{ + struct sfdisk *sf = (struct sfdisk *) fdisk_script_get_userdata(dp); + + assert(dp); + assert(buf); + assert(bufsz > 2); + + if (sf->interactive) { + char *p = readline(sf->prompt); + size_t len; + + if (!p) + return NULL; + len = strlen(p); + if (len > bufsz - 2) + len = bufsz - 2; + + memcpy(buf, p, len); + buf[len] = '\n'; /* append \n to be compatible with libc fgetc() */ + buf[len + 1] = '\0'; + free(p); + fflush(stdout); + return buf; + } + return fgets(buf, bufsz, f); +} +#endif + +static int ignore_partition(struct fdisk_partition *pa) +{ + /* incomplete partition setting */ + if (!fdisk_partition_has_start(pa) && !fdisk_partition_start_is_default(pa)) + return 1; + if (!fdisk_partition_has_size(pa) && !fdisk_partition_end_is_default(pa)) + return 1; + + /* probably dump from old sfdisk with start=0 size=0 */ + if (fdisk_partition_has_start(pa) && fdisk_partition_get_start(pa) == 0 && + fdisk_partition_has_size(pa) && fdisk_partition_get_size(pa) == 0) + return 1; + + return 0; +} + +static void follow_wipe_mode(struct sfdisk *sf) +{ + int dowipe = sf->wipemode == WIPEMODE_ALWAYS ? 1 : 0; + + if (sf->interactive && sf->wipemode == WIPEMODE_AUTO) + dowipe = 1; /* do it in interactive mode */ + + if (fdisk_is_ptcollision(sf->cxt) && sf->wipemode != WIPEMODE_NEVER) + dowipe = 1; /* always wipe old PT */ + + fdisk_enable_wipe(sf->cxt, dowipe); + if (sf->quiet) + return; + + if (dowipe) { + if (!fdisk_is_ptcollision(sf->cxt)) { + fdisk_info(sf->cxt, _("The old %s signature will be removed by a write command."), + fdisk_get_collision(sf->cxt)); + fputc('\n', stderr); + } + } else { + fdisk_warnx(sf->cxt, _( + "The old %s signature may remain on the device. " + "It is recommended to wipe the device with wipefs(8) or " + "sfdisk --wipe, in order to avoid possible collisions."), + fdisk_get_collision(sf->cxt)); + fputc('\n', stderr); + } +} + +static int wipe_partition(struct sfdisk *sf, size_t partno) +{ + int rc, yes = 0; + char *fstype = NULL; + struct fdisk_partition *tmp = NULL; + + DBG(MISC, ul_debug("checking for signature")); + + rc = fdisk_get_partition(sf->cxt, partno, &tmp); + if (rc) + goto done; + + rc = fdisk_partition_to_string(tmp, sf->cxt, FDISK_FIELD_FSTYPE, &fstype); + if (rc || fstype == NULL) + goto done; + + fdisk_warnx(sf->cxt, _("Partition #%zu contains a %s signature."), partno + 1, fstype); + + if (sf->pwipemode == WIPEMODE_AUTO && isatty(STDIN_FILENO)) + fdisk_ask_yesno(sf->cxt, _("Do you want to remove the signature?"), &yes); + else if (sf->pwipemode == WIPEMODE_ALWAYS) + yes = 1; + + if (yes) { + fdisk_info(sf->cxt, _("The signature will be removed by a write command.")); + rc = fdisk_wipe_partition(sf->cxt, partno, TRUE); + } +done: + fdisk_unref_partition(tmp); + free(fstype); + DBG(MISC, ul_debug("partition wipe check end [rc=%d]", rc)); + return rc; +} + +static void refresh_prompt_buffer(struct sfdisk *sf, const char *devname, + size_t next_partno, int created) +{ + if (created) { + char *partname = fdisk_partname(devname, next_partno + 1); + if (!partname) + err(EXIT_FAILURE, _("failed to allocate partition name")); + + if (!sf->prompt || !startswith(sf->prompt, partname)) { + free(sf->prompt); + xasprintf(&sf->prompt,"%s: ", partname); + } + free(partname); + } else if (!sf->prompt || !startswith(sf->prompt, SFDISK_PROMPT)) { + free(sf->prompt); + sf->prompt = xstrdup(SFDISK_PROMPT); + } +} + +/* + * sfdisk <device> [[-N] <partno>] + * + * Note that the option -N is there for backward compatibility only. + */ +static int command_fdisk(struct sfdisk *sf, int argc, char **argv) +{ + int rc = 0, partno = sf->partno, created = 0, unused = 0; + struct fdisk_script *dp; + struct fdisk_table *tb = NULL; + const char *devname = NULL, *label; + char buf[BUFSIZ]; + size_t next_partno = (size_t) -1; + + if (argc) + devname = argv[0]; + if (partno < 0 && argc > 1) + partno = strtou32_or_err(argv[1], + _("failed to parse partition number")); + if (!devname) + errx(EXIT_FAILURE, _("no disk device specified")); + + rc = fdisk_assign_device(sf->cxt, devname, 0); + if (rc) + err(EXIT_FAILURE, _("cannot open %s"), devname); + + dp = fdisk_new_script(sf->cxt); + if (!dp) + err(EXIT_FAILURE, _("failed to allocate script handler")); + fdisk_set_script(sf->cxt, dp); +#ifdef HAVE_LIBREADLINE + fdisk_script_set_fgets(dp, sfdisk_fgets); +#endif + fdisk_script_set_userdata(dp, (void *) sf); + + /* + * Don't create a new disklabel when [-N] <partno> specified. In this + * case reuse already specified disklabel. Let's check that the disk + * really contains the partition. + */ + if (partno >= 0) { + size_t n; + + if (!fdisk_has_label(sf->cxt)) + errx(EXIT_FAILURE, _("%s: cannot modify partition %d: " + "no partition table was found"), + devname, partno + 1); + n = fdisk_get_npartitions(sf->cxt); + if ((size_t) partno > n) + errx(EXIT_FAILURE, _("%s: cannot modify partition %d: " + "partition table contains only %zu " + "partitions"), + devname, partno + 1, n); + + if (!fdisk_is_partition_used(sf->cxt, partno)) { + fdisk_warnx(sf->cxt, _("warning: %s: partition %d is not defined yet"), + devname, partno + 1); + unused = 1; + } + created = 1; + next_partno = partno; + + if (sf->movedata) + sf->orig_pa = get_partition(sf->cxt, partno); + } + + if (sf->append) { + created = 1; + next_partno = last_pt_partno(sf) + 1; + } + + if (!sf->quiet && sf->interactive) { + color_scheme_enable("welcome", UL_COLOR_GREEN); + fdisk_info(sf->cxt, _("\nWelcome to sfdisk (%s)."), PACKAGE_STRING); + color_disable(); + fdisk_info(sf->cxt, _("Changes will remain in memory only, until you decide to write them.\n" + "Be careful before using the write command.\n")); + } + + if (!sf->noact && !sf->noreread) { + if (!sf->quiet) + fputs(_("Checking that no-one is using this disk right now ..."), stdout); + if (fdisk_device_is_used(sf->cxt)) { + if (!sf->quiet) + fputs(_(" FAILED\n\n"), stdout); + + fdisk_warnx(sf->cxt, _( + "This disk is currently in use - repartitioning is probably a bad idea.\n" + "Umount all file systems, and swapoff all swap partitions on this disk.\n" + "Use the --no-reread flag to suppress this check.\n")); + + if (!sf->force) + errx(EXIT_FAILURE, _("Use the --force flag to overrule all checks.")); + } else if (!sf->quiet) + fputs(_(" OK\n\n"), stdout); + } + + if (fdisk_get_collision(sf->cxt)) + follow_wipe_mode(sf); + + if (sf->backup) + backup_partition_table(sf, devname); + + if (!sf->quiet) { + list_disk_geometry(sf->cxt); + if (fdisk_has_label(sf->cxt)) { + fdisk_info(sf->cxt, _("\nOld situation:")); + list_disklabel(sf->cxt); + } + } + + if (sf->label) + label = sf->label; + else if (fdisk_has_label(sf->cxt)) + label = fdisk_label_get_name(fdisk_get_label(sf->cxt, NULL)); + else + label = "dos"; /* just for backward compatibility */ + + fdisk_script_set_header(dp, "label", label); + + + if (!sf->quiet && sf->interactive) { + if (!fdisk_has_label(sf->cxt) && !sf->label) + fdisk_info(sf->cxt, + _("\nsfdisk is going to create a new '%s' disk label.\n" + "Use 'label: <name>' before you define a first partition\n" + "to override the default."), label); + fdisk_info(sf->cxt, _("\nType 'help' to get more information.\n")); + } else if (!sf->quiet) + fputc('\n', stdout); + + tb = fdisk_script_get_table(dp); + assert(tb); + + do { + size_t nparts; + + DBG(PARSE, ul_debug("<---next-line--->")); + if (next_partno == (size_t) -1) + next_partno = fdisk_table_get_nents(tb); + + if (created + && partno < 0 + && next_partno == fdisk_get_npartitions(sf->cxt) + && !has_container(sf)) { + fdisk_info(sf->cxt, _("All partitions used.")); + rc = SFDISK_DONE_ASK; + break; + } + + refresh_prompt_buffer(sf, devname, next_partno, created); + + + if (sf->prompt && (sf->interactive || !sf->quiet)) { +#ifndef HAVE_LIBREADLINE + fputs(sf->prompt, stdout); +#else + if (!sf->interactive) + fputs(sf->prompt, stdout); +#endif + } + + rc = fdisk_script_read_line(dp, stdin, buf, sizeof(buf)); + if (rc < 0) { + DBG(PARSE, ul_debug("script parsing failed, trying sfdisk specific commands")); + buf[sizeof(buf) - 1] = '\0'; + rc = loop_control_commands(sf, dp, buf); + if (rc) + break; + continue; + } else if (rc == 1) { + rc = SFDISK_DONE_EOF; + if (!sf->quiet) + fputs(_("Done.\n"), stdout); + break; + } + + nparts = fdisk_table_get_nents(tb); + if (nparts) { + size_t cur_partno; + struct fdisk_partition *pa = fdisk_table_get_partition(tb, nparts - 1); + + assert(pa); + + if (ignore_partition(pa)) { + fdisk_info(sf->cxt, _("Ignoring partition.")); + next_partno++; + continue; + } + if (!created) { /* create a new disklabel */ + rc = fdisk_apply_script_headers(sf->cxt, dp); + created = !rc; + if (rc) + fdisk_warnx(sf->cxt, _( + "Failed to apply script headers, " + "disk label not created.")); + + if (rc == 0 && fdisk_get_collision(sf->cxt)) + follow_wipe_mode(sf); + } + if (!rc && partno >= 0) { /* -N <partno>, modify partition */ + rc = fdisk_set_partition(sf->cxt, partno, pa); + rc = rc == 0 ? SFDISK_DONE_ASK : SFDISK_DONE_ABORT; + break; + } else if (!rc) { /* add partition */ + if (!sf->interactive && !sf->quiet && + (!sf->prompt || startswith(sf->prompt, SFDISK_PROMPT))) { + refresh_prompt_buffer(sf, devname, next_partno, created); + fputs(sf->prompt, stdout); + } + rc = fdisk_add_partition(sf->cxt, pa, &cur_partno); + if (rc) { + errno = -rc; + fdisk_warn(sf->cxt, _("Failed to add #%d partition"), next_partno + 1); + } + } + + /* wipe partition on success + * + * Note that unused=1 means -N <partno> for unused, + * otherwise we wipe only newly created partitions. + */ + if (rc == 0 && (unused || partno < 0)) { + rc = wipe_partition(sf, unused ? (size_t) partno : cur_partno); + if (rc) + errno = -rc; + } + + if (!rc) { + /* success print result */ + if (sf->interactive) + sfdisk_print_partition(sf, cur_partno); + next_partno = cur_partno + 1; + } else if (pa) /* error, drop partition from script */ + fdisk_table_remove_partition(tb, pa); + } else + fdisk_info(sf->cxt, _("Script header accepted.")); + + if (rc && !sf->interactive) { + rc = SFDISK_DONE_ABORT; + break; + } + } while (1); + + /* create empty disk label if label, but no partition specified */ + if ((rc == SFDISK_DONE_EOF || rc == SFDISK_DONE_WRITE) && created == 0 + && fdisk_script_has_force_label(dp) == 1 + && fdisk_table_get_nents(tb) == 0 + && fdisk_script_get_header(dp, "label")) { + + int xrc = fdisk_apply_script_headers(sf->cxt, dp); + created = !xrc; + if (xrc) { + fdisk_warnx(sf->cxt, _( + "Failed to apply script headers, " + "disk label not created.")); + rc = SFDISK_DONE_ABORT; + } + } + + if (!sf->quiet && rc != SFDISK_DONE_ABORT) { + fdisk_info(sf->cxt, _("\nNew situation:")); + list_disk_identifier(sf->cxt); + list_disklabel(sf->cxt); + } + + switch (rc) { + case SFDISK_DONE_ASK: + case SFDISK_DONE_EOF: + if (sf->interactive) { + int yes = 0; + fdisk_ask_yesno(sf->cxt, _("Do you want to write this to disk?"), &yes); + if (!yes) { + fdisk_info(sf->cxt, _("Leaving.")); + rc = 0; + break; + } + } + /* fallthrough */ + case SFDISK_DONE_WRITE: + rc = write_changes(sf); + break; + case SFDISK_DONE_ABORT: + default: /* rc < 0 on error */ + fdisk_info(sf->cxt, _("Leaving.\n")); + break; + } + + fdisk_unref_script(dp); + return rc; +} + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *out = stdout; + fputs(USAGE_HEADER, out); + + fprintf(out, + _(" %1$s [options] <dev> [[-N] <part>]\n" + " %1$s [options] <command>\n"), program_invocation_short_name); + + fputs(USAGE_SEPARATOR, out); + fputs(_("Display or manipulate a disk partition table.\n"), out); + + fputs(USAGE_COMMANDS, out); + fputs(_(" -A, --activate <dev> [<part> ...] list or set bootable (P)MBR partitions\n"), out); + fputs(_(" -d, --dump <dev> dump partition table (usable for later input)\n"), out); + fputs(_(" -J, --json <dev> dump partition table in JSON format\n"), out); + fputs(_(" -g, --show-geometry [<dev> ...] list geometry of all or specified devices\n"), out); + fputs(_(" -l, --list [<dev> ...] list partitions of each device\n"), out); + fputs(_(" -F, --list-free [<dev> ...] list unpartitioned free areas of each device\n"), out); + fputs(_(" -r, --reorder <dev> fix partitions order (by start offset)\n"), out); + fputs(_(" -s, --show-size [<dev> ...] list sizes of all or specified devices\n"), out); + fputs(_(" -T, --list-types print the recognized types (see -X)\n"), out); + fputs(_(" -V, --verify [<dev> ...] test whether partitions seem correct\n"), out); + fputs(_(" --delete <dev> [<part> ...] delete all or specified partitions\n"), out); + + fputs(USAGE_SEPARATOR, out); + fputs(_(" --part-label <dev> <part> [<str>] print or change partition label\n"), out); + fputs(_(" --part-type <dev> <part> [<type>] print or change partition type\n"), out); + fputs(_(" --part-uuid <dev> <part> [<uuid>] print or change partition uuid\n"), out); + fputs(_(" --part-attrs <dev> <part> [<str>] print or change partition attributes\n"), out); + + fputs(USAGE_SEPARATOR, out); + fputs(_(" <dev> device (usually disk) path\n"), out); + fputs(_(" <part> partition number\n"), out); + fputs(_(" <type> partition type, GUID for GPT, hex for MBR\n"), out); + + fputs(USAGE_OPTIONS, out); + fputs(_(" -a, --append append partitions to existing partition table\n"), out); + fputs(_(" -b, --backup backup partition table sectors (see -O)\n"), out); + fputs(_(" --bytes print SIZE in bytes rather than in human readable format\n"), out); + fputs(_(" --move-data[=<typescript>] move partition data after relocation (requires -N)\n"), out); + fputs(_(" -f, --force disable all consistency checking\n"), out); + fputs(_(" --color[=<when>] colorize output (auto, always or never)\n"), out); + fprintf(out, + " %s\n", USAGE_COLORS_DEFAULT); + fputs(_(" -N, --partno <num> specify partition number\n"), out); + fputs(_(" -n, --no-act do everything except write to device\n"), out); + fputs(_(" --no-reread do not check whether the device is in use\n"), out); + fputs(_(" --no-tell-kernel do not tell kernel about changes\n"), out); + fputs(_(" -O, --backup-file <path> override default backup file name\n"), out); + fputs(_(" -o, --output <list> output columns\n"), out); + fputs(_(" -q, --quiet suppress extra info messages\n"), out); + fputs(_(" -w, --wipe <mode> wipe signatures (auto, always or never)\n"), out); + fputs(_(" -W, --wipe-partitions <mode> wipe signatures from new partitions (auto, always or never)\n"), out); + fputs(_(" -X, --label <name> specify label type (dos, gpt, ...)\n"), out); + fputs(_(" -Y, --label-nested <name> specify nested label type (dos, bsd)\n"), out); + fputs(USAGE_SEPARATOR, out); + fputs(_(" -G, --show-pt-geometry deprecated, alias to --show-geometry\n"), out); + fputs(_(" -L, --Linux deprecated, only for backward compatibility\n"), out); + fputs(_(" -u, --unit S deprecated, only sector unit is supported\n"), out); + + fputs(USAGE_SEPARATOR, out); + printf( " -h, --help %s\n", USAGE_OPTSTR_HELP); + printf( " -v, --version %s\n", USAGE_OPTSTR_VERSION); + + list_available_columns(out); + + printf(USAGE_MAN_TAIL("sfdisk(8)")); + exit(EXIT_SUCCESS); +} + + +int main(int argc, char *argv[]) +{ + const char *outarg = NULL; + int rc = -EINVAL, c, longidx = -1, bytes = 0; + int colormode = UL_COLORMODE_UNDEF; + struct sfdisk _sf = { + .partno = -1, + .wipemode = WIPEMODE_AUTO, + .pwipemode = WIPEMODE_AUTO, + .interactive = isatty(STDIN_FILENO) ? 1 : 0, + }, *sf = &_sf; + + enum { + OPT_CHANGE_ID = CHAR_MAX + 1, + OPT_PRINT_ID, + OPT_ID, + OPT_NOREREAD, + OPT_PARTUUID, + OPT_PARTLABEL, + OPT_PARTTYPE, + OPT_PARTATTRS, + OPT_BYTES, + OPT_COLOR, + OPT_MOVEDATA, + OPT_DELETE, + OPT_NOTELL + }; + + static const struct option longopts[] = { + { "activate",no_argument, NULL, 'A' }, + { "append", no_argument, NULL, 'a' }, + { "backup", no_argument, NULL, 'b' }, + { "backup-file", required_argument, NULL, 'O' }, + { "bytes", no_argument, NULL, OPT_BYTES }, + { "color", optional_argument, NULL, OPT_COLOR }, + { "delete", no_argument, NULL, OPT_DELETE }, + { "dump", no_argument, NULL, 'd' }, + { "help", no_argument, NULL, 'h' }, + { "force", no_argument, NULL, 'f' }, + { "json", no_argument, NULL, 'J' }, + { "label", required_argument, NULL, 'X' }, + { "label-nested", required_argument, NULL, 'Y' }, + { "list", no_argument, NULL, 'l' }, + { "list-free", no_argument, NULL, 'F' }, + { "list-types", no_argument, NULL, 'T' }, + { "no-act", no_argument, NULL, 'n' }, + { "no-reread", no_argument, NULL, OPT_NOREREAD }, + { "no-tell-kernel", no_argument, NULL, OPT_NOTELL }, + { "move-data", optional_argument, NULL, OPT_MOVEDATA }, + { "output", required_argument, NULL, 'o' }, + { "partno", required_argument, NULL, 'N' }, + { "reorder", no_argument, NULL, 'r' }, + { "show-geometry", no_argument, NULL, 'g' }, + { "quiet", no_argument, NULL, 'q' }, + { "verify", no_argument, NULL, 'V' }, + { "version", no_argument, NULL, 'v' }, + { "wipe", required_argument, NULL, 'w' }, + { "wipe-partitions", required_argument, NULL, 'W' }, + + { "part-uuid", no_argument, NULL, OPT_PARTUUID }, + { "part-label", no_argument, NULL, OPT_PARTLABEL }, + { "part-type", no_argument, NULL, OPT_PARTTYPE }, + { "part-attrs", no_argument, NULL, OPT_PARTATTRS }, + + { "show-pt-geometry", no_argument, NULL, 'G' }, /* deprecated */ + { "unit", required_argument, NULL, 'u' }, /* deprecated */ + { "Linux", no_argument, NULL, 'L' }, /* deprecated */ + { "show-size", no_argument, NULL, 's' }, /* deprecated */ + + { "change-id",no_argument, NULL, OPT_CHANGE_ID }, /* deprecated */ + { "id", no_argument, NULL, 'c' }, /* deprecated */ + { "print-id",no_argument, NULL, OPT_PRINT_ID }, /* deprecated */ + + { NULL, 0, NULL, 0 }, + }; + static const ul_excl_t excl[] = { /* rows and cols in ASCII order */ + { 's','u'}, /* --show-size --unit */ + { 0 } + }; + int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; + + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + while ((c = getopt_long(argc, argv, "aAbcdfFgGhJlLo:O:nN:qrsTu:vVX:Y:w:W:", + longopts, &longidx)) != -1) { + + err_exclusive_options(c, longopts, excl, excl_st); + + switch(c) { + case 'A': + sf->act = ACT_ACTIVATE; + break; + case 'a': + sf->append = 1; + break; + case 'b': + sf->backup = 1; + break; + case OPT_CHANGE_ID: + case OPT_PRINT_ID: + case OPT_ID: + warnx(_("%s is deprecated in favour of --part-type"), + longopts[longidx].name); + sf->act = ACT_PARTTYPE; + break; + case 'c': + warnx(_("--id is deprecated in favour of --part-type")); + sf->act = ACT_PARTTYPE; + break; + case 'J': + sf->json = 1; + /* fallthrough */ + case 'd': + sf->act = ACT_DUMP; + break; + case 'F': + sf->act = ACT_LIST_FREE; + break; + case 'f': + sf->force = 1; + break; + case 'G': + warnx(_("--show-pt-geometry is no more implemented. Using --show-geometry.")); + /* fallthrough */ + case 'g': + sf->act = ACT_SHOW_GEOM; + break; + case 'h': + usage(); + break; + case 'l': + sf->act = ACT_LIST; + break; + case 'L': + warnx(_("--Linux option is unnecessary and deprecated")); + break; + case 'o': + outarg = optarg; + break; + case 'O': + sf->backup = 1; + sf->backup_file = optarg; + break; + case 'n': + sf->noact = 1; + break; + case 'N': + sf->partno = strtou32_or_err(optarg, _("failed to parse partition number")) - 1; + break; + case 'q': + sf->quiet = 1; + break; + case 'r': + sf->act = ACT_REORDER; + break; + case 's': + sf->act = ACT_SHOW_SIZE; + break; + case 'T': + sf->act = ACT_LIST_TYPES; + break; + case 'u': + if (*optarg != 'S') + errx(EXIT_FAILURE, _("unsupported unit '%c'"), *optarg); + break; + case 'v': + printf(_("%s from %s\n"), program_invocation_short_name, + PACKAGE_STRING); + return EXIT_SUCCESS; + case 'V': + sf->verify = 1; + break; + case 'w': + sf->wipemode = wipemode_from_string(optarg); + if (sf->wipemode < 0) + errx(EXIT_FAILURE, _("unsupported wipe mode")); + break; + case 'W': + sf->pwipemode = wipemode_from_string(optarg); + if (sf->pwipemode < 0) + errx(EXIT_FAILURE, _("unsupported wipe mode")); + break; + case 'X': + sf->label = optarg; + break; + case 'Y': + sf->label_nested = optarg; + break; + + case OPT_PARTUUID: + sf->act = ACT_PARTUUID; + break; + case OPT_PARTTYPE: + sf->act = ACT_PARTTYPE; + break; + case OPT_PARTLABEL: + sf->act = ACT_PARTLABEL; + break; + case OPT_PARTATTRS: + sf->act = ACT_PARTATTRS; + break; + case OPT_NOREREAD: + sf->noreread = 1; + break; + case OPT_BYTES: + bytes = 1; + break; + case OPT_COLOR: + colormode = UL_COLORMODE_AUTO; + if (optarg) + colormode = colormode_or_err(optarg, + _("unsupported color mode")); + break; + case OPT_MOVEDATA: + sf->movedata = 1; + sf->move_typescript = optarg; + break; + case OPT_DELETE: + sf->act = ACT_DELETE; + break; + case OPT_NOTELL: + sf->notell = 1; + break; + default: + errtryhelp(EXIT_FAILURE); + } + } + + colors_init(colormode, "sfdisk"); + + sfdisk_init(sf); + if (bytes) + fdisk_set_size_unit(sf->cxt, FDISK_SIZEUNIT_BYTES); + + if (outarg) + init_fields(NULL, outarg, NULL); + + if (sf->verify && !sf->act) + sf->act = ACT_VERIFY; /* --verify make be used with --list too */ + else if (!sf->act) + sf->act = ACT_FDISK; /* default */ + + if (sf->movedata && !(sf->act == ACT_FDISK && sf->partno >= 0)) + errx(EXIT_FAILURE, _("--movedata requires -N")); + + switch (sf->act) { + case ACT_ACTIVATE: + rc = command_activate(sf, argc - optind, argv + optind); + break; + + case ACT_DELETE: + rc = command_delete(sf, argc - optind, argv + optind); + break; + + case ACT_LIST: + rc = command_list_partitions(sf, argc - optind, argv + optind); + break; + + case ACT_LIST_TYPES: + rc = command_list_types(sf); + break; + + case ACT_LIST_FREE: + rc = command_list_freespace(sf, argc - optind, argv + optind); + break; + + case ACT_FDISK: + rc = command_fdisk(sf, argc - optind, argv + optind); + break; + + case ACT_DUMP: + rc = command_dump(sf, argc - optind, argv + optind); + break; + + case ACT_SHOW_SIZE: + rc = command_show_size(sf, argc - optind, argv + optind); + break; + + case ACT_SHOW_GEOM: + rc = command_show_geometry(sf, argc - optind, argv + optind); + break; + + case ACT_VERIFY: + rc = command_verify(sf, argc - optind, argv + optind); + break; + + case ACT_PARTTYPE: + rc = command_parttype(sf, argc - optind, argv + optind); + break; + + case ACT_PARTUUID: + rc = command_partuuid(sf, argc - optind, argv + optind); + break; + + case ACT_PARTLABEL: + rc = command_partlabel(sf, argc - optind, argv + optind); + break; + + case ACT_PARTATTRS: + rc = command_partattrs(sf, argc - optind, argv + optind); + break; + + case ACT_REORDER: + rc = command_reorder(sf, argc - optind, argv + optind); + break; + } + + sfdisk_deinit(sf); + + DBG(MISC, ul_debug("bye! [rc=%d]", rc)); + return rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE; +} + diff --git a/disk-utils/swaplabel.8 b/disk-utils/swaplabel.8 new file mode 100644 index 0000000..d96d507 --- /dev/null +++ b/disk-utils/swaplabel.8 @@ -0,0 +1,70 @@ +.\" Copyright 2010 Jason Borden <jborden@bluehost.com> +.\" +.\" This file may be copied under the terms of the GNU Public License. +.\" +.TH SWAPLABEL 8 "April 2010" "util-linux" "System Administration" +.SH NAME +swaplabel \- print or change the label or UUID of a swap area +.SH SYNOPSIS +.B swaplabel +.RB [ \-L +.IR label ] +.RB [ \-U +.IR UUID ] +.I device +.SH DESCRIPTION +.B swaplabel +will display or change the label or UUID of a swap partition located on +.I device +(or regular file). +.PP +If the optional arguments +.B \-L +and +.B \-U +are not given, +.B swaplabel +will simply display the current swap-area label and UUID of +.IR device . +.PP +If an optional argument is present, then +.B swaplabel +will change the appropriate value on +.IR device . +These values can also be set during swap creation using +.BR mkswap (8). +The +.B swaplabel +utility allows to change the label or UUID on an actively used swap device. +.SH OPTIONS +.TP +.BR \-h , " \-\-help" +Display help text and exit. +.TP +.BR \-L , " \-\-label " \fIlabel\fR +Specify a new \fIlabel\fR for the device. +Swap partition labels can be at most 16 characters long. If +.I label +is longer than 16 characters, +.B swaplabel +will truncate it and print a warning message. +.TP +.BR \-U , " \-\-uuid " \fIUUID\fR +Specify a new \fIUUID\fR for the device. +The \fI UUID\fR +must be in the standard 8-4-4-4-12 character format, such as is output by +.BR uuidgen (1). +.PP +.SH AUTHOR +.B swaplabel +was written by Jason Borden <jborden@bluehost.com> and Karel Zak <kzak@redhat.com>. +.SH ENVIRONMENT +.IP LIBBLKID_DEBUG=all +enables libblkid debug output. +.SH AVAILABILITY +The swaplabel command is part of the util-linux package and is available from +https://www.kernel.org/pub/linux/utils/util-linux/. +.SH SEE ALSO +.BR uuidgen (1), +.BR mkswap (8), +.BR swapon (8) diff --git a/disk-utils/swaplabel.c b/disk-utils/swaplabel.c new file mode 100644 index 0000000..ccc226a --- /dev/null +++ b/disk-utils/swaplabel.c @@ -0,0 +1,195 @@ +/* + * swaplabel.c - Print or change the label / UUID of a swap partition + * + * Copyright (C) 2010 Jason Borden <jborden@bluehost.com> + * Copyright (C) 2010 Karel Zak <kzak@redhat.com> + * + * Usage: swaplabel [-L label] [-U UUID] device + * + * This file may be redistributed under the terms of the GNU Public License + * version 2 or later. + * + */ +#include <stdio.h> +#include <stddef.h> +#include <string.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <stdlib.h> +#include <getopt.h> + +#ifdef HAVE_LIBUUID +# include <uuid.h> +#endif + +#include "c.h" +#include "nls.h" +#include "all-io.h" +#include "strutils.h" +#include "closestream.h" + +#include "swapheader.h" +#include "swapprober.h" + +#define SWAP_UUID_OFFSET (offsetof(struct swap_header_v1_2, uuid)) +#define SWAP_LABEL_OFFSET (offsetof(struct swap_header_v1_2, volume_name)) + +/* Print the swap partition information */ +static int print_info(blkid_probe pr) +{ + const char *data; + + if (!blkid_probe_lookup_value(pr, "LABEL", &data, NULL)) + printf("LABEL: %s\n", data); + + if (!blkid_probe_lookup_value(pr, "UUID", &data, NULL)) + printf("UUID: %s\n", data); + + return 0; +} + +/* Change the swap partition info */ +#ifdef HAVE_LIBUUID +static int change_info(const char *devname, const char *label, const char *uuid) +#else +static int change_info(const char *devname, const char *label, + const char *uuid __attribute__((__unused__))) +#endif +{ + int fd; + + fd = open(devname, O_RDWR); + if (fd < 0) { + warn(_("cannot open %s"), devname); + goto err; + } +#ifdef HAVE_LIBUUID + /* Write the uuid if it was provided */ + if (uuid) { + uuid_t newuuid; + + if (uuid_parse(uuid, newuuid) == -1) + warnx(_("failed to parse UUID: %s"), uuid); + else { + if (lseek(fd, SWAP_UUID_OFFSET, SEEK_SET) != + SWAP_UUID_OFFSET) { + warn(_("%s: failed to seek to swap UUID"), devname); + goto err; + + } else if (write_all(fd, newuuid, sizeof(newuuid))) { + warn(_("%s: failed to write UUID"), devname); + goto err; + } + } + } +#endif + /* Write the label if it was provided */ + if (label) { + char newlabel[SWAP_LABEL_LENGTH]; + + if (lseek(fd, SWAP_LABEL_OFFSET, SEEK_SET) != SWAP_LABEL_OFFSET) { + warn(_("%s: failed to seek to swap label "), devname); + goto err; + } + memset(newlabel, 0, sizeof(newlabel)); + xstrncpy(newlabel, label, sizeof(newlabel)); + + if (strlen(label) > strlen(newlabel)) + warnx(_("label is too long. Truncating it to '%s'"), + newlabel); + if (write_all(fd, newlabel, sizeof(newlabel))) { + warn(_("%s: failed to write label"), devname); + goto err; + } + } + + if (close_fd(fd) != 0) { + warn(_("write failed: %s"), devname); + return -1; + } + return 0; +err: + if (fd >= 0) + close(fd); + return -1; +} + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *out = stdout; + fputs(USAGE_HEADER, out); + fprintf(out, _(" %s [options] <device>\n"), + program_invocation_short_name); + + fputs(USAGE_SEPARATOR, out); + fputs(_("Display or change the label or UUID of a swap area.\n"), out); + + fputs(USAGE_OPTIONS, out); + fputs(_(" -L, --label <label> specify a new label\n" + " -U, --uuid <uuid> specify a new uuid\n"), out); + fputs(USAGE_SEPARATOR, out); + printf(USAGE_HELP_OPTIONS(21)); + printf(USAGE_MAN_TAIL("swaplabel(8)")); + exit(EXIT_SUCCESS); +} + +int main(int argc, char *argv[]) +{ + blkid_probe pr = NULL; + char *uuid = NULL, *label = NULL, *devname; + int c, rc = -1; + + static const struct option longopts[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + { "label", required_argument, NULL, 'L' }, + { "uuid", required_argument, NULL, 'U' }, + { NULL, 0, NULL, 0 } + }; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + while ((c = getopt_long(argc, argv, "hVL:U:", longopts, NULL)) != -1) { + switch (c) { + case 'h': + usage(); + break; + case 'V': + printf(UTIL_LINUX_VERSION); + return EXIT_SUCCESS; + case 'L': + label = optarg; + break; + case 'U': +#ifdef HAVE_LIBUUID + uuid = optarg; +#else + warnx(_("ignore -U (UUIDs are unsupported)")); +#endif + break; + default: + errtryhelp(EXIT_FAILURE); + } + } + + if (optind == argc) { + warnx(_("no device specified")); + errtryhelp(EXIT_FAILURE); + } + devname = argv[optind]; + pr = get_swap_prober(devname); + if (pr) { + if (uuid || label) + rc = change_info(devname, label, uuid); + else + rc = print_info(pr); + blkid_free_probe(pr); + } + return rc ? EXIT_FAILURE : EXIT_SUCCESS; +} + |