diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-14 19:10:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-14 19:10:49 +0000 |
commit | cfe5e3905201349e9cf3f95d52ff4bd100bde37d (patch) | |
tree | d0baf160cbee3195249d095f85e52d20c21acf02 /disk-utils | |
parent | Initial commit. (diff) | |
download | util-linux-cfe5e3905201349e9cf3f95d52ff4bd100bde37d.tar.xz util-linux-cfe5e3905201349e9cf3f95d52ff4bd100bde37d.zip |
Adding upstream version 2.39.3.upstream/2.39.3
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
70 files changed, 24625 insertions, 0 deletions
diff --git a/disk-utils/Makemodule.am b/disk-utils/Makemodule.am new file mode 100644 index 0000000..d08e95d --- /dev/null +++ b/disk-utils/Makemodule.am @@ -0,0 +1,256 @@ + +if BUILD_MINIX +sbin_PROGRAMS += fsck.minix +MANPAGES += disk-utils/fsck.minix.8 +dist_noinst_DATA += disk-utils/fsck.minix.8.adoc +fsck_minix_SOURCES = \ + disk-utils/fsck.minix.c \ + disk-utils/minix_programs.h \ + lib/ismounted.c +fsck_minix_LDADD = $(LDADD) libcommon.la + +sbin_PROGRAMS += mkfs.minix +MANPAGES += disk-utils/mkfs.minix.8 +dist_noinst_DATA += disk-utils/mkfs.minix.8.adoc +mkfs_minix_SOURCES = \ + disk-utils/minix_programs.h \ + disk-utils/mkfs.minix.c \ + lib/ismounted.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 +MANPAGES += disk-utils/mkfs.8 +dist_noinst_DATA += disk-utils/mkfs.8.adoc +mkfs_SOURCES = disk-utils/mkfs.c +endif + + +if BUILD_ISOSIZE +usrbin_exec_PROGRAMS += isosize +MANPAGES += disk-utils/isosize.8 +dist_noinst_DATA += disk-utils/isosize.8.adoc +isosize_SOURCES = disk-utils/isosize.c +isosize_LDADD = $(LDADD) libcommon.la +endif + + +if BUILD_BFS +sbin_PROGRAMS += mkfs.bfs +MANPAGES += disk-utils/mkfs.bfs.8 +dist_noinst_DATA += disk-utils/mkfs.bfs.8.adoc +mkfs_bfs_SOURCES = \ + disk-utils/mkfs.bfs.c +mkfs_bfs_LDADD = $(LDADD) libcommon.la +endif + + +if BUILD_MKSWAP +sbin_PROGRAMS += mkswap +MANPAGES += disk-utils/mkswap.8 +dist_noinst_DATA += disk-utils/mkswap.8.adoc +mkswap_SOURCES = \ + disk-utils/mkswap.c \ + lib/ismounted.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 +mkswap_SOURCES += \ + lib/selinux-utils.c \ + include/selinux-utils.h +endif +endif # BUILD_MKSWAP + + +if BUILD_SWAPLABEL +sbin_PROGRAMS += swaplabel +MANPAGES += disk-utils/swaplabel.8 +dist_noinst_DATA += disk-utils/swaplabel.8.adoc +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 +MANPAGES += disk-utils/fsck.8 +dist_noinst_DATA += disk-utils/fsck.8.adoc +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 +MANPAGES += disk-utils/raw.8 +dist_noinst_DATA += disk-utils/raw.8.adoc +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 +MANPAGES += disk-utils/fsck.cramfs.8 +dist_noinst_DATA += disk-utils/fsck.cramfs.8.adoc +fsck_cramfs_SOURCES = disk-utils/fsck.cramfs.c $(cramfs_common_sources) +fsck_cramfs_LDADD = $(LDADD) -lz libcommon.la + +sbin_PROGRAMS += mkfs.cramfs +MANPAGES += disk-utils/mkfs.cramfs.8 +dist_noinst_DATA += disk-utils/mkfs.cramfs.8.adoc +mkfs_cramfs_SOURCES = disk-utils/mkfs.cramfs.c $(cramfs_common_sources) +mkfs_cramfs_LDADD = $(LDADD) -lz libcommon.la +endif + +if BUILD_FDFORMAT +usrsbin_exec_PROGRAMS += fdformat +MANPAGES += disk-utils/fdformat.8 +dist_noinst_DATA += disk-utils/fdformat.8.adoc +fdformat_SOURCES = disk-utils/fdformat.c +fdformat_LDADD = $(LDADD) libcommon.la +endif + +if BUILD_BLOCKDEV +sbin_PROGRAMS += blockdev +MANPAGES += disk-utils/blockdev.8 +dist_noinst_DATA += disk-utils/blockdev.8.adoc +blockdev_SOURCES = disk-utils/blockdev.c +blockdev_LDADD = $(LDADD) libcommon.la +endif + + +if BUILD_FDISK +sbin_PROGRAMS += fdisk +MANPAGES += disk-utils/fdisk.8 +dist_noinst_DATA += disk-utils/fdisk.8.adoc +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 \ + include/pager.h \ + lib/pager.c + +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 +MANPAGES += disk-utils/sfdisk.8 +dist_noinst_DATA += disk-utils/sfdisk.8.adoc +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 +MANPAGES += disk-utils/cfdisk.8 +dist_noinst_DATA += disk-utils/cfdisk.8.adoc +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 +MANPAGES += \ + disk-utils/addpart.8 \ + disk-utils/delpart.8 \ + disk-utils/resizepart.8 \ + disk-utils/partx.8 +dist_noinst_DATA += \ + disk-utils/addpart.8.adoc \ + disk-utils/delpart.8.adoc \ + disk-utils/resizepart.8.adoc \ + disk-utils/partx.8.adoc +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..74ec624 --- /dev/null +++ b/disk-utils/addpart.8 @@ -0,0 +1,85 @@ +'\" t +.\" Title: addpart +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-10-23 +.\" Manual: System Administration +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "ADDPART" "8" "2023-10-23" "util\-linux 2.39.3" "System Administration" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +addpart \- tell the kernel about the existence of a partition +.SH "SYNOPSIS" +.sp +\fBaddpart\fP \fIdevice partition start length\fP +.SH "DESCRIPTION" +.sp +\fBaddpart\fP tells the Linux kernel about the existence of the specified partition. The command is a simple wrapper around the "add partition" ioctl. +.sp +This command doesn\(cqt manipulate partitions on a block device. +.SH "PARAMETERS" +.sp +\fIdevice\fP +.RS 4 +The disk device. +.RE +.sp +\fIpartition\fP +.RS 4 +The partition number. +.RE +.sp +\fIstart\fP +.RS 4 +The beginning of the partition (in 512\-byte sectors). +.RE +.sp +\fIlength\fP +.RS 4 +The length of the partition (in 512\-byte sectors). +.RE +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.sp +\fB\-V\fP, \fB\-\-version\fP +.RS 4 +Print version and exit. +.RE +.SH "SEE ALSO" +.sp +\fBdelpart\fP(8), +\fBfdisk\fP(8), +\fBparted\fP(8), +\fBpartprobe\fP(8), +\fBpartx\fP(8) +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBaddpart\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "."
\ No newline at end of file diff --git a/disk-utils/addpart.8.adoc b/disk-utils/addpart.8.adoc new file mode 100644 index 0000000..1c1a9dc --- /dev/null +++ b/disk-utils/addpart.8.adoc @@ -0,0 +1,59 @@ +//po4a: entry man manual +//// +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 +//// += addpart(8) +:doctype: manpage +:man manual: System Administration +:man source: util-linux {release-version} +:page-layout: base +:command: addpart + +== NAME + +addpart - tell the kernel about the existence of a partition + +== SYNOPSIS + +*addpart* _device partition start length_ + +== DESCRIPTION + +*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. + +== PARAMETERS + +_device_:: +The disk device. + +_partition_:: +The partition number. + +_start_:: +The beginning of the partition (in 512-byte sectors). + +_length_:: +The length of the partition (in 512-byte sectors). + +include::man-common/help-version.adoc[] + +== SEE ALSO + +*delpart*(8), +*fdisk*(8), +*parted*(8), +*partprobe*(8), +*partx*(8) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/disk-utils/addpart.c b/disk-utils/addpart.c new file mode 100644 index 0000000..0586104 --- /dev/null +++ b/disk-utils/addpart.c @@ -0,0 +1,76 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * 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. + * + * Copyright (C) 2012-2023 Karel Zak <kzak@redhat.com> + */ +#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': + print_version(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..693b1ad --- /dev/null +++ b/disk-utils/blockdev.8 @@ -0,0 +1,191 @@ +'\" t +.\" Title: blockdev +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-12-01 +.\" Manual: System Administration +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "BLOCKDEV" "8" "2023-12-01" "util\-linux 2.39.3" "System Administration" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +blockdev \- call block device ioctls from the command line +.SH "SYNOPSIS" +.sp +\fBblockdev\fP [\fB\-q\fP] [\fB\-v\fP] \fIcommand\fP [\fIcommand\fP...] \fIdevice\fP [\fIdevice\fP...] +.sp +\fBblockdev\fP \fB\-\-report\fP [\fIdevice\fP...] +.sp +\fBblockdev\fP \fB\-h\fP|\fB\-V\fP +.SH "DESCRIPTION" +.sp +The utility \fBblockdev\fP allows one to call block device ioctls from the command line. +.SH "OPTIONS" +.sp +\fB\-q\fP +.RS 4 +Be quiet. +.RE +.sp +\fB\-v\fP +.RS 4 +Be verbose. +.RE +.sp +\fB\-\-report\fP +.RS 4 +Print a report for the specified device. It is possible to give multiple devices. If none is given, all devices which appear in \fI/proc/partitions\fP are shown. Note that the partition StartSec is in 512\-byte sectors. +.RE +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.sp +\fB\-V\fP, \fB\-\-version\fP +.RS 4 +Print version and exit. +.RE +.SH "COMMANDS" +.sp +It is possible to give multiple devices and multiple commands. +.sp +\fB\-\-flushbufs\fP +.RS 4 +Flush buffers. +.RE +.sp +\fB\-\-getalignoff\fP +.RS 4 +Get alignment offset. +.RE +.sp +\fB\-\-getbsz\fP +.RS 4 +Print the blocksize in bytes. This size does not describe device topology. It\(cqs the size used internally by the kernel and it may be modified (for example) by filesystem driver on mount. +.RE +.sp +\fB\-\-getdiscardzeroes\fP +.RS 4 +Get discard zeroes support status. +.RE +.sp +\fB\-\-getdiskseq\fP +.RS 4 +Get disk sequence number. +.RE +.sp +\fB\-\-getfra\fP +.RS 4 +Get filesystem readahead in 512\-byte sectors. +.RE +.sp +\fB\-\-getiomin\fP +.RS 4 +Get minimum I/O size. +.RE +.sp +\fB\-\-getioopt\fP +.RS 4 +Get optimal I/O size. +.RE +.sp +\fB\-\-getmaxsect\fP +.RS 4 +Get max sectors per request. +.RE +.sp +\fB\-\-getpbsz\fP +.RS 4 +Get physical block (sector) size. +.RE +.sp +\fB\-\-getra\fP +.RS 4 +Print readahead (in 512\-byte sectors). +.RE +.sp +\fB\-\-getro\fP +.RS 4 +Get read\-only. Print 1 if the device is read\-only, 0 otherwise. +.RE +.sp +\fB\-\-getsize64\fP +.RS 4 +Print device size in bytes. +.RE +.sp +\fB\-\-getsize\fP +.RS 4 +Print device size (32\-bit!) in sectors. Deprecated in favor of the \fB\-\-getsz\fP option. +.RE +.sp +\fB\-\-getss\fP +.RS 4 +Print logical sector size in bytes \- usually 512. +.RE +.sp +\fB\-\-getsz\fP +.RS 4 +Get size in 512\-byte sectors. +.RE +.sp +\fB\-\-rereadpt\fP +.RS 4 +Reread partition table +.RE +.sp +\fB\-\-setbsz\fP \fIbytes\fP +.RS 4 +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 \fBblockdev\fP has the device open, and is lost once \fBblockdev\fP exits. +.RE +.sp +\fB\-\-setfra\fP \fIsectors\fP +.RS 4 +Set filesystem readahead (same as \fB\-\-setra\fP on 2.6 kernels). +.RE +.sp +\fB\-\-setra\fP \fIsectors\fP +.RS 4 +Set readahead (in 512\-byte sectors). +.RE +.sp +\fB\-\-setro\fP +.RS 4 +Set read\-only. The currently active access to the device may not be affected by the change. For example, a filesystem already mounted in read\-write mode will not be affected. The change applies after remount. +.RE +.sp +\fB\-\-setrw\fP +.RS 4 +Set read\-write. +.RE +.SH "AUTHORS" +.sp +\fBblockdev\fP was written by Andries E. Brouwer and rewritten by Karel Zak. +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBblockdev\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "."
\ No newline at end of file diff --git a/disk-utils/blockdev.8.adoc b/disk-utils/blockdev.8.adoc new file mode 100644 index 0000000..16f1562 --- /dev/null +++ b/disk-utils/blockdev.8.adoc @@ -0,0 +1,124 @@ +//po4a: entry man manual +//// +Copyright 1998 Andries E. Brouwer (aeb@cwi.nl) +Copyright 2007 Karel Zak <kzak@redhat.com> + +May be distributed under the GNU General Public License +//// += blockdev(8) +:doctype: manpage +:man manual: System Administration +:man source: util-linux {release-version} +:page-layout: base +:command: blockdev + +== NAME + +blockdev - call block device ioctls from the command line + +== SYNOPSIS + +*blockdev* [*-q*] [*-v*] _command_ [_command_...] _device_ [_device_...] + +*blockdev* *--report* [_device_...] + +*blockdev* *-h*|*-V* + +== DESCRIPTION + +The utility *blockdev* allows one to call block device ioctls from the command line. + +== OPTIONS + +*-q*:: +Be quiet. + +*-v*:: +Be verbose. + +*--report*:: +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. + +include::man-common/help-version.adoc[] + +== COMMANDS + +It is possible to give multiple devices and multiple commands. + +*--flushbufs*:: +Flush buffers. + +*--getalignoff*:: +Get alignment offset. + +*--getbsz*:: +Print the blocksize in bytes. This size does not describe device topology. It's the size used internally by the kernel and it may be modified (for example) by filesystem driver on mount. + +*--getdiscardzeroes*:: +Get discard zeroes support status. + +*--getdiskseq*:: +Get disk sequence number. + +*--getfra*:: +Get filesystem readahead in 512-byte sectors. + +*--getiomin*:: +Get minimum I/O size. + +*--getioopt*:: +Get optimal I/O size. + +*--getmaxsect*:: +Get max sectors per request. + +*--getpbsz*:: +Get physical block (sector) size. + +*--getra*:: +Print readahead (in 512-byte sectors). + +*--getro*:: +Get read-only. Print 1 if the device is read-only, 0 otherwise. + +*--getsize64*:: +Print device size in bytes. + +*--getsize*:: +Print device size (32-bit!) in sectors. Deprecated in favor of the *--getsz* option. + +*--getss*:: +Print logical sector size in bytes - usually 512. + +*--getsz*:: +Get size in 512-byte sectors. + +*--rereadpt*:: +Reread partition table + +*--setbsz* _bytes_:: +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. + +*--setfra* _sectors_:: +Set filesystem readahead (same as *--setra* on 2.6 kernels). + +*--setra* _sectors_:: +Set readahead (in 512-byte sectors). + +*--setro*:: +Set read-only. The currently active access to the device may not be affected by the change. For example, a filesystem already mounted in read-write mode will not be affected. The change applies after remount. + +*--setrw*:: +Set read-write. + +== AUTHORS + +*blockdev* was written by Andries E. Brouwer and rewritten by Karel Zak. + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/disk-utils/blockdev.c b/disk-utils/blockdev.c new file mode 100644 index 0000000..9b1a0b4 --- /dev/null +++ b/disk-utils/blockdev.c @@ -0,0 +1,521 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * 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. + * + * blockdev.c --- Do various simple block device ioctls from the command line + * aeb, 991028 + * + * Copyright (C) 2007-2023 Karel Zak <kzak@redhat.com> + */ +#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 "strutils.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(BLKGETDISKSEQ), + .name = "--getdiskseq", + .argtype = ARG_ULLONG, + .argval = -1, + .help = N_("get disk sequence number") + },{ + 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); + close_stdout_atexit(); + + if (argc < 2) { + warnx(_("not enough arguments")); + errtryhelp(EXIT_FAILURE); + } + + /* -V not together with commands */ + if (!strcmp(argv[1], "-V") || !strcmp(argv[1], "--version")) + print_version(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 = strtos32_or_err(argv[++i], _("failed to parse command argument")); + } 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; + + snprintf(device, sizeof(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; + char start_str[16] = { "\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) + /* TRANSLATORS: Start sector not available. Max. 15 letters. */ + snprintf(start_str, sizeof(start_str), "%15s", _("N/A")); + } + ul_unref_path(pc); + } + if (!*start_str) + snprintf(start_str, sizeof(start_str), "%15ju", start); + + 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 %s %15lld %s\n", + ro ? "ro" : "rw", ra, ssz, bsz, start_str, 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..2e518b7 --- /dev/null +++ b/disk-utils/cfdisk.8 @@ -0,0 +1,238 @@ +'\" t +.\" Title: cfdisk +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-10-23 +.\" Manual: System Administration +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "CFDISK" "8" "2023-10-23" "util\-linux 2.39.3" "System Administration" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +cfdisk \- display or manipulate a disk partition table +.SH "SYNOPSIS" +.sp +\fBcfdisk\fP [options] [\fIdevice\fP] +.SH "DESCRIPTION" +.sp +\fBcfdisk\fP is a curses\-based program for partitioning any block device. The default device is \fI/dev/sda\fP. +.sp +Note that \fBcfdisk\fP provides basic partitioning functionality with a user\-friendly interface. If you need advanced features, use \fBfdisk\fP(8) instead. +.sp +All disk label changes will remain in memory only, and the disk will be unmodified until you decide to write your changes. Be careful before using the write command. +.sp +Since version 2.25 \fBcfdisk\fP 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 +Since version 2.25 \fBcfdisk\fP also does not provide a \*(Aqprint\*(Aq command any more. This functionality is provided by the utilities \fBpartx\fP(8) and \fBlsblk\fP(8) in a very comfortable and rich way. +.sp +If you want to remove an old partition table from a device, use \fBwipefs\fP(8). +.SH "OPTIONS" +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.sp +\fB\-V\fP, \fB\-\-version\fP +.RS 4 +Print version and exit. +.RE +.sp +\fB\-L\fP, \fB\-\-color\fP[\fB=\fP\fIwhen\fP] +.RS 4 +Colorize the output. The optional argument \fIwhen\fP can be \fBauto\fP, \fBnever\fP or \fBalways\fP. If the \fIwhen\fP argument is omitted, it defaults to \fBauto\fP. The colors can be disabled, for the current built\-in default see \fB\-\-help\fP output. See also the \fBCOLORS\fP section. +.RE +.sp +\fB\-\-lock\fP[=\fImode\fP] +.RS 4 +Use exclusive BSD lock for device or file it operates. The optional argument \fImode\fP can be \fByes\fP, \fBno\fP (or 1 and 0) or \fBnonblock\fP. If the \fImode\fP argument is omitted, it defaults to \fByes\fP. This option overwrites environment variable \fB$LOCK_BLOCK_DEVICE\fP. The default is not to use any lock at all, but it\(cqs recommended to avoid collisions with \fBsystemd\-udevd\fP(8) or other tools. +.RE +.sp +\fB\-r\fP, \fB\-\-read\-only\fP +.RS 4 +Forced open in read\-only mode. +.RE +.sp +\fB\-z\fP, \fB\-\-zero\fP +.RS 4 +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 \fBsfdisk\fP(8)\-compatible script. +.RE +.SH "COMMANDS" +.sp +The commands for \fBcfdisk\fP can be entered by pressing the corresponding key (pressing \fIEnter\fP after the command is not necessary). Here is a list of the available commands: +.sp +\fBb\fP +.RS 4 +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. +.RE +.sp +\fBd\fP +.RS 4 +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. +.RE +.sp +\fBh\fP +.RS 4 +Show the help screen. +.RE +.sp +\fBn\fP +.RS 4 +Create a new partition from free space. \fBcfdisk\fP 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. +.sp +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"). +.RE +.sp +\fBq\fP +.RS 4 +Quit the program. This will exit the program without writing any data to the disk. +.RE +.sp +\fBr\fP +.RS 4 +Reduce or enlarge the current partition. \fBcfdisk\fP then prompts you for the new size of the partition. The default size is the current size. A partition marked as free space or marked as unusable cannot be resized. +.sp +\fBNote that reducing the size of a partition might destroy data on that partition.\fP +.RE +.sp +\fBs\fP +.RS 4 +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. +.RE +.sp +\fBt\fP +.RS 4 +Change the partition type. By default, new partitions are created as \fILinux\fP partitions. +.RE +.sp +\fBu\fP +.RS 4 +Dump the current in\-memory partition table to an \fBsfdisk\fP(8)\-compatible script file. +.sp +The script files are compatible between \fBcfdisk\fP, \fBfdisk\fP(8) \fBsfdisk\fP(8) and other libfdisk applications. For more details see \fBsfdisk\fP(8). +.sp +It is also possible to load an sfdisk\-script into \fBcfdisk\fP if there is no partition table on the device or when you start \fBcfdisk\fP with the \fB\-\-zero\fP command\-line option. +.RE +.sp +\fBW\fP +.RS 4 +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\*(Aq or `no\*(Aq. If you enter `yes\*(Aq, \fBcfdisk\fP will write the partition table to disk and then tell the kernel to re\-read the partition table from the disk. +.sp +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 \fBpartprobe\fP(8) or \fBpartx\fP(8), or by rebooting the system. +.RE +.sp +\fBx\fP +.RS 4 +Toggle extra information about a partition. +.RE +.sp +\fIUp Arrow\fP, \fIDown Arrow\fP +.RS 4 +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. +.RE +.sp +\fILeft Arrow\fP, \fIRight Arrow\fP +.RS 4 +Select the preceding or the next menu item. Hitting \fIEnter\fP will execute the currently selected item. +.RE +.sp +All commands can be entered with either uppercase or lowercase letters (except for \fBW\fPrite). When in a submenu or at a prompt, you can hit the \fIEsc\fP key to return to the main menu. +.SH "COLORS" +.sp +The output colorization is implemented by \fBterminal\-colors.d\fP(5) functionality. +Implicit coloring can be disabled by an empty file +.RS 3 +.ll -.6i +.sp +\fI/etc/terminal\-colors.d/cfdisk.disable\fP +.br +.RE +.ll +.sp +for the \fBcfdisk\fP command or for all tools by +.RS 3 +.ll -.6i +.sp +\fI/etc/terminal\-colors.d/disable\fP +.br +.RE +.ll +.sp +The user\-specific \fI$XDG_CONFIG_HOME/terminal\-colors.d\fP +or \fI$HOME/.config/terminal\-colors.d\fP overrides the global setting. +.sp +Note that the output colorization may be enabled by default, and in this case +\fIterminal\-colors.d\fP directories do not have to exist yet. +.sp +\fBcfdisk\fP does not support color customization with a color\-scheme file. +.SH "ENVIRONMENT" +.sp +\fBCFDISK_DEBUG\fP=all +.RS 4 +enables cfdisk debug output. +.RE +.sp +\fBLIBFDISK_DEBUG\fP=all +.RS 4 +enables libfdisk debug output. +.RE +.sp +\fBLIBBLKID_DEBUG\fP=all +.RS 4 +enables libblkid debug output. +.RE +.sp +\fBLIBSMARTCOLS_DEBUG\fP=all +.RS 4 +enables libsmartcols debug output. +.RE +.sp +\fBLIBSMARTCOLS_DEBUG_PADDING\fP=on +.RS 4 +use visible padding characters. Requires enabled \fBLIBSMARTCOLS_DEBUG\fP. +.RE +.sp +\fBLOCK_BLOCK_DEVICE\fP=<mode> +.RS 4 +use exclusive BSD lock. The mode is "1" or "0". See \fB\-\-lock\fP for more details. +.RE +.SH "AUTHORS" +.sp +.MTO "kzak\(atredhat.com" "Karel Zak" "" +.sp +The current \fBcfdisk\fP implementation is based on the original \fBcfdisk\fP from \c +.MTO "martin\(atcs.unc.edu" "Kevin E. Martin" "." +.SH "SEE ALSO" +.sp +\fBfdisk\fP(8), +\fBparted\fP(8), +\fBpartprobe\fP(8), +\fBpartx\fP(8), +\fBsfdisk\fP(8) +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBcfdisk\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "."
\ No newline at end of file diff --git a/disk-utils/cfdisk.8.adoc b/disk-utils/cfdisk.8.adoc new file mode 100644 index 0000000..97fad62 --- /dev/null +++ b/disk-utils/cfdisk.8.adoc @@ -0,0 +1,159 @@ +//po4a: entry man manual +//// +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. +//// += cfdisk(8) +:doctype: manpage +:man manual: System Administration +:man source: util-linux {release-version} +:page-layout: base +:command: cfdisk + +== NAME + +cfdisk - display or manipulate a disk partition table + +== SYNOPSIS + +*cfdisk* [options] [_device_] + +== DESCRIPTION + +*cfdisk* is a curses-based program for partitioning any block device. The default device is _/dev/sda_. + +Note that *cfdisk* provides basic partitioning functionality with a user-friendly interface. If you need advanced features, use *fdisk*(8) instead. + +All disk label changes will remain in memory only, and the disk will be unmodified until you decide to write your changes. Be careful before using the write command. + +Since version 2.25 *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 *cfdisk* also does not provide a 'print' command any more. This functionality is provided by the utilities *partx*(8) and *lsblk*(8) in a very comfortable and rich way. + +If you want to remove an old partition table from a device, use *wipefs*(8). + +== OPTIONS + +include::man-common/help-version.adoc[] + +*-L*, *--color*[**=**__when__]:: +Colorize the output. The optional argument _when_ can be *auto*, *never* or *always*. If the _when_ argument is omitted, it defaults to *auto*. The colors can be disabled, for the current built-in default see *--help* output. See also the *COLORS* section. + +*--lock*[=_mode_]:: +Use exclusive BSD lock for device or file it operates. The optional argument _mode_ can be *yes*, *no* (or 1 and 0) or *nonblock*. If the _mode_ argument is omitted, it defaults to *yes*. This option overwrites environment variable *$LOCK_BLOCK_DEVICE*. The default is not to use any lock at all, but it's recommended to avoid collisions with *systemd-udevd*(8) or other tools. + +*-r*, *--read-only*:: +Forced open in read-only mode. + +*-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*(8)-compatible script. + +== COMMANDS + +The commands for *cfdisk* can be entered by pressing the corresponding key (pressing _Enter_ after the command is not necessary). Here is a list of the available commands: + +*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. + +*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. + +*h*:: +Show the help screen. + +*n*:: +Create a new partition from free space. *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"). + +*q*:: +Quit the program. This will exit the program without writing any data to the disk. + +*r*:: +Reduce or enlarge the current partition. *cfdisk* then prompts you for the new size of the partition. The default size is the current size. A partition marked as free space or marked as unusable cannot be resized. ++ +*Note that reducing the size of a partition might destroy data on that partition.* + +*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. + +*t*:: +Change the partition type. By default, new partitions are created as _Linux_ partitions. + +*u*:: +Dump the current in-memory partition table to an **sfdisk**(8)-compatible script file. ++ +The script files are compatible between *cfdisk*, *fdisk*(8) *sfdisk*(8) and other libfdisk applications. For more details see *sfdisk*(8). ++ +It is also possible to load an sfdisk-script into *cfdisk* if there is no partition table on the device or when you start *cfdisk* with the *--zero* command-line option. + +*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', *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 *partprobe*(8) or *partx*(8), or by rebooting the system. + +*x*:: +Toggle extra information about a partition. + +_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. + +_Left Arrow_, _Right Arrow_:: +Select the preceding or the next menu item. Hitting _Enter_ will execute the currently selected item. + +All commands can be entered with either uppercase or lowercase letters (except for **W**rite). When in a submenu or at a prompt, you can hit the _Esc_ key to return to the main menu. + +include::man-common/colors.adoc[] +*cfdisk* does not support color customization with a color-scheme file. + +== ENVIRONMENT + +*CFDISK_DEBUG*=all:: +enables cfdisk debug output. + +*LIBFDISK_DEBUG*=all:: +enables libfdisk debug output. + +*LIBBLKID_DEBUG*=all:: +enables libblkid debug output. + +*LIBSMARTCOLS_DEBUG*=all:: +enables libsmartcols debug output. + +*LIBSMARTCOLS_DEBUG_PADDING*=on:: +use visible padding characters. Requires enabled *LIBSMARTCOLS_DEBUG*. + +*LOCK_BLOCK_DEVICE*=<mode>:: +use exclusive BSD lock. The mode is "1" or "0". See *--lock* for more details. + +== AUTHORS + +mailto:kzak@redhat.com[Karel Zak] + +The current *cfdisk* implementation is based on the original *cfdisk* from mailto:martin@cs.unc.edu[Kevin E. Martin]. + +== SEE ALSO + +*fdisk*(8), +*parted*(8), +*partprobe*(8), +*partx*(8), +*sfdisk*(8) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/disk-utils/cfdisk.c b/disk-utils/cfdisk.c new file mode 100644 index 0000000..fde2029 --- /dev/null +++ b/disk-utils/cfdisk.c @@ -0,0 +1,2851 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * 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. + * + * 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. + */ +#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" +#include "blkdev.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 +}; +#ifdef HAVE_USE_DEFAULT_COLORS +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 } +}; +#endif + +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, ...) + __attribute__((__format__ (__printf__, 1, 2))); +static void ui_warn(const char *fmt, ...) + __attribute__((__format__ (__printf__, 1, 2))); +static void ui_info(const char *fmt, ...) + __attribute__((__format__ (__printf__, 1, 2))); + +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_table(tb); + + 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("%s", fdisk_ask_print_get_mesg(ask)); + break; + case FDISK_ASKTYPE_WARNX: + ui_warnx("%s", fdisk_ask_print_get_mesg(ask)); + break; + case FDISK_ASKTYPE_WARN: + ui_warn("%s", 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 __attribute__((__format__ (__printf__, 3, 0))) + 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 __attribute__((__format__ (__printf__, 2, 3))) + 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 __attribute__((__format__ (__printf__, 1, 2))) + 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 __attribute__((__format__ (__printf__, 1, 2))) + 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__)) + __attribute__((__format__ (__printf__, 2, 3))) + ui_err(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); + fprintf(stderr, ": %s\n", strerror(errno)); + va_end(ap); + + exit(rc); +} + +static int __attribute__((__noreturn__)) + __attribute__((__format__ (__printf__, 2, 3))) + 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 __attribute__((__format__ (__printf__, 1, 2))) + 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 __attribute__((__format__ (__printf__, 1, 2))) + 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; + } + + { + 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; + } + + { + 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("%s", _(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); + if (mnt_table_parse_fstab(cf->fstab, NULL) != 0) { + mnt_unref_table(cf->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 inline int iszero(const char *str) +{ + const char *p; + + for (p = str; p && *p == '0'; p++); + + return !p || *p == '\0'; +} + +static int has_uuid(struct fdisk_table *tb, const char *uuid) +{ + struct fdisk_partition *pa; + struct fdisk_iter *itr; + int rc = 0; + + if (!tb || !uuid || fdisk_table_is_empty(tb)) + return 0; + + itr = fdisk_new_iter(FDISK_ITER_FORWARD); + while (rc == 0 && fdisk_table_next_partition(tb, itr, &pa) == 0) { + const char *x = fdisk_partition_get_uuid(pa); + if (x) + rc = strcmp(x, uuid) == 0; + } + fdisk_free_iter(itr); + return rc; +} + +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); + + /* Search for mountpoint by PARTUUID= means that we need to + * check fstab and convert PARTUUID to the device name. This is + * unnecessary and overkill for newly created partitions. Let's + * check if the UUID already exist in the old layout, otherwise + * ignore it. + */ + if (!mountpoint && has_uuid(cf->original_layout, data)) + 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 (!iszero(data)) + extra_insert_pair(l, "BSIZE:", data); + free(data); + } + + if (!fdisk_partition_to_string(pa, cf->cxt, FDISK_FIELD_CPG, &data) && data) { + if (!iszero(data)) + extra_insert_pair(l, "CPG:", data); + free(data); + } + + if (!fdisk_partition_to_string(pa, cf->cxt, FDISK_FIELD_FSIZE, &data) && data) { + if (!iszero(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_DECIMAL_2DIGITS + | 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); + free(id); + + 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("%s", 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 */ + } 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) : "")); + 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, press 'L' to load script file, 'Q' quits.")); + 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_(" r Reduce or enlarge the current partition"), + 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) { + errno = -rc; + ui_err(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. Changes will remain in memory only.")); + else if (cf->device_is_used) + ui_warnx(_("Device is currently in use, repartitioning is probably a bad idea.")); + 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); + fprintf(out, + _(" -L, --color[=<when>] colorize output (%s, %s or %s)\n"), "auto", "always", "never"); + fprintf(out, + " %s\n", USAGE_COLORS_DEFAULT); + fputs(_(" -z, --zero start with zeroed partition table\n"), out); + fprintf(out, + _(" --lock[=<mode>] use exclusive device lock (%s, %s or %s)\n"), "yes", "no", "nonblock"); + fputs(_(" -r, --read-only forced open cfdisk in read-only mode\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, *lockmode = NULL; + int rc, c, colormode = UL_COLORMODE_UNDEF; + int read_only = 0; + struct cfdisk _cf = { .lines_idx = 0 }, + *cf = &_cf; + enum { + OPT_LOCK = CHAR_MAX + 1 + }; + static const struct option longopts[] = { + { "color", optional_argument, NULL, 'L' }, + { "lock", optional_argument, NULL, OPT_LOCK }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + { "zero", no_argument, NULL, 'z' }, + { "read-only", no_argument, NULL, 'r' }, + { NULL, 0, NULL, 0 }, + }; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + close_stdout_atexit(); + + while((c = getopt_long(argc, argv, "L::hVzr", 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 'r': + read_only = 1; + break; + case 'V': + print_version(EXIT_SUCCESS); + case 'z': + cf->zero_start = 1; + break; + case OPT_LOCK: + lockmode = "1"; + if (optarg) { + if (*optarg == '=') + optarg++; + lockmode = optarg; + } + 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, read_only); + if (rc == -EACCES && read_only == 0) + rc = fdisk_assign_device(cf->cxt, diskpath, 1); + if (rc != 0) + err(EXIT_FAILURE, _("cannot open %s"), diskpath); + + if (!fdisk_is_readonly(cf->cxt)) { + if (blkdev_lock(fdisk_get_devfd(cf->cxt), diskpath, lockmode) != 0) + return EXIT_FAILURE; + + 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); + free(cf->fields); + + 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..322dbf3 --- /dev/null +++ b/disk-utils/cramfs.h @@ -0,0 +1,110 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * 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 +#define HOST_IS_BIG_ENDIAN (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) +#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..400b146 --- /dev/null +++ b/disk-utils/cramfs_common.c @@ -0,0 +1,111 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * 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..da24ffc --- /dev/null +++ b/disk-utils/delpart.8 @@ -0,0 +1,65 @@ +'\" t +.\" Title: delpart +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-10-23 +.\" Manual: System Administration +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "DELPART" "8" "2023-10-23" "util\-linux 2.39.3" "System Administration" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +delpart \- tell the kernel to forget about a partition +.SH "SYNOPSIS" +.sp +\fBdelpart\fP \fIdevice partition\fP +.SH "DESCRIPTION" +.sp +\fBdelpart\fP asks the Linux kernel to forget about the specified \fIpartition\fP (a number) on the specified \fIdevice\fP. The command is a simple wrapper around the "del partition" ioctl. +.sp +This command doesn\(cqt manipulate partitions on a block device. +.SH "OPTIONS" +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.sp +\fB\-V\fP, \fB\-\-version\fP +.RS 4 +Print version and exit. +.RE +.SH "SEE ALSO" +.sp +\fBaddpart\fP(8), +\fBfdisk\fP(8), +\fBparted\fP(8), +\fBpartprobe\fP(8), +\fBpartx\fP(8) +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBdelpart\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "."
\ No newline at end of file diff --git a/disk-utils/delpart.8.adoc b/disk-utils/delpart.8.adoc new file mode 100644 index 0000000..e52e254 --- /dev/null +++ b/disk-utils/delpart.8.adoc @@ -0,0 +1,47 @@ +//po4a: entry man manual +//// +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 +//// += delpart(8) +:doctype: manpage +:man manual: System Administration +:man source: util-linux {release-version} +:page-layout: base +:command: delpart + +== NAME + +delpart - tell the kernel to forget about a partition + +== SYNOPSIS + +*delpart* _device partition_ + +== DESCRIPTION + +*delpart* asks the Linux kernel to forget about the specified _partition_ (a number) on the specified _device_. The command is a simple wrapper around the "del partition" ioctl. + +This command doesn't manipulate partitions on a block device. + +== OPTIONS + +include::man-common/help-version.adoc[] + +== SEE ALSO + +*addpart*(8), +*fdisk*(8), +*parted*(8), +*partprobe*(8), +*partx*(8) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/disk-utils/delpart.c b/disk-utils/delpart.c new file mode 100644 index 0000000..497dcc9 --- /dev/null +++ b/disk-utils/delpart.c @@ -0,0 +1,75 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * 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. + * + * Copyright (C) 2012-2023 Karel Zak <kzak@redhat.com> + */ +#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': + print_version(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..e145fdd --- /dev/null +++ b/disk-utils/fdformat.8 @@ -0,0 +1,123 @@ +'\" t +.\" Title: fdformat +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-10-23 +.\" Manual: System Administration +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "FDFORMAT" "8" "2023-10-23" "util\-linux 2.39.3" "System Administration" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +fdformat \- low\-level format a floppy disk +.SH "SYNOPSIS" +.sp +\fBfdformat\fP [options] \fIdevice\fP +.SH "DESCRIPTION" +.sp +\fBfdformat\fP does a low\-level format on a floppy disk. \fIdevice\fP is usually one of the following (for floppy devices the major = 2, and the minor is shown for informational purposes only): +.sp +.if n .RS 4 +.nf +.fam C +/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) +.fam +.fi +.if n .RE +.sp +.if n .RS 4 +.nf +.fam C +/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) +.fam +.fi +.if n .RE +.sp +The generic floppy devices, \fI/dev/fd0\fP and \fI/dev/fd1\fP, will fail to work with \fBfdformat\fP when a non\-standard format is being used, or if the format has not been autodetected earlier. In this case, use \fBsetfdprm\fP(8) to load the disk parameters. +.SH "OPTIONS" +.sp +\fB\-f\fP, \fB\-\-from\fP \fIN\fP +.RS 4 +Start at the track \fIN\fP (default is 0). +.RE +.sp +\fB\-t\fP, \fB\-\-to\fP \fIN\fP +.RS 4 +Stop at the track \fIN\fP. +.RE +.sp +\fB\-r\fP, \fB\-\-repair\fP \fIN\fP +.RS 4 +Try to repair tracks failed during the verification (max \fIN\fP retries). +.RE +.sp +\fB\-n\fP, \fB\-\-no\-verify\fP +.RS 4 +Skip the verification that is normally performed after the formatting. +.RE +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.sp +\fB\-V\fP, \fB\-\-version\fP +.RS 4 +Print version and exit. +.RE +.SH "NOTES" +.sp +This utility does not handle USB floppy disk drives. Use \fBufiformat\fP(8) instead. +.SH "AUTHORS" +.sp +.MTO "almesber\(atnessie.cs.id.ethz.ch" "Werner Almesberger" "" +.SH "SEE ALSO" +.sp +\fBfd\fP(4), +\fBemkfs\fP(8), +\fBmkfs\fP(8), +\fBsetfdprm\fP(8), +\fBufiformat\fP(8) +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBfdformat\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "."
\ No newline at end of file diff --git a/disk-utils/fdformat.8.adoc b/disk-utils/fdformat.8.adoc new file mode 100644 index 0000000..d57f49d --- /dev/null +++ b/disk-utils/fdformat.8.adoc @@ -0,0 +1,88 @@ +//po4a: entry man manual +//// +Copyright 1992, 1993 Rickard E. Faith (faith@cs.unc.edu) +May be distributed under the GNU General Public License +//// += fdformat(8) +:doctype: manpage +:man manual: System Administration +:man source: util-linux {release-version} +:page-layout: base +:command: fdformat + +== NAME + +fdformat - low-level format a floppy disk + +== SYNOPSIS + +*fdformat* [options] _device_ + +== DESCRIPTION + +*fdformat* does a low-level format on a floppy disk. _device_ is usually one of the following (for floppy devices the major = 2, and the minor is shown for informational purposes only): + +.... +/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) +.... +.... +/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) +.... + +The generic floppy devices, _/dev/fd0_ and _/dev/fd1_, will fail to work with *fdformat* when a non-standard format is being used, or if the format has not been autodetected earlier. In this case, use *setfdprm*(8) to load the disk parameters. + +== OPTIONS + +*-f*, *--from* _N_:: +Start at the track _N_ (default is 0). + +*-t*, *--to* _N_:: +Stop at the track _N_. + +*-r*, *--repair* _N_:: +Try to repair tracks failed during the verification (max _N_ retries). + +*-n*, *--no-verify*:: +Skip the verification that is normally performed after the formatting. + +include::man-common/help-version.adoc[] + +== NOTES + +This utility does not handle USB floppy disk drives. Use *ufiformat*(8) instead. + +== AUTHORS + +mailto:almesber@nessie.cs.id.ethz.ch[Werner Almesberger] + +== SEE ALSO + +*fd*(4), +*emkfs*(8), +*mkfs*(8), +*setfdprm*(8), +*ufiformat*(8) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/disk-utils/fdformat.c b/disk-utils/fdformat.c new file mode 100644 index 0000000..871bb07 --- /dev/null +++ b/disk-utils/fdformat.c @@ -0,0 +1,265 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * 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. + * + * Copyright (C) 1992 Werner Almesberger + * + * 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, ((off_t) 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); + close_stdout_atexit(); + + 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': + print_version(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 a 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..f93ed10 --- /dev/null +++ b/disk-utils/fdisk-list.c @@ -0,0 +1,553 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * 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. + * + * Copyright (C) 2014 Karel Zak <kzak@redhat.com> + */ +#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|O_NONBLOCK)) < 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_DECIMAL_2DIGITS + | 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, %ju sectors/track, %ju cylinders"), + fdisk_get_geom_heads(cxt), + (uintmax_t) fdisk_get_geom_sectors(cxt), + (uintmax_t) 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, "%s", ""); /* 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, "%s", ""); /* 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, "%s", ""); /* 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, "%s", ""); /* 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_DECIMAL_2DIGITS + | 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, "%s", ""); /* 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, int separator) +{ + if (fdisk_assign_device(cxt, device, 1) != 0) { /* read-only */ + if (warnme || errno == EACCES) + warn(_("cannot open %s"), device); + return -1; + } + + if (separator) + fputs("\n\n", stdout); + + 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, + int separator) +{ + if (fdisk_assign_device(cxt, device, 1) != 0) { /* read-only */ + if (warnme || errno == EACCES) + warn(_("cannot open %s"), device); + return -1; + } + + if (separator) + fputs("\n\n", stdout); + + 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 sep = 0; + char *dev; + + while ((dev = next_proc_partition(&f))) { + print_device_pt(cxt, dev, 0, verify, sep); + free(dev); + sep = 1; + } +} + +void print_all_devices_freespace(struct fdisk_context *cxt) +{ + FILE *f = NULL; + int sep = 0; + char *dev; + + while ((dev = next_proc_partition(&f))) { + print_device_freespace(cxt, dev, 0, sep); + free(dev); + sep = 1; + } +} + +/* 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..a30cd6a --- /dev/null +++ b/disk-utils/fdisk-list.h @@ -0,0 +1,57 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * 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. + * + * Copyright (C) 2014-2023 Karel Zak <kzak@redhat.com> + */ +#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, int separator); +extern int print_device_freespace(struct fdisk_context *cxt, char *device, int warnme, int separator); + +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..5cf0002 --- /dev/null +++ b/disk-utils/fdisk-menu.c @@ -0,0 +1,1127 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * 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. + * + * Copyright (C) 2014 Karel Zak <kzak@redhat.com> + */ +#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_BENT_NEST_H(k, t, l, p) { .title = t, .key = k, .expert = 1, .normal = 1, .label = l, .parent = p, .hidden = 1 } + +/* 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 (MBR)"), FDISK_DISKLABEL_BSD, FDISK_DISKLABEL_DOS), + + MENU_ENT_NEST('r', N_("return from protective/hybrid MBR to GPT"), FDISK_DISKLABEL_DOS, FDISK_DISKLABEL_GPT), + + { 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 MBR (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('F', N_("fix partitions C/H/S values")), + MENU_XENT('i', N_("change the disk identifier")), + + MENU_BENT_NEST_H('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] = { '\0' }; + + 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))) { + /* TRANSLATORS: these yes no questions use rpmatch(), + * and should be translated. */ + rc = get_user_reply( + _("\nAll unwritten changes will be lost, do you really want to quit? (y/n)"), + 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.")); + rc = 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")); + + fdisk_info(cxt, _("The partition table has been altered.")); + if (fdisk_get_parent(cxt)) + break; /* nested PT, don't leave */ + + 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); + 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 or 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; +} + + +/* + * 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 (backward compatibility only) */ + if (fdisk_get_parent(cxt)) { + *cxt0 = fdisk_get_parent(cxt); + + fdisk_info(cxt, _("Leaving nested disklabel.")); + fdisk_unref_context(cxt); + } + break; + case 'F': + rc = fdisk_dos_fix_chs(cxt); + if (rc) + fdisk_info(cxt, _("C/H/S values fixed.")); + else + fdisk_info(cxt, _("Nothing to do. C/H/S values are correct already.")); + 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; + const char *wanted = NULL; + 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. */ + wanted = "sgi"; + break; + } + } else { + switch (ent->key) { + case 'g': + wanted = "gpt"; + break; + case 'G': + wanted = "sgi"; + break; + case 'o': + wanted = "dos"; + break; + case 's': + wanted = "sun"; + break; + } + } + + if (wanted) { + rc = fdisk_create_disklabel(cxt, wanted); + if (rc) { + errno = -rc; + fdisk_warn(cxt, _("Failed to create '%s' disk label"), wanted); + } + } + 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..e9c7001 --- /dev/null +++ b/disk-utils/fdisk.8 @@ -0,0 +1,322 @@ +'\" t +.\" Title: fdisk +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-10-23 +.\" Manual: System Administration +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "FDISK" "8" "2023-10-23" "util\-linux 2.39.3" "System Administration" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +fdisk \- manipulate disk partition table +.SH "SYNOPSIS" +.sp +\fBfdisk\fP [options] \fIdevice\fP +.sp +\fBfdisk\fP \fB\-l\fP [\fIdevice\fP...] +.SH "DESCRIPTION" +.sp +\fBfdisk\fP is a dialog\-driven program for creation and manipulation of partition tables. It understands GPT, MBR, Sun, SGI and BSD partition tables. +.sp +Block devices can be divided into one or more logical disks called \fIpartitions\fP. This division is recorded in the \fIpartition table\fP, usually found in sector 0 of the disk. (In the BSD world one talks about `disk slices\*(Aq and a `disklabel\*(Aq.) +.sp +All partitioning is driven by device I/O limits (the topology) by default. \fBfdisk\fP 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\fP\*(Aqs 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. +.sp +CHS (Cylinder\-Head\-Sector) addressing is deprecated and not used by default. Please, do not follow old articles and recommendations with \fBfdisk \-S <n> \-H <n>\fP advices for SSD or 4K\-sector devices. +.sp +Note that \fBpartx\fP(8) provides a rich interface for scripts to print disk layouts, \fBfdisk\fP is mostly designed for humans. Backward compatibility in the output of \fBfdisk\fP is not guaranteed. The input (the commands) should always be backward compatible. +.SH "OPTIONS" +.sp +\fB\-b\fP, \fB\-\-sector\-size\fP \fIsectorsize\fP +.RS 4 +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\(cqs ideas.) Since util\-linux\-2.17, \fBfdisk\fP differentiates between logical and physical sector size. This option changes both sector sizes to \fIsectorsize\fP. +.RE +.sp +\fB\-B\fP, \fB\-\-protect\-boot\fP +.RS 4 +Don\(cqt erase the beginning of the first disk sector when creating a new disk label. This feature is supported for GPT and MBR. +.RE +.sp +\fB\-c\fP, \fB\-\-compatibility\fP[=\fImode\fP] +.RS 4 +Specify the compatibility mode, \*(Aqdos\*(Aq or \*(Aqnondos\*(Aq. The default is non\-DOS mode. For backward compatibility, it is possible to use the option without the \fImode\fP argument \(em then the default is used. Note that the optional \fImode\fP argument cannot be separated from the \fB\-c\fP option by a space, the correct form is for example \fB\-c\fP=\fIdos\fP. +.RE +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.sp +\fB\-V\fP, \fB\-\-version\fP +.RS 4 +Print version and exit. +.RE +.sp +\fB\-L\fP, \fB\-\-color\fP[=\fIwhen\fP] +.RS 4 +Colorize the output. The optional argument \fIwhen\fP can be \fBauto\fP, \fBnever\fP or \fBalways\fP. If the \fIwhen\fP argument is omitted, it defaults to \fBauto\fP. The colors can be disabled; for the current built\-in default see the \fB\-\-help\fP output. See also the \fBCOLORS\fP section. +.RE +.sp +\fB\-l\fP, \fB\-\-list\fP +.RS 4 +List the partition tables for the specified devices and then exit. +.sp +If no devices are given, the devices mentioned in \fI/proc/partitions\fP (if this file exists) are used. Devices are always listed in the order in which they are specified on the command\-line, or by the kernel listed in \fI/proc/partitions\fP. +.RE +.sp +\fB\-x\fP, \fB\-\-list\-details\fP +.RS 4 +Like \fB\-\-list\fP, but provides more details. +.RE +.sp +\fB\-\-lock\fP[=\fImode\fP] +.RS 4 +Use exclusive BSD lock for device or file it operates. The optional argument \fImode\fP can be \fByes\fP, \fBno\fP (or 1 and 0) or \fBnonblock\fP. If the \fImode\fP argument is omitted, it defaults to \fByes\fP. This option overwrites environment variable \fB$LOCK_BLOCK_DEVICE\fP. The default is not to use any lock at all, but it\(cqs recommended to avoid collisions with \fBsystemd\-udevd\fP(8) or other tools. +.RE +.sp +\fB\-n\fP, \fB\-\-noauto\-pt\fP +.RS 4 +Don\(cqt automatically create a default partition table on empty device. The partition table has to be explicitly created by user (by command like \*(Aqo\*(Aq, \*(Aqg\*(Aq, etc.). +.RE +.sp +\fB\-o\fP, \fB\-\-output\fP \fIlist\fP +.RS 4 +Specify which output columns to print. Use \fB\-\-help\fP 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). +.RE +.sp +\fB\-s\fP, \fB\-\-getsz\fP +.RS 4 +Print the size in 512\-byte sectors of each given block device. This option is DEPRECATED in favour of \fBblockdev\fP(8). +.RE +.sp +\fB\-t\fP, \fB\-\-type\fP \fItype\fP +.RS 4 +Enable support only for disklabels of the specified \fItype\fP, and disable support for all other types. +.RE +.sp +\fB\-u\fP, \fB\-\-units\fP[=\fIunit\fP] +.RS 4 +When listing partition tables, show sizes in \*(Aqsectors\*(Aq or in \*(Aqcylinders\*(Aq. The default is to show sizes in sectors. For backward compatibility, it is possible to use the option without the \fIunit\fP argument \(em then the default is used. Note that the optional \fIunit\fP argument cannot be separated from the \fB\-u\fP option by a space, the correct form is for example \*(Aq\fB\-u=\fP\fIcylinders\fP\*(Aq. +.RE +.sp +\fB\-C\fP, \fB\-\-cylinders\fP \fInumber\fP +.RS 4 +Specify the \fInumber\fP of cylinders of the disk. I have no idea why anybody would want to do so. +.RE +.sp +\fB\-H\fP, \fB\-\-heads\fP \fInumber\fP +.RS 4 +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. +.RE +.sp +\fB\-S\fP, \fB\-\-sectors\fP \fInumber\fP +.RS 4 +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. +.RE +.sp +\fB\-w\fP, \fB\-\-wipe\fP \fIwhen\fP +.RS 4 +Wipe filesystem, RAID and partition\-table signatures from the device, in order to avoid possible collisions. The argument \fIwhen\fP can be \fBauto\fP, \fBnever\fP or \fBalways\fP. When this option is not given, the default is \fBauto\fP, 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 \fBwipefs\fP(8) command. +.RE +.sp +\fB\-W\fP, \fB\-\-wipe\-partitions\fP \fIwhen\fP +.RS 4 +Wipe filesystem, RAID and partition\-table signatures from a newly created partitions, in order to avoid possible collisions. The argument \fIwhen\fP can be \fBauto\fP, \fBnever\fP or \fBalways\fP. When this option is not given, the default is \fBauto\fP, 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 \fBwipefs\fP(8) command. +.RE +.sp +\fB\-V\fP, \fB\-\-version\fP +.RS 4 +Display version information and exit. +.RE +.SH "DEVICES" +.sp +The \fIdevice\fP is usually \fI/dev/sda\fP, \fI/dev/sdb\fP 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 \fI/dev/hd*\fP (IDE) or \fI/dev/sd*\fP (SCSI). +.sp +The \fIpartition\fP is a device name followed by a partition number. For example, \fI/dev/sda1\fP is the first partition on the first hard disk in the system. See also Linux kernel documentation (the \fIDocumentation/admin\-guide/devices.txt\fP file). +.SH "SIZES" +.sp +The "last sector" dialog accepts partition size specified by number of sectors or by +/\-<size>{K,B,M,G,...} notation. +.sp +If the size is prefixed by \*(Aq+\*(Aq then it is interpreted as relative to the partition first sector. If the size is prefixed by \*(Aq\-\*(Aq then it is interpreted as relative to the high limit (last available sector for the partition). +.sp +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". +.sp +The relative sizes are always aligned according to device I/O limits. The +/\-<size>{K,B,M,G,...} notation is recommended. +.sp +For backward compatibility \fBfdisk\fP 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" +.sp +\fBfdisk\fP allows reading (by \*(AqI\*(Aq command) \fBsfdisk\fP(8) 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. +.sp +And vice\-versa it is possible to write the current in\-memory disk layout to the script file by command \*(AqO\*(Aq. +.sp +The script files are compatible between \fBcfdisk\fP(8), \fBsfdisk\fP(8), \fBfdisk\fP and other libfdisk applications. For more details see \fBsfdisk\fP(8). +.SH "DISK LABELS" +.sp +\fBGPT (GUID Partition Table)\fP +.RS 4 +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). +.sp +Note that the first sector is still reserved for a \fBprotective MBR\fP in the GPT specification. It prevents MBR\-only partitioning tools from mis\-recognizing and overwriting GPT disks. +.sp +GPT is always a better choice than MBR, especially on modern hardware with a UEFI boot loader. +.RE +.sp +\fBDOS\-type (MBR)\fP +.RS 4 +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\*(Aq). 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. +.sp +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 \fBCylinders/Heads/Sectors\fP triple (given in 10+8+6 bits). The former is OK \(em 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 \fBC/H/S addressing is deprecated\fP and may be unsupported in some later \fBfdisk\fP version. +.sp +\fBPlease, read the DOS\-mode section if you want DOS\-compatible partitions.\fP \fBfdisk\fP does not care about cylinder boundaries by default. +.RE +.sp +\fBBSD/Sun\-type\fP +.RS 4 +A BSD/Sun disklabel can describe 8 partitions, the third of which should be a `whole disk\*(Aq 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 \fBBSD label\fP is usually nested within a DOS partition. +.RE +.sp +\fBIRIX/SGI\-type\fP +.RS 4 +An IRIX/SGI disklabel can describe 16 partitions, the eleventh of which should be an entire `volume\*(Aq partition, while the ninth should be labeled `volume header\*(Aq. 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. +.sp +A \fBsync\fP(2) and an ioctl(BLKRRPART) (rereading the partition table from disk) are performed before exiting when the partition table has been updated. +.RE +.SH "DOS MODE AND DOS 6.X WARNING" +.sp +\fBNote that all this is deprecated. You don\(cqt have to care about things like\fP \fBgeometry and cylinders on modern operating systems. If you really want\fP \fBDOS\-compatible partitioning then you have to enable DOS mode and cylinder\fP \fBunits by using the \*(Aq\-c=dos \-u=cylinders\*(Aq fdisk command\-line options.\fP +.sp +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 \(em we consider this a bug in DOS FORMAT and DOS FDISK. +.sp +The bottom line is that if you use \fBfdisk\fP or \fBcfdisk\fP(8) to change the size of a DOS partition table entry, then you must also use \fBdd\fP(1) to \fBzero the first 512 bytes\fP of that partition before using DOS FORMAT to format the partition. For example, if you were using \fBfdisk\fP to make a DOS partition table entry for \fI/dev/sda1\fP, then (after exiting \fBfdisk\fP and rebooting Linux so that the partition table information is valid) you would use the command \fBdd if=/dev/zero of=/dev/sda1 bs=512 count=1\fP to zero the first 512 bytes of the partition. +.sp +\fBfdisk\fP 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. +.sp +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 \fBfdisk\fP 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. +.sp +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). +.sp +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. +.sp +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 \fBfdisk\fP or Linux \fBcfdisk\fP(8) programs. +.SH "COLORS" +.sp +The output colorization is implemented by \fBterminal\-colors.d\fP(5) functionality. +Implicit coloring can be disabled by an empty file +.RS 3 +.ll -.6i +.sp +\fI/etc/terminal\-colors.d/fdisk.disable\fP +.br +.RE +.ll +.sp +for the \fBfdisk\fP command or for all tools by +.RS 3 +.ll -.6i +.sp +\fI/etc/terminal\-colors.d/disable\fP +.br +.RE +.ll +.sp +The user\-specific \fI$XDG_CONFIG_HOME/terminal\-colors.d\fP +or \fI$HOME/.config/terminal\-colors.d\fP overrides the global setting. +.sp +Note that the output colorization may be enabled by default, and in this case +\fIterminal\-colors.d\fP directories do not have to exist yet. +.sp +The logical color names supported by \fBfdisk\fP are: +.sp +\fBheader\fP +.RS 4 +The header of the output tables. +.RE +.sp +\fBhelp\-title\fP +.RS 4 +The help section titles. +.RE +.sp +\fBwarn\fP +.RS 4 +The warning messages. +.RE +.sp +\fBwelcome\fP +.RS 4 +The welcome message. +.RE +.SH "ENVIRONMENT" +.sp +\fBFDISK_DEBUG\fP=all +.RS 4 +enables fdisk debug output. +.RE +.sp +\fBLIBFDISK_DEBUG\fP=all +.RS 4 +enables libfdisk debug output. +.RE +.sp +\fBLIBBLKID_DEBUG\fP=all +.RS 4 +enables libblkid debug output. +.RE +.sp +\fBLIBSMARTCOLS_DEBUG\fP=all +.RS 4 +enables libsmartcols debug output. +.RE +.sp +\fBLIBSMARTCOLS_DEBUG_PADDING\fP=on +.RS 4 +use visible padding characters. +.RE +.sp +\fBLOCK_BLOCK_DEVICE\fP=<mode> +.RS 4 +use exclusive BSD lock. The mode is "1" or "0". See \fB\-\-lock\fP for more details. +.RE +.SH "AUTHORS" +.sp +.MTO "kzak\(atredhat.com" "Karel Zak" "," +.MTO "dave\(atgnu.org" "Davidlohr Bueso" "" +.sp +The original version was written by Andries E. Brouwer, A. V. Le Blanc and others. +.SH "SEE ALSO" +.sp +\fBcfdisk\fP(8), +\fBmkfs\fP(8), +\fBpartx\fP(8), +\fBsfdisk\fP(8) +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBfdisk\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "."
\ No newline at end of file diff --git a/disk-utils/fdisk.8.adoc b/disk-utils/fdisk.8.adoc new file mode 100644 index 0000000..7d9a2a8 --- /dev/null +++ b/disk-utils/fdisk.8.adoc @@ -0,0 +1,233 @@ +//po4a: entry man manual +//// +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 +//// += fdisk(8) +:doctype: manpage +:man manual: System Administration +:man source: util-linux {release-version} +:page-layout: base +:command: fdisk +:plus: + + +== NAME + +fdisk - manipulate disk partition table + +== SYNOPSIS + +*fdisk* [options] _device_ + +*fdisk* *-l* [_device_...] + +== DESCRIPTION + +*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 _partitions_. This division is recorded in the _partition table_, usually found in sector 0 of the disk. (In the BSD world one talks about `disk slices' and a `disklabel'.) + +//TRANSLATORS: Keep {plus} untranslated. +All partitioning is driven by device I/O limits (the topology) by default. *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 *fdisk*'s defaults as the default values (e.g., first and last partition sectors) and partition sizes specified by the {plus}/-<size>{M,G,...} notation are always aligned according to the device properties. + +CHS (Cylinder-Head-Sector) addressing is deprecated and not used by default. Please, do not follow old articles and recommendations with *fdisk -S <n> -H <n>* advices for SSD or 4K-sector devices. + +Note that *partx*(8) provides a rich interface for scripts to print disk layouts, *fdisk* is mostly designed for humans. Backward compatibility in the output of *fdisk* is not guaranteed. The input (the commands) should always be backward compatible. + +== OPTIONS + +*-b*, *--sector-size* _sectorsize_:: +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, *fdisk* differentiates between logical and physical sector size. This option changes both sector sizes to _sectorsize_. + +*-B*, *--protect-boot*:: +Don't erase the beginning of the first disk sector when creating a new disk label. This feature is supported for GPT and MBR. + +*-c*, *--compatibility*[=_mode_]:: +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 _mode_ argument -- then the default is used. Note that the optional _mode_ argument cannot be separated from the *-c* option by a space, the correct form is for example *-c*=_dos_. + +include::man-common/help-version.adoc[] + +*-L*, *--color*[=_when_]:: +Colorize the output. The optional argument _when_ can be *auto*, *never* or *always*. If the _when_ argument is omitted, it defaults to *auto*. The colors can be disabled; for the current built-in default see the *--help* output. See also the *COLORS* section. + +*-l*, *--list*:: +List the partition tables for the specified devices and then exit. ++ +If no devices are given, the devices mentioned in _/proc/partitions_ (if this file exists) are used. Devices are always listed in the order in which they are specified on the command-line, or by the kernel listed in _/proc/partitions_. + +*-x*, *--list-details*:: +Like *--list*, but provides more details. + +*--lock*[=_mode_]:: +Use exclusive BSD lock for device or file it operates. The optional argument _mode_ can be *yes*, *no* (or 1 and 0) or *nonblock*. If the _mode_ argument is omitted, it defaults to *yes*. This option overwrites environment variable *$LOCK_BLOCK_DEVICE*. The default is not to use any lock at all, but it's recommended to avoid collisions with *systemd-udevd*(8) or other tools. + +*-n*, *--noauto-pt*:: +Don't automatically create a default partition table on empty device. The partition table has to be explicitly created by user (by command like 'o', 'g', etc.). + +*-o*, *--output* _list_:: +Specify which output columns to print. Use *--help* to get a list of all supported columns. ++ +The default list of columns may be extended if _list_ is specified in the format _{plus}list_ (e.g., *-o {plus}UUID*). +//TRANSLATORS: Keep {plus} untranslated. + +*-s*, *--getsz*:: +Print the size in 512-byte sectors of each given block device. This option is DEPRECATED in favour of *blockdev*(8). + +*-t*, *--type* _type_:: +Enable support only for disklabels of the specified _type_, and disable support for all other types. + +*-u*, *--units*[=_unit_]:: +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 _unit_ argument -- then the default is used. Note that the optional _unit_ argument cannot be separated from the *-u* option by a space, the correct form is for example '**-u=**__cylinders__'. + +*-C*, *--cylinders* _number_:: +Specify the _number_ of cylinders of the disk. I have no idea why anybody would want to do so. + +*-H*, *--heads* _number_:: +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. + +*-S*, *--sectors* _number_:: +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. + +*-w*, *--wipe* _when_:: +Wipe filesystem, RAID and partition-table signatures from the device, in order to avoid possible collisions. The argument _when_ can be *auto*, *never* or *always*. When this option is not given, the default is *auto*, 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 *wipefs*(8) command. + +*-W*, *--wipe-partitions* _when_:: +Wipe filesystem, RAID and partition-table signatures from a newly created partitions, in order to avoid possible collisions. The argument _when_ can be *auto*, *never* or *always*. When this option is not given, the default is *auto*, 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 *wipefs*(8) command. + +*-V*, *--version*:: +Display version information and exit. + +== DEVICES + +The _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 _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/admin-guide/devices.txt_ file). + +== SIZES + + +//TRANSLATORS: Keep {plus} untranslated. +The "last sector" dialog accepts partition size specified by number of sectors or by {plus}/-<size>{K,B,M,G,...} notation. + + +//TRANSLATORS: Keep {plus} untranslated. +If the size is prefixed by '{plus}' 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". + + +//TRANSLATORS: Keep {plus} untranslated. +The relative sizes are always aligned according to device I/O limits. The {plus}/-<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. + +== SCRIPT FILES + +*fdisk* allows reading (by 'I' command) *sfdisk*(8) 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. + +And vice-versa it is possible to write the current in-memory disk layout to the script file by command 'O'. + +The script files are compatible between *cfdisk*(8), *sfdisk*(8), *fdisk* and other libfdisk applications. For more details see *sfdisk*(8). + +== DISK LABELS + +*GPT (GUID Partition Table)*:: +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 *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. + +*DOS-type (MBR)*:: +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 *Cylinders/Heads/Sectors* triple (given in 10{plus}8{plus}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 *C/H/S addressing is deprecated* and may be unsupported in some later *fdisk* version. +//TRANSLATORS: Keep {plus} untranslated. ++ +*Please, read the DOS-mode section if you want DOS-compatible partitions.* *fdisk* does not care about cylinder boundaries by default. + +*BSD/Sun-type*:: +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 *BSD label* is usually nested within a DOS partition. + +*IRIX/SGI-type*:: +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. ++ +A *sync*(2) and an ioctl(BLKRRPART) (rereading the partition table from disk) are performed before exiting when the partition table has been updated. + +== DOS mode and DOS 6.x WARNING + +*Note that all this is deprecated. You don't have to care about things like* *geometry and cylinders on modern operating systems. If you really want* *DOS-compatible partitioning then you have to enable DOS mode and cylinder* *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 *fdisk* or *cfdisk*(8) to change the size of a DOS partition table entry, then you must also use *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 *fdisk* to make a DOS partition table entry for _/dev/sda1_, then (after exiting *fdisk* 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. + +*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*(8) programs. + +include::man-common/colors.adoc[] +The logical color names supported by *fdisk* are: + +*header*:: +The header of the output tables. + +*help-title*:: +The help section titles. + +*warn*:: +The warning messages. + +*welcome*:: +The welcome message. + +== ENVIRONMENT + +*FDISK_DEBUG*=all:: +enables fdisk debug output. + +*LIBFDISK_DEBUG*=all:: +enables libfdisk debug output. + +*LIBBLKID_DEBUG*=all:: +enables libblkid debug output. + +*LIBSMARTCOLS_DEBUG*=all:: +enables libsmartcols debug output. + +*LIBSMARTCOLS_DEBUG_PADDING*=on:: +use visible padding characters. + +*LOCK_BLOCK_DEVICE*=<mode>:: +use exclusive BSD lock. The mode is "1" or "0". See *--lock* for more details. + +== AUTHORS + +mailto:kzak@redhat.com[Karel Zak], +mailto:dave@gnu.org[Davidlohr Bueso] + +The original version was written by Andries E. Brouwer, A. V. Le Blanc and others. + +== SEE ALSO + +*cfdisk*(8), +*mkfs*(8), +*partx*(8), +*sfdisk*(8) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/disk-utils/fdisk.c b/disk-utils/fdisk.c new file mode 100644 index 0000000..6bf3efb --- /dev/null +++ b/disk-utils/fdisk.c @@ -0,0 +1,1186 @@ +/* + * SPDX-License-Identifier: GPL-1.0-or-later + * + * 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 of the License, or + * (at your option) any later version. + * + * 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> + */ +#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 + errno = 0; + 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; + clearerr(stdin); + goto done; + } + + /* + * cleanup the reply + */ + sz = ltrim_whitespace((unsigned char *) buf); + if (sz && *(buf + sz - 1) == '\n') + *(buf + sz - 1) = '\0'; + +done: +#ifdef HAVE_LIBREADLINE + if (is_interactive) + rl_callback_handler_remove(); +#endif + sigaction(SIGINT, &oldact, NULL); + DBG(ASK, ul_debug("user's reply: >>>%s<<< [rc=%d]", buf, ret)); + 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] = { '\0' }; + + 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, int *canceled) +{ + const char *q; + struct fdisk_label *lb; + + assert(cxt); + lb = fdisk_get_label(cxt, NULL); + + if (!lb) + return NULL; + + *canceled = 0; + + if (fdisk_label_has_parttypes_shortcuts(lb)) + q = fdisk_label_has_code_parttypes(lb) ? + _("Hex code or alias (type L to list all): ") : + _("Partition type or alias (type L to list all): "); + else + 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] = { '\0' }; + int rc = get_user_reply(q, buf, sizeof(buf)); + + if (rc) { + if (rc == -ECANCELED) + *canceled = 1; + break; + } + + if (buf[1] == '\0' && toupper(*buf) == 'L') + list_partition_types(cxt); + else if (*buf) { + struct fdisk_parttype *t = fdisk_label_advparse_parttype(lb, buf, + FDISK_PARTTYPE_PARSE_DATA + | FDISK_PARTTYPE_PARSE_ALIAS + | FDISK_PARTTYPE_PARSE_NAME + | FDISK_PARTTYPE_PARSE_SEQNUM); + if (!t) + fdisk_info(cxt, _("Failed to parse '%s' partition type."), buf); + return t; + } + } while (1); + + return NULL; +} + + +void list_partition_types(struct fdisk_context *cxt) +{ + size_t ntypes = 0, next = 0; + struct fdisk_label *lb; + int pager = 0; + + 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, 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("%s%02x ", 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]); + + putchar('\n'); + } else { + /* + * Prints 1 column in format <idx> <name> <typestr> + */ + size_t i; + + pager_open(); + pager = 1; + + 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)); + } + + } + + + /* + * Aliases + */ + if (fdisk_label_has_parttypes_shortcuts(lb)) { + const char *alias = NULL, *typestr = NULL; + int rc = 0; + + fputs(_("\nAliases:\n"), stdout); + + for (next = 0; rc == 0 || rc == 2; next++) { + /* rc: <0 error, 0 success, 1 end, 2 deprecated */ + rc = fdisk_label_get_parttype_shortcut(lb, + next, &typestr, NULL, &alias); + if (rc == 0) + printf(" %-14s - %s\n", alias, typestr); + } + } + + if (pager) + pager_close(); + +} + +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; + int canceled = 0; + + 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, &canceled); + if (canceled) + break; + } while (!t); + + if (canceled == 0 && t && 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) +{ + size_t i, l, next = 0; + + if (!buf) + return; + for (i = 0, l = 0; i < sz; i++, l++) { + if (l == 0) { + if (!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 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); + free(buf); + } +} + +void dump_firstsector(struct fdisk_context *cxt) +{ + assert(cxt); + + dump_blkdev(cxt, _("First sector"), 0, fdisk_get_sector_size(cxt)); +} + +void dump_disklabel(struct fdisk_context *cxt) +{ + 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); +} + +static fdisk_sector_t get_dev_blocks(char *dev) +{ + int fd, ret; + fdisk_sector_t size; + + if ((fd = open(dev, O_RDONLY|O_NONBLOCK)) < 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 device contains '%s' signature and it will be removed by a write command. " + "See fdisk(8) man page and --wipe option for more details."), + fdisk_get_collision(cxt)); + else + fdisk_warnx(cxt, _( + "The device contains '%s' signature and it 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); + fprintf(out, + _(" -L, --color[=<when>] colorize output (%s, %s or %s)\n"), "auto", "always", "never"); + fprintf(out, + " %s\n", USAGE_COLORS_DEFAULT); + fputs(_(" -l, --list display partitions and exit\n"), out); + fputs(_(" -x, --list-details like --list but with more details\n"), out); + + fputs(_(" -n, --noauto-pt don't create default partition table on empty devices\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); + fprintf(out, + _(" --lock[=<mode>] use exclusive device lock (%s, %s or %s)\n"), "yes", "no", "nonblock"); + fprintf(out, + _(" -w, --wipe <mode> wipe signatures (%s, %s or %s)\n"), "auto", "always", "never"); + fprintf(out, + _(" -W, --wipe-partitions <mode> wipe signatures from new partitions (%s, %s or %s)\n"), "auto", "always", "never"); + + 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_LIST_DETAILS, + ACT_SHOWSIZE +}; + +int main(int argc, char **argv) +{ + int rc, i, c, act = ACT_FDISK, noauto_pt = 0; + int colormode = UL_COLORMODE_UNDEF; + struct fdisk_context *cxt; + char *outarg = NULL; + const char *devname, *lockmode = NULL; + enum { + OPT_BYTES = CHAR_MAX + 1, + OPT_LOCK + }; + 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' }, + { "list-details", no_argument, NULL, 'x' }, + { "lock", optional_argument, NULL, OPT_LOCK }, + { "noauto-pt", no_argument, NULL, 'n' }, + { "sector-size", required_argument, NULL, 'b' }, + { "type", required_argument, NULL, 't' }, + { "units", optional_argument, NULL, 'u' }, + { "version", no_argument, NULL, 'V' }, + { "output", required_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); + close_stdout_atexit(); + + 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::no:sS:t:u::vVw:W:x", + 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 'x': + act = ACT_LIST_DETAILS; + break; + case 'L': + colormode = UL_COLORMODE_AUTO; + if (optarg) + colormode = colormode_or_err(optarg, + _("unsupported color mode")); + break; + case 'n': + noauto_pt = 1; + 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 */ + print_version(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; + case OPT_LOCK: + lockmode = "1"; + if (optarg) { + if (*optarg == '=') + optarg++; + lockmode = optarg; + } + 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: + case ACT_LIST_DETAILS: + fdisk_enable_listonly(cxt, 1); + + if (act == ACT_LIST_DETAILS) + fdisk_enable_details(cxt, 1); + + init_fields(cxt, outarg, NULL); + + if (argc > optind) { + int k; + + for (rc = 0, k = optind; k < argc; k++) + rc += print_device_pt(cxt, argv[k], 1, 0, k != optind); + + 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")); + + devname = argv[optind]; + rc = fdisk_assign_device(cxt, devname, 0); + if (rc == -EACCES) { + rc = fdisk_assign_device(cxt, devname, 1); + if (rc == 0) + fdisk_warnx(cxt, _("Device is open in read-only mode.")); + } + if (rc) + err(EXIT_FAILURE, _("cannot open %s"), devname); + + if (fdisk_device_is_used(cxt)) + fdisk_warnx(cxt, _( + "This disk is currently in use - repartitioning is probably a bad idea.\n" + "It's recommended to umount all file systems, and swapoff all swap\n" + "partitions on this disk.\n")); + + fflush(stdout); + + if (!fdisk_is_readonly(cxt) + && blkdev_lock(fdisk_get_devfd(cxt), devname, lockmode) != 0) { + fdisk_deassign_device(cxt, 1); + fdisk_unref_context(cxt); + return EXIT_FAILURE; + } + + 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.")); + if (!noauto_pt) + 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..e7edc06 --- /dev/null +++ b/disk-utils/fdisk.h @@ -0,0 +1,66 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * 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. + * + * Copyright (C) 2014-2023 Karel Zak <kzak@redhat.com> + */ +#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..1cb3992 --- /dev/null +++ b/disk-utils/fsck.8 @@ -0,0 +1,250 @@ +'\" t +.\" Title: fsck +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-11-21 +.\" Manual: System Administration +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "FSCK" "8" "2023-11-21" "util\-linux 2.39.3" "System Administration" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +fsck \- check and repair a Linux filesystem +.SH "SYNOPSIS" +.sp +\fBfsck\fP [\fB\-lsAVRTMNP\fP] [\fB\-r\fP [\fIfd\fP]] [\fB\-C\fP [\fIfd\fP]] [\fB\-t\fP \fIfstype\fP] [\fIfilesystem\fP...] [\fB\-\-\fP] [\fIfs\-specific\-options\fP] +.SH "DESCRIPTION" +.sp +\fBfsck\fP is used to check and optionally repair one or more Linux filesystems. \fIfilesystem\fP can be a device name (e.g., \fI/dev/hdc1\fP, \fI/dev/sdb2\fP), a mount point (e.g., \fI/\fP, \fI/usr\fP, \fI/home\fP), or a filesystem label or UUID specifier (e.g., UUID=8868abf6\-88c5\-4a83\-98b8\-bfc24057f7bd or LABEL=root). Normally, the \fBfsck\fP 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. +.sp +If no filesystems are specified on the command line, and the \fB\-A\fP option is not specified, \fBfsck\fP will default to checking filesystems in \fI/etc/fstab\fP serially. This is equivalent to the \fB\-As\fP options. +.sp +The exit status returned by \fBfsck\fP is the sum of the following conditions: +.sp +\fB0\fP +.RS 4 +No errors +.RE +.sp +\fB1\fP +.RS 4 +Filesystem errors corrected +.RE +.sp +\fB2\fP +.RS 4 +System should be rebooted +.RE +.sp +\fB4\fP +.RS 4 +Filesystem errors left uncorrected +.RE +.sp +\fB8\fP +.RS 4 +Operational error +.RE +.sp +\fB16\fP +.RS 4 +Usage or syntax error +.RE +.sp +\fB32\fP +.RS 4 +Checking canceled by user request +.RE +.sp +\fB128\fP +.RS 4 +Shared\-library error +.RE +.sp +The exit status returned when multiple filesystems are checked is the bit\-wise OR of the exit statuses for each filesystem that is checked. +.sp +In actuality, \fBfsck\fP is simply a front\-end for the various filesystem checkers (\fBfsck\fP.\fIfstype\fP) available under Linux. The filesystem\-specific checker is searched for in the \fBPATH\fP environment variable. If the \fBPATH\fP is undefined then fallback to \fI/sbin\fP. +.sp +Please see the filesystem\-specific checker manual pages for further details. +.SH "OPTIONS" +.sp +\fB\-l\fP +.RS 4 +Create an exclusive \fBflock\fP(2) lock file (\fI/run/fsck/<diskname>.lock\fP) for whole\-disk device. This option can be used with one device only (this means that \fB\-A\fP and \fB\-l\fP are mutually exclusive). This option is recommended when more \fBfsck\fP instances are executed in the same time. The option is ignored when used for multiple devices or for non\-rotating disks. \fBfsck\fP does not lock underlying devices when executed to check stacked devices (e.g. MD or DM) \- this feature is not implemented yet. +.RE +.sp +\fB\-r\fP [\fIfd\fP] +.RS 4 +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: +.sp +\fB/dev/sda1: status 0, rss 92828, real 4.002804, user 2.677592, sys 0.86186\fP +.sp +GUI front\-ends may specify a file descriptor \fIfd\fP, in which case the progress bar information will be sent to that file descriptor in a machine parsable format. For example: +.sp +\fB/dev/sda1 0 92828 4.002804 2.677592 0.86186\fP +.RE +.sp +\fB\-s\fP +.RS 4 +Serialize \fBfsck\fP operations. This is a good idea if you are checking multiple filesystems and the checkers are in an interactive mode. (Note: \fBe2fsck\fP(8) runs in an interactive mode by default. To make \fBe2fsck\fP(8) run in a non\-interactive mode, you must either specify the \fB\-p\fP or \fB\-a\fP option, if you wish for errors to be corrected automatically, or the \fB\-n\fP option if you do not.) +.RE +.sp +\fB\-t\fP \fIfslist\fP +.RS 4 +Specifies the type(s) of filesystem to be checked. When the \fB\-A\fP flag is specified, only filesystems that match \fIfslist\fP are checked. The \fIfslist\fP 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 \*(Aq\fBno\fP\*(Aq or \*(Aq\fB!\fP\*(Aq, which requests that only those filesystems not listed in \fIfslist\fP will be checked. If none of the filesystems in \fIfslist\fP is prefixed by a negation operator, then only those listed filesystems will be checked. +.sp +Options specifiers may be included in the comma\-separated \fIfslist\fP. They must have the format \fBopts=\fP\fIfs\-option\fP. If an options specifier is present, then only filesystems which contain \fIfs\-option\fP in their mount options field of \fI/etc/fstab\fP will be checked. If the options specifier is prefixed by a negation operator, then only those filesystems that do not have \fIfs\-option\fP in their mount options field of \fI/etc/fstab\fP will be checked. +.sp +For example, if \fBopts=ro\fP appears in \fIfslist\fP, then only filesystems listed in \fI/etc/fstab\fP with the \fBro\fP option will be checked. +.sp +For compatibility with Mandrake distributions whose boot scripts depend upon an unauthorized UI change to the \fBfsck\fP program, if a filesystem type of \fBloop\fP is found in \fIfslist\fP, it is treated as if \fBopts=loop\fP were specified as an argument to the \fB\-t\fP option. +.sp +Normally, the filesystem type is deduced by searching for \fIfilesys\fP in the \fI/etc/fstab\fP 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 \fB\-t\fP option, \fBfsck\fP will use the specified filesystem type. If this type is not available, then the default filesystem type (currently ext2) is used. +.RE +.sp +\fB\-A\fP +.RS 4 +Walk through the \fI/etc/fstab\fP file and try to check all filesystems in one run. This option is typically used from the \fI/etc/rc\fP system initialization file, instead of multiple commands for checking a single filesystem. +.sp +The root filesystem will be checked first unless the \fB\-P\fP option is specified (see below). After that, filesystems will be checked in the order specified by the \fIfs_passno\fP (the sixth) field in the \fI/etc/fstab\fP file. Filesystems with a \fIfs_passno\fP value of 0 are skipped and are not checked at all. Filesystems with a \fIfs_passno\fP value of greater than zero will be checked in order, with filesystems with the lowest \fIfs_passno\fP number being checked first. If there are multiple filesystems with the same pass number, \fBfsck\fP will attempt to check them in parallel, although it will avoid running multiple filesystem checks on the same physical disk. +.sp +\fBfsck\fP does not check stacked devices (RAIDs, dm\-crypt, ...) in parallel with any other device. See below for \fBFSCK_FORCE_ALL_PARALLEL\fP setting. The \fI/sys\fP filesystem is used to determine dependencies between devices. +.sp +Hence, a very common configuration in \fI/etc/fstab\fP files is to set the root filesystem to have a \fIfs_passno\fP value of 1 and to set all other filesystems to have a \fIfs_passno\fP value of 2. This will allow \fBfsck\fP 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 \- for example, if the machine in question is short on memory so that excessive paging is a concern. +.sp +\fBfsck\fP 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 \fI/etc/fstab\fP mount option \fBnofail\fP may be used to have \fBfsck\fP skip non\-existing devices. \fBfsck\fP also skips non\-existing devices that have the special filesystem type \fBauto\fP. +.RE +.sp +\fB\-C\fP [\fIfd\fP] +.RS 4 +Display completion/progress bars for those filesystem checkers (currently only for ext[234]) which support them. \fBfsck\fP 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 \fIfd\fP, in which case the progress bar information will be sent to that file descriptor. +.RE +.sp +\fB\-M\fP +.RS 4 +Do not check mounted filesystems and return an exit status of 0 for mounted filesystems. +.RE +.sp +\fB\-N\fP +.RS 4 +Don\(cqt execute, just show what would be done. +.RE +.sp +\fB\-P\fP +.RS 4 +When the \fB\-A\fP 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 \fBe2fsck\fP(8) executable might be corrupted! This option is mainly provided for those sysadmins who don\(cqt want to repartition the root filesystem to be small and compact (which is really the right solution). +.RE +.sp +\fB\-R\fP +.RS 4 +When checking all filesystems with the \fB\-A\fP flag, skip the root filesystem. (This is useful in case the root filesystem has already been mounted read\-write.) +.RE +.sp +\fB\-T\fP +.RS 4 +Don\(cqt show the title on startup. +.RE +.sp +\fB\-V\fP +.RS 4 +Produce verbose output, including all filesystem\-specific commands that are executed. +.RE +.sp +\fB\-?\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.sp +\fB\-\-version\fP +.RS 4 +Display version information and exit. +.RE +.SH "FILESYSTEM SPECIFIC OPTIONS" +.sp +\fBOptions which are not understood by fsck are passed to the filesystem\-specific checker!\fP +.sp +These options \fBmust\fP not take arguments, as there is no way for \fBfsck\fP to be able to properly guess which options take arguments and which don\(cqt. +.sp +Options and arguments which follow the \fB\-\-\fP are treated as filesystem\-specific options to be passed to the filesystem\-specific checker. +.sp +Please note that \fBfsck\fP is not designed to pass arbitrarily complicated options to filesystem\-specific checkers. If you\(cqre doing something complicated, please just execute the filesystem\-specific checker directly. If you pass \fBfsck\fP some horribly complicated options and arguments, and it doesn\(cqt do what you expect, \fBdon\(cqt bother reporting it as a bug.\fP You\(cqre almost certainly doing something that you shouldn\(cqt be doing with \fBfsck\fP. Options to different filesystem\-specific fsck\(cqs are not standardized. +.SH "ENVIRONMENT" +.sp +The \fBfsck\fP program\(cqs behavior is affected by the following environment variables: +.sp +\fBFSCK_FORCE_ALL_PARALLEL\fP +.RS 4 +If this environment variable is set, \fBfsck\fP 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 \fIfs_passno\fP value is still used. +.RE +.sp +\fBFSCK_MAX_INST\fP +.RS 4 +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 \fBfsck\fP 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 \fBfsck\fP may attempt to automatically determine how many filesystem checks can be run based on gathering accounting data from the operating system. +.RE +.sp +\fBPATH\fP +.RS 4 +The \fBPATH\fP environment variable is used to find filesystem checkers. +.RE +.sp +\fBFSTAB_FILE\fP +.RS 4 +This environment variable allows the system administrator to override the standard location of the \fI/etc/fstab\fP file. It is also useful for developers who are testing \fBfsck\fP. +.RE +.sp +\fBLIBBLKID_DEBUG=all\fP +.RS 4 +enables libblkid debug output. +.RE +.sp +\fBLIBMOUNT_DEBUG=all\fP +.RS 4 +enables libmount debug output. +.RE +.SH "FILES" +.sp +\fI/etc/fstab\fP +.SH "AUTHORS" +.sp +.MTO "tytso\(atmit.edu>" "Theodore Ts\(cqo" "," +.MTO "kzak\(atredhat.com" "Karel Zak" "" +.SH "SEE ALSO" +.sp +\fBfstab\fP(5), +\fBmkfs\fP(8), +\fBfsck.ext2\fP(8) or \fBfsck.ext3\fP(8) or \fBe2fsck\fP(8), +\fBfsck.cramfs\fP(8), +\fBfsck.jfs\fP(8), +\fBfsck.nfs\fP(8), +\fBfsck.minix\fP(8), +\fBfsck.msdos\fP(8), +\fBfsck.vfat\fP(8), +\fBfsck.xfs\fP(8), +\fBreiserfsck\fP(8) +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBfsck\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "."
\ No newline at end of file diff --git a/disk-utils/fsck.8.adoc b/disk-utils/fsck.8.adoc new file mode 100644 index 0000000..976e7ff --- /dev/null +++ b/disk-utils/fsck.8.adoc @@ -0,0 +1,179 @@ +//po4a: entry man manual +//// +Copyright 1993, 1994, 1995 by Theodore Ts'o. All Rights Reserved. +This file may be copied under the terms of the GNU Public License. +//// += fsck(8) +:doctype: manpage +:man manual: System Administration +:man source: util-linux {release-version} +:page-layout: base +:command: fsck + +== NAME + +fsck - check and repair a Linux filesystem + +== SYNOPSIS + +*fsck* [*-lsAVRTMNP*] [*-r* [_fd_]] [*-C* [_fd_]] [*-t* _fstype_] [_filesystem_...] [*--*] [_fs-specific-options_] + +== DESCRIPTION + +*fsck* is used to check and optionally repair one or more Linux filesystems. _filesystem_ can be a device name (e.g., _/dev/hdc1_, _/dev/sdb2_), a mount point (e.g., _/_, _/usr_, _/home_), or a filesystem label or UUID specifier (e.g., UUID=8868abf6-88c5-4a83-98b8-bfc24057f7bd or LABEL=root). Normally, the *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. + +If no filesystems are specified on the command line, and the *-A* option is not specified, *fsck* will default to checking filesystems in _/etc/fstab_ serially. This is equivalent to the *-As* options. + +The exit status returned by *fsck* is the sum of the following conditions: + +*0*:: +No errors +*1*:: +Filesystem errors corrected +*2*:: +System should be rebooted +*4*:: +Filesystem errors left uncorrected +*8*:: +Operational error +*16*:: +Usage or syntax error +*32*:: +Checking canceled by user request +*128*:: +Shared-library error + +The exit status returned when multiple filesystems are checked is the bit-wise OR of the exit statuses for each filesystem that is checked. + +In actuality, *fsck* is simply a front-end for the various filesystem checkers (*fsck*._fstype_) available under Linux. The filesystem-specific checker is searched for in the *PATH* environment variable. If the *PATH* is undefined then fallback to _/sbin_. + +Please see the filesystem-specific checker manual pages for further details. + +== OPTIONS + +*-l*:: +Create an exclusive *flock*(2) lock file (_/run/fsck/<diskname>.lock_) for whole-disk device. This option can be used with one device only (this means that *-A* and *-l* are mutually exclusive). This option is recommended when more *fsck* instances are executed in the same time. The option is ignored when used for multiple devices or for non-rotating disks. *fsck* does not lock underlying devices when executed to check stacked devices (e.g. MD or DM) - this feature is not implemented yet. + +*-r* [_fd_]:: +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 _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* + +*-s*:: +Serialize *fsck* operations. This is a good idea if you are checking multiple filesystems and the checkers are in an interactive mode. (Note: *e2fsck*(8) runs in an interactive mode by default. To make *e2fsck*(8) run in a non-interactive mode, you must either specify the *-p* or *-a* option, if you wish for errors to be corrected automatically, or the *-n* option if you do not.) + +*-t* _fslist_:: +Specifies the type(s) of filesystem to be checked. When the *-A* flag is specified, only filesystems that match _fslist_ are checked. The _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 '*no*' or '*!*', which requests that only those filesystems not listed in _fslist_ will be checked. If none of the filesystems in _fslist_ is prefixed by a negation operator, then only those listed filesystems will be checked. ++ +Options specifiers may be included in the comma-separated _fslist_. They must have the format **opts=**__fs-option__. If an options specifier is present, then only filesystems which contain _fs-option_ in their mount options field of _/etc/fstab_ will be checked. If the options specifier is prefixed by a negation operator, then only those filesystems that do not have _fs-option_ in their mount options field of _/etc/fstab_ will be checked. ++ +For example, if *opts=ro* appears in _fslist_, then only filesystems listed in _/etc/fstab_ with the *ro* option will be checked. ++ +For compatibility with Mandrake distributions whose boot scripts depend upon an unauthorized UI change to the *fsck* program, if a filesystem type of *loop* is found in _fslist_, it is treated as if *opts=loop* were specified as an argument to the *-t* option. ++ +Normally, the filesystem type is deduced by searching for _filesys_ in the _/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 *-t* option, *fsck* will use the specified filesystem type. If this type is not available, then the default filesystem type (currently ext2) is used. + +*-A*:: +Walk through the _/etc/fstab_ file and try to check all filesystems in one run. This option is typically used from the _/etc/rc_ system initialization file, instead of multiple commands for checking a single filesystem. ++ +The root filesystem will be checked first unless the *-P* option is specified (see below). After that, filesystems will be checked in the order specified by the _fs_passno_ (the sixth) field in the _/etc/fstab_ file. Filesystems with a _fs_passno_ value of 0 are skipped and are not checked at all. Filesystems with a _fs_passno_ value of greater than zero will be checked in order, with filesystems with the lowest _fs_passno_ number being checked first. If there are multiple filesystems with the same pass number, *fsck* will attempt to check them in parallel, although it will avoid running multiple filesystem checks on the same physical disk. ++ +*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. ++ +Hence, a very common configuration in _/etc/fstab_ files is to set the root filesystem to have a _fs_passno_ value of 1 and to set all other filesystems to have a _fs_passno_ value of 2. This will allow *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 - for example, if the machine in question is short on memory so that excessive paging is a concern. ++ +*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 _/etc/fstab_ mount option *nofail* may be used to have *fsck* skip non-existing devices. *fsck* also skips non-existing devices that have the special filesystem type *auto*. + +*-C* [_fd_]:: +Display completion/progress bars for those filesystem checkers (currently only for ext[234]) which support them. *fsck* 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 _fd_, in which case the progress bar information will be sent to that file descriptor. + +*-M*:: +Do not check mounted filesystems and return an exit status of 0 for mounted filesystems. + +*-N*:: +Don't execute, just show what would be done. + +*-P*:: +When the *-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 *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). + +*-R*:: +When checking all filesystems with the *-A* flag, skip the root filesystem. (This is useful in case the root filesystem has already been mounted read-write.) + +*-T*:: +Don't show the title on startup. + +*-V*:: +Produce verbose output, including all filesystem-specific commands that are executed. + +*-?*, *--help*:: +Display help text and exit. + +*--version*:: +Display version information and exit. + +== FILESYSTEM SPECIFIC OPTIONS + +*Options which are not understood by fsck are passed to the filesystem-specific checker!* + +These options *must* not take arguments, as there is no way for *fsck* to be able to properly guess which options take arguments and which don't. + +Options and arguments which follow the *--* are treated as filesystem-specific options to be passed to the filesystem-specific checker. + +Please note that *fsck* 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 *fsck* some horribly complicated options and arguments, and it doesn't do what you expect, *don't bother reporting it as a bug.* You're almost certainly doing something that you shouldn't be doing with *fsck*. Options to different filesystem-specific fsck's are not standardized. + +== ENVIRONMENT + +The *fsck* program's behavior is affected by the following environment variables: + +*FSCK_FORCE_ALL_PARALLEL*:: +If this environment variable is set, *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. + +*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 *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 *fsck* may attempt to automatically determine how many filesystem checks can be run based on gathering accounting data from the operating system. + +*PATH*:: +The *PATH* environment variable is used to find filesystem checkers. + +*FSTAB_FILE*:: +This environment variable allows the system administrator to override the standard location of the _/etc/fstab_ file. It is also useful for developers who are testing *fsck*. + +*LIBBLKID_DEBUG=all*:: +enables libblkid debug output. + +*LIBMOUNT_DEBUG=all*:: +enables libmount debug output. + +== FILES + +_/etc/fstab_ + +== AUTHORS + +mailto:tytso@mit.edu>[Theodore Ts'o], +mailto:kzak@redhat.com[Karel Zak] + +== SEE ALSO + +*fstab*(5), +*mkfs*(8), +*fsck.ext2*(8) or *fsck.ext3*(8) or *e2fsck*(8), +*fsck.cramfs*(8), +*fsck.jfs*(8), +*fsck.nfs*(8), +*fsck.minix*(8), +*fsck.msdos*(8), +*fsck.vfat*(8), +*fsck.xfs*(8), +*reiserfsck*(8) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/disk-utils/fsck.c b/disk-utils/fsck.c new file mode 100644 index 0000000..57e0758 --- /dev/null +++ b/disk-utils/fsck.c @@ -0,0 +1,1711 @@ +/* + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * 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. + * + * 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> + */ +#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 sig_atomic_t 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; + + errno = 0; + l = strtol(s, &p, 0); + if (errno || *p || l == LONG_MIN || l == LONG_MAX || l < 0 || l > INT_MAX) + return -1; + + 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); +} + +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); +} + +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" + " %"PRId64".%06"PRId64 + " %"PRId64".%06"PRId64 + " %"PRId64".%06"PRId64"\n", + fs_get_device(inst->fs), + inst->exit_status, + inst->rusage.ru_maxrss, + (int64_t)delta.tv_sec, (int64_t)delta.tv_usec, + (int64_t)inst->rusage.ru_utime.tv_sec, + (int64_t)inst->rusage.ru_utime.tv_usec, + (int64_t)inst->rusage.ru_stime.tv_sec, + (int64_t)inst->rusage.ru_stime.tv_usec); + else + fprintf(stdout, "%s: status %d, rss %ld, " + "real %"PRId64".%06"PRId64", " + "user %"PRId64".%06"PRId64", " + "sys %"PRId64".%06"PRId64"\n", + fs_get_device(inst->fs), + inst->exit_status, + inst->rusage.ru_maxrss, + (int64_t)delta.tv_sec, (int64_t)delta.tv_usec, + (int64_t)inst->rusage.ru_utime.tv_sec, + (int64_t)inst->rusage.ru_utime.tv_usec, + (int64_t)inst->rusage.ru_stime.tv_sec, + (int64_t)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; + if (inst->pid <= 0) + 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") != 0 && + strcmp(inst2->type, "ext3") && + strcmp(inst2->type, "ext4") != 0 && + strcmp(inst2->type, "ext4dev") != 0) + 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) != 0 && + strncmp(fstype, "opts=", 5) != 0 && strncmp(fstype, "loop", 4) != 0 && + !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 (%s) while executing fsck.%s for %s"), + retval, strerror(errno), 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_regularfs(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 = 1; +} + +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")) + print_version(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 (ul_strtos32(getenv("FSCK_MAX_INST"), &max_running, 10) != 0) + max_running = 0; +} + +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); + close_stdout_atexit(); + + strutils_set_exitcode(FSCK_EX_USAGE); + mnt_init_debug(0); /* init libmount debug mask */ + mntcache = mnt_new_cache(); /* no fatal error if failed */ + + if (mntcache) + /* Force libblkid to accept also filesystems with bad + * checksums. This feature is helpful for "fsck /dev/foo," but + * if it evaluates LABEL/UUIDs from fstab, then libmount may + * use cached data from udevd and udev accepts only properly + * detected filesystems. + */ + mnt_cache_set_sbprobe(mntcache, BLKID_SUBLKS_BADCSUM); + + + parse_argv(argc, argv); + + if (!notitle) + printf(UTIL_LINUX_VERSION); + + signal(SIGCHLD, SIG_DFL); /* clear any inherited settings */ + + 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..5946a87 --- /dev/null +++ b/disk-utils/fsck.cramfs.8 @@ -0,0 +1,106 @@ +'\" t +.\" Title: fsck.cramfs +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-10-23 +.\" Manual: System Administration +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "FSCK.CRAMFS" "8" "2023-10-23" "util\-linux 2.39.3" "System Administration" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +fsck.cramfs \- fsck compressed ROM file system +.SH "SYNOPSIS" +.sp +\fBfsck.cramfs\fP [options] \fIfile\fP +.SH "DESCRIPTION" +.sp +\fBfsck.cramfs\fP is used to check the cramfs file system. +.SH "OPTIONS" +.sp +\fB\-v\fP, \fB\-\-verbose\fP +.RS 4 +Enable verbose messaging. +.RE +.sp +\fB\-b\fP, \fB\-\-blocksize\fP \fIblocksize\fP +.RS 4 +Use this blocksize, defaults to page size. Must be equal to what was set at creation time. Only used for \fB\-\-extract\fP. +.RE +.sp +\fB\-\-extract\fP[=\fIdirectory\fP] +.RS 4 +Test to uncompress the whole file system. Optionally extract contents of the \fIfile\fP to \fIdirectory\fP. +.RE +.sp +\fB\-a\fP +.RS 4 +This option is silently ignored. +.RE +.sp +\fB\-y\fP +.RS 4 +This option is silently ignored. +.RE +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.sp +\fB\-V\fP, \fB\-\-version\fP +.RS 4 +Print version and exit. +.RE +.SH "EXIT STATUS" +.sp +\fB0\fP +.RS 4 +success +.RE +.sp +\fB4\fP +.RS 4 +file system was left uncorrected +.RE +.sp +\fB8\fP +.RS 4 +operation error, such as unable to allocate memory +.RE +.sp +\fB16\fP +.RS 4 +usage information was printed +.RE +.SH "SEE ALSO" +.sp +\fBmount\fP(8), +\fBmkfs.cramfs\fP(8) +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBfsck.cramfs\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "."
\ No newline at end of file diff --git a/disk-utils/fsck.cramfs.8.adoc b/disk-utils/fsck.cramfs.8.adoc new file mode 100644 index 0000000..ec75262 --- /dev/null +++ b/disk-utils/fsck.cramfs.8.adoc @@ -0,0 +1,62 @@ +//po4a: entry man manual += fsck.cramfs(8) +:doctype: manpage +:man manual: System Administration +:man source: util-linux {release-version} +:page-layout: base +:command: fsck.cramfs + +== NAME + +fsck.cramfs - fsck compressed ROM file system + +== SYNOPSIS + +*fsck.cramfs* [options] _file_ + +== DESCRIPTION + +*fsck.cramfs* is used to check the cramfs file system. + +== OPTIONS + +*-v*, *--verbose*:: +Enable verbose messaging. + +*-b*, *--blocksize* _blocksize_:: +Use this blocksize, defaults to page size. Must be equal to what was set at creation time. Only used for *--extract*. + +*--extract*[=_directory_]:: +Test to uncompress the whole file system. Optionally extract contents of the _file_ to _directory_. + +*-a*:: +This option is silently ignored. + +*-y*:: +This option is silently ignored. + +include::man-common/help-version.adoc[] + +== EXIT STATUS + +*0*:: +success +*4*:: +file system was left uncorrected +*8*:: +operation error, such as unable to allocate memory +*16*:: +usage information was printed + +== SEE ALSO + +*mount*(8), +*mkfs.cramfs*(8) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/disk-utils/fsck.cramfs.c b/disk-utils/fsck.cramfs.c new file mode 100644 index 0000000..5446084 --- /dev/null +++ b/disk-utils/fsck.cramfs.c @@ -0,0 +1,755 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * 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 <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/time.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 2 * blksize at a time */ +#define CRAMFS_ROMBUFFER_BITS 13 +#define CRAMFS_ROMBUFFERSIZE (1 << CRAMFS_ROMBUFFER_BITS) +#define CRAMFS_ROMBUFFERMASK (CRAMFS_ROMBUFFERSIZE - 1) + +/* Defaults, updated in main() according to block size */ +static size_t rombufbits = CRAMFS_ROMBUFFER_BITS; +static size_t rombufsize = CRAMFS_ROMBUFFERSIZE; +static size_t rombufmask = CRAMFS_ROMBUFFERMASK; + +static char *read_buffer; +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; + } + + if (magic == + u32_toggle_endianness(!HOST_IS_BIG_ENDIAN, CRAMFS_MAGIC)) { + cramfs_is_big_endian = !HOST_IS_BIG_ENDIAN; + return 0; + } + + return -1; +} + +static void test_super(int *start) +{ + struct stat st; + unsigned long long length; + + fd = open(filename, O_RDONLY); + if (fd < 0) + err(FSCK_EX_ERROR, _("cannot open %s"), filename); + + /* find the physical size of the file or block device */ + if (fstat(fd, &st) < 0) + err(FSCK_EX_ERROR, _("stat of %s failed"), filename); + + if (S_ISBLK(st.st_mode)) { + if (blkdev_get_size(fd, &length)) + err(FSCK_EX_ERROR, + _("ioctl failed: unable to determine device size: %s"), + filename); + } 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 >> rombufbits; + if (block != read_buffer_block) { + ssize_t x; + + read_buffer_block = block; + if (lseek(fd, block << rombufbits, SEEK_SET) == (off_t) -1) + warn(_("seek failed")); + + x = read(fd, read_buffer, rombufsize * 2); + if (x < 0) + warn(_("read romfs failed")); + } + return read_buffer + (offset & rombufmask); +} + +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; +} + +#ifndef 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) +{ + const struct timeval epoch[] = { {0,0}, {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, _("chmod failed: %s"), path); + } + if (S_ISLNK(i->mode)) + return; + if (utimes(path, epoch) < 0) + err(FSCK_EX_ERROR, _("utimes failed: %s"), path); +} + +static int is_dangerous_filename(char *name, int len) +{ + return (len == 1 && name[0] == '.') || + (len == 2 && name[0] == '.' && name[1] == '.'); +} + +static void __attribute__((__noreturn__)) + errx_path(const char *mesg, const char *name, size_t namelen) +{ + char buf[PATH_MAX] = { 0 }; + + namelen = min(namelen, sizeof(buf) - 1); + memcpy(buf, name, namelen); + + errx(FSCK_EX_UNCORRECTED, "%s: %s", mesg, buf); +} + +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); + char *name; + int size; + int newlen = child->namelen << 2; + + size = sizeof(struct cramfs_inode) + newlen; + count -= size; + + offset += sizeof(struct cramfs_inode); + name = romfs_read(offset); + + if (memchr(name, '/', newlen) != NULL) + errx_path(_("illegal filename"), name, newlen); + if (*extract_dir != '\0' && is_dangerous_filename(name, newlen)) + errx_path(_("dangerous filename"), name, newlen); + memcpy(newpath + pathlen, name, 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; + + 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); + close_stdout_atexit(); + + 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': + print_version(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); + test_crc(start); + + if (opt_extract) { + size_t bufsize = 0; + + if (blksize == 0) + blksize = getpagesize(); + + /* re-calculate according to blksize */ + bufsize = rombufsize = blksize * 2; + rombufbits = 0; + while (bufsize >>= 1) + rombufbits++; + rombufmask = rombufsize - 1; + + outbuffer = xmalloc(blksize * 2); + read_buffer = xmalloc(rombufsize * 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..f6913cd --- /dev/null +++ b/disk-utils/fsck.minix.8 @@ -0,0 +1,199 @@ +'\" t +.\" Title: fsck.minix +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-10-23 +.\" Manual: System Administration +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "FSCK.MINIX" "8" "2023-10-23" "util\-linux 2.39.3" "System Administration" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +fsck.minix \- check consistency of Minix filesystem +.SH "SYNOPSIS" +.sp +\fBfsck.minix\fP [options] \fIdevice\fP +.SH "DESCRIPTION" +.sp +\fBfsck.minix\fP performs a consistency check for the Linux MINIX filesystem. +.sp +The program assumes the filesystem is quiescent. \fBfsck.minix\fP 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. +.sp +The \fIdevice\fP name will usually have the following form: +.RS 3 +.ll -.6i +.TS +allbox tab(:); +lt lt. +T{ +.sp +/dev/hda[1\-63] +T}:T{ +.sp +IDE disk 1 +T} +T{ +.sp +/dev/hdb[1\-63] +T}:T{ +.sp +IDE disk 2 +T} +T{ +.sp +/dev/sda[1\-15] +T}:T{ +.sp +SCSI disk 1 +T} +T{ +.sp +/dev/sdb[1\-15] +T}:T{ +.sp +SCSI disk 2 +T} +.TE +.sp +.br +.RE +.ll +.sp +If the filesystem was changed, i.e., repaired, then \fBfsck.minix\fP will print "FILE SYSTEM HAS BEEN CHANGED" and will \fBsync\fP(2) three times before exiting. There is \fIno\fP need to reboot after check. +.SH "WARNING" +.sp +\fBfsck.minix\fP should \fBnot\fP be used on a mounted filesystem. Using \fBfsck.minix\fP 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 \fBfsck.minix\fP 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" +.sp +\fB\-l\fP, \fB\-\-list\fP +.RS 4 +List all filenames. +.RE +.sp +\fB\-r\fP, \fB\-\-repair\fP +.RS 4 +Perform interactive repairs. +.RE +.sp +\fB\-a\fP, \fB\-\-auto\fP +.RS 4 +Perform automatic repairs. This option implies \fB\-\-repair\fP 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. +.RE +.sp +\fB\-v\fP, \fB\-\-verbose\fP +.RS 4 +Be verbose. +.RE +.sp +\fB\-s\fP, \fB\-\-super\fP +.RS 4 +Output super\-block information. +.RE +.sp +\fB\-m\fP, \fB\-\-uncleared\fP +.RS 4 +Activate MINIX\-like "mode not cleared" warnings. +.RE +.sp +\fB\-f\fP, \fB\-\-force\fP +.RS 4 +Force a filesystem check even if the filesystem was marked as valid. Marking is done by the kernel when the filesystem is unmounted. +.RE +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.sp +\fB\-V\fP, \fB\-\-version\fP +.RS 4 +Print version and exit. +.RE +.SH "DIAGNOSTICS" +.sp +There are numerous diagnostic messages. The ones mentioned here are the most commonly seen in normal usage. +.sp +If the device does not exist, \fBfsck.minix\fP will print "unable to read super block". If the device exists, but is not a MINIX filesystem, \fBfsck.minix\fP will print "bad magic number in super\-block". +.SH "EXIT STATUS" +.sp +The exit status returned by \fBfsck.minix\fP is the sum of the following: +.sp +\fB0\fP +.RS 4 +No errors +.RE +.sp +\fB3\fP +.RS 4 +Filesystem errors corrected, system should be rebooted if filesystem was mounted +.RE +.sp +\fB4\fP +.RS 4 +Filesystem errors left uncorrected +.RE +.sp +\fB7\fP +.RS 4 +Combination of exit statuses 3 and 4 +.RE +.sp +\fB8\fP +.RS 4 +Operational error +.RE +.sp +\fB16\fP +.RS 4 +Usage or syntax error +.RE +.SH "AUTHORS" +.sp +.MTO "torvalds\(atcs.helsinki.fi" "Linus Torvalds" "." +Exit status values by +.MTO "faith\(atcs.unc.edu" "Rik Faith" "" +Added support for filesystem valid flag: +.MTO "greg%wind.uucp\(atplains.nodak.edu" "Dr. Wettstein" "." +Check to prevent fsck of mounted filesystem added by +.MTO "quinlan\(atyggdrasil.com" "Daniel Quinlan" "." +Minix v2 fs support by +.MTO "schwab\(atissan.informatik.uni\-dortmund.de" "Andreas Schwab" "," +updated by +.MTO "janl\(atmath.uio.no" "Nicolai Langfeldt" "." +Portability patch by +.MTO "rmk\(atecs.soton.ac.uk" "Russell King" "." +.SH "SEE ALSO" +.sp +\fBfsck\fP(8), +\fBfsck.ext2\fP(8), +\fBmkfs\fP(8), +\fBmkfs.ext2\fP(8), +\fBmkfs.minix\fP(8), +\fBreboot\fP(8) +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBfsck.minix\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "."
\ No newline at end of file diff --git a/disk-utils/fsck.minix.8.adoc b/disk-utils/fsck.minix.8.adoc new file mode 100644 index 0000000..37baace --- /dev/null +++ b/disk-utils/fsck.minix.8.adoc @@ -0,0 +1,113 @@ +//po4a: entry man manual +//// +Copyright 1992, 1993, 1994 Rickard E. Faith (faith@cs.unc.edu) +May be freely distributed. +//// += fsck.minix(8) +:doctype: manpage +:man manual: System Administration +:man source: util-linux {release-version} +:page-layout: base +:command: fsck.minix + +== NAME + +fsck.minix - check consistency of Minix filesystem + +== SYNOPSIS + +*fsck.minix* [options] _device_ + +== DESCRIPTION + +*fsck.minix* performs a consistency check for the Linux MINIX filesystem. + +The program assumes the filesystem is quiescent. *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. + +The _device_ name will usually have the following form: + +____ +[cols=",",] +|=== +|/dev/hda[1-63] |IDE disk 1 +|/dev/hdb[1-63] |IDE disk 2 +|/dev/sda[1-15] |SCSI disk 1 +|/dev/sdb[1-15] |SCSI disk 2 +|=== +____ + +If the filesystem was changed, i.e., repaired, then *fsck.minix* will print "FILE SYSTEM HAS BEEN CHANGED" and will *sync*(2) three times before exiting. There is _no_ need to reboot after check. + +== WARNING + +*fsck.minix* should *not* be used on a mounted filesystem. Using *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 *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. + +== OPTIONS + +*-l*, *--list*:: +List all filenames. + +*-r*, *--repair*:: +Perform interactive repairs. + +*-a*, *--auto*:: +Perform automatic repairs. This option implies *--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. + +*-v*, *--verbose*:: +Be verbose. + +*-s*, *--super*:: +Output super-block information. + +*-m*, *--uncleared*:: +Activate MINIX-like "mode not cleared" warnings. + +*-f*, *--force*:: +Force a filesystem check even if the filesystem was marked as valid. Marking is done by the kernel when the filesystem is unmounted. + +include::man-common/help-version.adoc[] + +== DIAGNOSTICS + +There are numerous diagnostic messages. The ones mentioned here are the most commonly seen in normal usage. + +If the device does not exist, *fsck.minix* will print "unable to read super block". If the device exists, but is not a MINIX filesystem, *fsck.minix* will print "bad magic number in super-block". + +== EXIT STATUS + +The exit status returned by *fsck.minix* is the sum of the following: + +*0*:: +No errors +*3*:: +Filesystem errors corrected, system should be rebooted if filesystem was mounted +*4*:: +Filesystem errors left uncorrected +*7*:: +Combination of exit statuses 3 and 4 +*8*:: +Operational error +*16*:: +Usage or syntax error + + +== AUTHORS + +mailto:torvalds@cs.helsinki.fi[Linus Torvalds]. Exit status values by mailto:faith@cs.unc.edu[Rik Faith] Added support for filesystem valid flag: mailto:greg%wind.uucp@plains.nodak.edu[Dr. Wettstein]. Check to prevent fsck of mounted filesystem added by mailto:quinlan@yggdrasil.com[Daniel Quinlan]. Minix v2 fs support by mailto:schwab@issan.informatik.uni-dortmund.de[Andreas Schwab], updated by mailto:janl@math.uio.no[Nicolai Langfeldt]. Portability patch by mailto:rmk@ecs.soton.ac.uk[Russell King]. + +== SEE ALSO + +*fsck*(8), +*fsck.ext2*(8), +*mkfs*(8), +*mkfs.ext2*(8), +*mkfs.minix*(8), +*reboot*(8) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/disk-utils/fsck.minix.c b/disk-utils/fsck.minix.c new file mode 100644 index 0000000..66b46d4 --- /dev/null +++ b/disk-utils/fsck.minix.c @@ -0,0 +1,1441 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * 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. + * + * 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); + } +} + + +static int is_valid_zone_nr(unsigned short nr) +{ + if (nr < get_first_zone()) + return 0; + 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")); +} + +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) != 0) { + 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) != 0) { + 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--; +} + +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) != 0) { + 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) != 0) { + 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--; +} + +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); + close_stdout_atexit(); + + 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': + print_version(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; + } + + 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..40f8c0c --- /dev/null +++ b/disk-utils/isosize.8 @@ -0,0 +1,89 @@ +'\" t +.\" Title: isosize +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-10-23 +.\" Manual: System Administration +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "ISOSIZE" "8" "2023-10-23" "util\-linux 2.39.3" "System Administration" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +isosize \- output the length of an iso9660 filesystem +.SH "SYNOPSIS" +.sp +\fBisosize\fP [options] \fIiso9660_image_file\fP +.SH "DESCRIPTION" +.sp +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. \fI/dev/hdd\fP or \fI/dev/sr0\fP). 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" +.sp +\fB\-x\fP, \fB\-\-sectors\fP +.RS 4 +Show the block count and block size in human\-readable form. The output uses the term "sectors" for "blocks". +.RE +.sp +\fB\-d\fP, \fB\-\-divisor\fP \fInumber\fP +.RS 4 +Only has an effect when \fB\-x\fP is not given. The value shown (if no errors) is the iso9660 file size in bytes divided by \fInumber\fP. So if \fInumber\fP is the block size then the shown value will be the block count. +.sp +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. +.RE +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.sp +\fB\-V\fP, \fB\-\-version\fP +.RS 4 +Print version and exit. +.RE +.SH "EXIT STATUS" +.sp +\fB0\fP +.RS 4 +success +.RE +.sp +\fB1\fP +.RS 4 +generic failure, such as invalid usage +.RE +.sp +\fB32\fP +.RS 4 +all failed +.RE +.sp +\fB64\fP +.RS 4 +some failed +.RE +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBisosize\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "."
\ No newline at end of file diff --git a/disk-utils/isosize.8.adoc b/disk-utils/isosize.8.adoc new file mode 100644 index 0000000..2142aaa --- /dev/null +++ b/disk-utils/isosize.8.adoc @@ -0,0 +1,50 @@ +//po4a: entry man manual += isosize(8) +:doctype: manpage +:man manual: System Administration +:man source: util-linux {release-version} +:page-layout: base +:command: isosize + +== NAME + +isosize - output the length of an iso9660 filesystem + +== SYNOPSIS + +*isosize* [options] _iso9660_image_file_ + +== DESCRIPTION + +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). + +== OPTIONS + +*-x*, *--sectors*:: +Show the block count and block size in human-readable form. The output uses the term "sectors" for "blocks". + +*-d*, *--divisor* _number_:: +Only has an effect when *-x* is not given. The value shown (if no errors) is the iso9660 file size in bytes divided by _number_. So if _number_ is the block size then the shown value will be the block count. ++ +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. + +include::man-common/help-version.adoc[] + +== EXIT STATUS + +*0*:: +success +*1*:: +generic failure, such as invalid usage +*32*:: +all failed +*64*:: +some failed + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/disk-utils/isosize.c b/disk-utils/isosize.c new file mode 100644 index 0000000..cfcb985 --- /dev/null +++ b/disk-utils/isosize.c @@ -0,0 +1,174 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * 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. + * + * Copyright (C) 2000 Andries Brouwer + * Copyright (C) 2023 Karel Zak <kzak@redhat.com> + * + * 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" +#include "iso9660.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 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); + close_stdout_atexit(); + + 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': + print_version(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/meson.build b/disk-utils/meson.build new file mode 100644 index 0000000..39b6cba --- /dev/null +++ b/disk-utils/meson.build @@ -0,0 +1,95 @@ +mkfs_sources = files( + 'mkfs.c', +) + +mkfs_bfs_sources = files( + 'mkfs.bfs.c', +) + +isosize_sources = files( + 'isosize.c', +) + +mkswap_sources = files( + 'mkswap.c', +) + \ + ismounted_c +if lib_selinux.found() + mkswap_sources += selinux_utils_c +endif + +swaplabel_sources = files( + 'swaplabel.c', +) + \ + swapprober_c + +fsck_sources = files( + 'fsck.c', +) + \ + monotonic_c + +mkfs_minix_sources = files( + 'mkfs.minix.c', + 'minix_programs.h', +) + \ + ismounted_c + +fsck_minix_sources = files( + 'fsck.minix.c', + 'minix_programs.h', +) + \ + ismounted_c + +mkfs_cramfs_sources = files( + 'mkfs.cramfs.c', + 'cramfs.h', + 'cramfs_common.c', +) + +fsck_cramfs_sources = files( + 'fsck.cramfs.c', + 'cramfs.h', + 'cramfs_common.c', +) + +raw_sources = files( + 'raw.c', +) + +fdformat_sources = files( + 'fdformat.c', +) + +blockdev_sources = files( + 'blockdev.c', +) + +fdisk_sources = files( + 'fdisk.c', + 'fdisk.h', + 'fdisk-menu.c', + 'fdisk-list.c', + 'fdisk-list.h') + \ + pager_c + +sfdisk_sources = files( + 'sfdisk.c', + 'fdisk-list.c', + 'fdisk-list.h') + +cfdisk_sources = files( + 'cfdisk.c', +) + +addpart_sources = files( + 'addpart.c', +) +delpart_sources = files( + 'delpart.c', +) +resizepart_sources = files( + 'resizepart.c', +) +partx_sources = files( + 'partx.c', +) diff --git a/disk-utils/minix_programs.h b/disk-utils/minix_programs.h new file mode 100644 index 0000000..2bb311c --- /dev/null +++ b/disk-utils/minix_programs.h @@ -0,0 +1,128 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * 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. + * + * Copyright (C) 2011 Sami Kerola <kerolasa@iki.fi> + */ +#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..d653d63 --- /dev/null +++ b/disk-utils/mkfs.8 @@ -0,0 +1,103 @@ +'\" t +.\" Title: mkfs +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-10-23 +.\" Manual: System Administration +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "MKFS" "8" "2023-10-23" "util\-linux 2.39.3" "System Administration" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +mkfs \- build a Linux filesystem +.SH "SYNOPSIS" +.sp +\fBmkfs\fP [options] [\fB\-t\fP \fItype\fP] [\fIfs\-options\fP] \fIdevice\fP [\fIsize\fP] +.SH "DESCRIPTION" +.sp +\fBThis mkfs frontend is deprecated in favour of filesystem specific mkfs.<type> utils.\fP +.sp +\fBmkfs\fP is used to build a Linux filesystem on a device, usually a hard disk partition. The \fIdevice\fP argument is either the device name (e.g., \fI/dev/hda1\fP, \fI/dev/sdb2\fP), or a regular file that shall contain the filesystem. The \fIsize\fP argument is the number of blocks to be used for the filesystem. +.sp +The exit status returned by \fBmkfs\fP is 0 on success and 1 on failure. +.sp +In actuality, \fBmkfs\fP is simply a front\-end for the various filesystem builders (\fBmkfs.\fP\fIfstype\fP) available under Linux. The filesystem\-specific builder is searched for via your \fBPATH\fP environment setting only. Please see the filesystem\-specific builder manual pages for further details. +.SH "OPTIONS" +.sp +\fB\-t\fP, \fB\-\-type\fP \fItype\fP +.RS 4 +Specify the \fItype\fP of filesystem to be built. If not specified, the default filesystem type (currently ext2) is used. +.RE +.sp +\fIfs\-options\fP +.RS 4 +Filesystem\-specific options to be passed to the real filesystem builder. +.RE +.sp +\fB\-V\fP, \fB\-\-verbose\fP +.RS 4 +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. +.RE +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.sp +\fB\-V\fP, \fB\-\-version\fP +.RS 4 +Print version and exit. +(Option \fB\-V\fP will display version information only when it is the only parameter, otherwise it will work as \fB\-\-verbose\fP.) +.RE +.SH "BUGS" +.sp +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 \fIsize\fP parameter to be specified. +.SH "AUTHORS" +.sp +.MTO "david\(atods.com" "David Engel" "," +.MTO "waltje\(atuwalt.nl.mugnet.org" "Fred N. van Kempen" "," +.MTO "sommel\(atsci.kun.nl" "Ron Sommeling" "." +.sp +The manual page was shamelessly adapted from Remy Card\(cqs version for the ext2 filesystem. +.SH "SEE ALSO" +.sp +\fBfs\fP(5), +\fBbadblocks\fP(8), +\fBfsck\fP(8), +\fBmkdosfs\fP(8), +\fBmke2fs\fP(8), +\fBmkfs.bfs\fP(8), +\fBmkfs.ext2\fP(8), +\fBmkfs.ext3\fP(8), +\fBmkfs.ext4\fP(8), +\fBmkfs.minix\fP(8), +\fBmkfs.msdos\fP(8), +\fBmkfs.vfat\fP(8), +\fBmkfs.xfs\fP(8) +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBmkfs\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "."
\ No newline at end of file diff --git a/disk-utils/mkfs.8.adoc b/disk-utils/mkfs.8.adoc new file mode 100644 index 0000000..45f100f --- /dev/null +++ b/disk-utils/mkfs.8.adoc @@ -0,0 +1,75 @@ +//po4a: entry man manual += mkfs(8) +:doctype: manpage +:man manual: System Administration +:man source: util-linux {release-version} +:page-layout: base +:command: mkfs + +== NAME + +mkfs - build a Linux filesystem + +== SYNOPSIS + +*mkfs* [options] [*-t* _type_] [_fs-options_] _device_ [_size_] + +== DESCRIPTION + +*This mkfs frontend is deprecated in favour of filesystem specific mkfs.<type> utils.* + +*mkfs* is used to build a Linux filesystem on a device, usually a hard disk partition. The _device_ argument is either the device name (e.g., _/dev/hda1_, _/dev/sdb2_), or a regular file that shall contain the filesystem. The _size_ argument is the number of blocks to be used for the filesystem. + +The exit status returned by *mkfs* is 0 on success and 1 on failure. + +In actuality, *mkfs* is simply a front-end for the various filesystem builders (**mkfs.**__fstype__) 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. + +== OPTIONS + +*-t*, *--type* _type_:: +Specify the _type_ of filesystem to be built. If not specified, the default filesystem type (currently ext2) is used. + +_fs-options_:: +Filesystem-specific options to be passed to the real filesystem builder. + +*-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. + +include::man-common/help-version.adoc[] +(Option *-V* will display version information only when it is the only parameter, otherwise it will work as *--verbose*.) + +== 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 _size_ parameter to be specified. + +== AUTHORS + +mailto:david@ods.com[David Engel], +mailto:waltje@uwalt.nl.mugnet.org[Fred N. van Kempen], +mailto:sommel@sci.kun.nl[Ron Sommeling]. + +The manual page was shamelessly adapted from Remy Card's version for the ext2 filesystem. + +== SEE ALSO + +*fs*(5), +*badblocks*(8), +*fsck*(8), +*mkdosfs*(8), +*mke2fs*(8), +*mkfs.bfs*(8), +*mkfs.ext2*(8), +*mkfs.ext3*(8), +*mkfs.ext4*(8), +*mkfs.minix*(8), +*mkfs.msdos*(8), +*mkfs.vfat*(8), +*mkfs.xfs*(8) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/disk-utils/mkfs.bfs.8 b/disk-utils/mkfs.bfs.8 new file mode 100644 index 0000000..16919ab --- /dev/null +++ b/disk-utils/mkfs.bfs.8 @@ -0,0 +1,100 @@ +'\" t +.\" Title: mkfs.bfs +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-11-21 +.\" Manual: System Administration +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "MKFS.BFS" "8" "2023-11-21" "util\-linux 2.39.3" "System Administration" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +mkfs.bfs \- make an SCO bfs filesystem +.SH "SYNOPSIS" +.sp +\fBmkfs.bfs\fP [options] \fIdevice\fP [\fIblock\-count\fP] +.SH "DESCRIPTION" +.sp +\fBmkfs.bfs\fP creates an SCO bfs filesystem on a block device (usually a disk partition or a file accessed via the loop device). +.sp +The \fIblock\-count\fP parameter is the desired size of the filesystem, in blocks. If nothing is specified, the entire partition will be used. +.SH "OPTIONS" +.sp +\fB\-N\fP, \fB\-\-inodes\fP \fInumber\fP +.RS 4 +Specify the desired \fInumber\fP of inodes (at most 512). If nothing is specified, some default number in the range 48\-512 is picked depending on the size of the partition. +.RE +.sp +\fB\-V\fP, \fB\-\-vname\fP \fIlabel\fP +.RS 4 +Specify the volume \fIlabel\fP. I have no idea if/where this is used. +.RE +.sp +\fB\-F\fP, \fB\-\-fname\fP \fIname\fP +.RS 4 +Specify the filesystem \fIname\fP. I have no idea if/where this is used. +.RE +.sp +\fB\-\-lock\fP[=\fImode\fP] +.RS 4 +Use exclusive BSD lock for device or file it operates. The optional argument \fImode\fP can be \fByes\fP, \fBno\fP (or 1 and 0) or \fBnonblock\fP. If the \fImode\fP argument is omitted, it defaults to \fByes\fP. This option overwrites environment variable \fB$LOCK_BLOCK_DEVICE\fP. The default is not to use any lock at all, but it\(cqs recommended to avoid collisions with \fBsystemd\-udevd\fP(8) or other tools. +.RE +.sp +\fB\-v\fP, \fB\-\-verbose\fP +.RS 4 +Explain what is being done. +.RE +.sp +\fB\-c\fP +.RS 4 +This option is silently ignored. +.RE +.sp +\fB\-l\fP +.RS 4 +This option is silently ignored. +.RE +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.sp +\fB\-V\fP, \fB\-\-version\fP +.RS 4 +Print version and exit. +Option \fB\-V\fP only works as \fB\-\-version\fP when it is the only option. +.RE +.SH "EXIT STATUS" +.sp +The exit status returned by \fBmkfs.bfs\fP is 0 when all went well, and 1 when something went wrong. +.SH "SEE ALSO" +.sp +\fBmkfs\fP(8) +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBmkfs.bfs\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "."
\ No newline at end of file diff --git a/disk-utils/mkfs.bfs.8.adoc b/disk-utils/mkfs.bfs.8.adoc new file mode 100644 index 0000000..198d61f --- /dev/null +++ b/disk-utils/mkfs.bfs.8.adoc @@ -0,0 +1,67 @@ +//po4a: entry man manual +//// +Copyright 1999 Andries E. Brouwer (aeb@cwi.nl) +May be freely distributed. +//// += mkfs.bfs(8) +:doctype: manpage +:man manual: System Administration +:man source: util-linux {release-version} +:page-layout: base +:command: mkfs.bfs + +== NAME + +mkfs.bfs - make an SCO bfs filesystem + +== SYNOPSIS + +*mkfs.bfs* [options] _device_ [_block-count_] + +== DESCRIPTION + +*mkfs.bfs* creates an SCO bfs filesystem on a block device (usually a disk partition or a file accessed via the loop device). + +The _block-count_ parameter is the desired size of the filesystem, in blocks. If nothing is specified, the entire partition will be used. + +== OPTIONS + +*-N*, *--inodes* _number_:: +Specify the desired _number_ of inodes (at most 512). If nothing is specified, some default number in the range 48-512 is picked depending on the size of the partition. + +*-V*, *--vname* _label_:: +Specify the volume _label_. I have no idea if/where this is used. + +*-F*, *--fname* _name_:: +Specify the filesystem _name_. I have no idea if/where this is used. + +*--lock*[=_mode_]:: +Use exclusive BSD lock for device or file it operates. The optional argument _mode_ can be *yes*, *no* (or 1 and 0) or *nonblock*. If the _mode_ argument is omitted, it defaults to *yes*. This option overwrites environment variable *$LOCK_BLOCK_DEVICE*. The default is not to use any lock at all, but it's recommended to avoid collisions with *systemd-udevd*(8) or other tools. + +*-v*, *--verbose*:: +Explain what is being done. + +*-c*:: +This option is silently ignored. + +*-l*:: +This option is silently ignored. + +include::man-common/help-version.adoc[] +Option *-V* only works as *--version* when it is the only option. + +== EXIT STATUS + +The exit status returned by *mkfs.bfs* is 0 when all went well, and 1 when something went wrong. + +== SEE ALSO + +*mkfs*(8) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/disk-utils/mkfs.bfs.c b/disk-utils/mkfs.bfs.c new file mode 100644 index 0000000..06271a7 --- /dev/null +++ b/disk-utils/mkfs.bfs.c @@ -0,0 +1,328 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * 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. + * + * mkfs.bfs - Create SCO BFS filesystem - aeb, 1999-09-07 + * + * Copyright (C) 1999 Andries E. Brouwe + * + * 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" +#include "exitcodes.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" + " --lock[=<mode>] use exclusive device lock (yes, no or nonblock)\n" + )); + printf(USAGE_HELP_OPTIONS(21)); + + printf(USAGE_MAN_TAIL("mkfs.bfs(8)")); + exit(EXIT_SUCCESS); +} + +int main(int argc, char **argv) +{ + char *device, *volume, *fsname; + char *lockmode = 0; + 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, + OPT_LOCK + }; + 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'}, + {"lock", optional_argument, NULL, OPT_LOCK}, + {NULL, 0, NULL, 0} + }; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + close_stdout_atexit(); + + if (argc < 2) { + warnx(_("not enough arguments")); + errtryhelp(EXIT_FAILURE); + } + if (argc == 2 && !strcmp(argv[1], "-V")) + print_version(EXIT_SUCCESS); + + 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 OPT_LOCK: + lockmode = "1"; + if (optarg) { + if (*optarg == '=') + optarg++; + lockmode = optarg; + } + break; + + case VERSION_OPTION: + print_version(EXIT_SUCCESS); + 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 (blkdev_lock(fd, device, lockmode) != 0) + exit(MKFS_EX_ERROR); + + 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..4254ef0 --- /dev/null +++ b/disk-utils/mkfs.c @@ -0,0 +1,141 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * 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. + * + * mkfs A simple generic frontend for the mkfs program + * under Linux. See the manual page for details. + * + * Copyright (C) David Engel, <david@ods.com> + * Copyright (C) Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG> + * Copyright (C) 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); +} + +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); + close_stdout_atexit(); + + if (argc == 2 && !strcmp(argv[1], "-V")) + print_version(EXIT_SUCCESS); + + /* 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(EXIT_SUCCESS); + 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..9eef51a --- /dev/null +++ b/disk-utils/mkfs.cramfs.8 @@ -0,0 +1,142 @@ +'\" t +.\" Title: mkfs.cramfs +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-11-21 +.\" Manual: System Administration +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "MKFS.CRAMFS" "8" "2023-11-21" "util\-linux 2.39.3" "System Administration" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +mkfs.cramfs \- make compressed ROM file system +.SH "SYNOPSIS" +.sp +\fBmkfs.cramfs\fP [options] \fIdirectory file\fP +.SH "DESCRIPTION" +.sp +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. +.sp +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 (\fBmkcramfs\fP(8)) to pack files into new cramfs images. +.sp +File sizes are limited to less than 16 MB. +.sp +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" +.sp +The \fIdirectory\fP is simply the root of the directory tree that we want to generate a compressed filesystem out of. +.sp +The \fIfile\fP will contain the cram file system, which later can be mounted. +.SH "OPTIONS" +.sp +\fB\-v\fP +.RS 4 +Enable verbose messaging. +.RE +.sp +\fB\-E\fP +.RS 4 +Treat all warnings as errors, which are reflected as command exit status. +.RE +.sp +\fB\-b\fP \fIblocksize\fP +.RS 4 +Use defined block size, which has to be divisible by page size. +.RE +.sp +\fB\-e\fP \fIedition\fP +.RS 4 +Use defined file system edition number in superblock. +.RE +.sp +\fB\-N\fP \fIbig, little, host\fP +.RS 4 +Use defined endianness. Value defaults to \fIhost\fP. +.RE +.sp +\fB\-i\fP \fIfile\fP +.RS 4 +Insert a \fIfile\fP to cramfs file system. +.RE +.sp +\fB\-n\fP \fIname\fP +.RS 4 +Set name of the cramfs file system. +.RE +.sp +\fB\-p\fP +.RS 4 +Pad by 512 bytes for boot code. +.RE +.sp +\fB\-s\fP +.RS 4 +This option is ignored. Originally the \fB\-s\fP turned on directory entry sorting. +.RE +.sp +\fB\-z\fP +.RS 4 +Make explicit holes. +.RE +.sp +\fB\-l\fP[=\fImode\fP] +.RS 4 +Use exclusive BSD lock for device or file it operates. The optional argument +\fImode\fP can be \fIyes\fP, \fIno\fP (or 1 and 0) or \fInonblock\fP. If the \fImode\fP +argument is omitted, it defaults to \fI"yes"\fP. This option overwrites +environment variable \fB$LOCK_BLOCK_DEVICE\fP. The default is not to use any +lock at all, but it\(cqs recommended to avoid collisions with udevd or other +tools. +.RE +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.sp +\fB\-V\fP, \fB\-\-version\fP +.RS 4 +Print version and exit. +.RE +.SH "EXIT STATUS" +.sp +\fB0\fP +.RS 4 +success +.RE +.sp +\fB8\fP +.RS 4 +operation error, such as unable to allocate memory +.RE +.SH "SEE ALSO" +.sp +\fBfsck.cramfs\fP(8), +\fBmount\fP(8) +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBmkfs.cramfs\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "."
\ No newline at end of file diff --git a/disk-utils/mkfs.cramfs.8.adoc b/disk-utils/mkfs.cramfs.8.adoc new file mode 100644 index 0000000..c5ffa20 --- /dev/null +++ b/disk-utils/mkfs.cramfs.8.adoc @@ -0,0 +1,93 @@ +//po4a: entry man manual += mkfs.cramfs(8) +:doctype: manpage +:man manual: System Administration +:man source: util-linux {release-version} +:page-layout: base +:command: mkfs.cramfs + +== NAME + +mkfs.cramfs - make compressed ROM file system + +== SYNOPSIS + +*mkfs.cramfs* [options] _directory file_ + +== 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. + +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*(8)) to pack files into new cramfs images. + +File sizes are limited to less than 16 MB. + +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.) + +== ARGUMENTS + +The _directory_ is simply the root of the directory tree that we want to generate a compressed filesystem out of. + +The _file_ will contain the cram file system, which later can be mounted. + +== OPTIONS + +*-v*:: +Enable verbose messaging. + +*-E*:: +Treat all warnings as errors, which are reflected as command exit status. + +*-b* _blocksize_:: +Use defined block size, which has to be divisible by page size. + +*-e* _edition_:: +Use defined file system edition number in superblock. + +*-N* _big, little, host_:: +Use defined endianness. Value defaults to _host_. + +*-i* _file_:: +Insert a _file_ to cramfs file system. + +*-n* _name_:: +Set name of the cramfs file system. + +*-p*:: +Pad by 512 bytes for boot code. + +*-s*:: +This option is ignored. Originally the *-s* turned on directory entry sorting. + +*-z*:: +Make explicit holes. + +*-l*[=_mode_]:: + Use exclusive BSD lock for device or file it operates. The optional argument + _mode_ can be _yes_, _no_ (or 1 and 0) or _nonblock_. If the _mode_ + argument is omitted, it defaults to _"yes"_. This option overwrites + environment variable *$LOCK_BLOCK_DEVICE*. The default is not to use any + lock at all, but it's recommended to avoid collisions with udevd or other + tools. + +include::man-common/help-version.adoc[] + +== EXIT STATUS + +*0*:: +success +*8*:: +operation error, such as unable to allocate memory + +== SEE ALSO + +*fsck.cramfs*(8), +*mount*(8) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/disk-utils/mkfs.cramfs.c b/disk-utils/mkfs.cramfs.c new file mode 100644 index 0000000..fcc1314 --- /dev/null +++ b/disk-utils/mkfs.cramfs.c @@ -0,0 +1,943 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * 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 "blkdev.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) +{ + fputs(USAGE_HEADER, stdout); + printf(_(" %s [-h] [-v] [-b blksize] [-e edition] [-N endian] [-i file] [-n name] dirname outfile\n"), + program_invocation_short_name); + fputs(USAGE_SEPARATOR, stdout); + puts(_("Make compressed ROM file system.")); + fputs(USAGE_OPTIONS, stdout); + puts(_( " -v be verbose")); + puts(_( " -E make all warnings errors (non-zero exit status)")); + puts(_( " -b blksize use this blocksize, must equal page size")); + puts(_( " -e edition set edition number (part of fsid)")); + printf(_(" -N endian set cramfs endianness (%s|%s|%s), default %s\n"), "big", "little", "host", "host"); + puts(_( " -i file insert a file image into the filesystem")); + puts(_( " -n name set name of cramfs filesystem")); + printf(_(" -p pad by %d bytes for boot code\n"), PAD_SIZE); + puts(_( " -s sort directory entries (old option, ignored)")); + puts(_( " -z make explicit holes")); + puts(_( " -l[=<mode>] use exclusive device lock (yes, no or nonblock)")); + puts(_( " dirname root of the filesystem to be compressed")); + puts(_( " outfile output file")); + 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)) { + /* The link buffer is unnecessary to terminate by null as it's + * always used as buffer rather than a string */ + 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)))))))); + + /* 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; + char *lockmode = 0; + 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); + close_stdout_atexit(); + + if (argc > 1) { + /* first arg may be one of our standard longopts */ + if (!strcmp(argv[1], "--help")) + usage(); + if (!strcmp(argv[1], "--version")) { + print_version(EXIT_SUCCESS); + exit(MKFS_EX_OK); + } + } + strutils_set_exitcode(MKFS_EX_USAGE); + + /* command line options */ + while ((c = getopt(argc, argv, "hb:Ee:i:n:N:l::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 'l': + lockmode = "1"; + if (optarg) { + if (*optarg == '=') + optarg++; + lockmode = optarg; + } + 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': + print_version(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); + + if (blkdev_lock(fd, outfile, lockmode) != 0) + exit(MKFS_EX_ERROR); + + 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..a3be59c --- /dev/null +++ b/disk-utils/mkfs.minix.8 @@ -0,0 +1,143 @@ +'\" t +.\" Title: mkfs.minix +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-10-23 +.\" Manual: System Administration +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "MKFS.MINIX" "8" "2023-10-23" "util\-linux 2.39.3" "System Administration" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +mkfs.minix \- make a Minix filesystem +.SH "SYNOPSIS" +.sp +\fBmkfs.minix\fP [options] \fIdevice\fP [\fIsize\-in\-blocks\fP] +.SH "DESCRIPTION" +.sp +\fBmkfs.minix\fP creates a Linux MINIX filesystem on a device (usually a disk partition). +.sp +The \fIdevice\fP is usually of the following form: +.sp +.if n .RS 4 +.nf +.fam C +/dev/hda[1\-8] (IDE disk 1) +/dev/hdb[1\-8] (IDE disk 2) +/dev/sda[1\-8] (SCSI disk 1) +/dev/sdb[1\-8] (SCSI disk 2) +.fam +.fi +.if n .RE +.sp +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 :\-). +.sp +The \fIsize\-in\-blocks\fP 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" +.sp +\fB\-c\fP, \fB\-\-check\fP +.RS 4 +Check the device for bad blocks before creating the filesystem. If any are found, the count is printed. +.RE +.sp +\fB\-n\fP, \fB\-\-namelength\fP \fIlength\fP +.RS 4 +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. +.RE +.sp +\fB\-\-lock\fP[=\fImode\fP] +.RS 4 +Use exclusive BSD lock for device or file it operates. The optional argument \fImode\fP can be \fByes\fP, \fBno\fP (or 1 and 0) or \fBnonblock\fP. If the \fImode\fP argument is omitted, it defaults to \fByes\fP. This option overwrites environment variable \fB$LOCK_BLOCK_DEVICE\fP. The default is not to use any lock at all, but it\(cqs recommended to avoid collisions with \fBsystemd\-udevd\fP(8) or other tools. +.RE +.sp +\fB\-i\fP, \fB\-\-inodes\fP \fInumber\fP +.RS 4 +Specify the number of inodes for the filesystem. +.RE +.sp +\fB\-l\fP, \fB\-\-badblocks\fP \fIfilename\fP +.RS 4 +Read the list of bad blocks from \fIfilename\fP. The file has one bad\-block number per line. The count of bad blocks read is printed. +.RE +.sp +\fB\-1\fP +.RS 4 +Make a Minix version 1 filesystem. This is the default. +.RE +.sp +\fB\-2\fP, \fB\-v\fP +.RS 4 +Make a Minix version 2 filesystem. +.RE +.sp +\fB\-3\fP +.RS 4 +Make a Minix version 3 filesystem. +.RE +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.sp +\fB\-V\fP, \fB\-\-version\fP +.RS 4 +Print version and exit. +The long option cannot be combined with other options. +.RE +.SH "ENVIRONMENT" +.sp +LOCK_BLOCK_DEVICE=<mode> +.RS 4 +use exclusive BSD lock. The mode is "1" or "0". See \fB\-\-lock\fP for more details. +.RE +.SH "EXIT STATUS" +.sp +The exit status returned by \fBmkfs.minix\fP is one of the following: +.sp +0 +.RS 4 +No errors +.RE +.sp +8 +.RS 4 +Operational error +.RE +.sp +16 +.RS 4 +Usage or syntax error +.RE +.SH "SEE ALSO" +.sp +\fBfsck\fP(8), +\fBmkfs\fP(8), +\fBreboot\fP(8) +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBmkfs.minix\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "."
\ No newline at end of file diff --git a/disk-utils/mkfs.minix.8.adoc b/disk-utils/mkfs.minix.8.adoc new file mode 100644 index 0000000..c1628ac --- /dev/null +++ b/disk-utils/mkfs.minix.8.adoc @@ -0,0 +1,98 @@ +//po4a: entry man manual +//// +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 +//// += mkfs.minix(8) +:doctype: manpage +:man manual: System Administration +:man source: util-linux {release-version} +:page-layout: base +:command: mkfs.minix + +== NAME + +mkfs.minix - make a Minix filesystem + +== SYNOPSIS + +*mkfs.minix* [options] _device_ [_size-in-blocks_] + +== DESCRIPTION + +*mkfs.minix* creates a Linux MINIX filesystem on a device (usually a disk partition). + +The _device_ is usually of the following form: + +.... +/dev/hda[1-8] (IDE disk 1) +/dev/hdb[1-8] (IDE disk 2) +/dev/sda[1-8] (SCSI disk 1) +/dev/sdb[1-8] (SCSI disk 2) +.... + +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 :-). + +The _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. + +== OPTIONS + +*-c*, *--check*:: +Check the device for bad blocks before creating the filesystem. If any are found, the count is printed. + +*-n*, *--namelength* _length_:: +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. + +*--lock*[=_mode_]:: +Use exclusive BSD lock for device or file it operates. The optional argument _mode_ can be *yes*, *no* (or 1 and 0) or *nonblock*. If the _mode_ argument is omitted, it defaults to *yes*. This option overwrites environment variable *$LOCK_BLOCK_DEVICE*. The default is not to use any lock at all, but it's recommended to avoid collisions with *systemd-udevd*(8) or other tools. + +*-i*, *--inodes* _number_:: +Specify the number of inodes for the filesystem. + +*-l*, *--badblocks* _filename_:: +Read the list of bad blocks from _filename_. The file has one bad-block number per line. The count of bad blocks read is printed. + +*-1*:: +Make a Minix version 1 filesystem. This is the default. + +*-2*, *-v*:: +Make a Minix version 2 filesystem. + +*-3*:: +Make a Minix version 3 filesystem. + +include::man-common/help-version.adoc[] +The long option cannot be combined with other options. + +== ENVIRONMENT + +LOCK_BLOCK_DEVICE=<mode>:: +use exclusive BSD lock. The mode is "1" or "0". See *--lock* for more details. + +== EXIT STATUS + +The exit status returned by *mkfs.minix* is one of the following: + +0:: +No errors +8:: +Operational error +16:: +Usage or syntax error + +== SEE ALSO + +*fsck*(8), +*mkfs*(8), +*reboot*(8) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/disk-utils/mkfs.minix.c b/disk-utils/mkfs.minix.c new file mode 100644 index 0000000..08263ad --- /dev/null +++ b/disk-utils/mkfs.minix.c @@ -0,0 +1,861 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * 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. + * 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 */ + char *lockmode; /* as specified by --lock */ + 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); + fprintf(out, _( + " --lock[=<mode>] use exclusive device lock (%s, %s or %s)\n"), "yes", "no", "nonblock"); + 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"); + uint64_t sec; + + if (str && sscanf(str, "%"SCNd64, &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 = 0; + + 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; + enum { + OPT_LOCK = CHAR_MAX + 1 + }; + 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'}, + {"lock",optional_argument, NULL, OPT_LOCK}, + {NULL, 0, NULL, 0} + }; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + close_stdout_atexit(); + + 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 OPT_LOCK: + ctl.lockmode = "1"; + if (optarg) { + if (*optarg == '=') + optarg++; + ctl.lockmode = optarg; + } + break; + case 'V': + print_version(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); + if (blkdev_lock(ctl.device_fd, ctl.device_name, ctl.lockmode) != 0) + exit(MKFS_EX_ERROR); + 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..3beeecc --- /dev/null +++ b/disk-utils/mkswap.8 @@ -0,0 +1,178 @@ +'\" t +.\" Title: mkswap +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-12-01 +.\" Manual: System Administration +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "MKSWAP" "8" "2023-12-01" "util\-linux 2.39.3" "System Administration" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +mkswap \- set up a Linux swap area +.SH "SYNOPSIS" +.sp +\fBmkswap\fP [options] \fIdevice\fP [\fIsize\fP] +.SH "DESCRIPTION" +.sp +\fBmkswap\fP sets up a Linux swap area on a device or in a file. +.sp +The \fIdevice\fP argument will usually be a disk partition (something like \fI/dev/sdb7\fP) 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) +.sp +The \fIsize\fP parameter is superfluous but retained for backwards compatibility. (It specifies the desired size of the swap area in 1024\-byte blocks. \fBmkswap\fP will use the entire partition or file if it is omitted. Specifying it is unwise \- a typo may destroy your disk.) +.sp +After creating the swap area, you need the \fBswapon\fP(8) command to start using it. Usually swap areas are listed in \fI/etc/fstab\fP so that they can be taken into use at boot time by a \fBswapon \-a\fP command in some boot script. +.SH "WARNING" +.sp +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. +.sp +\fBmkswap\fP, like many others mkfs\-like utils, \fBerases the first partition block to make any previous filesystem invisible.\fP +.sp +However, \fBmkswap\fP refuses to erase the first block on a device with a disk label (SUN, BSD, ...). +.SH "OPTIONS" +.sp +\fB\-c\fP, \fB\-\-check\fP +.RS 4 +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. +.RE +.sp +\fB\-f\fP, \fB\-\-force\fP +.RS 4 +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. +.sp +Also, without this option, \fBmkswap\fP will refuse to erase the first block on a device with a partition table. +.RE +.sp +\fB\-q\fP, \fB\-\-quiet\fP +.RS 4 +Suppress output and warning messages. +.RE +.sp +\fB\-L\fP, \fB\-\-label\fP \fIlabel\fP +.RS 4 +Specify a \fIlabel\fP for the device, to allow \fBswapon\fP(8) by label. +.RE +.sp +\fB\-\-lock\fP[=\fImode\fP] +.RS 4 +Use exclusive BSD lock for device or file it operates. The optional argument \fImode\fP can be \fByes\fP, \fBno\fP (or 1 and 0) or \fBnonblock\fP. If the \fImode\fP argument is omitted, it defaults to \fByes\fP. This option overwrites environment variable \fB$LOCK_BLOCK_DEVICE\fP. The default is not to use any lock at all, but it\(cqs recommended to avoid collisions with \fBsystemd\-udevd\fP(8) or other tools. +.RE +.sp +\fB\-p\fP, \fB\-\-pagesize\fP \fIsize\fP +.RS 4 +Specify the page \fIsize\fP (in bytes) to use. This option is usually unnecessary; \fBmkswap\fP reads the size from the kernel. +.RE +.sp +\fB\-U\fP, \fB\-\-uuid\fP \fIUUID\fP +.RS 4 +Specify the \fIUUID\fP to use. The default is to generate a UUID. The format of the UUID is a series of +hex digits separated by hyphens, like this: "c1b9d5a2\-f162\-11cf\-9ece\-0020afc76f16". The UUID parameter +may also be one of the following: +.sp +\fBclear\fP +.RS 4 +clear the filesystem UUID +.RE +.sp +\fBrandom\fP +.RS 4 +generate a new randomly\-generated UUID +.RE +.sp +\fBtime\fP +.RS 4 +generate a new time\-based UUID +.RE +.RE +.sp +\fB\-e\fP, \fB\-\-endianness\fP \fIENDIANNESS\fP +.RS 4 +Specify the \fIENDIANNESS\fP to use, valid arguments are \fBnative\fP, \fBlittle\fP or \fBbig\fP. The default is \fBnative\fP. +.RE +.sp +\fB\-v\fP, \fB\-\-swapversion 1\fP +.RS 4 +Specify the swap\-space version. (This option is currently pointless, as the old \fB\-v 0\fP option has become obsolete and now only \fB\-v 1\fP 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).) +.RE +.sp +\fB\-\-verbose\fP +.RS 4 +Verbose execution. With this option \fBmkswap\fP will output more details about detected problems during swap area set up. +.RE +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.sp +\fB\-V\fP, \fB\-\-version\fP +.RS 4 +Print version and exit. +.RE +.SH "ENVIRONMENT" +.sp +LIBBLKID_DEBUG=all +.RS 4 +enables libblkid debug output. +.RE +.sp +LOCK_BLOCK_DEVICE=<mode> +.RS 4 +use exclusive BSD lock. The mode is "1" or "0". See \fB\-\-lock\fP for more details. +.RE +.SH "NOTES" +.sp +The maximum useful size of a swap area depends on the architecture and the kernel version. +.sp +The maximum number of the pages that is possible to address by swap area header is 4294967295 (32\-bit unsigned int). The remaining space on the swap device is ignored. +.sp +Presently, Linux allows 32 swap areas. The areas in use can be seen in the file \fI/proc/swaps\fP. +.sp +\fBmkswap\fP refuses areas smaller than 10 pages. +.sp +If you don\(cqt know the page size that your machine uses, you can look it up with \fBgetconf PAGESIZE\fP. +.sp +To set up a swap file, it is necessary to create that file before initializing it with \fBmkswap\fP, e.g. using a command like +.sp +.if n .RS 4 +.nf +.fam C +# dd if=/dev/zero of=swapfile bs=1MiB count=$((8*1024)) +.fam +.fi +.if n .RE +.sp +to create 8GiB swapfile. +.sp +Please read notes from \fBswapon\fP(8) about \fBthe swap file use restrictions\fP (holes, preallocation and copy\-on\-write issues). +.SH "SEE ALSO" +.sp +\fBfdisk\fP(8), +\fBswapon\fP(8) +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBmkswap\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "."
\ No newline at end of file diff --git a/disk-utils/mkswap.8.adoc b/disk-utils/mkswap.8.adoc new file mode 100644 index 0000000..904467d --- /dev/null +++ b/disk-utils/mkswap.8.adoc @@ -0,0 +1,125 @@ +//po4a: entry man manual +//// +Copyright 1998 Andries E. Brouwer (aeb@cwi.nl) +May be distributed under the GNU General Public License +//// += mkswap(8) +:doctype: manpage +:man manual: System Administration +:man source: util-linux {release-version} +:page-layout: base +:command: mkswap + +== NAME + +mkswap - set up a Linux swap area + +== SYNOPSIS + +*mkswap* [options] _device_ [_size_] + +== DESCRIPTION + +*mkswap* sets up a Linux swap area on a device or in a file. + +The _device_ argument will usually be a disk partition (something like _/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. (*Warning: Solaris also uses this type. Be careful not to kill your Solaris partitions.*) + +The _size_ parameter is superfluous but retained for backwards compatibility. (It specifies the desired size of the swap area in 1024-byte blocks. *mkswap* will use the entire partition or file if it is omitted. Specifying it is unwise - a typo may destroy your disk.) + +After creating the swap area, you need the *swapon*(8) command to start using it. Usually swap areas are listed in _/etc/fstab_ so that they can be taken into use at boot time by a *swapon -a* command in some boot script. + +== 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. + +*mkswap*, like many others mkfs-like utils, *erases the first partition block to make any previous filesystem invisible.* + +However, *mkswap* refuses to erase the first block on a device with a disk label (SUN, BSD, ...). + +== OPTIONS + +*-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. + +*-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, *mkswap* will refuse to erase the first block on a device with a partition table. + +*-q*, *--quiet*:: +Suppress output and warning messages. + +*-L*, *--label* _label_:: +Specify a _label_ for the device, to allow *swapon*(8) by label. + +*--lock*[=_mode_]:: +Use exclusive BSD lock for device or file it operates. The optional argument _mode_ can be *yes*, *no* (or 1 and 0) or *nonblock*. If the _mode_ argument is omitted, it defaults to *yes*. This option overwrites environment variable *$LOCK_BLOCK_DEVICE*. The default is not to use any lock at all, but it's recommended to avoid collisions with *systemd-udevd*(8) or other tools. + +*-p*, *--pagesize* _size_:: +Specify the page _size_ (in bytes) to use. This option is usually unnecessary; *mkswap* reads the size from the kernel. + +*-U*, *--uuid* _UUID_:: +Specify the _UUID_ to use. The default is to generate a UUID. The format of the UUID is a series of +hex digits separated by hyphens, like this: "c1b9d5a2-f162-11cf-9ece-0020afc76f16". The UUID parameter +may also be one of the following: ++ +*clear*;; +clear the filesystem UUID +*random*;; +generate a new randomly-generated UUID +*time*;; +generate a new time-based UUID + +*-e*, *--endianness* _ENDIANNESS_:: +Specify the _ENDIANNESS_ to use, valid arguments are *native*, *little* or *big*. The default is *native*. + +*-v*, *--swapversion 1*:: +Specify the swap-space version. (This option is currently pointless, as the old *-v 0* option has become obsolete and now only *-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).) + +*--verbose*:: +Verbose execution. With this option *mkswap* will output more details about detected problems during swap area set up. + +include::man-common/help-version.adoc[] + +== ENVIRONMENT + +LIBBLKID_DEBUG=all:: +enables libblkid debug output. + +LOCK_BLOCK_DEVICE=<mode>:: +use exclusive BSD lock. The mode is "1" or "0". See *--lock* for more details. + +== 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 (32-bit unsigned int). 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 _/proc/swaps_. + +*mkswap* refuses areas smaller than 10 pages. + +If you don't know the page size that your machine uses, you can look it up with *getconf PAGESIZE*. + +To set up a swap file, it is necessary to create that file before initializing it with *mkswap*, e.g. using a command like + +.... +# dd if=/dev/zero of=swapfile bs=1MiB count=$((8*1024)) +.... + +to create 8GiB swapfile. + +Please read notes from *swapon*(8) about *the swap file use restrictions* (holes, preallocation and copy-on-write issues). + +== SEE ALSO + +*fdisk*(8), +*swapon*(8) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/disk-utils/mkswap.c b/disk-utils/mkswap.c new file mode 100644 index 0000000..30c4ea8 --- /dev/null +++ b/disk-utils/mkswap.c @@ -0,0 +1,746 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * 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. + * + * 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 <sys/ioctl.h> +#include <errno.h> +#include <getopt.h> +#include <assert.h> +#ifdef HAVE_LIBSELINUX +# include <selinux/selinux.h> +# include <selinux/context.h> +# include "selinux-utils.h" +#endif +#ifdef HAVE_LINUX_FIEMAP_H +# include <linux/fs.h> +# include <linux/fiemap.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" +#include "optutils.h" +#include "bitops.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" + +enum ENDIANNESS { + ENDIANNESS_NATIVE, + ENDIANNESS_LITTLE, + ENDIANNESS_BIG, +}; + +struct mkswap_control { + struct swap_header_v1_2 *hdr; /* swap header */ + void *signature_page;/* buffer with swap header */ + + char *devname; /* device or file name */ + const char *lockmode; /* as specified by --lock */ + 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 */ + + size_t nbad_extents; + + enum ENDIANNESS endianness; + + unsigned int check:1, /* --check */ + verbose:1, /* --verbose */ + quiet:1, /* --quiet */ + force:1; /* --force */ +}; + +static uint32_t cpu32_to_endianness(uint32_t v, enum ENDIANNESS e) +{ + switch (e) { + case ENDIANNESS_NATIVE: return v; + case ENDIANNESS_LITTLE: return cpu_to_le32(v); + case ENDIANNESS_BIG: return cpu_to_be32(v); + } + abort(); +} + +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->quiet && 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 (!ctl->quiet + && strlen(ctl->opt_label) > strlen(ctl->hdr->volume_name)) + warnx(_("Label was truncated.")); + } + + /* report results */ + if (!ctl->quiet && (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; + + fputs(USAGE_HEADER, out); + fprintf(out, _(" %s [options] device [size]\n"), program_invocation_short_name); + + fputs(USAGE_SEPARATOR, out); + fputs(_("Set up a Linux swap area.\n"), out); + + fputs(USAGE_OPTIONS, out); + fputs(_(" -c, --check check bad blocks before creating the swap area\n"), out); + fputs(_(" -f, --force allow swap size area be larger than device\n"), out); + fputs(_(" -q, --quiet suppress output and warning messages\n"), out); + fputs(_(" -p, --pagesize SIZE specify page size in bytes\n"), out); + fputs(_(" -L, --label LABEL specify label\n"), out); + fputs(_(" -v, --swapversion NUM specify swap-space version number\n"), out); + fputs(_(" -U, --uuid UUID specify the uuid to use\n"), out); + fprintf(out, + _(" -e, --endianness=<value> specify the endianness to use " + "(%s, %s or %s)\n"), "native", "little", "big"); + fputs(_(" --verbose verbose output\n"), out); + + fprintf(out, + _(" --lock[=<mode>] use exclusive device lock (%s, %s or %s)\n"), "yes", "no", "nonblock"); + + 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; + off_t offset = (off_t) current_page * ctl->pagesize; + + if (do_seek && lseek(ctl->fd, offset, SEEK_SET) != offset) + 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++; + } + + if (!ctl->quiet) + printf(P_("%lu bad page\n", "%lu bad pages\n", ctl->nbadpages), ctl->nbadpages); + free(buffer); +} + + +#ifdef HAVE_LINUX_FIEMAP_H +static void warn_extent(struct mkswap_control *ctl, const char *msg, uint64_t off) +{ + if (ctl->nbad_extents == 0) { + fputc('\n', stderr); + fprintf(stderr, _( + + "mkswap: %s contains holes or other unsupported extents.\n" + " This swap file can be rejected by kernel on swap activation!\n"), + ctl->devname); + + if (ctl->verbose) + fputc('\n', stderr); + else + fprintf(stderr, _( + " Use --verbose for more details.\n")); + + } + if (ctl->verbose) { + fputs(" - ", stderr); + fprintf(stderr, msg, off); + fputc('\n', stderr); + } + ctl->nbad_extents++; +} + +static void check_extents(struct mkswap_control *ctl) +{ + char buf[BUFSIZ] = { 0 }; + struct fiemap *fiemap = (struct fiemap *) buf; + int last = 0; + uint64_t last_logical = 0; + + memset(fiemap, 0, sizeof(struct fiemap)); + + do { + int rc; + size_t n, i; + + fiemap->fm_length = ~0ULL; + fiemap->fm_flags = FIEMAP_FLAG_SYNC; + fiemap->fm_extent_count = + (sizeof(buf) - sizeof(*fiemap)) / sizeof(struct fiemap_extent); + + rc = ioctl(ctl->fd, FS_IOC_FIEMAP, (unsigned long) fiemap); + if (rc < 0) + return; + + n = fiemap->fm_mapped_extents; + if (n == 0) + break; + + for (i = 0; i < n; i++) { + struct fiemap_extent *e = &fiemap->fm_extents[i]; + + if (e->fe_logical > last_logical) + warn_extent(ctl, _("hole detected at offset %ju"), + (uintmax_t) last_logical); + + last_logical = (e->fe_logical + e->fe_length); + + if (e->fe_flags & FIEMAP_EXTENT_LAST) + last = 1; + if (e->fe_flags & FIEMAP_EXTENT_DATA_INLINE) + warn_extent(ctl, _("data inline extent at offset %ju"), + (uintmax_t) e->fe_logical); + if (e->fe_flags & FIEMAP_EXTENT_SHARED) + warn_extent(ctl, _("shared extent at offset %ju"), + (uintmax_t) e->fe_logical); + if (e->fe_flags & FIEMAP_EXTENT_DELALLOC) + warn_extent(ctl, _("unallocated extent at offset %ju"), + (uintmax_t) e->fe_logical); + + if (!ctl->verbose && ctl->nbad_extents) + goto done; + } + fiemap->fm_start = fiemap->fm_extents[n - 1].fe_logical + + fiemap->fm_extents[n - 1].fe_length; + } while (last == 0); + + if (last_logical < (uint64_t) ctl->devstat.st_size) + warn_extent(ctl, _("hole detected at offset %ju"), + (uintmax_t) last_logical); +done: + if (ctl->nbad_extents) + fputc('\n', stderr); +} +#endif /* HAVE_LINUX_FIEMAP_H */ + +/* 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) + err(EXIT_FAILURE, _("cannot determine size of %s"), ctl->devname); + 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 (blkdev_lock(ctl->fd, ctl->devname, ctl->lockmode) != 0) + exit(EXIT_FAILURE); + + if (ctl->check && S_ISREG(ctl->devstat.st_mode)) { + ctl->check = 0; + if (!ctl->quiet) + 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; + const char *v = NULL; +#endif + if (!ctl->force) { + 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 (!ctl->quiet + && 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 if (!ctl->quiet) { + 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, .endianness = ENDIANNESS_NATIVE }; + 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 + enum { + OPT_LOCK = CHAR_MAX + 1, + OPT_VERBOSE + }; + static const struct option longopts[] = { + { "check", no_argument, NULL, 'c' }, + { "force", no_argument, NULL, 'f' }, + { "quiet", no_argument, NULL, 'q' }, + { "pagesize", required_argument, NULL, 'p' }, + { "label", required_argument, NULL, 'L' }, + { "swapversion", required_argument, NULL, 'v' }, + { "uuid", required_argument, NULL, 'U' }, + { "endianness", required_argument, NULL, 'e' }, + { "version", no_argument, NULL, 'V' }, + { "help", no_argument, NULL, 'h' }, + { "lock", optional_argument, NULL, OPT_LOCK }, + { "verbose", no_argument, NULL, OPT_VERBOSE }, + { NULL, 0, NULL, 0 } + }; + + static const ul_excl_t excl[] = { /* rows and cols in ASCII order */ + { 'c', 'q' }, + { 0 } + }; + int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + close_stdout_atexit(); + + while((c = getopt_long(argc, argv, "cfp:qL:v:U:e:Vh", longopts, NULL)) != -1) { + + err_exclusive_options(c, longopts, excl, excl_st); + + 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 'q': + ctl.quiet = 1; + 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 'e': + if (strcmp(optarg, "native") == 0) { + ctl.endianness = ENDIANNESS_NATIVE; + } else if (strcmp(optarg, "little") == 0) { + ctl.endianness = ENDIANNESS_LITTLE; + } else if (strcmp(optarg, "big") == 0) { + ctl.endianness = ENDIANNESS_BIG; + } else { + errx(EXIT_FAILURE, + _("invalid endianness %s is not supported"), optarg); + } + break; + case 'V': + print_version(EXIT_SUCCESS); + break; + case OPT_LOCK: + ctl.lockmode = "1"; + if (optarg) { + if (*optarg == '=') + optarg++; + ctl.lockmode = optarg; + } + break; + case OPT_VERBOSE: + ctl.verbose = 1; + break; + 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 (strcmp(opt_uuid, "clear") == 0) + uuid_clear(uuid_dat); + else if (strcmp(opt_uuid, "random") == 0) + uuid_generate_random(uuid_dat); + else if (strcmp(opt_uuid, "time") == 0) + uuid_generate_time(uuid_dat); + else 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; + if (!ctl.quiet) + 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.quiet && (ctl.devstat.st_mode & permMask) != 0) + warnx(_("%s: insecure permissions %04o, fix with: chmod %04o %s"), + ctl.devname, ctl.devstat.st_mode & 07777, + ~permMask & 0666, ctl.devname); + if (!ctl.quiet + && getuid() == 0 && S_ISREG(ctl.devstat.st_mode) && ctl.devstat.st_uid != 0) + warnx(_("%s: insecure file owner %d, fix with: chown 0:0 %s"), + ctl.devname, ctl.devstat.st_uid, ctl.devname); + + + if (ctl.check) + check_blocks(&ctl); +#ifdef HAVE_LINUX_FIEMAP_H + if (!ctl.quiet && S_ISREG(ctl.devstat.st_mode)) + check_extents(&ctl); +#endif + + wipe_device(&ctl); + + assert(ctl.hdr); + ctl.hdr->version = cpu32_to_endianness(version, ctl.endianness); + ctl.hdr->last_page = cpu32_to_endianness(ctl.npages - 1, ctl.endianness); + ctl.hdr->nr_badpages = cpu32_to_endianness(ctl.nbadpages, ctl.endianness); + + 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); + + if (!ctl.quiet) + 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) { + const char *context_string; + char *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 (ul_selinux_get_default_context(ctl.devname, + ctl.devstat.st_mode, &oldcontext)) + errx(EXIT_FAILURE, + _("%s: unable to obtain default selinux file label"), + ctl.devname); + } + 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..c205a03 --- /dev/null +++ b/disk-utils/partx.8 @@ -0,0 +1,228 @@ +'\" t +.\" Title: partx +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-10-23 +.\" Manual: System Administration +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "PARTX" "8" "2023-10-23" "util\-linux 2.39.3" "System Administration" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +partx \- tell the kernel about the presence and numbering of on\-disk partitions +.SH "SYNOPSIS" +.sp +\fBpartx\fP [\fB\-a\fP|\fB\-d\fP|\fB\-P\fP|\fB\-r\fP|\fB\-s\fP|\fB\-u\fP] [\fB\-t\fP \fItype\fP] [\fB\-n\fP \fIM\fP:_N_] [\-] \fIdisk\fP +.sp +\fBpartx\fP [\fB\-a\fP|\fB\-d\fP|\fB\-P\fP|\fB\-r\fP|\fB\-s\fP|\fB\-u\fP] [\fB\-t\fP \fItype\fP] \fIpartition\fP [\fIdisk\fP] +.SH "DESCRIPTION" +.sp +Given a device or disk\-image, \fBpartx\fP tries to parse the partition table and list its contents. It can also tell the kernel to add or remove partitions from its bookkeeping. +.sp +The \fIdisk\fP argument is optional when a \fIpartition\fP 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 3 +.ll -.6i +.sp +partx \-\-show \- /dev/sda3 +.br +.RE +.ll +.sp +This will see sda3 as a whole\-disk rather than as a partition. +.sp +\fBpartx is not an fdisk program\fP \- 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" +.sp +\fB\-a\fP, \fB\-\-add\fP +.RS 4 +Add the specified partitions, or read the disk and add all partitions. +.RE +.sp +\fB\-b\fP, \fB\-\-bytes\fP +.RS 4 +Print the sizes in bytes rather than in a human\-readable format. +.sp +By default, the unit, sizes are expressed in, is byte, and unit prefixes are in +power of 2^10 (1024). Abbreviations of symbols are exhibited truncated in order +to reach a better readability, by exhibiting alone the first letter of them; +examples: "1 KiB" and "1 MiB" are respectively exhibited as "1 K" and "1 M", +then omitting on purpose the mention "iB", which is part of these abbreviations. +.RE +.sp +\fB\-d\fP, \fB\-\-delete\fP +.RS 4 +Delete the specified partitions or all partitions. It is not error to remove non\-existing partitions, so this option is possible to use together with large \fB\-\-nr\fP ranges without care about the current partitions set on the device. +.RE +.sp +\fB\-g\fP, \fB\-\-noheadings\fP +.RS 4 +Do not print a header line with \fB\-\-show\fP or \fB\-\-raw\fP. +.RE +.sp +\fB\-l\fP, \fB\-\-list\fP +.RS 4 +List the partitions. Note that all numbers are in 512\-byte sectors. This output format is DEPRECATED in favour of \fB\-\-show\fP. Do not use it in newly written scripts. +.RE +.sp +\fB\-n\fP, \fB\-\-nr\fP \fIM\fP\fB:\fP\fIN\fP +.RS 4 +Specify the range of partitions. For backward compatibility also the format \fIM\fP\fB\-\fP\fIN\fP is supported. The range may contain negative numbers, for example \fB\-\-nr \-1:\-1\fP means the last partition, and \fB\-\-nr \-2:\-1\fP means the last two partitions. Supported range specifications are: +.sp +\fIM\fP +.RS 4 +Specifies just one partition (e.g. \fB\-\-nr 3\fP). +.RE +.sp +\fIM\fP\fB:\fP +.RS 4 +Specifies the lower limit only (e.g. \fB\-\-nr 2:\fP). +.RE +.sp +\fB:\fP\fIN\fP +.RS 4 +Specifies the upper limit only (e.g. \fB\-\-nr :4\fP). +.RE +.sp +\fIM\fP\fB:\fP\fIN\fP +.RS 4 +Specifies the lower and upper limits (e.g. \fB\-\-nr 2:4\fP). +.RE +.RE +.sp +\fB\-o\fP, \fB\-\-output\fP \fIlist\fP +.RS 4 +Define the output columns to use for \fB\-\-show\fP, \fB\-\-pairs\fP and \fB\-\-raw\fP output. If no output arrangement is specified, then a default set is used. Use \fB\-\-help\fP to get \fIlist\fP of all supported columns. This option cannot be combined with the \fB\-\-add\fP, \fB\-\-delete\fP, \fB\-\-update\fP or \fB\-\-list\fP options. +.RE +.sp +\fB\-\-output\-all\fP +.RS 4 +Output all available columns. +.RE +.sp +\fB\-P\fP, \fB\-\-pairs\fP +.RS 4 +List the partitions using the KEY="value" format. +.RE +.sp +\fB\-r\fP, \fB\-\-raw\fP +.RS 4 +List the partitions using the raw output format. +.RE +.sp +\fB\-s\fP, \fB\-\-show\fP +.RS 4 +List the partitions. The output columns can be selected and rearranged with the \fB\-\-output\fP option. All numbers (except SIZE) are in 512\-byte sectors. +.RE +.sp +\fB\-t\fP, \fB\-\-type\fP \fItype\fP +.RS 4 +Specify the partition table type. +.RE +.sp +\fB\-\-list\-types\fP +.RS 4 +List supported partition types and exit. +.RE +.sp +\fB\-u\fP, \fB\-\-update\fP +.RS 4 +Update the specified partitions. +.RE +.sp +\fB\-S\fP, \fB\-\-sector\-size\fP \fIsize\fP +.RS 4 +Overwrite default sector size. +.RE +.sp +\fB\-v\fP, \fB\-\-verbose\fP +.RS 4 +Verbose mode. +.RE +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.sp +\fB\-V\fP, \fB\-\-version\fP +.RS 4 +Print version and exit. +.RE +.SH "ENVIRONMENT" +.sp +LIBBLKID_DEBUG=all +.RS 4 +enables libblkid debug output. +.RE +.SH "EXAMPLE" +.sp +partx \-\-show /dev/sdb3, partx \-\-show \-\-nr 3 /dev/sdb, partx \-\-show /dev/sdb3 /dev/sdb +.RS 4 +All three commands list partition 3 of \fI/dev/sdb\fP. +.RE +.sp +partx \-\-show \- /dev/sdb3 +.RS 4 +Lists all subpartitions on \fI/dev/sdb3\fP (the device is used as whole\-disk). +.RE +.sp +partx \-o START \-g \-\-nr 5 /dev/sdb +.RS 4 +Prints the start sector of partition 5 on \fI/dev/sdb\fP without header. +.RE +.sp +partx \-o SECTORS,SIZE /dev/sda5 /dev/sda +.RS 4 +Lists the length in sectors and human\-readable size of partition 5 on \fI/dev/sda\fP. +.RE +.sp +partx \-\-add \-\-nr 3:5 /dev/sdd +.RS 4 +Adds all available partitions from 3 to 5 (inclusive) on \fI/dev/sdd\fP. +.RE +.sp +partx \-d \-\-nr :\-1 /dev/sdd +.RS 4 +Removes the last partition on \fI/dev/sdd\fP. +.RE +.SH "AUTHORS" +.sp +.MTO "dave\(atgnu.org" "Davidlohr Bueso" "," +.MTO "kzak\(atredhat.com" "Karel Zak" "" +.sp +The original version was written by \c +.MTO "aeb\(atcwi.nl" "Andries E. Brouwer" "" +.SH "SEE ALSO" +.sp +\fBaddpart\fP(8), +\fBdelpart\fP(8), +\fBfdisk\fP(8), +\fBparted\fP(8), +\fBpartprobe\fP(8) +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBpartx\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "."
\ No newline at end of file diff --git a/disk-utils/partx.8.adoc b/disk-utils/partx.8.adoc new file mode 100644 index 0000000..bfc0c5a --- /dev/null +++ b/disk-utils/partx.8.adoc @@ -0,0 +1,149 @@ +//po4a: entry man manual +//// +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 +//// += partx(8) +:doctype: manpage +:man manual: System Administration +:man source: util-linux {release-version} +:page-layout: base +:command: partx + +== NAME + +partx - tell the kernel about the presence and numbering of on-disk partitions + +== SYNOPSIS + +*partx* [*-a*|*-d*|*-P*|*-r*|*-s*|*-u*] [*-t* _type_] [*-n* _M_:_N_] [-] _disk_ + +*partx* [*-a*|*-d*|*-P*|*-r*|*-s*|*-u*] [*-t* _type_] _partition_ [_disk_] + +== DESCRIPTION + +Given a device or disk-image, *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. + +The _disk_ argument is optional when a _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: + +____ +partx --show - /dev/sda3 +____ + +This will see sda3 as a whole-disk rather than as a partition. + +*partx is not an fdisk program* - adding and removing partitions does not change the disk, it just tells the kernel about the presence and numbering of on-disk partitions. + +== OPTIONS + +*-a*, *--add*:: +Add the specified partitions, or read the disk and add all partitions. + +*-b*, *--bytes*:: +include::man-common/in-bytes.adoc[] + +*-d*, *--delete*:: +Delete the specified partitions or all partitions. It is not error to remove non-existing partitions, so this option is possible to use together with large *--nr* ranges without care about the current partitions set on the device. + +*-g*, *--noheadings*:: +Do not print a header line with *--show* or *--raw*. + +*-l*, *--list*:: +List the partitions. Note that all numbers are in 512-byte sectors. This output format is DEPRECATED in favour of *--show*. Do not use it in newly written scripts. + +*-n*, *--nr* __M__**:**_N_:: +Specify the range of partitions. For backward compatibility also the format __M__**-**_N_ is supported. The range may contain negative numbers, for example *--nr -1:-1* means the last partition, and *--nr -2:-1* means the last two partitions. Supported range specifications are: ++ +_M_;; +Specifies just one partition (e.g. *--nr 3*). +__M__**:**;; +Specifies the lower limit only (e.g. *--nr 2:*). +**:**__N__;; +Specifies the upper limit only (e.g. *--nr :4*). +__M__**:**_N_;; +Specifies the lower and upper limits (e.g. *--nr 2:4*). + +*-o*, *--output* _list_:: +Define the output columns to use for *--show*, *--pairs* and *--raw* output. If no output arrangement is specified, then a default set is used. Use *--help* to get _list_ of all supported columns. This option cannot be combined with the *--add*, *--delete*, *--update* or *--list* options. + +*--output-all*:: +Output all available columns. + +*-P*, *--pairs*:: +List the partitions using the KEY="value" format. + +*-r*, *--raw*:: +List the partitions using the raw output format. + +*-s*, *--show*:: +List the partitions. The output columns can be selected and rearranged with the *--output* option. All numbers (except SIZE) are in 512-byte sectors. + +*-t*, *--type* _type_:: +Specify the partition table type. + +*--list-types*:: +List supported partition types and exit. + +*-u*, *--update*:: +Update the specified partitions. + +*-S*, *--sector-size* _size_:: +Overwrite default sector size. + +*-v*, *--verbose*:: +Verbose mode. + +include::man-common/help-version.adoc[] + +== ENVIRONMENT + +LIBBLKID_DEBUG=all:: +enables libblkid debug output. + +== EXAMPLE + +partx --show /dev/sdb3:: +partx --show --nr 3 /dev/sdb:: +partx --show /dev/sdb3 /dev/sdb:: +All three commands list partition 3 of _/dev/sdb_. + +partx --show - /dev/sdb3:: +Lists all subpartitions on _/dev/sdb3_ (the device is used as whole-disk). + +partx -o START -g --nr 5 /dev/sdb:: +Prints the start sector of partition 5 on _/dev/sdb_ without header. + +partx -o SECTORS,SIZE /dev/sda5 /dev/sda:: +Lists the length in sectors and human-readable size of partition 5 on _/dev/sda_. + +partx --add --nr 3:5 /dev/sdd:: +Adds all available partitions from 3 to 5 (inclusive) on _/dev/sdd_. + +partx -d --nr :-1 /dev/sdd:: +Removes the last partition on _/dev/sdd_. + +== AUTHORS + +mailto:dave@gnu.org[Davidlohr Bueso], +mailto:kzak@redhat.com[Karel Zak] + +The original version was written by mailto:aeb@cwi.nl[Andries E. Brouwer] + +== SEE ALSO + +*addpart*(8), +*delpart*(8), +*fdisk*(8), +*parted*(8), +*partprobe*(8) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/disk-utils/partx.c b/disk-utils/partx.c new file mode 100644 index 0000000..8078e9c --- /dev/null +++ b/disk-utils/partx.c @@ -0,0 +1,1081 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * 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. + * + * + * 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)) != 0) + 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++) { + if (partx_del_partition(fd, i) == 0) { + if (verbose) + printf(_("%s: partition #%d removed\n"), device, i); + continue; + } + + 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); + close_stdout_atexit(); + + 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': + print_version(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); + free(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); + } + + free(wholedisk); + + 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..a0ad860 --- /dev/null +++ b/disk-utils/raw.8 @@ -0,0 +1,93 @@ +'\" t +.\" Title: raw +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-10-23 +.\" Manual: System Administration +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "RAW" "8" "2023-10-23" "util\-linux 2.39.3" "System Administration" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +raw \- bind a Linux raw character device +.SH "SYNOPSIS" +.sp +\fBraw\fP \fI/dev/raw/raw<N>\fP \fI<major>\fP \fI<minor>\fP +.sp +\fBraw\fP \fI/dev/raw/raw<N>\fP \fI/dev/<blockdev>\fP +.sp +\fBraw\fP \fB\-q\fP \fI/dev/raw/raw<N>\fP +.sp +\fBraw\fP \fB\-qa\fP +.SH "DESCRIPTION" +.sp +\fBraw\fP 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). +.sp +\fBraw\fP is used in two modes: it either sets raw device bindings, or it queries existing bindings. When setting a raw device, \fI/dev/raw/raw<N>\fP 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 \fImajor\fP and \fIminor\fP device numbers, or as a path name \fI/dev/<blockdev>\fP to an existing block device file. +.sp +The bindings already in existence can be queried with the \fB\-q\fP option, which is used either with a raw device filename to query that one device, or with the \fB\-a\fP option to query all bound raw devices. +.sp +Unbinding can be done by specifying major and minor 0. +.sp +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\(cqs 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. +.sp +Because raw I/O involves direct hardware access to a process\(cqs 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" +.sp +\fB\-q\fP, \fB\-\-query\fP +.RS 4 +Set query mode. \fBraw\fP will query an existing binding instead of setting a new one. +.RE +.sp +\fB\-a\fP, \fB\-\-all\fP +.RS 4 +With \fB\-q\fP, specify that all bound raw devices should be queried. +.RE +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.sp +\fB\-V\fP, \fB\-\-version\fP +.RS 4 +Print version and exit. +.RE +.SH "NOTES" +.sp +Rather than using raw devices applications should prefer \fBopen\fP(2) devices, such as \fI/dev/sda1\fP, with the \fBO_DIRECT\fP flag. +.SH "BUGS" +.sp +The Linux \fBdd\fP(1) command should be used without the \fBbs=\fP 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 (\fBEINVAL\fP). +.sp +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 as either a bug or a feature, depending on who you ask! +.SH "AUTHORS" +.sp +.MTO "sct\(atredhat.com" "Stephen Tweedie" "" +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBraw\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "."
\ No newline at end of file diff --git a/disk-utils/raw.8.adoc b/disk-utils/raw.8.adoc new file mode 100644 index 0000000..3c570eb --- /dev/null +++ b/disk-utils/raw.8.adoc @@ -0,0 +1,67 @@ +//po4a: entry man manual += raw(8) +:doctype: manpage +:man manual: System Administration +:man source: util-linux {release-version} +:page-layout: base +:command: raw + +== NAME + +raw - bind a Linux raw character device + +== SYNOPSIS + +*raw* _/dev/raw/raw<N>_ _<major>_ _<minor>_ + +*raw* _/dev/raw/raw<N>_ _/dev/<blockdev>_ + +*raw* *-q* _/dev/raw/raw<N>_ + +*raw* *-qa* + +== DESCRIPTION + +*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). + +*raw* is used in two modes: it either sets raw device bindings, or it queries existing bindings. When setting a raw device, _/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 _major_ and _minor_ device numbers, or as a path name _/dev/<blockdev>_ to an existing block device file. + +The bindings already in existence can be queried with the *-q* option, which is used either with a raw device filename to query that one device, or with the *-a* option to query all bound raw devices. + +Unbinding can be done by specifying major and minor 0. + +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. + +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. + +== OPTIONS + +*-q*, *--query*:: +Set query mode. *raw* will query an existing binding instead of setting a new one. + +*-a*, *--all*:: +With *-q*, specify that all bound raw devices should be queried. + +include::man-common/help-version.adoc[] + +== NOTES + +Rather than using raw devices applications should prefer *open*(2) devices, such as _/dev/sda1_, with the *O_DIRECT* flag. + +== BUGS + +The Linux *dd*(1) command should be used without the *bs=* 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*). + +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 as either a bug or a feature, depending on who you ask! + +== AUTHORS + +mailto:sct@redhat.com[Stephen Tweedie] + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/disk-utils/raw.8.deps b/disk-utils/raw.8.deps new file mode 100644 index 0000000..e78010b --- /dev/null +++ b/disk-utils/raw.8.deps @@ -0,0 +1 @@ +/home/proj-me/util-linux/util-linux/util-linux-2.39.3/disk-utils/raw.8: /home/proj-me/util-linux/util-linux/man-common/help-version.adoc /home/proj-me/util-linux/util-linux/man-common/bugreports.adoc /home/proj-me/util-linux/util-linux/man-common/footer.adoc
\ No newline at end of file diff --git a/disk-utils/raw.c b/disk-utils/raw.c new file mode 100644 index 0000000..b44a581 --- /dev/null +++ b/disk-utils/raw.c @@ -0,0 +1,276 @@ +/* + * 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); + close_stdout_atexit(); + + 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': + print_version(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..f969661 --- /dev/null +++ b/disk-utils/resizepart.8 @@ -0,0 +1,81 @@ +'\" t +.\" Title: resizepart +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-10-23 +.\" Manual: System Administration +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "RESIZEPART" "8" "2023-10-23" "util\-linux 2.39.3" "System Administration" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +resizepart \- tell the kernel about the new size of a partition +.SH "SYNOPSIS" +.sp +\fBresizepart\fP \fIdevice partition length\fP +.SH "DESCRIPTION" +.sp +\fBresizepart\fP tells the Linux kernel about the new size of the specified partition. The command is a simple wrapper around the "resize partition" ioctl. +.sp +This command doesn\(cqt manipulate partitions on a block device. +.SH "PARAMETERS" +.sp +\fIdevice\fP +.RS 4 +The disk device. +.RE +.sp +\fIpartition\fP +.RS 4 +The partition number. +.RE +.sp +\fIlength\fP +.RS 4 +The new length of the partition (in 512\-byte sectors). +.RE +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.sp +\fB\-V\fP, \fB\-\-version\fP +.RS 4 +Print version and exit. +.RE +.SH "SEE ALSO" +.sp +\fBaddpart\fP(8), +\fBdelpart\fP(8), +\fBfdisk\fP(8), +\fBparted\fP(8), +\fBpartprobe\fP(8), +\fBpartx\fP(8) +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBresizepart\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "."
\ No newline at end of file diff --git a/disk-utils/resizepart.8.adoc b/disk-utils/resizepart.8.adoc new file mode 100644 index 0000000..b07680b --- /dev/null +++ b/disk-utils/resizepart.8.adoc @@ -0,0 +1,57 @@ +//po4a: entry man manual +//// +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 +//// += resizepart(8) +:doctype: manpage +:man manual: System Administration +:man source: util-linux {release-version} +:page-layout: base +:command: resizepart + +== NAME + +resizepart - tell the kernel about the new size of a partition + +== SYNOPSIS + +*resizepart* _device partition length_ + +== DESCRIPTION + +*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. + +== PARAMETERS + +_device_:: +The disk device. + +_partition_:: +The partition number. + +_length_:: +The new length of the partition (in 512-byte sectors). + +include::man-common/help-version.adoc[] + +== SEE ALSO + +*addpart*(8), +*delpart*(8), +*fdisk*(8), +*parted*(8), +*partprobe*(8), +*partx*(8) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/disk-utils/resizepart.c b/disk-utils/resizepart.c new file mode 100644 index 0000000..8c34f8a --- /dev/null +++ b/disk-utils/resizepart.c @@ -0,0 +1,128 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * 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. + * + * Copyright (C) 2012 Karel Zak <kzak@redhat.com> + */ +#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); + close_stdout_atexit(); + + while ((c = getopt_long(argc, argv, "Vh", longopts, NULL)) != -1) + switch (c) { + case 'V': + print_version(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..9705eab --- /dev/null +++ b/disk-utils/sfdisk.8 @@ -0,0 +1,692 @@ +'\" t +.\" Title: sfdisk +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-11-21 +.\" Manual: System Administration +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "SFDISK" "8" "2023-11-21" "util\-linux 2.39.3" "System Administration" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +sfdisk \- display or manipulate a disk partition table +.SH "SYNOPSIS" +.sp +\fBsfdisk\fP [options] \fIdevice\fP [\fB\-N\fP \fIpartition\-number\fP] +.sp +\fBsfdisk\fP [options] \fIcommand\fP +.SH "DESCRIPTION" +.sp +\fBsfdisk\fP is a script\-oriented tool for partitioning any block device. It runs in interactive mode if executed on a terminal (stdin refers to a terminal). +.sp +Since version 2.26 \fBsfdisk\fP 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 +\fBsfdisk\fP protects the first disk sector when create a new disk label. The option \fB\-\-wipe always\fP disables this protection. Note that \fBfdisk\fP(8) and \fBcfdisk\fP(8) completely erase this area by default. +.sp +\fBsfdisk\fP (since version 2.26) \fBaligns the start and end of partitions\fP 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. +.sp +The recommended way is not to specify start offsets at all and specify partition size in MiB, GiB (or so). In this case \fBsfdisk\fP aligns 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 \fBsfdisk\fP entirely follows specified numbers without any optimization. +.sp +\fBsfdisk\fP does not create the standard system partitions for SGI and SUN disk labels like \fBfdisk\fP(8) does. It is necessary to explicitly create all partitions including whole\-disk system partitions. +.sp +\fBsfdisk\fP uses \fBBLKRRPART\fP (reread partition table) ioctl to make sure that the device is not used by system or other tools (see also \fB\-\-no\-reread\fP). It\(cqs possible that this feature or another \fBsfdisk\fP activity races with \fBsystemd\-udevd\fP(8). The recommended way how to avoid possible collisions is to use \fB\-\-lock\fP option. The exclusive lock will cause \fBsystemd\-udevd\fP to skip the event handling on the device. +.sp +The \fBsfdisk\fP prompt is only a hint for users and a displayed partition number does not mean that the same partition table entry will be created (if \fB\-N\fP not specified), especially for tables with gaps. +.SH "COMMANDS" +.sp +The commands are mutually exclusive. +.sp +[\fB\-N\fP \fIpartition\-number\fP] \fIdevice\fP +.RS 4 +The default \fBsfdisk\fP command is to read the specification for the desired partitioning of \fIdevice\fP 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\fP starts an interactive session. +.sp +If the option \fB\-N\fP is specified, then the changes are applied to the partition addressed by \fIpartition\-number\fP. The unspecified fields of the partition are not modified. +.sp +Note that it\(cqs possible to address an unused partition with \fB\-N\fP. For example, an MBR always contains 4 partitions, but the number of used partitions may be smaller. In this case \fBsfdisk\fP follows the default values from the partition table and does not use built\-in defaults for the unused partition given with \fB\-N\fP. See also \fB\-\-append\fP. +.RE +.sp +\fB\-A\fP, \fB\-\-activate\fP \fIdevice\fP [\fIpartition\-number\fP...] +.RS 4 +Switch on the bootable flag for the specified partitions and switch off the bootable flag on all unspecified partitions. The special placeholder \*(Aq\-\*(Aq may be used instead of the partition numbers to switch off the bootable flag on all partitions. +.sp +The activation command is supported for MBR and PMBR only. If a GPT label is detected, then \fBsfdisk\fP prints warning and automatically enters PMBR. +.sp +If no \fIpartition\-number\fP is specified, then list the partitions with an enabled flag. +.RE +.sp +\fB\-\-backup\-pt\-sectors\fP \fIdevice\fP +.RS 4 +Back up the current partition table sectors in binary format and exit. See the \fBBACKING UP THE PARTITION TABLE\fP section. +.RE +.sp +\fB\-\-delete\fP \fIdevice\fP [\fIpartition\-number\fP...] +.RS 4 +Delete all or the specified partitions. +.RE +.sp +\fB\-d\fP, \fB\-\-dump\fP \fIdevice\fP +.RS 4 +Dump the partitions of a device in a format that is usable as input to \fBsfdisk\fP. See the \fBBACKING UP THE PARTITION TABLE\fP section. +.RE +.sp +\fB\-g\fP, \fB\-\-show\-geometry\fP [\fIdevice\fP...] +.RS 4 +List the geometry of all or the specified devices. For backward compatibility the deprecated option \fB\-\-show\-pt\-geometry\fP have the same meaning as this one. +.RE +.sp +\fB\-J\fP, \fB\-\-json\fP \fIdevice\fP +.RS 4 +Dump the partitions of a device in JSON format. Note that \fBsfdisk\fP is not able to use JSON as input format. +.RE +.sp +\fB\-l\fP, \fB\-\-list\fP [\fIdevice\fP...] +.RS 4 +List the partitions of all or the specified devices. This command can be used together with \fB\-\-verify\fP. +.RE +.sp +\fB\-F\fP, \fB\-\-list\-free\fP [\fIdevice\fP...] +.RS 4 +List the free unpartitioned areas on all or the specified devices. +.RE +.sp +\fB\-\-part\-attrs\fP \fIdevice partition\-number\fP [\fIattributes\fP] +.RS 4 +Change the GPT partition attribute bits. If \fIattributes\fP is not specified, then print the current partition settings. The \fIattributes\fP argument is a comma\- or space\-delimited list of bits numbers or bit names. For example, the string "RequiredPartition,50,51" sets three bits. The currently supported attribute bits are: +.sp +\fBBit 0 (RequiredPartition)\fP +.RS 4 +If this bit is set, the partition is required for the platform to function. The creator of the partition indicates that deletion or modification of the contents can result in loss of platform features or failure for the platform to boot or operate. The system cannot function normally if this partition is removed, and it should be considered part of the hardware of the system. +.RE +.sp +\fBBit 1 (NoBlockIOProtocol)\fP +.RS 4 +EFI firmware should ignore the content of the partition and not try to read from it. +.RE +.sp +\fBBit 2 (LegacyBIOSBootable)\fP +.RS 4 +The partition may be bootable by legacy BIOS firmware. +.RE +.sp +\fBBits 3\-47\fP +.RS 4 +Undefined and must be zero. Reserved for expansion by future versions of the UEFI specification. +.RE +.sp +\fBBits 48\-63\fP +.RS 4 +Reserved for GUID specific use. The use of these bits will vary depending on the partition type. For example Microsoft uses bit 60 to indicate read\-only, 61 for shadow copy of another partition, 62 for hidden partitions and 63 to disable automount. +.RE +.RE +.sp +\fB\-\-part\-label\fP \fIdevice partition\-number\fP [\fIlabel\fP] +.RS 4 +Change the GPT partition name (label). If \fIlabel\fP is not specified, then print the current partition label. +.RE +.sp +\fB\-\-part\-type\fP \fIdevice partition\-number\fP [\fItype\fP] +.RS 4 +Change the partition type. If \fItype\fP is not specified, then print the current partition type. +.sp +The \fItype\fP argument is hexadecimal for MBR, GUID for GPT, type alias (e.g. "linux") or type shortcut (e.g. \*(AqL\*(Aq). For backward compatibility the options \fB\-c\fP and \fB\-\-id\fP have the same meaning as this one. +.RE +.sp +\fB\-\-part\-uuid\fP \fIdevice partition\-number\fP [\fIuuid\fP] +.RS 4 +Change the GPT partition UUID. If \fIuuid\fP is not specified, then print the current partition UUID. +.RE +.sp +\fB\-\-disk\-id\fP \fIdevice\fP [\fIid\fP] +.RS 4 +Change the disk identifier. If \fIid\fP is not specified, then print the current identifier. The identifier is UUID for GPT or unsigned integer for MBR. +.RE +.sp +\fB\-r\fP, \fB\-\-reorder\fP \fIdevice\fP +.RS 4 +Renumber the partitions, ordering them by their start offset. +.RE +.sp +\fB\-s\fP, \fB\-\-show\-size\fP [\fIdevice\fP...] +.RS 4 +List the sizes of all or the specified devices in units of 1024 byte size. This command is DEPRECATED in favour of \fBblockdev\fP(8). +.RE +.sp +\fB\-T\fP, \fB\-\-list\-types\fP +.RS 4 +Print all supported types for the current disk label or the label specified by \fB\-\-label\fP. +.RE +.sp +\fB\-V\fP, \fB\-\-verify\fP [\fIdevice\fP...] +.RS 4 +Test whether the partition table and partitions seem correct. +.RE +.sp +\fB\-\-relocate\fP \fIoper\fP \fIdevice\fP +.RS 4 +Relocate partition table header. This command is currently supported for GPT header only. The argument \fIoper\fP can be: +.sp +\fBgpt\-bak\-std\fP +.RS 4 +Move GPT backup header to the standard location at the end of the device. +.RE +.sp +\fBgpt\-bak\-mini\fP +.RS 4 +Move GPT backup header behind the last partition. Note that UEFI standard requires the backup header at the end of the device and partitioning tools can automatically relocate the header to follow the standard. +.RE +.RE +.SH "OPTIONS" +.sp +\fB\-a\fP, \fB\-\-append\fP +.RS 4 +Don\(cqt create a new partition table, but only append the specified partitions. +.sp +Note that unused partition maybe be re\-used in this case although it is not the last partition in the partition table. See also \fB\-N\fP to specify entry in the partition table. +.RE +.sp +\fB\-b\fP, \fB\-\-backup\fP +.RS 4 +Back up the current partition table sectors before starting the partitioning. The default backup file name is \fI~/sfdisk\-<device>\-<offset>.bak\fP; to use another name see option \fB\-O\fP, \fB\-\-backup\-file\fP. See section \fBBACKING UP THE PARTITION TABLE\fP for more details. +.RE +.sp +\fB\-\-color\fP[\fB=\fP\fIwhen\fP] +.RS 4 +Colorize the output. The optional argument \fIwhen\fP can be \fBauto\fP, \fBnever\fP or \fBalways\fP. If the \fIwhen\fP argument is omitted, it defaults to \fBauto\fP. The colors can be disabled; for the current built\-in default see the \fB\-\-help\fP output. See also the \fBCOLORS\fP section. +.RE +.sp +\fB\-f\fP, \fB\-\-force\fP +.RS 4 +Disable all consistency checking. +.RE +.sp +\fB\-\-Linux\fP +.RS 4 +Deprecated and ignored option. Partitioning that is compatible with Linux (and other modern operating systems) is the default. +.RE +.sp +\fB\-\-lock\fP[=\fImode\fP] +.RS 4 +Use exclusive BSD lock for device or file it operates. The optional argument \fImode\fP can be \fByes\fP, \fBno\fP (or 1 and 0) or \fBnonblock\fP. If the \fImode\fP argument is omitted, it defaults to \fByes\fP. This option overwrites environment variable \fB$LOCK_BLOCK_DEVICE\fP. The default is not to use any lock at all, but it\(cqs recommended to avoid collisions with \fBsystemd\-udevd\fP(8) or other tools. +.RE +.sp +\fB\-n\fP, \fB\-\-no\-act\fP +.RS 4 +Do everything except writing to the device. +.RE +.sp +\fB\-\-no\-reread\fP +.RS 4 +Do not check through the re\-read\-partition\-table ioctl whether the device is in use. +.RE +.sp +\fB\-\-no\-tell\-kernel\fP +.RS 4 +Don\(cqt tell the kernel about partition changes. This option is recommended together with \fB\-\-no\-reread\fP to modify a partition on used disk. The modified partition should not be used (e.g., mounted). +.RE +.sp +\fB\-O\fP, \fB\-\-backup\-file\fP \fIpath\fP +.RS 4 +Override the default backup file name. Note that the device name and offset are always appended to the file name. +.RE +.sp +\fB\-\-move\-data\fP[\fB=\fP\fIpath\fP] +.RS 4 +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\fP in order to be processed on one specific partition only. +.sp +The optional \fIpath\fP specifies log file name. The log file contains information about all read/write operations on the partition data. The word "@default" as a \fIpath\fP forces \fBsfdisk\fP to use \fI~/sfdisk\-<devname>.move\fP for the log. The log is optional since v2.35. +.sp +Note that this operation is risky and not atomic. \fBDon\(cqt forget to backup your data!\fP +.sp +See also \fB\-\-move\-use\-fsync\fP. +.sp +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 3 +.ll -.6i +.sp +\fBecho \*(Aq+100M,\*(Aq | sfdisk \-\-move\-data /dev/sdc \-N 1\fP +.sp +\fBecho \*(Aq2048,\*(Aq | sfdisk /dev/sdc \-\-append\fP +.sp +\fBsfdisk /dev/sdc \-\-reorder\fP +.br +.RE +.ll +.RE +.sp +\fB\-\-move\-use\-fsync\fP +.RS 4 +Use the \fBfsync\fP(2) system call after each write when moving data to a new location by \fB\-\-move\-data\fP. +.RE +.sp +\fB\-o\fP, \fB\-\-output\fP \fIlist\fP +.RS 4 +Specify which output columns to print. Use \fB\-\-help\fP 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). +.RE +.sp +\fB\-q\fP, \fB\-\-quiet\fP +.RS 4 +Suppress extra info messages. +.RE +.sp +\fB\-u\fP, \fB\-\-unit S\fP +.RS 4 +Deprecated option. Only the sector unit is supported. This option is not supported when using the \fB\-\-show\-size\fP command. +.RE +.sp +\fB\-X\fP, \fB\-\-label\fP \fItype\fP +.RS 4 +Specify the disk label type (e.g., \fBdos\fP, \fBgpt\fP, ...). If this option is not given, then \fBsfdisk\fP defaults to the existing label, but if there is no label on the device yet, then the type defaults to \fBdos\fP. The default or the current label may be overwritten by the "label: <name>" script header line. The option \fB\-\-label\fP does not force \fBsfdisk\fP to create empty disk label (see the \fBEMPTY DISK LABEL\fP section below). +.RE +.sp +\fB\-Y\fP, \fB\-\-label\-nested\fP \fItype\fP +.RS 4 +Force editing of a nested disk label. The primary disk label has to exist already. This option allows editing for example a hybrid/protective MBR on devices with GPT. +.RE +.sp +\fB\-w\fP, \fB\-\-wipe\fP \fIwhen\fP +.RS 4 +Wipe filesystem, RAID and partition\-table signatures from the device, in order to avoid possible collisions. The argument \fIwhen\fP can be \fBauto\fP, \fBnever\fP or \fBalways\fP. When this option is not given, the default is \fBauto\fP, 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\fP is not \fBnever\fP. The \fBauto\fP mode also does not wipe the first sector (boot sector), it is necessary to use the \fBalways\fP mode to wipe this area. In all cases detected signatures are reported by warning messages before a new partition table is created. See also the \fBwipefs\fP(8) command. +.RE +.sp +\fB\-W\fP, \fB\-\-wipe\-partitions\fP \fIwhen\fP +.RS 4 +Wipe filesystem, RAID and partition\-table signatures from a newly created partition, in order to avoid possible collisions. The argument \fIwhen\fP can be \fBauto\fP, \fBnever\fP or \fBalways\fP. When this option is not given, the default is \fBauto\fP, 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 \fBwipefs\fP(8) command. +.RE +.sp +\fB\-v\fP, \fB\-\-version\fP +.RS 4 +Display version information and exit. +.RE +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.SH "INPUT FORMATS" +.sp +\fBsfdisk\fP supports two input formats and generic header lines. +.SS "Header lines" +.sp +The optional header lines specify generic information that apply to the partition table. The header\-line format is: +.sp +\fB<name>: <value>\fP +.sp +The currently recognized headers are: +.sp +\fBunit\fP +.RS 4 +Specify the partitioning unit. The only supported unit is \fBsectors\fP. +.RE +.sp +\fBlabel\fP +.RS 4 +Specify the partition table type. For example \fBdos\fP or \fBgpt\fP. +.RE +.sp +\fBlabel\-id\fP +.RS 4 +Specify the partition table identifier. It should be a hexadecimal number (with a 0x prefix) for MBR and a UUID for GPT. +.RE +.sp +\fBfirst\-lba\fP +.RS 4 +Specify the first usable sector for GPT partitions. This header is ignored if the script and device sector size differ. In this case \fBsfdisk\fP uses label specific default. +.RE +.sp +\fBlast\-lba\fP +.RS 4 +Specify the last usable sector for GPT partitions. This header is ignored if the script and device sector size differ. In this case \fBsfdisk\fP uses label specific default. +.RE +.sp +\fBtable\-length\fP +.RS 4 +Specify the maximal number of GPT partitions. +.RE +.sp +\fBgrain\fP +.RS 4 +Specify minimal size in bytes used to calculate partitions alignment. The default is 1MiB and it\(cqs strongly recommended to use the default. Do not modify this variable if you\(cqre not sure. +.RE +.sp +\fBsector\-size\fP +.RS 4 +Specify sector size. \fBsfdisk\fP always uses device sector size. Since version 2.39 \fBsfdisk\fP recalculates sizes from dump if the script and device sector size differ. +.RE +.sp +Note that it is only possible to use header lines before the first partition is specified in the input. +.SS "Unnamed\-fields format" +.RS 3 +.ll -.6i +.sp +\fIstart size type bootable\fP +.br +.RE +.ll +.sp +where each line fills one partition descriptor. +.sp +Fields are separated by whitespace, comma (recommended) 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 \*(Aq\-\*(Aq a default value is used. But when the \fB\-N\fP option (change a single partition) is given, the default for each field is its previous value. +.sp +The default value of \fIstart\fP is the first non\-assigned sector aligned according to device I/O limits. The default start offset for the first partition is 1 MiB. If the offset is followed by the multiplicative suffixes (KiB, MiB, GiB, TiB, PiB, EiB, ZiB and YiB), then the number is interpreted as offset in bytes. Since v2.38 when the \fB\-N\fP option (change a single partition) is given, a \*(Aq+\*(Aq can be used to enlarge partition by move start of the partition if there is a free space before the partition. +.sp +The default value of \fIsize\fP 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 \*(Aq+\*(Aq can be used instead of a number to enlarge the partition as much as possible. Note \*(Aq+\*(Aq is equivalent to the default behaviour for a new partition; existing partitions will be resized as required. +.sp +The partition \fItype\fP is given in hex for MBR (DOS) where 0x prefix is optional; a GUID string for GPT; a shortcut or an alias. It\(cqs recommended to use two letters for MBR hex codes to avoid collision between deprecated shortcut \*(AqE\*(Aq and \*(Aq0E\*(Aq MBR hex code. For backward compatibility \fBsfdisk\fP tries to interpret \fItype\fP as a shortcut as a first possibility in partitioning scripts although on other places (e.g. \fB\-\-part\-type\fP command) it tries shortcuts as the last possibility. +.sp +Since v2.36 libfdisk supports partition type aliases as extension to shortcuts. The alias is a simple human readable word (e.g. "linux"). +.sp +Since v2.37 libfdisk supports partition type names on input, ignoring the case of the characters and all non\-alphanumeric and non\-digit characters in the name (e.g. "Linux /usr x86" is the same as "linux usr\-x86"). +.sp +Supported shortcuts and aliases: +.sp +\fBL \- alias \*(Aqlinux\*(Aq\fP +.RS 4 +Linux; means 83 for MBR and 0FC63DAF\-8483\-4772\-8E79\-3D69D8477DE4 for GPT. +.RE +.sp +\fBS \- alias \*(Aqswap\*(Aq\fP +.RS 4 +swap area; means 82 for MBR and 0657FD6D\-A4AB\-43C4\-84E5\-0933C84B4F4F for GPT +.RE +.sp +\fBEx \- alias \*(Aqextended\*(Aq\fP +.RS 4 +MBR extended partition; means 05 for MBR. The original shortcut \*(AqE\*(Aq is deprecated due to collision with 0x0E MBR partition type. +.RE +.sp +\fBH \- alias \*(Aqhome\*(Aq\fP +.RS 4 +home partition; means 933AC7E1\-2EB4\-4F13\-B844\-0E14E2AEF915 for GPT +.RE +.sp +\fBU \- alias \*(Aquefi\*(Aq\fP +.RS 4 +EFI System partition, means EF for MBR and C12A7328\-F81F\-11D2\-BA4B\-00A0C93EC93B for GPT +.RE +.sp +\fBR \- alias \*(Aqraid\*(Aq\fP +.RS 4 +Linux RAID; means FD for MBR and A19D880F\-05FC\-4D3B\-A006\-743F0F84911E for GPT +.RE +.sp +\fBV \- alias \*(Aqlvm\*(Aq\fP +.RS 4 +LVM; means 8E for MBR and E6D6D379\-F507\-44C2\-A23C\-238F2A3DF928 for GPT +.RE +.sp +The default \fItype\fP value is \fIlinux\fP. +.sp +The shortcut \*(AqX\*(Aq for Linux extended partition (85) is deprecated in favour of \*(AqEx\*(Aq. +.sp +\fIbootable\fP is specified as [\fB*\fP|\fB\-\fP], with as default not\-bootable. The value of this field is irrelevant for Linux \- when Linux runs it has been booted already \- but it might play a role for certain boot loaders and for other operating systems. +.SS "Named\-fields format" +.sp +This format is more readable, robust, extensible and allows specifying additional information (e.g., a UUID). It is recommended to use this format to keep your scripts more readable. +.RS 3 +.ll -.6i +.sp +[\fIdevice\fP \fB:\fP] \fIname\fP[\fB=\fP\fIvalue\fP], ... +.br +.RE +.ll +.sp +The \fIdevice\fP field is optional. \fBsfdisk\fP extracts the partition number from the device name. It allows specifying the partitions in random order. This functionality is mostly used by \fB\-\-dump\fP. Don\(cqt use it if you are not sure. +.sp +The \fIvalue\fP can be between quotation marks (e.g., name="This is partition name"). The fields \fBstart=\fP and \fBsize=\fP support \*(Aq+\*(Aq and \*(Aq\-\*(Aq in the same way as \fBUnnamed\-fields format\fP. +.sp +The currently supported fields are: +.sp +\fBstart=\fP\fInumber\fP +.RS 4 +The first non\-assigned sector aligned according to device I/O limits. The default start offset for the first partition is 1 MiB. If the offset is followed by the multiplicative suffixes (KiB, MiB, GiB, TiB, PiB, EiB, ZiB and YiB), then the number is interpreted as offset in bytes. +.RE +.sp +\fBsize=\fP\fInumber\fP +.RS 4 +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\(cqs interpreted as size in bytes and the size is aligned according to device I/O limits. +.RE +.sp +\fBbootable\fP +.RS 4 +Mark the partition as bootable. +.RE +.sp +\fBattrs=\fP\fIstring\fP +.RS 4 +Partition attributes, usually GPT partition attribute bits. See \fB\-\-part\-attrs\fP for more details about the GPT\-bits string format. +.RE +.sp +\fBuuid=\fP\fIstring\fP +.RS 4 +GPT partition UUID. +.RE +.sp +\fBname=\fP\fIstring\fP +.RS 4 +GPT partition name. +.RE +.sp +\fBtype=\fP\fIcode\fP +.RS 4 +A hexadecimal number (without 0x) for an MBR partition, a GUID for a GPT partition, a shortcut as for unnamed\-fields format or a type name (e.g. type="Linux /usr (x86)"). See above the section about the unnamed\-fields format for more details. For backward compatibility the \fBId=\fP field has the same meaning. +.RE +.SH "EMPTY DISK LABEL" +.sp +\fBsfdisk\fP 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 3 +.ll -.6i +.sp +\fBecho \*(Aqlabel: gpt\*(Aq | sfdisk /dev/sdb\fP +.br +.RE +.ll +.sp +creates empty GPT partition table. Note that the \fB\-\-append\fP disables this feature. +.SH "BACKING UP THE PARTITION TABLE" +.sp +It is recommended to save the layout of your devices. \fBsfdisk\fP supports two ways. +.SS "Dump in sfdisk compatible format" +.sp +Use the \fB\-\-dump\fP command to save a description of the device layout to a text file. +The dump format is suitable for later \fBsfdisk\fP input. For example: +.RS 3 +.ll -.6i +.sp +\fBsfdisk \-\-dump /dev/sda > sda.dump\fP +.br +.RE +.ll +.sp +This can later be restored by: +.RS 3 +.ll -.6i +.sp +\fBsfdisk /dev/sda < sda.dump\fP +.br +.RE +.ll +.sp +Note that sfdisk completely restores partition types and partition UUIDs. This could potentially become problematic if you duplicate the same layout to different disks, as it may result in duplicate UUIDs within your system. +.SS "Full binary backup" +.sp +If you want to do a full binary backup of all sectors where the partition table is stored, then use the \fB\-\-backup\-pt\-sectors\fP command. It writes the sectors to \fI~/sfdisk\-<device>\-<offset>.bak\fP files. The default name of the backup file can be changed with the \fB\-\-backup\-file\fP option. The backup files contain only raw data from the \fIdevice\fP. For example: +.RS 3 +.ll -.6i +.sp +\fBsfdisk \-\-backup\-pt\-sectors /dev/sda\fP +.br +.RE +.ll +.sp +The GPT header can later be restored by: +.RS 3 +.ll -.6i +.sp +\fBdd if=~/sfdisk\-sda\-0x00000200.bak of=/dev/sda seek=$((0x00000200)) bs=1 conv=notrunc\fP +.br +.RE +.ll +.sp +It\(cqs also possible to use the \fB\-\-backup\fP option to create the same backup immediately after startup for other \fBsfdisk\fP commands. For example, backup partition table before deleting all partitions from partition table: +.RS 3 +.ll -.6i +.sp +\fBsfdisk \-\-backup \-\-delete /dev/sda\fP +.br +.RE +.ll +.sp +The same concept of backup files is used by \fBwipefs\fP(8). +.sp +Note that \fBsfdisk\fP since version 2.26 no longer provides the \fB\-I\fP option to restore sectors. \fBdd\fP(1) provides all necessary functionality. +.SH "COLORS" +.sp +The output colorization is implemented by \fBterminal\-colors.d\fP(5) functionality. +Implicit coloring can be disabled by an empty file +.RS 3 +.ll -.6i +.sp +\fI/etc/terminal\-colors.d/sfdisk.disable\fP +.br +.RE +.ll +.sp +for the \fBsfdisk\fP command or for all tools by +.RS 3 +.ll -.6i +.sp +\fI/etc/terminal\-colors.d/disable\fP +.br +.RE +.ll +.sp +The user\-specific \fI$XDG_CONFIG_HOME/terminal\-colors.d\fP +or \fI$HOME/.config/terminal\-colors.d\fP overrides the global setting. +.sp +Note that the output colorization may be enabled by default, and in this case +\fIterminal\-colors.d\fP directories do not have to exist yet. +.sp +The logical color names supported by \fBsfdisk\fP are: +.sp +\fBheader\fP +.RS 4 +The header of the output tables. +.RE +.sp +\fBwarn\fP +.RS 4 +The warning messages. +.RE +.sp +\fBwelcome\fP +.RS 4 +The welcome message. +.RE +.SH "ENVIRONMENT" +.sp +\fBSFDISK_DEBUG\fP=all +.RS 4 +enables \fBsfdisk\fP debug output. +.RE +.sp +\fBLIBFDISK_DEBUG\fP=all +.RS 4 +enables libfdisk debug output. +.RE +.sp +\fBLIBBLKID_DEBUG\fP=all +.RS 4 +enables libblkid debug output. +.RE +.sp +\fBLIBSMARTCOLS_DEBUG\fP=all +.RS 4 +enables libsmartcols debug output. +.RE +.sp +\fBLOCK_BLOCK_DEVICE\fP=<mode> +.RS 4 +use exclusive BSD lock. The mode is "1" or "0". See \fB\-\-lock\fP for more details. +.RE +.SH "NOTES" +.sp +Since version 2.26 \fBsfdisk\fP no longer provides the \fB\-R\fP or \fB\-\-re\-read\fP option to force the kernel to reread the partition table. Use \fBblockdev \-\-rereadpt\fP instead. +.sp +Since version 2.26 \fBsfdisk\fP does not provide the \fB\-\-DOS\fP, \fB\-\-IBM\fP, \fB\-\-DOS\-extended\fP, \fB\-\-unhide\fP, \fB\-\-show\-extended\fP, \fB\-\-cylinders\fP, \fB\-\-heads\fP, \fB\-\-sectors\fP, \fB\-\-inside\-outer\fP, \fB\-\-not\-inside\-outer\fP options. +.SH "EXAMPLES" +.sp +\fBsfdisk \-\-list \-\-label\-nested=mbr /dev/sda\fP +.RS 4 +Print protective MBR on device with GPT disk label. +.RE +.sp +\fBecho \-e \*(Aq,10M,L\(rsn,10M,L\(rsn,+,\(rsn\*(Aq | sfdisk /dev/sdc\fP +.RS 4 +Create three Linux partitions, with the default start, the size of the first two partitions is 10MiB, and the last partition fills all available space on the device. +.RE +.sp +\fBecho \-e \*(Aqsize=10M, type=L\(rsn size=10M, type=L\(rsn size=+\(rsn\*(Aq | sfdisk /dev/sdc\fP +.RS 4 +The same as the previous example, but in named\-fields format. +.RE +.sp +\fBecho \-e \*(Aqtype=swap\*(Aq | sfdisk \-N 3 /dev/sdc\fP +.RS 4 +Set type of the 3rd partition to \*(Aqswap\*(Aq. +.RE +.sp +\fBsfdisk \-\-part\-type /dev/sdc 3 swap\fP +.RS 4 +The same as the previous example, but without script use. +.RE +.sp +\fBsfdisk \-\-delete /dev/sdc 2\fP +.RS 4 +Delete 2nd partition. +.RE +.sp +\fBecho "+,+" | sfdisk \-N 3 \-\-move\-data /dev/sdc\fP +.RS 4 +Enlarge 3rd partition in both directions, move start to use free space before the partition and enlarge the size to use all free space after to the partition, and move partition data too. +.RE +.SH "AUTHORS" +.sp +.MTO "kzak\(atredhat.com" "Karel Zak" "" +.sp +The current \fBsfdisk\fP implementation is based on the original \fBsfdisk\fP from Andries E. Brouwer. +.SH "SEE ALSO" +.sp +\fBfdisk\fP(8), +\fBcfdisk\fP(8), +\fBparted\fP(8), +\fBpartprobe\fP(8), +\fBpartx\fP(8) +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBsfdisk\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "."
\ No newline at end of file diff --git a/disk-utils/sfdisk.8.adoc b/disk-utils/sfdisk.8.adoc new file mode 100644 index 0000000..f586d61 --- /dev/null +++ b/disk-utils/sfdisk.8.adoc @@ -0,0 +1,469 @@ +//po4a: entry man manual +//// +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. +//// += sfdisk(8) +:doctype: manpage +:man manual: System Administration +:man source: util-linux {release-version} +:page-layout: base +:command: sfdisk +:plus: + +:asterisk: * + +== NAME + +sfdisk - display or manipulate a disk partition table + +== SYNOPSIS + +*sfdisk* [options] _device_ [*-N* _partition-number_] + +*sfdisk* [options] _command_ + +== DESCRIPTION + +*sfdisk* is a script-oriented tool for partitioning any block device. It runs in interactive mode if executed on a terminal (stdin refers to a terminal). + +Since version 2.26 *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. + +*sfdisk* protects the first disk sector when create a new disk label. The option *--wipe always* disables this protection. Note that *fdisk*(8) and *cfdisk*(8) completely erase this area by default. + +*sfdisk* (since version 2.26) *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* aligns 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. + +*sfdisk* does not create the standard system partitions for SGI and SUN disk labels like *fdisk*(8) does. It is necessary to explicitly create all partitions including whole-disk system partitions. + +*sfdisk* uses *BLKRRPART* (reread partition table) ioctl to make sure that the device is not used by system or other tools (see also *--no-reread*). It's possible that this feature or another *sfdisk* activity races with *systemd-udevd*(8). The recommended way how to avoid possible collisions is to use *--lock* option. The exclusive lock will cause *systemd-udevd* to skip the event handling on the device. + +The *sfdisk* prompt is only a hint for users and a displayed partition number does not mean that the same partition table entry will be created (if *-N* not specified), especially for tables with gaps. + +== COMMANDS + +The commands are mutually exclusive. + +[*-N* _partition-number_] __device__:: +The default *sfdisk* command is to read the specification for the desired partitioning of _device_ 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 *sfdisk* starts an interactive session. ++ +If the option *-N* is specified, then the changes are applied to the partition addressed by _partition-number_. The unspecified fields of the partition are not modified. ++ +Note that it's possible to address an unused partition with *-N*. For example, an MBR always contains 4 partitions, but the number of used partitions may be smaller. In this case *sfdisk* follows the default values from the partition table and does not use built-in defaults for the unused partition given with *-N*. See also *--append*. + +*-A*, *--activate* __device__ [__partition-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 a GPT label is detected, then *sfdisk* prints warning and automatically enters PMBR. ++ +If no _partition-number_ is specified, then list the partitions with an enabled flag. + +*--backup-pt-sectors* _device_:: +Back up the current partition table sectors in binary format and exit. See the *BACKING UP THE PARTITION TABLE* section. + +*--delete* _device_ [__partition-number__...]:: +Delete all or the specified partitions. + +*-d*, *--dump* _device_:: +Dump the partitions of a device in a format that is usable as input to *sfdisk*. See the *BACKING UP THE PARTITION TABLE* section. + +*-g*, *--show-geometry* [__device__...]:: +List the geometry of all or the specified devices. For backward compatibility the deprecated option *--show-pt-geometry* have the same meaning as this one. + +*-J*, *--json* _device_:: +Dump the partitions of a device in JSON format. Note that *sfdisk* is not able to use JSON as input format. + +*-l*, *--list* [__device__...]:: +List the partitions of all or the specified devices. This command can be used together with *--verify*. + +*-F*, *--list-free* [__device__...]:: +List the free unpartitioned areas on all or the specified devices. + +*--part-attrs* _device partition-number_ [__attributes__]:: +Change the GPT partition attribute bits. If _attributes_ is not specified, then print the current partition settings. The _attributes_ argument is a comma- or space-delimited list of bits numbers or bit names. For example, the string "RequiredPartition,50,51" sets three bits. The currently supported attribute bits are: ++ +*Bit 0 (RequiredPartition)*;; +If this bit is set, the partition is required for the platform to function. The creator of the partition indicates that deletion or modification of the contents can result in loss of platform features or failure for the platform to boot or operate. The system cannot function normally if this partition is removed, and it should be considered part of the hardware of the system. +*Bit 1 (NoBlockIOProtocol)*;; +EFI firmware should ignore the content of the partition and not try to read from it. +*Bit 2 (LegacyBIOSBootable)*;; +The partition may be bootable by legacy BIOS firmware. +*Bits 3-47*;; +Undefined and must be zero. Reserved for expansion by future versions of the UEFI specification. +*Bits 48-63*;; +Reserved for GUID specific use. The use of these bits will vary depending on the partition type. For example Microsoft uses bit 60 to indicate read-only, 61 for shadow copy of another partition, 62 for hidden partitions and 63 to disable automount. + +*--part-label* _device partition-number_ [__label__]:: +Change the GPT partition name (label). If _label_ is not specified, then print the current partition label. + +*--part-type* _device partition-number_ [__type__]:: +Change the partition type. If _type_ is not specified, then print the current partition type. ++ +The _type_ argument is hexadecimal for MBR, GUID for GPT, type alias (e.g. "linux") or type shortcut (e.g. 'L'). For backward compatibility the options *-c* and *--id* have the same meaning as this one. + +*--part-uuid* _device partition-number_ [__uuid__]:: +Change the GPT partition UUID. If _uuid_ is not specified, then print the current partition UUID. + +*--disk-id* _device_ [__id__]:: +Change the disk identifier. If _id_ is not specified, then print the current identifier. The identifier is UUID for GPT or unsigned integer for MBR. + +*-r*, *--reorder* _device_:: +Renumber the partitions, ordering them by their start offset. + +*-s*, *--show-size* [__device__...]:: +List the sizes of all or the specified devices in units of 1024 byte size. This command is DEPRECATED in favour of *blockdev*(8). + +*-T*, *--list-types*:: +Print all supported types for the current disk label or the label specified by *--label*. + +*-V*, *--verify* [__device__...]:: +Test whether the partition table and partitions seem correct. + +*--relocate* _oper_ _device_:: +Relocate partition table header. This command is currently supported for GPT header only. The argument _oper_ can be: ++ +*gpt-bak-std*;; +Move GPT backup header to the standard location at the end of the device. +*gpt-bak-mini*;; +Move GPT backup header behind the last partition. Note that UEFI standard requires the backup header at the end of the device and partitioning tools can automatically relocate the header to follow the standard. + +== OPTIONS + +*-a*, *--append*:: +Don't create a new partition table, but only append the specified partitions. ++ +Note that unused partition maybe be re-used in this case although it is not the last partition in the partition table. See also *-N* to specify entry in the partition table. + +*-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 *-O*, *--backup-file*. See section *BACKING UP THE PARTITION TABLE* for more details. + +*--color*[**=**__when__]:: +Colorize the output. The optional argument _when_ can be *auto*, *never* or *always*. If the _when_ argument is omitted, it defaults to *auto*. The colors can be disabled; for the current built-in default see the *--help* output. See also the *COLORS* section. + +*-f*, *--force*:: +Disable all consistency checking. + +*--Linux*:: +Deprecated and ignored option. Partitioning that is compatible with Linux (and other modern operating systems) is the default. + +*--lock*[=_mode_]:: +Use exclusive BSD lock for device or file it operates. The optional argument _mode_ can be *yes*, *no* (or 1 and 0) or *nonblock*. If the _mode_ argument is omitted, it defaults to *yes*. This option overwrites environment variable *$LOCK_BLOCK_DEVICE*. The default is not to use any lock at all, but it's recommended to avoid collisions with *systemd-udevd*(8) or other tools. + +*-n*, *--no-act*:: +Do everything except writing to the device. + +*--no-reread*:: +Do not check through the re-read-partition-table ioctl whether the device is in use. + +*--no-tell-kernel*:: +Don't tell the kernel about partition changes. This option is recommended together with *--no-reread* to modify a partition on used disk. The modified partition should not be used (e.g., mounted). + +*-O*, *--backup-file* _path_:: +Override the default backup file name. Note that the device name and offset are always appended to the file name. + +*--move-data*[**=**__path__]:: +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 *-N* in order to be processed on one specific partition only. ++ +The optional _path_ specifies log file name. The log file contains information about all read/write operations on the partition data. The word "@default" as a _path_ forces *sfdisk* to use _~/sfdisk-<devname>.move_ for the log. The log is optional since v2.35. ++ +Note that this operation is risky and not atomic. *Don't forget to backup your data!* ++ +See also *--move-use-fsync*. ++ +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). ++ +____ +*echo '+100M,' | sfdisk --move-data /dev/sdc -N 1* + +*echo '2048,' | sfdisk /dev/sdc --append* + +*sfdisk /dev/sdc --reorder* +____ + +*--move-use-fsync*:: +Use the *fsync*(2) system call after each write when moving data to a new location by *--move-data*. + +*-o*, *--output* _list_:: +Specify which output columns to print. Use *--help* to get a list of all supported columns. ++ +The default list of columns may be extended if _list_ is specified in the format _{plus}list_ (e.g., *-o +UUID*). +//TRANSLATORS: Keep {plus} untranslated. + +*-q*, *--quiet*:: +Suppress extra info messages. + +*-u*, *--unit S*:: +Deprecated option. Only the sector unit is supported. This option is not supported when using the *--show-size* command. + +*-X*, *--label* _type_:: +Specify the disk label type (e.g., *dos*, *gpt*, ...). If this option is not given, then *sfdisk* defaults to the existing label, but if there is no label on the device yet, then the type defaults to *dos*. The default or the current label may be overwritten by the "label: <name>" script header line. The option *--label* does not force *sfdisk* to create empty disk label (see the *EMPTY DISK LABEL* section below). + +*-Y*, *--label-nested* _type_:: +Force editing of a nested disk label. The primary disk label has to exist already. This option allows editing for example a hybrid/protective MBR on devices with GPT. + +*-w*, *--wipe* _when_:: +Wipe filesystem, RAID and partition-table signatures from the device, in order to avoid possible collisions. The argument _when_ can be *auto*, *never* or *always*. When this option is not given, the default is *auto*, 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 _when_ is not *never*. The *auto* mode also does not wipe the first sector (boot sector), it is necessary to use the *always* mode to wipe this area. In all cases detected signatures are reported by warning messages before a new partition table is created. See also the *wipefs*(8) command. + +*-W*, *--wipe-partitions* _when_:: +Wipe filesystem, RAID and partition-table signatures from a newly created partition, in order to avoid possible collisions. The argument _when_ can be *auto*, *never* or *always*. When this option is not given, the default is *auto*, 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 *wipefs*(8) command. + +*-v*, *--version*:: +Display version information and exit. + +*-h*, *--help*:: +Display help text and exit. + +== INPUT FORMATS + +*sfdisk* supports two input formats and generic header lines. + +=== Header lines + +The optional header lines specify generic information that apply to the partition table. The header-line format is: + +*<name>: <value>* + +The currently recognized headers are: + +*unit*:: +Specify the partitioning unit. The only supported unit is *sectors*. +*label*:: +Specify the partition table type. For example *dos* or *gpt*. +*label-id*:: +Specify the partition table identifier. It should be a hexadecimal number (with a 0x prefix) for MBR and a UUID for GPT. +*first-lba*:: +Specify the first usable sector for GPT partitions. This header is ignored if the script and device sector size differ. In this case *sfdisk* uses label specific default. +*last-lba*:: +Specify the last usable sector for GPT partitions. This header is ignored if the script and device sector size differ. In this case *sfdisk* uses label specific default. +*table-length*:: +Specify the maximal number of GPT partitions. +*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. +*sector-size*:: +Specify sector size. *sfdisk* always uses device sector size. Since version 2.39 *sfdisk* recalculates sizes from dump if the script and device sector size differ. + +Note that it is only possible to use header lines before the first partition is specified in the input. + +=== Unnamed-fields format + +____ +_start size type bootable_ +____ + +where each line fills one partition descriptor. + +Fields are separated by whitespace, comma (recommended) 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 *-N* option (change a single partition) is given, the default for each field is its previous value. + +The default value of _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. If the offset is followed by the multiplicative suffixes (KiB, MiB, GiB, TiB, PiB, EiB, ZiB and YiB), then the number is interpreted as offset in bytes. Since v2.38 when the *-N* option (change a single partition) is given, a '{plus}' can be used to enlarge partition by move start of the partition if there is a free space before the partition. + +//TRANSLATORS: Keep {plus} untranslated. +The default value of _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 '{plus}' can be used instead of a number to enlarge the partition as much as possible. Note '{plus}' is equivalent to the default behaviour for a new partition; existing partitions will be resized as required. + +The partition _type_ is given in hex for MBR (DOS) where 0x prefix is optional; a GUID string for GPT; a shortcut or an alias. It's recommended to use two letters for MBR hex codes to avoid collision between deprecated shortcut 'E' and '0E' MBR hex code. For backward compatibility *sfdisk* tries to interpret _type_ as a shortcut as a first possibility in partitioning scripts although on other places (e.g. *--part-type* command) it tries shortcuts as the last possibility. + +Since v2.36 libfdisk supports partition type aliases as extension to shortcuts. The alias is a simple human readable word (e.g. "linux"). + +Since v2.37 libfdisk supports partition type names on input, ignoring the case of the characters and all non-alphanumeric and non-digit characters in the name (e.g. "Linux /usr x86" is the same as "linux usr-x86"). + +Supported shortcuts and aliases: + +*L - alias 'linux'*:: +Linux; means 83 for MBR and 0FC63DAF-8483-4772-8E79-3D69D8477DE4 for GPT. + +*S - alias 'swap'*:: +swap area; means 82 for MBR and 0657FD6D-A4AB-43C4-84E5-0933C84B4F4F for GPT + +*Ex - alias 'extended'*:: +MBR extended partition; means 05 for MBR. The original shortcut 'E' is deprecated due to collision with 0x0E MBR partition type. + +*H - alias 'home'*:: +home partition; means 933AC7E1-2EB4-4F13-B844-0E14E2AEF915 for GPT + +*U - alias 'uefi'*:: +EFI System partition, means EF for MBR and C12A7328-F81F-11D2-BA4B-00A0C93EC93B for GPT + +*R - alias 'raid'*:: +Linux RAID; means FD for MBR and A19D880F-05FC-4D3B-A006-743F0F84911E for GPT + +*V - alias 'lvm'*:: +LVM; means 8E for MBR and E6D6D379-F507-44C2-A23C-238F2A3DF928 for GPT + +The default _type_ value is _linux_. + +The shortcut 'X' for Linux extended partition (85) is deprecated in favour of 'Ex'. + +// TRANSLATORS: Keep {asterisk} untranslated. +_bootable_ is specified as [*{asterisk}*|*-*], with as default not-bootable. The value of this field is irrelevant for Linux - when Linux runs it has been booted already - but it might play a role for certain boot loaders and for other operating systems. + +=== Named-fields format + +This format is more readable, robust, extensible and allows specifying additional information (e.g., a UUID). It is recommended to use this format to keep your scripts more readable. + +____ +[_device_ *:*] _name_[**=**__value__], ... +____ + +The _device_ field is optional. *sfdisk* extracts the partition number from the device name. It allows specifying the partitions in random order. This functionality is mostly used by *--dump*. Don't use it if you are not sure. + +The _value_ can be between quotation marks (e.g., name="This is partition name"). The fields *start=* and *size=* support '{plus}' and '-' in the same way as *Unnamed-fields format*. + +The currently supported fields are: + +**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. If the offset is followed by the multiplicative suffixes (KiB, MiB, GiB, TiB, PiB, EiB, ZiB and YiB), then the number is interpreted as offset in bytes. + +**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. + +*bootable*:: +Mark the partition as bootable. + +**attrs=**__string__:: +Partition attributes, usually GPT partition attribute bits. See *--part-attrs* for more details about the GPT-bits string format. + +**uuid=**__string__:: +GPT partition UUID. + +**name=**__string__:: +GPT partition name. + +**type=**__code__:: +A hexadecimal number (without 0x) for an MBR partition, a GUID for a GPT partition, a shortcut as for unnamed-fields format or a type name (e.g. type="Linux /usr (x86)"). See above the section about the unnamed-fields format for more details. For backward compatibility the *Id=* field has the same meaning. + +== EMPTY DISK LABEL + +*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: + +____ +*echo 'label: gpt' | sfdisk /dev/sdb* +____ + +creates empty GPT partition table. Note that the *--append* disables this feature. + +== BACKING UP THE PARTITION TABLE + +It is recommended to save the layout of your devices. *sfdisk* supports two ways. + +=== Dump in sfdisk compatible format + +Use the *--dump* command to save a description of the device layout to a text file. +The dump format is suitable for later *sfdisk* input. For example: +____ +*sfdisk --dump /dev/sda > sda.dump* +____ + +This can later be restored by: +____ +*sfdisk /dev/sda < sda.dump* +____ + +Note that sfdisk completely restores partition types and partition UUIDs. This could potentially become problematic if you duplicate the same layout to different disks, as it may result in duplicate UUIDs within your system. + +=== Full binary backup + +If you want to do a full binary backup of all sectors where the partition table is stored, then use the *--backup-pt-sectors* command. It writes the sectors to _~/sfdisk-<device>-<offset>.bak_ files. The default name of the backup file can be changed with the *--backup-file* option. The backup files contain only raw data from the _device_. For example: + +____ +*sfdisk --backup-pt-sectors /dev/sda* +____ + +The GPT header can later be restored by: + +____ +*dd if=~/sfdisk-sda-0x00000200.bak of=/dev/sda seek=$\((0x00000200)) bs=1 conv=notrunc* +____ + + +It's also possible to use the *--backup* option to create the same backup immediately after startup for other *sfdisk* commands. For example, backup partition table before deleting all partitions from partition table: +____ +*sfdisk --backup --delete /dev/sda* +____ + + +The same concept of backup files is used by *wipefs*(8). + +Note that *sfdisk* since version 2.26 no longer provides the *-I* option to restore sectors. *dd*(1) provides all necessary functionality. + +include::man-common/colors.adoc[] + +The logical color names supported by *sfdisk* are: + +*header*:: +The header of the output tables. +*warn*:: +The warning messages. +*welcome*:: +The welcome message. + +== ENVIRONMENT + +*SFDISK_DEBUG*=all:: +enables *sfdisk* debug output. +*LIBFDISK_DEBUG*=all:: +enables libfdisk debug output. +*LIBBLKID_DEBUG*=all:: +enables libblkid debug output. +*LIBSMARTCOLS_DEBUG*=all:: +enables libsmartcols debug output. +*LOCK_BLOCK_DEVICE*=<mode>:: +use exclusive BSD lock. The mode is "1" or "0". See *--lock* for more details. + +== NOTES + +Since version 2.26 *sfdisk* no longer provides the *-R* or *--re-read* option to force the kernel to reread the partition table. Use *blockdev --rereadpt* instead. + +Since version 2.26 *sfdisk* does not provide the *--DOS*, *--IBM*, *--DOS-extended*, *--unhide*, *--show-extended*, *--cylinders*, *--heads*, *--sectors*, *--inside-outer*, *--not-inside-outer* options. + +== EXAMPLES + +*sfdisk --list --label-nested=mbr /dev/sda*:: +Print protective MBR on device with GPT disk label. + +*echo -e ',10M,L\n,10M,L\n,+,\n' | sfdisk /dev/sdc*:: +Create three Linux partitions, with the default start, the size of the first two partitions is 10MiB, and the last partition fills all available space on the device. + +*echo -e 'size=10M, type=L\n size=10M, type=L\n size=+\n' | sfdisk /dev/sdc*:: +The same as the previous example, but in named-fields format. + +*echo -e 'type=swap' | sfdisk -N 3 /dev/sdc*:: +Set type of the 3rd partition to 'swap'. + +*sfdisk --part-type /dev/sdc 3 swap*:: +The same as the previous example, but without script use. + +*sfdisk --delete /dev/sdc 2*:: +Delete 2nd partition. + +*echo "\+,+" | sfdisk -N 3 --move-data /dev/sdc*:: +Enlarge 3rd partition in both directions, move start to use free space before the partition and enlarge the size to use all free space after to the partition, and move partition data too. + +== AUTHORS + +mailto:kzak@redhat.com[Karel Zak] + +The current *sfdisk* implementation is based on the original *sfdisk* from Andries E. Brouwer. + +== SEE ALSO + +*fdisk*(8), +*cfdisk*(8), +*parted*(8), +*partprobe*(8), +*partx*(8) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/disk-utils/sfdisk.c b/disk-utils/sfdisk.c new file mode 100644 index 0000000..0e85e63 --- /dev/null +++ b/disk-utils/sfdisk.c @@ -0,0 +1,2477 @@ +/* + * 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 <sys/time.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 "ttyutils.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_RELOCATE, + ACT_SHOW_SIZE, + ACT_SHOW_GEOM, + ACT_VERIFY, + ACT_PARTTYPE, + ACT_PARTUUID, + ACT_PARTLABEL, + ACT_PARTATTRS, + ACT_DISKID, + ACT_DELETE, + ACT_BACKUP_SECTORS, +}; + +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 *lockmode; /* as specified by --lock */ + 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 */ + unused : 1, /* PT contains unused partition */ + append : 1, /* don't create new PT, append partitions only */ + json : 1, /* JSON dump */ + movedata: 1, /* move data after resize */ + movefsync: 1, /* use fsync() after each write() */ + 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; + xstrncpy(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] = { '\0' }; + 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); + + if (sf->wipemode != WIPEMODE_ALWAYS) + 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 || strcmp(filename, "@default") == 0) { + 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 assign_device(struct sfdisk *sf, const char *devname, int rdonly) +{ + struct fdisk_context *cxt = sf->cxt; + + if (fdisk_assign_device(cxt, devname, rdonly) != 0) + err(EXIT_FAILURE, _("cannot open %s"), devname); + + if (!fdisk_is_readonly(cxt)) { + if (blkdev_lock(fdisk_get_devfd(cxt), devname, sf->lockmode) != 0) { + fdisk_deassign_device(cxt, 1); + exit(EXIT_FAILURE); + } + if (sf->backup) + backup_partition_table(sf, devname); + } + return 0; +} + + +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, prev; + size_t io, ss, step_bytes, cc, ioerr = 0; + uintmax_t src, dst, nbytes; + int progress = 0, rc = 0; + struct timeval prev_time; + uint64_t bytes_per_sec = 0; + + 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); + + /* set move direction and overlay */ + 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")); + } + + /* set optimal step size -- nearest to 1MiB aligned to optimal I/O */ + io = fdisk_get_optimal_iosize(sf->cxt); + ss = fdisk_get_sector_size(sf->cxt); + if (!io) + io = ss; + if (io < 1024 * 1024) + step_bytes = ((1024 * 1024) + io/2) / io * io; + else + step_bytes = io; + + step = step_bytes / ss; + nbytes = nsectors * 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) + ignore_result( posix_fadvise(fd, from * ss, + nsectors * ss, POSIX_FADV_SEQUENTIAL) ); +#endif + devname = fdisk_partname(fdisk_get_devname(sf->cxt), partno+1); + if (sf->move_typescript) + typescript = mk_backup_filename_tpl(sf->move_typescript, devname, ".move"); + + if (!sf->quiet) { + fdisk_info(sf->cxt, "%s", ""); + color_scheme_enable("header", UL_COLOR_BOLD); + fdisk_info(sf->cxt, sf->noact ? _("Data move: (--no-act)") : _("Data move:")); + color_disable(); + if (typescript) + fdisk_info(sf->cxt, _(" typescript file: %s"), typescript); + printf(_(" start sector: (from/to) %ju / %ju\n"), (uintmax_t) from, (uintmax_t) to); + printf(_(" sectors: %ju\n"), (uintmax_t) nsectors); + printf(_(" step size: %zu bytes\n"), step_bytes); + putchar('\n'); + fflush(stdout); + + if (isatty(fileno(stdout))) + progress = 1; + } + + 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.")); + free(devname); + return 0; + } + } + + if (typescript) { + f = fopen(typescript, "w"); + if (!f) { + rc = -errno; + fdisk_warn(sf->cxt, _("cannot open %s"), typescript); + goto done; + } + + /* 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, "# Sector size: %zu\n", ss); + 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, "# Step size (sectors/bytes): %" PRIu64 "/%zu\n", step, step_bytes); + fprintf(f, "# Steps: %ju\n", ((uintmax_t) nsectors / step) + 1); + 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)); + + gettimeofday(&prev_time, NULL); + prev = 0; + + for (cc = 1, i = 0; i < nsectors && nbytes > 0; i += step, cc++) { + + if (nbytes < step_bytes) { + DBG(MISC, ul_debug("aligning step #%05zu from %zu to %ju", + cc, step_bytes, nbytes)); + step_bytes = nbytes; + } + nbytes -= step_bytes; + + if (backward) + src -= step_bytes, dst -= step_bytes; + + DBG(MISC, ul_debug("#%05zu: src=%ju dst=%ju", cc, src, dst)); + + if (!sf->noact) { + /* read source */ + if (lseek(fd, src, SEEK_SET) == (off_t) -1 || + read_all(fd, buf, step_bytes) != (ssize_t) step_bytes) { + if (f) + fprintf(f, "%05zu: read error %12ju %12ju\n", cc, src, dst); + fdisk_warn(sf->cxt, + _("cannot read at offset: %ju; continue"), src); + ioerr++; + goto next; + } + + /* write target */ + if (lseek(fd, dst, SEEK_SET) == (off_t) -1 || + write_all(fd, buf, step_bytes) != 0) { + if (f) + fprintf(f, "%05zu: write error %12ju %12ju\n", cc, src, dst); + fdisk_warn(sf->cxt, + _("cannot write at offset: %ju; continue"), dst); + ioerr++; + goto next; + } + if (sf->movefsync && fsync(fd) != 0) + fdisk_warn(sf->cxt, + _("cannot fsync at offset: %ju; continue"), dst); + } + + /* write log */ + if (f) + fprintf(f, "%05zu: %12ju %12ju\n", cc, src, dst); + + if (progress && i % 10 == 0) { + unsigned int elapsed = 0; /* usec */ + struct timeval cur_time; + + gettimeofday(&cur_time, NULL); + if (cur_time.tv_sec - prev_time.tv_sec > 1) { + elapsed = ((cur_time.tv_sec - prev_time.tv_sec) * 1000000) + + (cur_time.tv_usec - prev_time.tv_usec); + + bytes_per_sec = ((i - prev) * ss) / elapsed; /* per usec */ + bytes_per_sec *= 1000000; /* per sec */ + + prev_time = cur_time; + prev = i; + } + + if (bytes_per_sec) + fprintf(stdout, _("Moved %ju from %ju sectors (%.3f%%, %.1f MiB/s)."), + i + 1, nsectors, + 100.0 / ((double) nsectors/(i+1)), + (double) (bytes_per_sec / (1024 * 1024))); + else + fprintf(stdout, _("Moved %ju from %ju sectors (%.3f%%)."), + i + 1, nsectors, + 100.0 / ((double) nsectors/(i+1))); + fflush(stdout); + fputc('\r', stdout); + + } +next: + if (!backward) + src += step_bytes, dst += step_bytes; + } + + if (progress && nsectors) { + int x = get_terminal_width(80); + for (; x > 0; x--) + fputc(' ', stdout); + fflush(stdout); + fputc('\r', stdout); + + if (i > nsectors) + /* see for() above; @i has to be greater than @nsectors + * on success due to i += step */ + i = nsectors; + + fprintf(stdout, _("Moved %ju from %ju sectors (%.0f%%)."), + i, nsectors, + 100.0 / ((double) nsectors/(i+1))); + fputc('\n', stdout); + } + rc = 0; +done: + if (f) + fclose(f); + free(buf); + free(typescript); + + if (sf->noact) + fdisk_info(sf->cxt, _("Your data has not been moved (--no-act).")); + if (ioerr) { + fdisk_info(sf->cxt, _("%zu I/O errors detected!"), ioerr); + rc = -EIO; + } else if (rc) + warn(_("%s: failed to move data"), devname); + + free(devname); + + return rc; +} + +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 (!sf->noact && !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; + + for (i = 0; i < argc; i++) + if (print_device_pt(sf->cxt, argv[i], 1, sf->verify, i) != 0) + fail++; + } 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; + + for (i = 0; i < argc; i++) + if (print_device_freespace(sf->cxt, argv[i], 1, i) != 0) + fail++; + } 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); + + assign_device(sf, devname, 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); + + assign_device(sf, devname, 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; + + assign_device(sf, devname, listonly); + + 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")); + + 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]; + + assign_device(sf, devname, 0); + + /* 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")); + + assign_device(sf, devname, 0); /* read-write */ + + 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")); + + assign_device(sf, devname, 1); /* read-only */ + + 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; +} + +/* + * sfdisk --backup-pt-sectors <device> + */ +static int command_backup_sectors(struct sfdisk *sf, int argc, char **argv) +{ + const char *devname = NULL; + + if (argc) + devname = argv[0]; + if (!devname) + errx(EXIT_FAILURE, _("no disk device specified")); + + assign_device(sf, devname, 1); /* read-only */ + + if (!fdisk_has_label(sf->cxt)) + errx(EXIT_FAILURE, _("%s: does not contain a recognized partition table"), devname); + + backup_partition_table(sf, devname); + + 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); + + if (!fdisk_is_readonly(sf->cxt) + && blkdev_lock(fdisk_get_devfd(sf->cxt), devname, sf->lockmode) != 0) { + fdisk_deassign_device(sf->cxt, 1); + return; + } + 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_advparse_parttype(lb, typestr, + FDISK_PARTTYPE_PARSE_DATA + | FDISK_PARTTYPE_PARSE_ALIAS + | FDISK_PARTTYPE_PARSE_NAME + | FDISK_PARTTYPE_PARSE_SHORTCUT); + 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); +} + +/* + * sfdisk --disk-id <device> [<str>] + */ +static int command_diskid(struct sfdisk *sf, int argc, char **argv) +{ + const char *devname = NULL; + char *str = NULL; + + if (!argc) + errx(EXIT_FAILURE, _("no disk device specified")); + devname = argv[0]; + + if (argc == 2) + str = argv[1]; + else if (argc > 2) + errx(EXIT_FAILURE, _("unexpected arguments")); + + assign_device(sf, devname, !str); + + /* print */ + if (!str) { + fdisk_get_disklabel_id(sf->cxt, &str); + if (str) + printf("%s\n", str); + free(str); + fdisk_deassign_device(sf->cxt, 1); + return 0; + } + + if (fdisk_set_disklabel_id_from_string(sf->cxt, str) != 0) + errx(EXIT_FAILURE, _("%s: failed to set disklabel ID"), devname); + + return write_changes(sf); +} + +/* + * sfdisk --relocate <mode> <device> + */ +static int command_relocate(struct sfdisk *sf, int argc, char **argv) +{ + const char *devname = NULL; + const char *oper = NULL; + struct fdisk_label *lb; + + if (!argc) + errx(EXIT_FAILURE, _("no relocate operation specified")); + if (argc < 2) + errx(EXIT_FAILURE, _("no disk device specified")); + if (argc > 2) + errx(EXIT_FAILURE, _("unexpected arguments")); + + oper = argv[0]; + devname = argv[1]; + lb = fdisk_get_label(sf->cxt, "gpt"); + + if (strcmp(oper, "gpt-bak-mini") == 0) + fdisk_gpt_enable_minimize(lb, 1); + + else if (strcmp(oper, "gpt-bak-std") != 0) + errx(EXIT_FAILURE, _("unsupported relocation operation")); + + assign_device(sf, devname, 0); + + fdisk_label_set_changed(lb, 1); + + 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,Ex,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_or_unused(struct sfdisk *sf) +{ + size_t i, nparts; + struct fdisk_partition *pa = NULL; + + if (sf->container || sf->unused) + return 1; + + nparts = fdisk_get_npartitions(sf->cxt); + for (i = 0; i < nparts; i++) { + + if (!fdisk_is_partition_used(sf->cxt, i)) { + sf->unused = 1; + continue; + } + if (fdisk_get_partition(sf->cxt, i, &pa) != 0) + continue; + if (fdisk_partition_is_container(pa)) + sf->container = 1; + } + + fdisk_unref_partition(pa); + return sf->container || sf->unused; +} + +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_warnx(sf->cxt, _( + "The device contains '%s' signature and it may be removed by a write command. " + "See sfdisk(8) man page and --wipe option for more details."), + fdisk_get_collision(sf->cxt)); + fputc('\n', stdout); + } + } else { + fdisk_warnx(sf->cxt, _( + "The device contains '%s' signature and it 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, ignored = 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")); + + assign_device(sf, devname, 0); + + 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->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 */ + + if (fdisk_script_set_header(dp, "label", label) != 0) + errx(EXIT_FAILURE, _("failed to set script header")); + + 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_or_unused(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 == -ENOTSUP) { + buf[sizeof(buf) - 1] = '\0'; + fdisk_warnx(sf->cxt, _("Unknown script header '%s' -- ignore."), buf); + continue; + } + + 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; + } + + 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 = (size_t) -1; + 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++; + ignored++; + continue; + } + if (!created) { /* create a new disklabel */ + rc = fdisk_apply_script_headers(sf->cxt, dp); + created = !rc; + if (rc) { + errno = -rc; + fdisk_warn(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; + } + + 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 #%zu 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) == (size_t) ignored + && fdisk_script_get_header(dp, "label")) { + + int xrc = fdisk_apply_script_headers(sf->cxt, dp); + 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_set_script(sf->cxt, NULL); + 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(_(" -B, --backup-pt-sectors <dev> binary partition table backup (see -b and -O)\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(_(" --disk-id <dev> [<str>] print or change disk label ID (UUID)\n"), out); + fputs(_(" --relocate <oper> <dev> move partition header\n"), out); + + fputs(USAGE_ARGUMENTS, 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(_(" --move-use-fsync use fsync after each write when move data\n"), out); + fputs(_(" -f, --force disable all consistency checking\n"), out); + + fprintf(out, + _(" --color[=<when>] colorize output (%s, %s or %s)\n"), "auto", "always", "never"); + fprintf(out, + " %s\n", USAGE_COLORS_DEFAULT); + fprintf(out, + _(" --lock[=<mode>] use exclusive device lock (%s, %s or %s)\n"), "yes", "no", "nonblock"); + 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); + fprintf(out, + _(" -w, --wipe <mode> wipe signatures (%s, %s or %s)\n"), "auto", "always", "never"); + fprintf(out, + _(" -W, --wipe-partitions <mode> wipe signatures from new partitions (%s, %s or %s)\n"), "auto", "always", "never"); + 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_DISKID, + OPT_BYTES, + OPT_COLOR, + OPT_MOVEDATA, + OPT_MOVEFSYNC, + OPT_DELETE, + OPT_NOTELL, + OPT_RELOCATE, + OPT_LOCK, + }; + + static const struct option longopts[] = { + { "activate",no_argument, NULL, 'A' }, + { "append", no_argument, NULL, 'a' }, + { "backup-pt-sectors", no_argument, NULL, 'B' }, + { "backup", no_argument, NULL, 'b' }, + { "backup-file", required_argument, NULL, 'O' }, + { "bytes", no_argument, NULL, OPT_BYTES }, + { "color", optional_argument, NULL, OPT_COLOR }, + { "lock", optional_argument, NULL, OPT_LOCK }, + { "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 }, + { "move-use-fsync", no_argument, NULL, OPT_MOVEFSYNC }, + { "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' }, + + { "relocate", no_argument, NULL, OPT_RELOCATE }, + + { "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 }, + + { "disk-id", no_argument, NULL, OPT_DISKID }, + + { "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 */ + { 'F','d'}, /* --list-free --dump */ + { 'F','J'}, /* --list-free --json */ + { 's','u'}, /* --show-size --unit */ + { 0 } + }; + int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; + + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + close_stdout_atexit(); + + while ((c = getopt_long(argc, argv, "aAbBcdfFgGhJlLo: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 'B': + sf->act = ACT_BACKUP_SECTORS; + 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': + print_version(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_DISKID: + sf->act = ACT_DISKID; + 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_MOVEFSYNC: + sf->movefsync = 1; + break; + case OPT_DELETE: + sf->act = ACT_DELETE; + break; + case OPT_NOTELL: + sf->notell = 1; + break; + case OPT_RELOCATE: + sf->act = ACT_RELOCATE; + break; + case OPT_LOCK: + sf->lockmode = "1"; + if (optarg) { + if (*optarg == '=') + optarg++; + sf->lockmode = optarg; + } + 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_BACKUP_SECTORS: + rc = command_backup_sectors(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_DISKID: + rc = command_diskid(sf, argc - optind, argv + optind); + break; + + case ACT_REORDER: + rc = command_reorder(sf, argc - optind, argv + optind); + break; + + case ACT_RELOCATE: + rc = command_relocate(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..2a889b6 --- /dev/null +++ b/disk-utils/swaplabel.8 @@ -0,0 +1,87 @@ +'\" t +.\" Title: swaplabel +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.20 +.\" Date: 2023-10-23 +.\" Manual: System Administration +.\" Source: util-linux 2.39.3 +.\" Language: English +.\" +.TH "SWAPLABEL" "8" "2023-10-23" "util\-linux 2.39.3" "System Administration" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +swaplabel \- print or change the label or UUID of a swap area +.SH "SYNOPSIS" +.sp +\fBswaplabel\fP [\fB\-L\fP \fIlabel\fP] [\fB\-U\fP \fIUUID\fP] \fIdevice\fP +.SH "DESCRIPTION" +.sp +\fBswaplabel\fP will display or change the label or UUID of a swap partition located on \fIdevice\fP (or regular file). +.sp +If the optional arguments \fB\-L\fP and \fB\-U\fP are not given, \fBswaplabel\fP will simply display the current swap\-area label and UUID of \fIdevice\fP. +.sp +If an optional argument is present, then \fBswaplabel\fP will change the appropriate value on \fIdevice\fP. These values can also be set during swap creation using \fBmkswap\fP(8). The \fBswaplabel\fP utility allows changing the label or UUID on an actively used swap device. +.SH "OPTIONS" +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.sp +\fB\-V\fP, \fB\-\-version\fP +.RS 4 +Print version and exit. +.RE +.sp +\fB\-L\fP, \fB\-\-label\fP \fIlabel\fP +.RS 4 +Specify a new \fIlabel\fP for the device. Swap partition labels can be at most 16 characters long. If \fIlabel\fP is longer than 16 characters, \fBswaplabel\fP will truncate it and print a warning message. +.RE +.sp +\fB\-U\fP, \fB\-\-uuid\fP \fIUUID\fP +.RS 4 +Specify a new \fIUUID\fP for the device. The \fIUUID\fP must be in the standard 8\-4\-4\-4\-12 character format, such as is output by \fBuuidgen\fP(1). +.RE +.SH "ENVIRONMENT" +.sp +LIBBLKID_DEBUG=all +.RS 4 +enables libblkid debug output. +.RE +.SH "AUTHORS" +.sp +\fBswaplabel\fP was written by \c +.MTO "jborden\(atbluehost.com" "Jason Borden" "" +and +.MTO "kzak\(atredhat.com" "Karel Zak" "." +.SH "SEE ALSO" +.sp +\fBuuidgen\fP(1), +\fBmkswap\fP(8), +\fBswapon\fP(8) +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBswaplabel\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "."
\ No newline at end of file diff --git a/disk-utils/swaplabel.8.adoc b/disk-utils/swaplabel.8.adoc new file mode 100644 index 0000000..14ab6c4 --- /dev/null +++ b/disk-utils/swaplabel.8.adoc @@ -0,0 +1,60 @@ +//po4a: entry man manual +//// +Copyright 2010 Jason Borden <jborden@bluehost.com> +This file may be copied under the terms of the GNU Public License. +//// += swaplabel(8) +:doctype: manpage +:man manual: System Administration +:man source: util-linux {release-version} +:page-layout: base +:command: swaplabel + +== NAME + +swaplabel - print or change the label or UUID of a swap area + +== SYNOPSIS + +*swaplabel* [*-L* _label_] [*-U* _UUID_] _device_ + +== DESCRIPTION + +*swaplabel* will display or change the label or UUID of a swap partition located on _device_ (or regular file). + +If the optional arguments *-L* and *-U* are not given, *swaplabel* will simply display the current swap-area label and UUID of _device_. + +If an optional argument is present, then *swaplabel* will change the appropriate value on _device_. These values can also be set during swap creation using *mkswap*(8). The *swaplabel* utility allows changing the label or UUID on an actively used swap device. + +== OPTIONS + +include::man-common/help-version.adoc[] + +*-L*, *--label* _label_:: + Specify a new _label_ for the device. Swap partition labels can be at most 16 characters long. If _label_ is longer than 16 characters, *swaplabel* will truncate it and print a warning message. + +*-U*, *--uuid* _UUID_:: + Specify a new _UUID_ for the device. The _UUID_ must be in the standard 8-4-4-4-12 character format, such as is output by *uuidgen*(1). + +== ENVIRONMENT + +LIBBLKID_DEBUG=all:: + enables libblkid debug output. + +== AUTHORS + +*swaplabel* was written by mailto:jborden@bluehost.com[Jason Borden] and mailto:kzak@redhat.com[Karel Zak]. + +== SEE ALSO + +*uuidgen*(1), +*mkswap*(8), +*swapon*(8) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/disk-utils/swaplabel.c b/disk-utils/swaplabel.c new file mode 100644 index 0000000..8498d2e --- /dev/null +++ b/disk-utils/swaplabel.c @@ -0,0 +1,196 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * 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); + close_stdout_atexit(); + + while ((c = getopt_long(argc, argv, "hVL:U:", longopts, NULL)) != -1) { + switch (c) { + case 'h': + usage(); + break; + case 'V': + print_version(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; +} + |