summaryrefslogtreecommitdiffstats
path: root/misc
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 15:49:25 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 15:49:25 +0000
commit464df1d5e5ab1322e2dd0a7796939fff1aeefa9a (patch)
tree6a403684e0978f0287d7f0ec0e5aab1fd31a59e1 /misc
parentInitial commit. (diff)
downloade2fsprogs-464df1d5e5ab1322e2dd0a7796939fff1aeefa9a.tar.xz
e2fsprogs-464df1d5e5ab1322e2dd0a7796939fff1aeefa9a.zip
Adding upstream version 1.47.0.upstream/1.47.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--misc/Android.bp355
-rw-r--r--misc/Makefile.in922
-rw-r--r--misc/badblocks.8.in234
-rw-r--r--misc/badblocks.c1379
-rw-r--r--misc/base_device.c171
-rw-r--r--misc/base_device.tst16
-rw-r--r--misc/blkid.8.in164
-rw-r--r--misc/blkid.c433
-rw-r--r--misc/chattr.1.in299
-rw-r--r--misc/chattr.c359
-rw-r--r--misc/check_fuzzer.c61
-rw-r--r--misc/create_inode.c1092
-rw-r--r--misc/create_inode.h57
-rw-r--r--misc/dumpe2fs.8.in115
-rw-r--r--misc/dumpe2fs.c780
-rw-r--r--misc/e2freefrag.8.in96
-rw-r--r--misc/e2freefrag.c452
-rw-r--r--misc/e2freefrag.h20
-rw-r--r--misc/e2fuzz.c372
-rwxr-xr-xmisc/e2fuzz.sh327
-rw-r--r--misc/e2image.8.in335
-rw-r--r--misc/e2image.c1745
-rw-r--r--misc/e2initrd_helper.c398
-rw-r--r--misc/e2label.8.in58
-rw-r--r--misc/e2label.c121
-rw-r--r--misc/e2mmpstatus.8.in59
-rw-r--r--misc/e2undo.8.in84
-rw-r--r--misc/e2undo.c658
-rw-r--r--misc/e4crypt.8.in74
-rw-r--r--misc/e4crypt.c893
-rw-r--r--misc/e4defrag.8.in90
-rw-r--r--misc/e4defrag.c2035
-rw-r--r--misc/ext4.5.in815
-rw-r--r--misc/filefrag.8.in77
-rw-r--r--misc/filefrag.c695
-rw-r--r--misc/findfs.8.in33
-rw-r--r--misc/findsuper.c269
-rw-r--r--misc/fsck.8.in414
-rw-r--r--misc/fsck.c1353
-rw-r--r--misc/fsck.h73
-rw-r--r--misc/fsmap.h89
-rw-r--r--misc/fuse2fs.1.in79
-rw-r--r--misc/fuse2fs.c4009
-rw-r--r--misc/ismounted.c218
-rw-r--r--misc/logsave.8.in61
-rw-r--r--misc/logsave.c334
-rw-r--r--misc/lsattr.1.in52
-rw-r--r--misc/lsattr.c234
-rw-r--r--misc/mk_hugefiles.c491
-rw-r--r--misc/mke2fs-hurd.conf42
-rw-r--r--misc/mke2fs.8.in891
-rw-r--r--misc/mke2fs.c3592
-rw-r--r--misc/mke2fs.conf.5.in566
-rw-r--r--misc/mke2fs.conf.in45
-rw-r--r--misc/mke2fs.h30
-rw-r--r--misc/mklost+found.8.in43
-rw-r--r--misc/mklost+found.c87
-rw-r--r--misc/partinfo.c80
-rw-r--r--misc/profile-to-c.awk13
-rw-r--r--misc/tune2fs.8.in849
-rw-r--r--misc/tune2fs.c3769
-rw-r--r--misc/tune2fs.h26
-rw-r--r--misc/util.c330
-rw-r--r--misc/util.h29
-rw-r--r--misc/uuidd.8.in97
-rw-r--r--misc/uuidd.c600
-rw-r--r--misc/uuidd.rc55
-rw-r--r--misc/uuidgen.1.in63
-rw-r--r--misc/uuidgen.c80
69 files changed, 34837 insertions, 0 deletions
diff --git a/misc/Android.bp b/misc/Android.bp
new file mode 100644
index 0000000..0656bf4
--- /dev/null
+++ b/misc/Android.bp
@@ -0,0 +1,355 @@
+// Copyright 2017 The Android Open Source Project
+
+// Library used to export files from this directory to other programs in this
+// project.
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "external_e2fsprogs_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-GPL
+ // SPDX-license-identifier-LGPL
+ // SPDX-license-identifier-LGPL-2.1
+ // SPDX-license-identifier-LGPL-3.0
+ default_applicable_licenses: ["external_e2fsprogs_license"],
+}
+
+cc_library {
+ name: "libext2_misc",
+ host_supported: true,
+ recovery_available: true,
+ defaults: ["e2fsprogs-defaults"],
+
+ target: {
+ windows: {
+ enabled: true,
+ },
+ },
+
+ srcs: [
+ "create_inode.c",
+ ],
+ shared_libs: [
+ "libext2fs",
+ "libext2_com_err",
+ "libext2_quota",
+ ],
+ export_include_dirs: ["."],
+}
+
+//########################################################################
+// Build mke2fs
+
+cc_defaults {
+ name: "mke2fs_defaults",
+ recovery_available: true,
+ defaults: ["e2fsprogs-defaults"],
+
+ srcs: [
+ "mke2fs.c",
+ "util.c",
+ "mk_hugefiles.c",
+ "default_profile.c",
+ ],
+ stl: "libc++_static",
+ include_dirs: ["external/e2fsprogs/e2fsck"],
+}
+
+cc_binary {
+ name: "mke2fs",
+ host_supported: true,
+ defaults: ["mke2fs_defaults"],
+ target: {
+ host: {
+ static_libs: [
+ "libext2_blkid",
+ "libext2_misc",
+ "libext2_uuid",
+ "libext2_quota",
+ "libext2_com_err",
+ "libext2_e2p",
+ "libext2fs",
+ "libsparse",
+ "libbase",
+ "libz",
+ ],
+ },
+ not_windows: {
+ required: [
+ "mke2fs.conf",
+ ],
+ },
+ windows: {
+ ldflags: ["-static"],
+ enabled: true,
+ },
+ android: {
+ required: [
+ "mke2fs.conf",
+ ],
+ shared_libs: [
+ "libext2fs",
+ "libext2_blkid",
+ "libext2_misc",
+ "libext2_uuid",
+ "libext2_quota",
+ "libext2_com_err",
+ "libext2_e2p",
+ ],
+ symlinks: [
+ "mkfs.ext2",
+ "mkfs.ext3",
+ "mkfs.ext4",
+ ],
+ },
+ },
+}
+
+cc_binary {
+ name: "mke2fs.microdroid",
+ defaults: ["mke2fs_defaults"],
+ bootstrap: true,
+ target: {
+ android: {
+ required: [
+ "mke2fs.conf",
+ ],
+ shared_libs: [
+ "libext2fs",
+ "libext2_blkid",
+ "libext2_misc",
+ "libext2_uuid",
+ "libext2_quota",
+ "libext2_com_err",
+ "libext2_e2p",
+ ],
+ symlinks: ["mkfs.ext4.microdroid"],
+ },
+ },
+ installable: false,
+ stem: "mke2fs",
+ visibility: ["//packages/modules/Virtualization/microdroid"],
+}
+
+//##########################################################################
+// Build tune2fs
+
+cc_defaults {
+ name: "tune2fs-defaults",
+ defaults: ["e2fsprogs-defaults"],
+ srcs: [
+ "tune2fs.c",
+ "util.c",
+ ],
+ cflags: ["-DNO_RECOVERY"],
+ include_dirs: ["external/e2fsprogs/e2fsck"],
+}
+
+tune2fs_libs = [
+ "libext2_blkid",
+ "libext2_com_err",
+ "libext2_quota",
+ "libext2_uuid",
+ "libext2_e2p",
+ "libext2fs",
+]
+
+cc_binary {
+ name: "tune2fs",
+ host_supported: true,
+ vendor_ramdisk_available: true,
+ defaults: ["tune2fs-defaults"],
+
+ shared_libs: tune2fs_libs,
+}
+
+cc_binary {
+ name: "tune2fs_static",
+ static_executable: true,
+ defaults: ["tune2fs-defaults"],
+
+ static_libs: tune2fs_libs,
+}
+
+cc_binary {
+ name: "tune2fs_ramdisk",
+ stem: "tune2fs",
+ static_executable: true,
+ ramdisk: true,
+ defaults: ["tune2fs-defaults"],
+ static_libs: tune2fs_libs,
+}
+
+cc_library_static {
+ name: "libtune2fs",
+ defaults: ["tune2fs-defaults"],
+
+ cflags: ["-DBUILD_AS_LIB"],
+ static_libs: tune2fs_libs,
+}
+
+//########################################################################
+// Build badblocks
+
+cc_binary {
+ name: "badblocks",
+ host_supported: true,
+ defaults: ["e2fsprogs-defaults"],
+
+ srcs: ["badblocks.c"],
+ shared_libs: [
+ "libext2fs",
+ "libext2_com_err",
+ "libext2_uuid",
+ "libext2_blkid",
+ "libext2_e2p",
+ ],
+}
+
+//########################################################################
+// Build chattr
+
+cc_binary {
+ name: "chattr-e2fsprogs",
+ host_supported: true,
+ defaults: ["e2fsprogs-defaults"],
+
+ srcs: ["chattr.c"],
+ shared_libs: [
+ "libext2_com_err",
+ "libext2_e2p",
+ ],
+}
+
+//########################################################################
+// Build lsattr
+
+cc_defaults {
+ name: "lsattr-defaults",
+ srcs: ["lsattr.c"],
+ defaults: ["e2fsprogs-defaults"],
+}
+
+lsattr_libs = [
+ "libext2_com_err",
+ "libext2_e2p",
+]
+
+cc_binary {
+ name: "lsattr-e2fsprogs",
+ host_supported: true,
+ defaults: ["lsattr-defaults"],
+
+ shared_libs: lsattr_libs,
+}
+
+cc_binary {
+ name: "lsattr_static",
+ static_executable: true,
+ defaults: ["lsattr-defaults"],
+
+ static_libs: lsattr_libs,
+}
+
+//########################################################################
+// Build blkid
+
+cc_binary {
+ name: "blkid",
+ host_supported: true,
+ defaults: ["e2fsprogs-defaults"],
+
+ srcs: ["blkid.c"],
+ shared_libs: [
+ "libext2fs",
+ "libext2_blkid",
+ "libext2_com_err",
+ "libext2_e2p",
+ ],
+}
+
+cc_binary {
+ name: "blkid_static",
+ host_supported: true,
+ static_executable: true,
+ defaults: ["e2fsprogs-defaults"],
+
+ srcs: ["blkid.c"],
+ static_libs: [
+ "libext2fs",
+ "libext2_blkid",
+ "libext2_com_err",
+ "libext2_e2p",
+ "libext2_uuid",
+ ],
+}
+
+//########################################################################
+// Build e4crypt
+
+cc_binary {
+ name: "e4crypt",
+ host_supported: true,
+ defaults: ["e2fsprogs-defaults"],
+
+ srcs: ["e4crypt.c"],
+ shared_libs: [
+ "libext2fs",
+ "libext2_uuid",
+ ],
+
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
+}
+
+//##########################################################################
+// Build e2image
+
+cc_binary {
+ name: "e2image",
+ host_supported: true,
+ defaults: ["e2fsprogs-defaults"],
+
+ srcs: ["e2image.c"],
+ shared_libs: [
+ "libext2fs",
+ "libext2_blkid",
+ "libext2_com_err",
+ "libext2_quota",
+ ],
+}
+
+//##########################################################################
+// Build filefrag
+
+cc_binary {
+ name: "filefrag",
+ host_supported: true,
+ defaults: ["e2fsprogs-defaults"],
+
+ srcs: ["filefrag.c"],
+ shared_libs: [
+ "libext2fs",
+ ],
+}
+
+//##########################################################################
+// Build e2freefrag
+
+cc_binary {
+ name: "e2freefrag",
+ host_supported: true,
+ defaults: ["e2fsprogs-defaults"],
+
+ srcs: [
+ "e2freefrag.c",
+ ],
+ header_libs: ["libext2-headers"],
+ shared_libs: [
+ "libext2fs",
+ "libext2_com_err",
+ ],
+}
diff --git a/misc/Makefile.in b/misc/Makefile.in
new file mode 100644
index 0000000..e5420bb
--- /dev/null
+++ b/misc/Makefile.in
@@ -0,0 +1,922 @@
+#
+# Standard e2fsprogs prologue....
+#
+
+srcdir = @srcdir@
+top_srcdir = @top_srcdir@
+VPATH = @srcdir@
+top_builddir = ..
+my_dir = misc
+INSTALL = @INSTALL@
+MKDIR_P = @MKDIR_P@
+
+@MCONFIG@
+
+@DEFRAG_CMT@@LINUX_CMT@E4DEFRAG_PROG= e4defrag
+@DEFRAG_CMT@@LINUX_CMT@E4DEFRAG_MAN= e4defrag.8
+
+@LINUX_CMT@E4CRYPT_PROG = e4crypt
+@LINUX_CMT@E4CRYPT_MAN= e4crypt.8
+
+@IMAGER_CMT@E2IMAGE_PROG= e2image
+@IMAGER_CMT@E2IMAGE_STATIC= e2image.static
+@IMAGER_CMT@E2IMAGE_MAN= e2image.8
+
+@UUIDD_CMT@UUIDD_PROG= uuidd
+@UUIDD_CMT@UUIDD_STATIC= uuidd.static
+@UUIDD_CMT@UUIDD_MAN= uuidd.8
+@UUIDD_CMT@UUIDD_PROFILED= uuidd.profiled
+
+@BLKID_CMT@BLKID_PROG= blkid
+@BLKID_CMT@BLKID_STATIC= blkid.static
+@BLKID_CMT@BLKID_MAN= blkid.8
+
+@BLKID_CMT@FINDFS_LINK= findfs
+@BLKID_CMT@FINDFS_MAN= findfs.8
+
+@FUSE_CMT@FUSE_PROG= fuse2fs
+
+SPROGS= mke2fs badblocks tune2fs dumpe2fs $(BLKID_PROG) logsave \
+ $(E2IMAGE_PROG) @FSCK_PROG@ e2undo
+USPROGS= mklost+found filefrag e2freefrag $(UUIDD_PROG) \
+ $(E4DEFRAG_PROG) $(E4CRYPT_PROG)
+SMANPAGES= tune2fs.8 mklost+found.8 mke2fs.8 dumpe2fs.8 badblocks.8 \
+ e2label.8 $(FINDFS_MAN) $(BLKID_MAN) $(E2IMAGE_MAN) \
+ logsave.8 filefrag.8 e2freefrag.8 e2undo.8 \
+ $(UUIDD_MAN) $(E4DEFRAG_MAN) $(E4CRYPT_MAN) @FSCK_MAN@ \
+ e2mmpstatus.8
+FMANPAGES= mke2fs.conf.5 ext4.5
+
+UPROGS= chattr lsattr $(FUSE_PROG) @UUID_CMT@ uuidgen
+UMANPAGES= chattr.1 lsattr.1 @UUID_CMT@ uuidgen.1
+UMANPAGES+= @FUSE_CMT@ fuse2fs.1
+
+LPROGS= @E2INITRD_PROG@
+
+TUNE2FS_OBJS= tune2fs.o util.o journal.o recovery.o revoke.o
+MKLPF_OBJS= mklost+found.o
+MKE2FS_OBJS= mke2fs.o util.o default_profile.o mk_hugefiles.o \
+ create_inode.o
+CHATTR_OBJS= chattr.o
+LSATTR_OBJS= lsattr.o
+UUIDGEN_OBJS= uuidgen.o
+UUIDD_OBJS= uuidd.o
+DUMPE2FS_OBJS= dumpe2fs.o
+BADBLOCKS_OBJS= badblocks.o
+E2IMAGE_OBJS= e2image.o
+FSCK_OBJS= fsck.o base_device.o ismounted.o
+BLKID_OBJS= blkid.o
+FILEFRAG_OBJS= filefrag.o
+E2UNDO_OBJS= e2undo.o
+E4DEFRAG_OBJS= e4defrag.o
+E4CRYPT_OBJS= e4crypt.o
+E2FREEFRAG_OBJS= e2freefrag.o
+E2FUZZ_OBJS= e2fuzz.o
+FUSE2FS_OBJS= fuse2fs.o journal.o recovery.o revoke.o
+
+PROFILED_TUNE2FS_OBJS= profiled/tune2fs.o profiled/util.o profiled/journal.o \
+ profiled/recovery.o profiled/revoke.o
+PROFILED_MKLPF_OBJS= profiled/mklost+found.o
+PROFILED_MKE2FS_OBJS= profiled/mke2fs.o profiled/util.o \
+ profiled/default_profile.o \
+ profiled/mk_hugefiles.o \
+ profiled/create_inode.o
+PROFILED_CHATTR_OBJS= profiled/chattr.o
+PROFILED_LSATTR_OBJS= profiled/lsattr.o
+PROFILED_UUIDGEN_OBJS= profiled/uuidgen.o
+PROFILED_UUIDD_OBJS= profiled/uuidd.o
+PROFILED_DUMPE2FS_OBJS= profiled/dumpe2fs.o
+PROFILED_BADBLOCKS_OBJS= profiled/badblocks.o
+PROFILED_E2IMAGE_OBJS= profiled/e2image.o
+PROFILED_FSCK_OBJS= profiled/fsck.o profiled/base_device.o \
+ profiled/ismounted.o
+PROFILED_BLKID_OBJS= profiled/blkid.o
+PROFILED_FILEFRAG_OBJS= profiled/filefrag.o
+PROFILED_E2FREEFRAG_OBJS= profiled/e2freefrag.o
+PROFILED_E2UNDO_OBJS= profiled/e2undo.o
+PROFILED_E4DEFRAG_OBJS= profiled/e4defrag.o
+PROFILED_E4CRYPT_OBJS= profiled/e4crypt.o
+PROFILED_FUSE2FS_OJBS= profiled/fuse2fs.o profiled/journal.o \
+ profiled/recovery.o profiled/revoke.o
+
+SRCS= $(srcdir)/tune2fs.c $(srcdir)/mklost+found.c $(srcdir)/mke2fs.c $(srcdir)/mk_hugefiles.c \
+ $(srcdir)/chattr.c $(srcdir)/lsattr.c $(srcdir)/dumpe2fs.c \
+ $(srcdir)/badblocks.c $(srcdir)/fsck.c $(srcdir)/util.c \
+ $(srcdir)/uuidgen.c $(srcdir)/blkid.c $(srcdir)/logsave.c \
+ $(srcdir)/filefrag.c $(srcdir)/base_device.c \
+ $(srcdir)/ismounted.c $(srcdir)/e2undo.c \
+ $(srcdir)/e2freefrag.c $(srcdir)/create_inode.c \
+ $(srcdir)/fuse2fs.c $(srcdir)/e2fuzz.c \
+ $(srcdir)/check_fuzzer.c \
+ $(srcdir)/../debugfs/journal.c $(srcdir)/../e2fsck/revoke.c \
+ $(srcdir)/../e2fsck/recovery.c
+
+LIBS= $(LIBEXT2FS) $(LIBCOM_ERR) $(LIBSUPPORT)
+DEPLIBS= $(LIBEXT2FS) $(DEPLIBCOM_ERR) $(DEPLIBSUPPORT)
+PROFILED_LIBS= $(LIBSUPPORT) $(PROFILED_LIBEXT2FS) $(PROFILED_LIBCOM_ERR)
+PROFILED_DEPLIBS= $(DEPLIBSUPPORT) $(PROFILED_LIBEXT2FS) $(DEPPROFILED_LIBCOM_ERR)
+
+STATIC_LIBS= $(LIBSUPPORT) $(STATIC_LIBEXT2FS) $(STATIC_LIBCOM_ERR)
+STATIC_DEPLIBS= $(DEPLIBSUPPORT) $(STATIC_LIBEXT2FS) $(DEPSTATIC_LIBCOM_ERR)
+
+LIBS_E2P= $(LIBE2P) $(LIBCOM_ERR)
+DEPLIBS_E2P= $(LIBE2P) $(DEPLIBCOM_ERR)
+
+COMPILE_ET= _ET_DIR_OVERRIDE=$(srcdir)/../lib/et/et ../lib/et/compile_et
+
+# This nastiness is needed because of jfs_user.h hackery; when we finally
+# clean up this mess, we should be able to drop it
+JOURNAL_CFLAGS = -I$(srcdir)/../e2fsck $(ALL_CFLAGS) -DDEBUGFS
+DEPEND_CFLAGS = -I$(top_srcdir)/e2fsck
+
+.c.o:
+ $(E) " CC $<"
+ $(Q) $(CC) -c $(ALL_CFLAGS) $< -o $@
+ $(Q) $(CHECK_CMD) $(ALL_CFLAGS) $<
+ $(Q) $(CPPCHECK_CMD) $(CPPFLAGS) $<
+@PROFILE_CMT@ $(Q) $(CC) $(ALL_CFLAGS) -g -pg -o profiled/$*.o -c $<
+
+all:: profiled $(SPROGS) $(UPROGS) $(USPROGS) $(SMANPAGES) $(UMANPAGES) \
+ $(FMANPAGES) $(LPROGS) $(E4DEFRAG_PROG) $(E4CRYPT_PROGS) e2fuzz
+
+all-static:: $(E2IMAGE_STATIC) $(UUIDD_STATIC) $(BLKID_STATIC) \
+ dumpe2fs.static mke2fs.static tune2fs.static lsattr.static chattr.static
+
+@PROFILE_CMT@all:: tune2fs.profiled blkid.profiled e2image.profiled \
+ e2undo.profiled mke2fs.profiled dumpe2fs.profiled fsck.profiled \
+ logsave.profiled filefrag.profiled uuidgen.profiled $(UUIDD_PROFILED) \
+ e2image.profiled e4defrag.profiled e4crypt.profiled \
+ e2freefrag.profiled
+
+profiled:
+@PROFILE_CMT@ $(E) " MKDIR $@"
+@PROFILE_CMT@ $(Q) mkdir profiled
+
+mke2fs.conf: $(srcdir)/mke2fs.conf.in
+ if test -f $(srcdir)/mke2fs.conf.custom.in ; then \
+ cp $(srcdir)/mke2fs.conf.custom.in mke2fs.conf; \
+ else \
+ cp $(srcdir)/mke2fs.conf.in mke2fs.conf; \
+ fi
+
+default_profile.c: mke2fs.conf $(srcdir)/profile-to-c.awk
+ $(E) " PROFILE_TO_C mke2fs.conf"
+ $(Q) $(AWK) -f $(srcdir)/profile-to-c.awk < mke2fs.conf \
+ > default_profile.c
+findsuper: findsuper.o
+ $(E) " LD $@"
+ $(Q) $(CC) $(ALL_LDFLAGS) -o findsuper findsuper.o $(LIBS) $(SYSLIBS)
+
+partinfo: partinfo.o $(DEPLIBCOM_ERR)
+ $(E) " LD $@"
+ $(Q) $(CC) $(ALL_LDFLAGS) -o partinfo partinfo.o $(LIBCOM_ERR)
+
+e2initrd_helper: e2initrd_helper.o $(DEPLIBS) $(DEPLIBBLKID) $(LIBEXT2FS)
+ $(E) " LD $@"
+ $(Q) $(CC) $(ALL_LDFLAGS) -o e2initrd_helper e2initrd_helper.o $(LIBS) \
+ $(LIBBLKID) $(LIBEXT2FS) $(LIBINTL) $(SYSLIBS)
+
+tune2fs: $(TUNE2FS_OBJS) $(DEPLIBS) $(DEPLIBS_E2P) $(DEPLIBBLKID) \
+ $(DEPLIBUUID) $(LIBEXT2FS)
+ $(E) " LD $@"
+ $(Q) $(CC) $(ALL_LDFLAGS) -o tune2fs $(TUNE2FS_OBJS) $(LIBS) \
+ $(LIBBLKID) $(LIBUUID) $(LIBEXT2FS) $(LIBS_E2P) \
+ $(LIBINTL) $(SYSLIBS) $(LIBBLKID) $(LIBMAGIC)
+
+tune2fs.static: $(TUNE2FS_OBJS) $(STATIC_DEPLIBS) $(STATIC_LIBE2P) $(DEPSTATIC_LIBBLKID)
+ $(E) " LD $@"
+ $(Q) $(CC) $(LDFLAGS_STATIC) -o tune2fs.static $(TUNE2FS_OBJS) \
+ $(STATIC_LIBS) $(STATIC_LIBBLKID) $(STATIC_LIBUUID) \
+ $(STATIC_LIBE2P) $(LIBINTL) $(SYSLIBS) \
+ $(STATIC_LIBBLKID) $(LIBMAGIC)
+
+tune2fs.profiled: $(TUNE2FS_OBJS) $(PROFILED_DEPLIBS) \
+ $(PROFILED_E2P) $(DEPPROFILED_LIBBLKID) $(DEPPROFILED_LIBUUID)
+ $(E) " LD $@"
+ $(Q) $(CC) $(ALL_LDFLAGS) -g -pg -o tune2fs.profiled \
+ $(PROFILED_TUNE2FS_OBJS) $(PROFILED_LIBS) \
+ $(PROFILED_LIBBLKID) $(PROFILED_LIBUUID) $(PROFILED_LIBE2P) \
+ $(LIBINTL) $(SYSLIBS) $(PROFILED_LIBUUID) $(LIBMAGIC)
+
+blkid: $(BLKID_OBJS) $(DEPLIBBLKID) $(LIBEXT2FS)
+ $(E) " LD $@"
+ $(Q) $(CC) $(ALL_LDFLAGS) -o blkid $(BLKID_OBJS) $(LIBBLKID) $(LIBINTL) \
+ $(LIBEXT2FS) $(SYSLIBS)
+
+blkid.static: $(BLKID_OBJS) $(STATIC_DEPLIBS) $(DEPSTATIC_LIBBLKID)
+ $(E) " LD $@"
+ $(Q) $(CC) $(ALL_LDFLAGS) -o blkid.static $(BLKID_OBJS) $(STATIC_LIBS) \
+ $(STATIC_LIBBLKID) $(LIBINTL) $(SYSLIBS)
+
+blkid.profiled: $(BLKID_OBJS) $(DEPPROFILED_LIBBLKID) \
+ $(PROFILED_LIBEXT2FS)
+ $(E) " LD $@"
+ $(Q) $(CC) $(ALL_LDFLAGS) -g -pg -o blkid.profiled $(PROFILED_BLKID_OBJS) \
+ $(PROFILED_LIBBLKID) $(LIBINTL) $(PROFILED_LIBEXT2FS) $(SYSLIBS)
+
+e2image: $(E2IMAGE_OBJS) $(DEPLIBS) $(DEPLIBBLKID)
+ $(E) " LD $@"
+ $(Q) $(CC) $(ALL_LDFLAGS) -o e2image $(E2IMAGE_OBJS) $(LIBS) \
+ $(LIBINTL) $(SYSLIBS) $(LIBBLKID) $(LIBMAGIC)
+
+e2image.profiled: $(E2IMAGE_OBJS) $(PROFILED_DEPLIBS) $(DEPLIBBLKID)
+ $(E) " LD $@"
+ $(Q) $(CC) $(ALL_LDFLAGS) -g -pg -o e2image.profiled \
+ $(PROFILED_E2IMAGE_OBJS) $(PROFILED_LIBS) $(LIBINTL) $(SYSLIBS) \
+ $(LIBBLKID) $(LIBMAGIC)
+
+e2image.static: $(E2IMAGE_OBJS) $(PROFILED_DEPLIBS) $(DEPLIBBLKID)
+ $(E) " LD $@"
+ $(Q) $(CC) $(LDFLAGS_STATIC) -g -pg -o e2image.static \
+ $(E2IMAGE_OBJS) $(STATIC_LIBS) $(LIBINTL) $(SYSLIBS) \
+ $(STATIC_LIBBLKID) $(LIBMAGIC)
+
+e2undo: $(E2UNDO_OBJS) $(DEPLIBS)
+ $(E) " LD $@"
+ $(Q) $(CC) $(ALL_LDFLAGS) -o e2undo $(E2UNDO_OBJS) $(LIBS) \
+ $(LIBINTL) $(SYSLIBS)
+
+e2undo.profiled: $(E2UNDO_OBJS) $(PROFILED_DEPLIBS)
+ $(E) " LD $@"
+ $(Q) $(CC) $(ALL_LDFLAGS) -g -pg -o e2undo.profiled \
+ $(PROFILED_E2UNDO_OBJS) $(PROFILED_LIBS) $(LIBINTL) $(SYSLIBS)
+
+e4defrag: $(E4DEFRAG_OBJS) $(DEPLIBS)
+ $(E) " LD $@"
+ $(Q) $(CC) $(ALL_LDFLAGS) -o e4defrag $(E4DEFRAG_OBJS) $(LIBS) \
+ $(SYSLIBS)
+
+e4crypt: $(E4CRYPT_OBJS) $(DEPLIBS) $(DEPSTATIC_LIBUUID)
+ $(E) " LD $@"
+ $(Q) $(CC) $(ALL_LDFLAGS) -o e4crypt $(E4CRYPT_OBJS) \
+ $(LIBUUID) $(LIBS) $(SYSLIBS)
+
+e4defrag.profiled: $(E4DEFRAG_OBJS) $(PROFILED_DEPLIBS)
+ $(E) " LD $@"
+ $(Q) $(CC) $(ALL_LDFLAGS) -g -pg -o e4defrag.profiled \
+ $(PROFILED_E4DEFRAG_OBJS) $(PROFILED_LIBS) $(SYSLIBS)
+
+e4crypt.profiled: $(E4CRYPT_OBJS) $(DEPPROFILED_LIBUUID) $(PROFILED_DEPLIBS)
+ $(E) " LD $@"
+ $(Q) $(CC) $(ALL_LDFLAGS) -g -pg -o e4crypt.profiled \
+ $(PROFILED_E4CRYPT_OBJS) $(PROFILED_LIBUUID) $(PROFILED_LIBS) \
+ $(SYSLIBS)
+
+base_device: base_device.c
+ $(E) " LD $@"
+ $(Q) $(CC) $(ALL_CFLAGS) $(ALL_LDFLAGS) $(srcdir)/base_device.c \
+ -DDEBUG -o base_device $(SYSLIBS)
+
+fullcheck check:: base_device
+ ./base_device < $(srcdir)/base_device.tst > base_device.out
+ cmp $(srcdir)/base_device.tst base_device.out
+
+mklost+found: $(MKLPF_OBJS)
+ $(E) " LD $@"
+ $(Q) $(CC) $(ALL_LDFLAGS) -o mklost+found $(MKLPF_OBJS) \
+ $(LIBINTL) $(SYSLIBS)
+
+mke2fs: $(MKE2FS_OBJS) $(DEPLIBS) $(LIBE2P) $(DEPLIBBLKID) $(DEPLIBUUID) \
+ $(LIBEXT2FS)
+ $(E) " LD $@"
+ $(Q) $(CC) $(ALL_LDFLAGS) -o mke2fs $(MKE2FS_OBJS) $(LIBS) $(LIBBLKID) \
+ $(LIBUUID) $(LIBEXT2FS) $(LIBE2P) $(LIBINTL) \
+ $(SYSLIBS) $(LIBMAGIC)
+
+mke2fs.static: $(MKE2FS_OBJS) $(STATIC_DEPLIBS) $(STATIC_LIBE2P) $(DEPSTATIC_LIBUUID) \
+ $(DEPSTATIC_LIBBLKID)
+ $(E) " LD $@"
+ $(Q) $(CC) $(LDFLAGS_STATIC) -o mke2fs.static $(MKE2FS_OBJS) \
+ $(STATIC_LIBS) $(STATIC_LIBE2P) \
+ $(STATIC_LIBBLKID) $(STATIC_LIBUUID) $(LIBINTL) $(SYSLIBS) \
+ $(LIBMAGIC)
+
+mke2fs.profiled: $(MKE2FS_OBJS) $(PROFILED_DEPLIBS) \
+ $(PROFILED_LIBE2P) $(PROFILED_DEPLIBBLKID) $(PROFILED_DEPLIBUUID)
+ $(E) " LD $@"
+ $(Q) $(CC) $(ALL_LDFLAGS) -g -pg -o mke2fs.profiled \
+ $(PROFILED_MKE2FS_OBJS) $(PROFILED_LIBBLKID) \
+ $(PROFILED_LIBUUID) $(PROFILED_LIBE2P) \
+ $(LIBINTL) $(PROFILED_LIBS) $(SYSLIBS) $(LIBMAGIC)
+
+chattr: $(CHATTR_OBJS) $(DEPLIBS_E2P)
+ $(E) " LD $@"
+ $(Q) $(CC) $(ALL_LDFLAGS) -o chattr $(CHATTR_OBJS) $(LIBS_E2P) \
+ $(LIBINTL) $(SYSLIBS)
+
+chattr.static: $(CHATTR_OBJS) $(STATIC_LIBE2P) $(STATIC_LIBCOM_ERR)
+ $(E) " LD $@"
+ $(Q) $(CC) $(ALL_LDFLAGS) -o chattr.static $(CHATTR_OBJS) $(STATIC_LIBE2P) \
+ $(STATIC_LIBCOM_ERR) $(LIBINTL) $(SYSLIBS)
+
+lsattr: $(LSATTR_OBJS) $(DEPLIBS_E2P)
+ $(E) " LD $@"
+ $(Q) $(CC) $(ALL_LDFLAGS) -o lsattr $(LSATTR_OBJS) $(LIBS_E2P) \
+ $(LIBINTL) $(SYSLIBS)
+
+lsattr.static: $(LSATTR_OBJS) $(STATIC_LIBE2P) $(STATIC_LIBCOM_ERR)
+ $(E) " LD $@"
+ $(Q) $(CC) $(ALL_LDFLAGS) -o lsattr.static $(LSATTR_OBJS) $(STATIC_LIBE2P) \
+ $(STATIC_LIBCOM_ERR) $(LIBINTL) $(SYSLIBS)
+
+uuidgen: $(UUIDGEN_OBJS) $(DEPLIBUUID)
+ $(E) " LD $@"
+ $(Q) $(CC) $(ALL_LDFLAGS) -o uuidgen $(UUIDGEN_OBJS) $(LIBUUID) \
+ $(LIBINTL) $(SYSLIBS)
+
+uuidgen.profiled: $(UUIDGEN_OBJS) $(PROFILED_DEPLIBUUID)
+ $(E) " LD $@"
+ $(Q) $(CC) $(ALL_LDFLAGS) -g -pg -o uuidgen.profiled \
+ $(PROFILED_UUIDGEN_OBJS) $(PROFILED_LIBUUID) $(LIBINTL) \
+ $(SYSLIBS)
+
+uuidd: $(UUIDD_OBJS) $(DEPLIBUUID)
+ $(E) " LD $@"
+ $(Q) $(CC) $(ALL_LDFLAGS) -o uuidd $(UUIDD_OBJS) $(LIBUUID) \
+ $(LIBINTL) $(SYSLIBS)
+
+uuidd.profiled: $(UUIDD_OBJS) $(PROFILED_DEPLIBUUID)
+ $(E) " LD $@"
+ $(Q) $(CC) $(ALL_LDFLAGS) -g -pg -o uuidd.profiled $(PROFILED_UUIDD_OBJS) \
+ $(PROFILED_LIBUUID) $(LIBINTL) $(SYSLIBS)
+
+dumpe2fs: $(DUMPE2FS_OBJS) $(DEPLIBS) $(DEPLIBS_E2P) $(DEPLIBUUID) $(DEPLIBBLKID)
+ $(E) " LD $@"
+ $(Q) $(CC) $(ALL_LDFLAGS) -o dumpe2fs $(DUMPE2FS_OBJS) $(LIBS) \
+ $(LIBS_E2P) $(LIBUUID) $(LIBINTL) $(SYSLIBS) $(LIBBLKID) \
+ $(LIBMAGIC)
+
+dumpe2fs.profiled: $(DUMPE2FS_OBJS) $(PROFILED_DEPLIBS) \
+ $(PROFILED_LIBE2P) $(PROFILED_DEPLIBUUID) $(PROFILED_DEPLIBBLKID)
+ $(E) " LD $@"
+ $(Q) $(CC) $(ALL_LDFLAGS) -g -pg -o dumpe2fs.profiled \
+ $(PROFILED_DUMPE2FS_OBJS) $(PROFILED_LIBS) \
+ $(PROFILED_LIBE2P) $(PROFILED_LIBUUID) $(LIBINTL) $(SYSLIBS) \
+ $(PROFILED_LIBBLKID) $(LIBMAGIC)
+
+dumpe2fs.static: $(DUMPE2FS_OBJS) $(DEPLIBS) $(DEPLIBS_E2P) $(DEPLIBUUID) $(DEPLIBBLKID)
+ $(E) " LD $@"
+ $(Q) $(CC) $(LDFLAGS_STATIC) -o dumpe2fs.static $(DUMPE2FS_OBJS) \
+ $(STATIC_LIBS) $(STATIC_LIBE2P) $(STATIC_LIBUUID) \
+ $(LIBINTL) $(SYSLIBS) $(STATIC_LIBBLKID) $(LIBMAGIC)
+
+fsck: $(FSCK_OBJS) $(DEPLIBBLKID) $(DEPLIBS)
+ $(E) " LD $@"
+ $(Q) $(CC) $(ALL_LDFLAGS) -o fsck $(FSCK_OBJS) $(LIBBLKID) \
+ $(LIBINTL) $(SYSLIBS) $(LIBS)
+
+fsck.profiled: $(FSCK_OBJS) $(PROFILED_DEPLIBBLKID) $(PROFILED_DEPLIBS)
+ $(E) " LD $@"
+ $(Q) $(CC) $(ALL_LDFLAGS) -g -pg -o fsck.profiled $(PROFILED_FSCK_OBJS) \
+ $(PROFILED_LIBBLKID) $(LIBINTL) $(SYSLIBS) $(PROFILED_LIBS)
+
+badblocks: $(BADBLOCKS_OBJS) $(DEPLIBS)
+ $(E) " LD $@"
+ $(Q) $(CC) $(ALL_LDFLAGS) -o badblocks $(BADBLOCKS_OBJS) $(LIBS) \
+ $(LIBINTL) $(SYSLIBS)
+
+badblocks.profiled: $(BADBLOCKS_OBJS) $(PROFILED_DEPLIBS)
+ $(E) " LD $@"
+ $(Q) $(CC) $(ALL_LDFLAGS) -g -pg -o badblocks.profiled \
+ $(PROFILED_BADBLOCKS_OBJS) $(PROFILED_LIBS) $(LIBINTL) \
+ $(SYSLIBS)
+
+logsave: logsave.o
+ $(E) " LD $@"
+ $(Q) $(CC) $(ALL_LDFLAGS) -o logsave logsave.o $(SYSLIBS)
+
+logsave.profiled: logsave.o
+ $(E) " LD $@"
+ $(Q) $(CC) $(ALL_LDFLAGS) -g -pg -o logsave.profiled \
+ profiled/logsave.o $(SYSLIBS)
+
+e2freefrag: $(E2FREEFRAG_OBJS)
+ $(E) " LD $@"
+ $(Q) $(CC) $(ALL_LDFLAGS) -o e2freefrag $(E2FREEFRAG_OBJS) \
+ $(LIBS) $(SYSLIBS)
+
+e2freefrag.profiled: $(E2FREEFRAG_OBJS) $(PROFILED_DEPLIBS)
+ $(E) " LD $@"
+ $(Q) $(CC) $(ALL_LDFLAGS) -g -pg -o e2freefrag.profiled \
+ $(PROFILED_E2FREEFRAG_OBJS) $(PROFILED_LIBS) $(SYSLIBS)
+
+e2fuzz: $(E2FUZZ_OBJS) $(DEPLIBS) $(DEPLIBBLKID) $(DEPLIBUUID) \
+ $(LIBEXT2FS)
+ $(E) " LD $@"
+ $(Q) $(CC) $(ALL_LDFLAGS) -o e2fuzz $(E2FUZZ_OBJS) $(LIBS) \
+ $(LIBBLKID) $(LIBUUID) $(LIBEXT2FS) $(SYSLIBS)
+
+check_fuzzer: check_fuzzer.o $(DEPLIBS)
+ $(E) " LD $@"
+ $(Q) $(CC) $(ALL_LDFLAGS) -o check_fuzzer check_fuzzer.o $(LIBS)
+
+filefrag: $(FILEFRAG_OBJS)
+ $(E) " LD $@"
+ $(Q) $(CC) $(ALL_LDFLAGS) -o filefrag $(FILEFRAG_OBJS) $(SYSLIBS)
+
+filefrag.profiled: $(FILEFRAG_OBJS)
+ $(E) " LD $@"
+ $(Q) $(CC) $(ALL_LDFLAGS) -g -pg -o filefrag.profiled \
+ $(PROFILED_FILEFRAG_OBJS)
+
+fuse2fs: $(FUSE2FS_OBJS) $(DEPLIBS) $(DEPLIBBLKID) $(DEPLIBUUID) \
+ $(LIBEXT2FS)
+ $(E) " LD $@"
+ $(Q) $(CC) $(ALL_LDFLAGS) -o fuse2fs $(FUSE2FS_OBJS) $(LIBS) \
+ $(LIBFUSE) $(LIBBLKID) $(LIBUUID) $(LIBEXT2FS) $(LIBINTL) \
+ $(CLOCK_GETTIME_LIB) $(SYSLIBS)
+
+journal.o: $(srcdir)/../debugfs/journal.c
+ $(E) " CC $<"
+ $(Q) $(CC) -c $(JOURNAL_CFLAGS) -I$(srcdir) \
+ $(srcdir)/../debugfs/journal.c -o $@
+@PROFILE_CMT@ $(Q) $(CC) $(JOURNAL_CFLAGS) -g -pg -o profiled/$*.o -c $<
+
+recovery.o: $(srcdir)/../e2fsck/recovery.c
+ $(E) " CC $<"
+ $(Q) $(CC) -c $(JOURNAL_CFLAGS) -I$(srcdir) \
+ $(srcdir)/../e2fsck/recovery.c -o $@
+@PROFILE_CMT@ $(Q) $(CC) $(JOURNAL_CFLAGS) -g -pg -o profiled/$*.o -c $<
+
+revoke.o: $(srcdir)/../e2fsck/revoke.c
+ $(E) " CC $<"
+ $(Q) $(CC) -c $(JOURNAL_CFLAGS) -I$(srcdir) \
+ $(srcdir)/../e2fsck/revoke.c -o $@
+@PROFILE_CMT@ $(Q) $(CC) $(JOURNAL_CFLAGS) -g -pg -o profiled/$*.o -c $<
+
+tst_ismounted: $(srcdir)/ismounted.c $(STATIC_LIBEXT2FS) $(DEPLIBCOM_ERR)
+ $(E) " LD $@"
+ $(CC) -o tst_ismounted $(srcdir)/ismounted.c -DDEBUG $(ALL_CFLAGS) \
+ $(LIBCOM_ERR) $(SYSLIBS)
+@PROFILE_CMT@ $(Q) $(CC) $(ALL_CFLAGS) -g -pg -o profiled/$*.o -c $<
+
+tune2fs.8: $(DEP_SUBSTITUTE) $(srcdir)/tune2fs.8.in
+ $(E) " SUBST $@"
+ $(Q) $(SUBSTITUTE_UPTIME) $(srcdir)/tune2fs.8.in tune2fs.8
+
+mklost+found.8: $(DEP_SUBSTITUTE) $(srcdir)/mklost+found.8.in
+ $(E) " SUBST $@"
+ $(Q) $(SUBSTITUTE_UPTIME) $(srcdir)/mklost+found.8.in mklost+found.8
+
+mke2fs.8: $(DEP_SUBSTITUTE) $(srcdir)/mke2fs.8.in
+ $(E) " SUBST $@"
+ $(Q) $(SUBSTITUTE_UPTIME) $(srcdir)/mke2fs.8.in mke2fs.8
+
+mke2fs.conf.5: $(DEP_SUBSTITUTE) $(srcdir)/mke2fs.conf.5.in
+ $(E) " SUBST $@"
+ $(Q) $(SUBSTITUTE_UPTIME) $(srcdir)/mke2fs.conf.5.in mke2fs.conf.5
+
+ext4.5: $(DEP_SUBSTITUTE) $(srcdir)/ext4.5.in
+ $(E) " SUBST $@"
+ $(Q) $(SUBSTITUTE_UPTIME) $(srcdir)/$@.in $@
+
+e2label.8: $(DEP_SUBSTITUTE) $(srcdir)/e2label.8.in
+ $(E) " SUBST $@"
+ $(Q) $(SUBSTITUTE_UPTIME) $(srcdir)/e2label.8.in e2label.8
+
+e2undo.8: $(DEP_SUBSTITUTE) $(srcdir)/e2undo.8.in
+ $(E) " SUBST $@"
+ $(Q) $(SUBSTITUTE_UPTIME) $(srcdir)/e2undo.8.in e2undo.8
+
+findfs.8: $(DEP_SUBSTITUTE) $(srcdir)/findfs.8.in
+ $(E) " SUBST $@"
+ $(Q) $(SUBSTITUTE_UPTIME) $(srcdir)/findfs.8.in findfs.8
+
+e2image.8: $(DEP_SUBSTITUTE) $(srcdir)/e2image.8.in
+ $(E) " SUBST $@"
+ $(Q) $(SUBSTITUTE_UPTIME) $(srcdir)/e2image.8.in e2image.8
+
+e4defrag.8: $(DEP_SUBSTITUTE) $(srcdir)/e4defrag.8.in
+ $(E) " SUBST $@"
+ $(Q) $(SUBSTITUTE_UPTIME) $(srcdir)/e4defrag.8.in e4defrag.8
+
+e4crypt.8: $(DEP_SUBSTITUTE) $(srcdir)/e4crypt.8.in
+ $(E) " SUBST $@"
+ $(Q) $(SUBSTITUTE_UPTIME) $(srcdir)/e4crypt.8.in e4crypt.8
+
+dumpe2fs.8: $(DEP_SUBSTITUTE) $(srcdir)/dumpe2fs.8.in
+ $(E) " SUBST $@"
+ $(Q) $(SUBSTITUTE_UPTIME) $(srcdir)/dumpe2fs.8.in dumpe2fs.8
+
+e2mmpstatus.8: $(DEP_SUBSTITUTE) $(srcdir)/e2mmpstatus.8.in
+ $(E) " SUBST $@"
+ $(Q) $(SUBSTITUTE_UPTIME) $(srcdir)/e2mmpstatus.8.in e2mmpstatus.8
+
+badblocks.8: $(DEP_SUBSTITUTE) $(srcdir)/badblocks.8.in
+ $(E) " SUBST $@"
+ $(Q) $(SUBSTITUTE_UPTIME) $(srcdir)/badblocks.8.in badblocks.8
+
+fsck.8: $(DEP_SUBSTITUTE) $(srcdir)/fsck.8.in
+ $(E) " SUBST $@"
+ $(Q) $(SUBSTITUTE_UPTIME) $(srcdir)/fsck.8.in fsck.8
+
+blkid.8: $(DEP_SUBSTITUTE) $(srcdir)/blkid.8.in
+ $(E) " SUBST $@"
+ $(Q) $(SUBSTITUTE_UPTIME) $(srcdir)/blkid.8.in blkid.8
+
+logsave.8: $(DEP_SUBSTITUTE) $(srcdir)/logsave.8.in
+ $(E) " SUBST $@"
+ $(Q) $(SUBSTITUTE_UPTIME) $(srcdir)/logsave.8.in logsave.8
+
+uuidd.8: $(DEP_SUBSTITUTE) $(srcdir)/uuidd.8.in
+ $(E) " SUBST $@"
+ $(Q) $(SUBSTITUTE_UPTIME) $(srcdir)/uuidd.8.in uuidd.8
+
+chattr.1: $(DEP_SUBSTITUTE) $(srcdir)/chattr.1.in
+ $(E) " SUBST $@"
+ $(Q) $(SUBSTITUTE_UPTIME) $(srcdir)/chattr.1.in chattr.1
+
+fuse2fs.1: $(DEP_SUBSTITUTE) $(srcdir)/fuse2fs.1.in
+ $(E) " SUBST $@"
+ $(Q) $(SUBSTITUTE_UPTIME) $(srcdir)/fuse2fs.1.in fuse2fs.1
+
+lsattr.1: $(DEP_SUBSTITUTE) $(srcdir)/lsattr.1.in
+ $(E) " SUBST $@"
+ $(Q) $(SUBSTITUTE_UPTIME) $(srcdir)/lsattr.1.in lsattr.1
+
+uuidgen.1: $(DEP_SUBSTITUTE) $(srcdir)/uuidgen.1.in
+ $(E) " SUBST $@"
+ $(Q) $(SUBSTITUTE_UPTIME) $(srcdir)/uuidgen.1.in uuidgen.1
+
+blkid.1: $(DEP_SUBSTITUTE) $(srcdir)/blkid.1.in
+ $(E) " SUBST $@"
+ $(Q) $(SUBSTITUTE_UPTIME) $(srcdir)/blkid.1.in blkid.1
+
+e2freefrag.8: $(DEP_SUBSTITUTE) $(srcdir)/e2freefrag.8.in
+ $(E) " SUBST $@"
+ @$(SUBSTITUTE_UPTIME) $(srcdir)/e2freefrag.8.in e2freefrag.8
+
+filefrag.8: $(DEP_SUBSTITUTE) $(srcdir)/filefrag.8.in
+ $(E) " SUBST $@"
+ $(Q) $(SUBSTITUTE_UPTIME) $(srcdir)/filefrag.8.in filefrag.8
+
+installdirs:
+ $(E) " MKDIR_P $(sbindir) $(root_sbindir) $(bindir) $(man1dir) $(man8dir) $(libdir) $(root_sysconfdir)"
+ $(Q) $(MKDIR_P) $(DESTDIR)$(sbindir) \
+ $(DESTDIR)$(root_sbindir) $(DESTDIR)$(bindir) \
+ $(DESTDIR)$(man1dir) $(DESTDIR)$(man8dir) \
+ $(DESTDIR)$(man1dir) $(DESTDIR)$(man5dir) \
+ $(DESTDIR)$(libdir) $(DESTDIR)/$(root_sysconfdir)
+
+install: all $(SMANPAGES) $(UMANPAGES) installdirs
+ $(Q) for i in $(SPROGS); do \
+ $(ES) " INSTALL $(root_sbindir)/$$i"; \
+ $(INSTALL_PROGRAM) $$i $(DESTDIR)$(root_sbindir)/$$i; \
+ done
+ $(Q) for i in $(USPROGS); do \
+ $(ES) " INSTALL $(sbindir)/$$i"; \
+ $(INSTALL_PROGRAM) $$i $(DESTDIR)$(sbindir)/$$i; \
+ done
+ $(Q) for i in ext2 ext3 ext4; do \
+ $(ES) " LINK $(root_sbindir)/mkfs.$$i"; \
+ (cd $(DESTDIR)$(root_sbindir); \
+ $(LN) $(LINK_INSTALL_FLAGS) mke2fs mkfs.$$i); \
+ done
+ $(Q) (cd $(DESTDIR)$(root_sbindir); \
+ $(LN) $(LINK_INSTALL_FLAGS) dumpe2fs e2mmpstatus)
+ $(Q) (cd $(DESTDIR)$(root_sbindir); \
+ $(LN) $(LINK_INSTALL_FLAGS) tune2fs e2label)
+ $(Q) if test -n "$(FINDFS_LINK)"; then \
+ $(ES) " LINK $(root_sbindir)/findfs"; \
+ (cd $(DESTDIR)$(root_sbindir); \
+ $(LN) $(LINK_INSTALL_FLAGS) tune2fs $(FINDFS_LINK)); \
+ fi
+ $(Q) for i in $(UPROGS); do \
+ $(ES) " INSTALL $(bindir)/$$i"; \
+ $(INSTALL_PROGRAM) $$i $(DESTDIR)$(bindir)/$$i; \
+ done
+ $(Q) for i in $(LPROGS); do \
+ $(ES) " INSTALL $(libdir)/$$i"; \
+ $(INSTALL_PROGRAM) $$i $(DESTDIR)$(libdir)/$$i; \
+ done
+ $(Q) for i in $(SMANPAGES); do \
+ for j in $(COMPRESS_EXT); do \
+ $(RM) -f $(DESTDIR)$(man8dir)/$$i.$$j; \
+ done; \
+ $(ES) " INSTALL_DATA $(man8dir)/$$i"; \
+ $(INSTALL_DATA) $$i $(DESTDIR)$(man8dir)/$$i; \
+ done
+ $(Q) $(RM) -f $(DESTDIR)$(man8dir)/mkfs.ext2.8.gz \
+ $(DESTDIR)$(man8dir)/mkfs.ext3.8.gz
+ $(Q) for i in ext2 ext3 ext4; do \
+ $(ES) " LINK mkfs.$$i.8"; \
+ (cd $(DESTDIR)$(man8dir); \
+ $(LN) $(LINK_INSTALL_FLAGS) mke2fs.8 mkfs.$$i.8); \
+ done
+ $(Q) for i in $(UMANPAGES); do \
+ for j in $(COMPRESS_EXT); do \
+ $(RM) -f $(DESTDIR)$(man1dir)/$$i.$$j; \
+ done; \
+ $(ES) " INSTALL_DATA $(man1dir)/$$i"; \
+ $(INSTALL_DATA) $$i $(DESTDIR)$(man1dir)/$$i; \
+ done
+ $(Q) for i in $(FMANPAGES); do \
+ for j in $(COMPRESS_EXT); do \
+ $(RM) -f $(DESTDIR)$(man5dir)/$$i.$$j; \
+ done; \
+ $(ES) " INSTALL_DATA $(man5dir)/$$i"; \
+ $(INSTALL_DATA) $$i $(DESTDIR)$(man5dir)/$$i; \
+ done
+ $(Q) for i in ext2 ext3; do \
+ $(ES) " LINK $$i.5"; \
+ (cd $(DESTDIR)$(man5dir); \
+ $(LN) $(LINK_INSTALL_FLAGS) ext4.5 $$i.5); \
+ done
+ $(Q) if test -f $(DESTDIR)$(root_sysconfdir)/mke2fs.conf; then \
+ if cmp -s $(DESTDIR)$(root_sysconfdir)/mke2fs.conf \
+ mke2fs.conf; then \
+ true; \
+ else \
+ if grep -q ext4dev $(DESTDIR)$(root_sysconfdir)/mke2fs.conf ; then \
+ $(ES) " INSTALL_DATA $(root_sysconfdir)/mke2fs.conf.e2fsprogs-new"; \
+ $(INSTALL_DATA) mke2fs.conf \
+ $(DESTDIR)$(root_sysconfdir)/mke2fs.conf.e2fsprogs-new; \
+ echo "Warning: installing mke2fs.conf in $(DESTDIR)$(root_sysconfdir)/mke2fs.conf.e2fsprogs-new"; \
+ echo "Check to see if you need to update your $(root_sysconfdir)/mke2fs.conf"; \
+ else \
+ $(ES) " INSTALL_DATA $(root_sysconfdir)/mke2fs.conf"; \
+ mv $(DESTDIR)$(root_sysconfdir)/mke2fs.conf \
+ $(DESTDIR)$(root_sysconfdir)/mke2fs.conf.e2fsprogs-old; \
+ $(INSTALL_DATA) mke2fs.conf \
+ $(DESTDIR)$(root_sysconfdir)/mke2fs.conf; \
+ echo "Your mke2fs.conf is too old. Backing up old version in"; \
+ echo "$(DESTDIR)$(root_sysconfdir)/mke2fs.conf.e2fsprogs-old. Please check to see"; \
+ echo "if you have any local customizations that you wish to preserve."; \
+ fi; \
+ echo " "; \
+ fi; \
+ else \
+ $(ES) " INSTALL_DATA $(root_sysconfdir)/mke2fs.conf"; \
+ $(INSTALL_DATA) mke2fs.conf \
+ $(DESTDIR)$(root_sysconfdir)/mke2fs.conf; \
+ fi
+
+install-strip: install
+ $(Q) for i in $(SPROGS); do \
+ $(E) " STRIP $(root_sbindir)/$$i"; \
+ $(STRIP) $(DESTDIR)$(root_sbindir)/$$i; \
+ done
+ $(Q) for i in $(USPROGS); do \
+ $(E) " STRIP $(sbindir)/$$i"; \
+ $(STRIP) $(DESTDIR)$(sbindir)/$$i; \
+ done
+
+uninstall:
+ for i in $(SPROGS); do \
+ $(RM) -f $(DESTDIR)$(root_sbindir)/$$i; \
+ done
+ for i in $(USPROGS); do \
+ $(RM) -f $(DESTDIR)$(sbindir)/$$i; \
+ done
+ for i in $(LPROGS); do \
+ $(RM) -f $(DESTDIR)$(libdir)/$$i; \
+ done
+ $(RM) -f $(DESTDIR)$(root_sbindir)/mkfs.ext2 \
+ $(DESTDIR)$(root_sbindir)/mkfs.ext3 \
+ $(DESTDIR)$(root_sbindir)/mkfs.ext4
+ for i in $(UPROGS); do \
+ $(RM) -f $(DESTDIR)$(bindir)/$$i; \
+ done
+ for i in $(SMANPAGES); do \
+ $(RM) -f $(DESTDIR)$(man8dir)/$$i; \
+ done
+ $(RM) -f $(DESTDIR)$(man8dir)/mkfs.ext2.8 \
+ $(DESTDIR)$(man8dir)/mkfs.ext3.8 \
+ $(DESTDIR)$(man8dir)/mkfs.ext4.8 \
+ $(DESTDIR)$(man8dir)/fsck.ext2.8 \
+ $(DESTDIR)$(man8dir)/fsck.ext3.8 \
+ $(DESTDIR)$(man8dir)/fsck.ext4.8
+
+ for i in $(UMANPAGES); do \
+ $(RM) -f $(DESTDIR)$(man1dir)/$$i; \
+ done
+ for i in $(FINDFS_LINK) e2label e2mmpstatus ; do \
+ $(RM) -f $(DESTDIR)$(root_sbindir)/$$i; \
+ done
+ for i in $(FMANPAGES); do \
+ $(RM) -f $(DESTDIR)$(man5dir)/$$i; \
+ done
+ $(Q) for i in ext2 ext3; do \
+ $(ES) " LINK $$i.5"; \
+ $(RM) -f $(DESTDIR)$(man5dir)/$$i.5; \
+ done
+ if cmp -s mke2fs.conf $(DESTDIR)/$(root_sysconfdir)/mke2fs.conf; then \
+ $(RM) $(DESTDIR)/$(root_sysconfdir)/mke2fs.conf; \
+ fi
+
+clean::
+ $(RM) -f $(SPROGS) $(USPROGS) $(UPROGS) $(UMANPAGES) $(SMANPAGES) \
+ $(FMANPAGES) profile.h \
+ base_device base_device.out mke2fs.static filefrag e2freefrag \
+ e2initrd_helper partinfo prof_err.[ch] default_profile.c \
+ uuidd e2image tune2fs.static tst_ismounted fsck.profiled \
+ blkid.profiled tune2fs.profiled e2image.profiled \
+ e2undo.profiled mke2fs.profiled dumpe2fs.profiled \
+ dumpe2fs.static e2image.static \
+ logsave.profiled filefrag.profiled uuidgen.profiled \
+ uuidd.profiled e2image.profiled e2fuzz mke2fs.conf \
+ profiled/*.o \#* *.s *.o *.a *~ core gmon.out
+
+mostlyclean: clean
+distclean: clean
+ $(RM) -f .depend Makefile $(srcdir)/TAGS $(srcdir)/Makefile.in.old
+
+# +++ Dependency line eater +++
+#
+# Makefile dependencies follow. This must be the last section in
+# the Makefile.in file
+#
+tune2fs.o: $(srcdir)/tune2fs.c $(top_builddir)/lib/config.h \
+ $(top_builddir)/lib/dirpaths.h $(top_srcdir)/lib/ext2fs/ext2_fs.h \
+ $(top_builddir)/lib/ext2fs/ext2_types.h $(top_srcdir)/lib/ext2fs/ext2fs.h \
+ $(top_srcdir)/lib/ext2fs/ext3_extents.h $(top_srcdir)/lib/et/com_err.h \
+ $(top_srcdir)/lib/ext2fs/ext2_io.h $(top_builddir)/lib/ext2fs/ext2_err.h \
+ $(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/hashmap.h \
+ $(top_srcdir)/lib/ext2fs/bitops.h $(top_srcdir)/lib/ext2fs/kernel-jbd.h \
+ $(top_srcdir)/lib/ext2fs/jfs_compat.h $(top_srcdir)/lib/ext2fs/kernel-list.h \
+ $(top_srcdir)/lib/ext2fs/compiler.h $(top_srcdir)/lib/support/plausible.h \
+ $(top_srcdir)/lib/support/quotaio.h $(top_srcdir)/lib/support/dqblk_v2.h \
+ $(top_srcdir)/lib/support/quotaio_tree.h $(top_srcdir)/lib/support/devname.h \
+ $(top_srcdir)/lib/e2p/e2p.h $(srcdir)/util.h $(top_srcdir)/version.h \
+ $(top_srcdir)/lib/support/nls-enable.h
+mklost+found.o: $(srcdir)/mklost+found.c $(top_builddir)/lib/config.h \
+ $(top_builddir)/lib/dirpaths.h $(top_srcdir)/lib/ext2fs/ext2_fs.h \
+ $(top_builddir)/lib/ext2fs/ext2_types.h $(top_srcdir)/version.h \
+ $(top_srcdir)/lib/support/nls-enable.h
+mke2fs.o: $(srcdir)/mke2fs.c $(top_builddir)/lib/config.h \
+ $(top_builddir)/lib/dirpaths.h $(top_srcdir)/lib/ext2fs/ext2_fs.h \
+ $(top_builddir)/lib/ext2fs/ext2_types.h $(top_srcdir)/lib/ext2fs/ext2fsP.h \
+ $(top_srcdir)/lib/ext2fs/ext2fs.h $(top_srcdir)/lib/ext2fs/ext3_extents.h \
+ $(top_srcdir)/lib/et/com_err.h $(top_srcdir)/lib/ext2fs/ext2_io.h \
+ $(top_builddir)/lib/ext2fs/ext2_err.h \
+ $(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/hashmap.h \
+ $(top_srcdir)/lib/ext2fs/bitops.h $(srcdir)/util.h \
+ $(top_srcdir)/lib/support/nls-enable.h $(top_srcdir)/lib/support/plausible.h \
+ $(top_srcdir)/lib/support/profile.h $(top_builddir)/lib/support/prof_err.h \
+ $(top_srcdir)/version.h $(top_srcdir)/lib/support/quotaio.h \
+ $(top_srcdir)/lib/ext2fs/ext2fs.h $(top_srcdir)/lib/support/dqblk_v2.h \
+ $(top_srcdir)/lib/support/quotaio_tree.h $(srcdir)/mke2fs.h \
+ $(srcdir)/create_inode.h $(top_srcdir)/lib/e2p/e2p.h
+mk_hugefiles.o: $(srcdir)/mk_hugefiles.c $(top_builddir)/lib/config.h \
+ $(top_builddir)/lib/dirpaths.h $(top_srcdir)/lib/ext2fs/ext2_fs.h \
+ $(top_builddir)/lib/ext2fs/ext2_types.h $(top_srcdir)/lib/ext2fs/ext2fsP.h \
+ $(top_srcdir)/lib/ext2fs/ext2fs.h $(top_srcdir)/lib/ext2fs/ext3_extents.h \
+ $(top_srcdir)/lib/et/com_err.h $(top_srcdir)/lib/ext2fs/ext2_io.h \
+ $(top_builddir)/lib/ext2fs/ext2_err.h \
+ $(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/hashmap.h \
+ $(top_srcdir)/lib/ext2fs/bitops.h $(top_srcdir)/lib/e2p/e2p.h \
+ $(top_srcdir)/lib/ext2fs/ext2fs.h $(srcdir)/util.h \
+ $(top_srcdir)/lib/support/profile.h $(top_builddir)/lib/support/prof_err.h \
+ $(top_srcdir)/lib/support/nls-enable.h $(srcdir)/mke2fs.h
+chattr.o: $(srcdir)/chattr.c $(top_builddir)/lib/config.h \
+ $(top_builddir)/lib/dirpaths.h $(top_srcdir)/lib/ext2fs/ext2_fs.h \
+ $(top_builddir)/lib/ext2fs/ext2_types.h $(top_srcdir)/lib/et/com_err.h \
+ $(top_srcdir)/lib/e2p/e2p.h $(top_srcdir)/lib/support/nls-enable.h \
+ $(top_srcdir)/version.h
+lsattr.o: $(srcdir)/lsattr.c $(top_builddir)/lib/config.h \
+ $(top_builddir)/lib/dirpaths.h $(top_srcdir)/lib/ext2fs/ext2_fs.h \
+ $(top_builddir)/lib/ext2fs/ext2_types.h $(top_srcdir)/lib/et/com_err.h \
+ $(top_srcdir)/lib/e2p/e2p.h $(top_srcdir)/lib/support/nls-enable.h \
+ $(top_srcdir)/version.h
+dumpe2fs.o: $(srcdir)/dumpe2fs.c $(top_builddir)/lib/config.h \
+ $(top_builddir)/lib/dirpaths.h $(top_srcdir)/lib/ext2fs/ext2_fs.h \
+ $(top_builddir)/lib/ext2fs/ext2_types.h $(top_srcdir)/lib/ext2fs/ext2fs.h \
+ $(top_srcdir)/lib/ext2fs/ext3_extents.h $(top_srcdir)/lib/et/com_err.h \
+ $(top_srcdir)/lib/ext2fs/ext2_io.h $(top_builddir)/lib/ext2fs/ext2_err.h \
+ $(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/hashmap.h \
+ $(top_srcdir)/lib/ext2fs/bitops.h $(top_srcdir)/lib/e2p/e2p.h \
+ $(top_srcdir)/lib/ext2fs/kernel-jbd.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
+ $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/support/nls-enable.h $(top_srcdir)/lib/support/plausible.h \
+ $(top_srcdir)/version.h
+badblocks.o: $(srcdir)/badblocks.c $(top_builddir)/lib/config.h \
+ $(top_builddir)/lib/dirpaths.h $(top_srcdir)/lib/et/com_err.h \
+ $(top_srcdir)/lib/ext2fs/ext2_io.h $(top_builddir)/lib/ext2fs/ext2_types.h \
+ $(top_srcdir)/lib/ext2fs/ext2_fs.h $(top_srcdir)/lib/ext2fs/ext2fs.h \
+ $(top_srcdir)/lib/ext2fs/ext3_extents.h \
+ $(top_builddir)/lib/ext2fs/ext2_err.h \
+ $(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/hashmap.h \
+ $(top_srcdir)/lib/ext2fs/bitops.h $(top_srcdir)/lib/support/nls-enable.h
+fsck.o: $(srcdir)/fsck.c $(top_builddir)/lib/config.h \
+ $(top_builddir)/lib/dirpaths.h $(top_srcdir)/version.h \
+ $(top_srcdir)/lib/support/devname.h $(top_srcdir)/lib/support/nls-enable.h \
+ $(srcdir)/fsck.h
+util.o: $(srcdir)/util.c $(top_builddir)/lib/config.h \
+ $(top_builddir)/lib/dirpaths.h $(top_srcdir)/lib/et/com_err.h \
+ $(top_srcdir)/lib/e2p/e2p.h $(top_srcdir)/lib/ext2fs/ext2_fs.h \
+ $(top_builddir)/lib/ext2fs/ext2_types.h $(top_srcdir)/lib/ext2fs/ext2fs.h \
+ $(top_srcdir)/lib/ext2fs/ext3_extents.h $(top_srcdir)/lib/ext2fs/ext2_io.h \
+ $(top_builddir)/lib/ext2fs/ext2_err.h \
+ $(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/hashmap.h \
+ $(top_srcdir)/lib/ext2fs/bitops.h $(top_srcdir)/lib/support/nls-enable.h \
+ $(top_srcdir)/lib/support/devname.h $(srcdir)/util.h
+uuidgen.o: $(srcdir)/uuidgen.c $(top_builddir)/lib/config.h \
+ $(top_builddir)/lib/dirpaths.h $(top_srcdir)/lib/support/nls-enable.h
+blkid.o: $(srcdir)/blkid.c $(top_builddir)/lib/config.h \
+ $(top_builddir)/lib/dirpaths.h $(top_srcdir)/lib/ext2fs/ext2fs.h \
+ $(top_builddir)/lib/ext2fs/ext2_types.h $(top_srcdir)/lib/ext2fs/ext2_fs.h \
+ $(top_srcdir)/lib/ext2fs/ext3_extents.h $(top_srcdir)/lib/et/com_err.h \
+ $(top_srcdir)/lib/ext2fs/ext2_io.h $(top_builddir)/lib/ext2fs/ext2_err.h \
+ $(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/hashmap.h \
+ $(top_srcdir)/lib/ext2fs/bitops.h
+logsave.o: $(srcdir)/logsave.c $(top_builddir)/lib/config.h \
+ $(top_builddir)/lib/dirpaths.h
+filefrag.o: $(srcdir)/filefrag.c $(top_builddir)/lib/config.h \
+ $(top_builddir)/lib/dirpaths.h $(top_srcdir)/lib/ext2fs/ext2fs.h \
+ $(top_builddir)/lib/ext2fs/ext2_types.h $(top_srcdir)/lib/ext2fs/ext2_fs.h \
+ $(top_srcdir)/lib/ext2fs/ext3_extents.h $(top_srcdir)/lib/et/com_err.h \
+ $(top_srcdir)/lib/ext2fs/ext2_io.h $(top_builddir)/lib/ext2fs/ext2_err.h \
+ $(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/hashmap.h \
+ $(top_srcdir)/lib/ext2fs/bitops.h $(top_srcdir)/lib/ext2fs/fiemap.h \
+ $(top_srcdir)/version.h
+base_device.o: $(srcdir)/base_device.c $(top_builddir)/lib/config.h \
+ $(top_builddir)/lib/dirpaths.h $(srcdir)/fsck.h
+ismounted.o: $(srcdir)/ismounted.c $(top_builddir)/lib/config.h \
+ $(top_builddir)/lib/dirpaths.h $(srcdir)/fsck.h \
+ $(top_srcdir)/lib/et/com_err.h
+e2undo.o: $(srcdir)/e2undo.c $(top_builddir)/lib/config.h \
+ $(top_builddir)/lib/dirpaths.h $(top_srcdir)/lib/ext2fs/ext2fs.h \
+ $(top_builddir)/lib/ext2fs/ext2_types.h $(top_srcdir)/lib/ext2fs/ext2_fs.h \
+ $(top_srcdir)/lib/ext2fs/ext3_extents.h $(top_srcdir)/lib/et/com_err.h \
+ $(top_srcdir)/lib/ext2fs/ext2_io.h $(top_builddir)/lib/ext2fs/ext2_err.h \
+ $(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/hashmap.h \
+ $(top_srcdir)/lib/ext2fs/bitops.h $(top_srcdir)/lib/support/nls-enable.h
+e2freefrag.o: $(srcdir)/e2freefrag.c $(top_builddir)/lib/config.h \
+ $(top_builddir)/lib/dirpaths.h $(top_srcdir)/lib/ext2fs/ext2_fs.h \
+ $(top_builddir)/lib/ext2fs/ext2_types.h $(top_srcdir)/lib/ext2fs/ext2fs.h \
+ $(top_srcdir)/lib/ext2fs/ext3_extents.h $(top_srcdir)/lib/et/com_err.h \
+ $(top_srcdir)/lib/ext2fs/ext2_io.h $(top_builddir)/lib/ext2fs/ext2_err.h \
+ $(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/hashmap.h \
+ $(top_srcdir)/lib/ext2fs/bitops.h $(srcdir)/e2freefrag.h $(srcdir)/fsmap.h
+create_inode.o: $(srcdir)/create_inode.c $(top_builddir)/lib/config.h \
+ $(top_builddir)/lib/dirpaths.h $(top_srcdir)/lib/ext2fs/ext2fs.h \
+ $(top_builddir)/lib/ext2fs/ext2_types.h $(top_srcdir)/lib/ext2fs/ext2_fs.h \
+ $(top_srcdir)/lib/ext2fs/ext3_extents.h $(top_srcdir)/lib/et/com_err.h \
+ $(top_srcdir)/lib/ext2fs/ext2_io.h $(top_builddir)/lib/ext2fs/ext2_err.h \
+ $(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/hashmap.h \
+ $(top_srcdir)/lib/ext2fs/bitops.h $(top_srcdir)/lib/ext2fs/fiemap.h \
+ $(srcdir)/create_inode.h $(top_srcdir)/lib/e2p/e2p.h \
+ $(top_srcdir)/lib/support/nls-enable.h
+fuse2fs.o: $(srcdir)/fuse2fs.c $(top_builddir)/lib/config.h \
+ $(top_builddir)/lib/dirpaths.h $(top_srcdir)/lib/ext2fs/ext2fs.h \
+ $(top_builddir)/lib/ext2fs/ext2_types.h $(top_srcdir)/lib/ext2fs/ext2_fs.h \
+ $(top_srcdir)/lib/ext2fs/ext3_extents.h $(top_srcdir)/lib/et/com_err.h \
+ $(top_srcdir)/lib/ext2fs/ext2_io.h $(top_builddir)/lib/ext2fs/ext2_err.h \
+ $(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/hashmap.h \
+ $(top_srcdir)/lib/ext2fs/bitops.h $(top_srcdir)/version.h
+e2fuzz.o: $(srcdir)/e2fuzz.c $(top_builddir)/lib/config.h \
+ $(top_builddir)/lib/dirpaths.h $(top_srcdir)/lib/ext2fs/ext2_fs.h \
+ $(top_builddir)/lib/ext2fs/ext2_types.h $(top_srcdir)/lib/ext2fs/ext2fs.h \
+ $(top_srcdir)/lib/ext2fs/ext3_extents.h $(top_srcdir)/lib/et/com_err.h \
+ $(top_srcdir)/lib/ext2fs/ext2_io.h $(top_builddir)/lib/ext2fs/ext2_err.h \
+ $(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/hashmap.h \
+ $(top_srcdir)/lib/ext2fs/bitops.h
+check_fuzzer.o: $(srcdir)/check_fuzzer.c $(top_srcdir)/lib/ext2fs/ext2_fs.h \
+ $(top_builddir)/lib/ext2fs/ext2_types.h $(top_srcdir)/lib/ext2fs/ext2fs.h \
+ $(top_srcdir)/lib/ext2fs/ext3_extents.h $(top_srcdir)/lib/et/com_err.h \
+ $(top_srcdir)/lib/ext2fs/ext2_io.h $(top_builddir)/lib/ext2fs/ext2_err.h \
+ $(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/hashmap.h \
+ $(top_srcdir)/lib/ext2fs/bitops.h
+journal.o: $(srcdir)/../debugfs/journal.c $(top_builddir)/lib/config.h \
+ $(top_builddir)/lib/dirpaths.h $(srcdir)/../debugfs/journal.h \
+ $(top_srcdir)/e2fsck/jfs_user.h $(top_srcdir)/e2fsck/e2fsck.h \
+ $(top_srcdir)/lib/ext2fs/ext2_fs.h $(top_builddir)/lib/ext2fs/ext2_types.h \
+ $(top_srcdir)/lib/ext2fs/ext2fs.h $(top_srcdir)/lib/ext2fs/ext3_extents.h \
+ $(top_srcdir)/lib/et/com_err.h $(top_srcdir)/lib/ext2fs/ext2_io.h \
+ $(top_builddir)/lib/ext2fs/ext2_err.h \
+ $(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/hashmap.h \
+ $(top_srcdir)/lib/ext2fs/bitops.h $(top_srcdir)/lib/support/profile.h \
+ $(top_builddir)/lib/support/prof_err.h $(top_srcdir)/lib/support/quotaio.h \
+ $(top_srcdir)/lib/support/dqblk_v2.h \
+ $(top_srcdir)/lib/support/quotaio_tree.h \
+ $(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
+ $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/ext2fs/kernel-jbd.h
+revoke.o: $(srcdir)/../e2fsck/revoke.c $(srcdir)/../e2fsck/jfs_user.h \
+ $(top_builddir)/lib/config.h $(top_builddir)/lib/dirpaths.h \
+ $(srcdir)/../e2fsck/e2fsck.h $(top_srcdir)/lib/ext2fs/ext2_fs.h \
+ $(top_builddir)/lib/ext2fs/ext2_types.h $(top_srcdir)/lib/ext2fs/ext2fs.h \
+ $(top_srcdir)/lib/ext2fs/ext3_extents.h $(top_srcdir)/lib/et/com_err.h \
+ $(top_srcdir)/lib/ext2fs/ext2_io.h $(top_builddir)/lib/ext2fs/ext2_err.h \
+ $(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/hashmap.h \
+ $(top_srcdir)/lib/ext2fs/bitops.h $(top_srcdir)/lib/support/profile.h \
+ $(top_builddir)/lib/support/prof_err.h $(top_srcdir)/lib/support/quotaio.h \
+ $(top_srcdir)/lib/support/dqblk_v2.h \
+ $(top_srcdir)/lib/support/quotaio_tree.h \
+ $(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
+ $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/ext2fs/kernel-jbd.h
+recovery.o: $(srcdir)/../e2fsck/recovery.c $(srcdir)/../e2fsck/jfs_user.h \
+ $(top_builddir)/lib/config.h $(top_builddir)/lib/dirpaths.h \
+ $(srcdir)/../e2fsck/e2fsck.h $(top_srcdir)/lib/ext2fs/ext2_fs.h \
+ $(top_builddir)/lib/ext2fs/ext2_types.h $(top_srcdir)/lib/ext2fs/ext2fs.h \
+ $(top_srcdir)/lib/ext2fs/ext3_extents.h $(top_srcdir)/lib/et/com_err.h \
+ $(top_srcdir)/lib/ext2fs/ext2_io.h $(top_builddir)/lib/ext2fs/ext2_err.h \
+ $(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/hashmap.h \
+ $(top_srcdir)/lib/ext2fs/bitops.h $(top_srcdir)/lib/support/profile.h \
+ $(top_builddir)/lib/support/prof_err.h $(top_srcdir)/lib/support/quotaio.h \
+ $(top_srcdir)/lib/support/dqblk_v2.h \
+ $(top_srcdir)/lib/support/quotaio_tree.h \
+ $(top_srcdir)/lib/ext2fs/fast_commit.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \
+ $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \
+ $(top_srcdir)/lib/ext2fs/kernel-jbd.h
diff --git a/misc/badblocks.8.in b/misc/badblocks.8.in
new file mode 100644
index 0000000..6c96cdc
--- /dev/null
+++ b/misc/badblocks.8.in
@@ -0,0 +1,234 @@
+.\" -*- nroff -*-
+.TH BADBLOCKS 8 "@E2FSPROGS_MONTH@ @E2FSPROGS_YEAR@" "E2fsprogs version @E2FSPROGS_VERSION@"
+.SH NAME
+badblocks \- search a device for bad blocks
+.SH SYNOPSIS
+.B badblocks
+[
+.B \-svwnfBX
+]
+[
+.B \-b
+.I block_size
+]
+[
+.B \-c
+.I blocks_at_once
+]
+[
+.B \-d
+.I read_delay_factor
+]
+[
+.B \-e
+.I max_bad_blocks
+]
+[
+.B \-i
+.I input_file
+]
+[
+.B \-o
+.I output_file
+]
+[
+.B \-p
+.I num_passes
+]
+[
+.B \-t
+.I test_pattern
+]
+.I device
+[
+.I last_block
+] [
+.I first_block
+]
+.SH DESCRIPTION
+.B badblocks
+is used to search for bad blocks on a device (usually a disk partition).
+.I device
+is the special file corresponding to the device (e.g
+.IR /dev/hdc1 ).
+.I last_block
+is the last block to be checked; if it is not specified, the last block
+on the device is used as a default.
+.I first_block
+is an optional parameter specifying the starting block number
+for the test, which allows the testing to start in the middle of the
+disk. If it is not specified the first block on the disk is used as a default.
+.PP
+.B Important note:
+If the output of
+.B badblocks
+is going to be fed to the
+.B e2fsck
+or
+.B mke2fs
+programs, it is important that the block size is properly specified,
+since the block numbers which are generated are very dependent on the
+block size in use by the file system.
+For this reason, it is strongly recommended that
+users
+.B not
+run
+.B badblocks
+directly, but rather use the
+.B \-c
+option of the
+.B e2fsck
+and
+.B mke2fs
+programs.
+.SH OPTIONS
+.TP
+.BI \-b " block_size"
+Specify the size of blocks in bytes. The default is 1024.
+.TP
+.BI \-c " number of blocks"
+is the number of blocks which are tested at a time. The default is 64.
+.TP
+.BI \-d " read delay factor"
+This parameter, if passed and non-zero, will cause bad blocks to sleep
+between reads if there were no errors encountered in the read
+operation; the delay will be calculated as a percentage of the time it
+took for the read operation to be performed. In other words, a value of
+100 will cause each read to be delayed by the amount the previous read
+took, and a value of 200 by twice the amount.
+.TP
+.BI \-e " max bad block count"
+Specify a maximum number of bad blocks before aborting the test. The
+default is 0, meaning the test will continue until the end of the test
+range is reached.
+.TP
+.B \-f
+Normally, badblocks will refuse to do a read/write or a non-destructive
+test on a device which is mounted, since either can cause the system to
+potentially crash and/or damage the file system even if it is mounted
+read-only. This can be overridden using the
+.B \-f
+flag, but should almost never be used --- if you think you're smarter
+than the
+.B badblocks
+program, you almost certainly aren't. The only time when this option
+might be safe to use is if the /etc/mtab file is incorrect, and the device
+really isn't mounted.
+.TP
+.BI \-i " input_file"
+Read a list of already existing known bad blocks.
+.B Badblocks
+will skip testing these blocks since they are known to be bad. If
+.I input_file
+is specified as "-", the list will be read from the standard input.
+Blocks listed in this list will be omitted from the list of
+.I new
+bad blocks produced on the standard output or in the output file.
+The
+.B \-b
+option of
+.BR dumpe2fs (8)
+can be used to retrieve the list of blocks currently marked bad on
+an existing file system, in a format suitable for use with this option.
+.TP
+.B \-n
+Use non-destructive read-write mode. By default only a non-destructive
+read-only test is done. This option must not be combined with the
+.B \-w
+option, as they are mutually exclusive.
+.TP
+.BI \-o " output_file"
+Write the list of bad blocks to the specified file. Without this option,
+.B badblocks
+displays the list on its standard output. The format of this file is suitable
+for use by the
+.
+.B \-l
+option in
+.BR e2fsck (8)
+or
+.BR mke2fs (8).
+.TP
+.BI \-p " num_passes"
+Repeat scanning the disk until there are no new blocks discovered in
+num_passes consecutive scans of the disk.
+Default is 0, meaning
+.B badblocks
+will exit after the first pass.
+.TP
+.B \-s
+Show the progress of the scan by writing out rough percentage completion
+of the current badblocks pass over the disk. Note that badblocks may do
+multiple test passes over the disk, in particular if the
+.B \-p
+or
+.B \-w
+option is requested by the user.
+.TP
+.BI \-t " test_pattern"
+Specify a test pattern to be read (and written) to disk blocks. The
+.I test_pattern
+may either be a numeric value between 0 and ULONG_MAX-1 inclusive, or the word
+"random", which specifies that the block should be filled with a random
+bit pattern.
+For read/write (\fB-w\fR) and non-destructive (\fB-n\fR) modes,
+one or more test patterns may be specified by specifying the
+.B -t
+option for each test pattern desired. For
+read-only mode only a single pattern may be specified and it may not be
+"random". Read-only testing with a pattern assumes that the
+specified pattern has previously been written to the disk - if not, large
+numbers of blocks will fail verification.
+If multiple patterns
+are specified then all blocks will be tested with one pattern
+before proceeding to the next pattern.
+.TP
+.B \-v
+Verbose mode. Will write the number of read errors, write errors and data-
+corruptions to stderr.
+.TP
+.B \-w
+Use write-mode test. With this option,
+.B badblocks
+scans for bad blocks by writing some patterns (0xaa, 0x55, 0xff, 0x00) on
+every block of the device, reading every block and comparing the contents.
+This option may not be combined with the
+.B \-n
+option, as they are mutually exclusive.
+.TP
+.B \-B
+Use buffered I/O and do not use Direct I/O, even if it is available.
+.TP
+.B \-X
+Internal flag only to be used by
+.BR e2fsck (8)
+and
+.BR mke2fs (8).
+It bypasses the exclusive mode in-use device safety check.
+.SH WARNING
+Never use the
+.B \-w
+option on a device containing an existing file system.
+This option erases data! If you want to do write-mode testing on
+an existing file system, use the
+.B \-n
+option instead. It is slower, but it will preserve your data.
+.PP
+The
+.B \-e
+option will cause badblocks to output a possibly incomplete list of
+bad blocks. Therefore it is recommended to use it only when one wants
+to know if there are any bad blocks at all on the device, and not when
+the list of bad blocks is wanted.
+.SH AUTHOR
+.B badblocks
+was written by Remy Card <Remy.Card@linux.org>. Current maintainer is
+Theodore Ts'o <tytso@alum.mit.edu>. Non-destructive read/write test
+implemented by David Beattie <dbeattie@softhome.net>.
+.SH AVAILABILITY
+.B badblocks
+is part of the e2fsprogs package and is available from
+http://e2fsprogs.sourceforge.net.
+.SH SEE ALSO
+.BR e2fsck (8),
+.BR mke2fs (8)
diff --git a/misc/badblocks.c b/misc/badblocks.c
new file mode 100644
index 0000000..2b5ff6d
--- /dev/null
+++ b/misc/badblocks.c
@@ -0,0 +1,1379 @@
+/*
+ * badblocks.c - Bad blocks checker
+ *
+ * Copyright (C) 1992, 1993, 1994 Remy Card <card@masi.ibp.fr>
+ * Laboratoire MASI, Institut Blaise Pascal
+ * Universite Pierre et Marie Curie (Paris VI)
+ *
+ * Copyright 1995, 1996, 1997, 1998, 1999 by Theodore Ts'o
+ * Copyright 1999 by David Beattie
+ *
+ * This file is based on the minix file system programs fsck and mkfs
+ * written and copyrighted by Linus Torvalds <Linus.Torvalds@cs.helsinki.fi>
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ * %End-Header%
+ */
+
+/*
+ * History:
+ * 93/05/26 - Creation from e2fsck
+ * 94/02/27 - Made a separate bad blocks checker
+ * 99/06/30...99/07/26 - Added non-destructive write-testing,
+ * configurable blocks-at-once parameter,
+ * loading of badblocks list to avoid testing
+ * blocks known to be bad, multiple passes to
+ * make sure that no new blocks are added to the
+ * list. (Work done by David Beattie)
+ */
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE /* for O_DIRECT */
+#endif
+
+#include "config.h"
+#include <errno.h>
+#include <fcntl.h>
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#else
+extern char *optarg;
+extern int optind;
+#endif
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <setjmp.h>
+#include <time.h>
+#include <limits.h>
+#ifdef HAVE_MBSTOWCS
+#include <wchar.h>
+#endif
+
+#include <sys/time.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+
+#include "et/com_err.h"
+#include "ext2fs/ext2_io.h"
+#include "ext2fs/ext2_fs.h"
+#include "ext2fs/ext2fs.h"
+#include "support/nls-enable.h"
+
+#ifndef O_LARGEFILE
+#define O_LARGEFILE 0
+#endif
+
+/* Maximum number of bad blocks we support */
+#define MAX_BAD_BLOCKS (INT_MAX/2)
+
+static const char * program_name = "badblocks";
+static const char * done_string = N_("done \n");
+
+static int v_flag; /* verbose */
+static int w_flag; /* do r/w test: 0=no, 1=yes,
+ * 2=non-destructive */
+static int s_flag; /* show progress of test */
+static int force; /* force check of mounted device */
+static int t_flag; /* number of test patterns */
+static int t_max; /* allocated test patterns */
+static unsigned int *t_patts; /* test patterns */
+static int use_buffered_io;
+static int exclusive_ok;
+static unsigned int max_bb = MAX_BAD_BLOCKS; /* Abort test if more than this
+ * number of bad blocks has been
+ * encountered */
+static unsigned int d_flag; /* delay factor between reads */
+static struct timeval time_start;
+
+#define T_INC 32
+
+static unsigned int sys_page_size = 4096;
+
+static void usage(void)
+{
+ fprintf(stderr, _(
+"Usage: %s [-b block_size] [-i input_file] [-o output_file] [-svwnfBX]\n"
+" [-c blocks_at_once] [-d delay_factor_between_reads] [-e max_bad_blocks]\n"
+" [-p num_passes] [-t test_pattern [-t test_pattern [...]]]\n"
+" device [last_block [first_block]]\n"),
+ program_name);
+ exit (1);
+}
+
+static void exclusive_usage(void)
+{
+ fprintf(stderr,
+ _("%s: The -n and -w options are mutually exclusive.\n\n"),
+ program_name);
+ exit(1);
+}
+
+static blk_t currently_testing = 0;
+static blk_t num_blocks = 0;
+static blk_t num_read_errors = 0;
+static blk_t num_write_errors = 0;
+static blk_t num_corruption_errors = 0;
+static ext2_badblocks_list bb_list = NULL;
+static FILE *out;
+static blk_t next_bad = 0;
+static ext2_badblocks_iterate bb_iter = NULL;
+
+enum error_types { READ_ERROR, WRITE_ERROR, CORRUPTION_ERROR };
+
+static void *allocate_buffer(size_t size)
+{
+ void *ret = 0;
+
+#ifdef HAVE_POSIX_MEMALIGN
+ if (posix_memalign(&ret, sys_page_size, size) != 0)
+ ret = 0;
+#else
+#ifdef HAVE_MEMALIGN
+ ret = memalign(sys_page_size, size);
+#else
+#ifdef HAVE_VALLOC
+ ret = valloc(size);
+#endif /* HAVE_VALLOC */
+#endif /* HAVE_MEMALIGN */
+#endif /* HAVE_POSIX_MEMALIGN */
+
+ if (!ret)
+ ret = malloc(size);
+
+ return ret;
+}
+
+/*
+ * This routine reports a new bad block. If the bad block has already
+ * been seen before, then it returns 0; otherwise it returns 1.
+ */
+static int bb_output (blk_t bad, enum error_types error_type)
+{
+ errcode_t errcode;
+
+ if (ext2fs_badblocks_list_test(bb_list, bad))
+ return 0;
+
+ fprintf(out, "%lu\n", (unsigned long) bad);
+ fflush(out);
+
+ errcode = ext2fs_badblocks_list_add (bb_list, bad);
+ if (errcode) {
+ com_err (program_name, errcode, "adding to in-memory bad block list");
+ exit (1);
+ }
+
+ /* kludge:
+ increment the iteration through the bb_list if
+ an element was just added before the current iteration
+ position. This should not cause next_bad to change. */
+ if (bb_iter && bad < next_bad)
+ ext2fs_badblocks_list_iterate (bb_iter, &next_bad);
+
+ if (error_type == READ_ERROR) {
+ num_read_errors++;
+ } else if (error_type == WRITE_ERROR) {
+ num_write_errors++;
+ } else if (error_type == CORRUPTION_ERROR) {
+ num_corruption_errors++;
+ }
+ return 1;
+}
+
+static char *time_diff_format(struct timeval *tv1,
+ struct timeval *tv2, char *buf)
+{
+ time_t diff = (tv1->tv_sec - tv2->tv_sec);
+ int hr,min,sec;
+
+ sec = diff % 60;
+ diff /= 60;
+ min = diff % 60;
+ hr = diff / 60;
+
+ if (hr)
+ sprintf(buf, "%d:%02d:%02d", hr, min, sec);
+ else
+ sprintf(buf, "%d:%02d", min, sec);
+ return buf;
+}
+
+static float calc_percent(unsigned long current, unsigned long total) {
+ float percent = 0.0;
+ if (total <= 0)
+ return percent;
+ if (current >= total) {
+ percent = 100.0;
+ } else {
+ percent=(100.0*(float)current/(float)total);
+ }
+ return percent;
+}
+
+static void print_status(void)
+{
+ struct timeval time_end;
+ char diff_buf[32], line_buf[128];
+#ifdef HAVE_MBSTOWCS
+ wchar_t wline_buf[128];
+#endif
+ int len;
+
+ gettimeofday(&time_end, 0);
+ len = snprintf(line_buf, sizeof(line_buf),
+ _("%6.2f%% done, %s elapsed. "
+ "(%d/%d/%d errors)"),
+ calc_percent((unsigned long) currently_testing,
+ (unsigned long) num_blocks),
+ time_diff_format(&time_end, &time_start, diff_buf),
+ num_read_errors,
+ num_write_errors,
+ num_corruption_errors);
+#ifdef HAVE_MBSTOWCS
+ mbstowcs(wline_buf, line_buf, sizeof(line_buf));
+ len = wcswidth(wline_buf, sizeof(line_buf));
+ if (len < 0)
+ len = strlen(line_buf); /* Should never happen... */
+#endif
+ fputs(line_buf, stderr);
+ memset(line_buf, '\b', len);
+ line_buf[len] = 0;
+ fputs(line_buf, stderr);
+ fflush (stderr);
+}
+
+static void alarm_intr(int alnum EXT2FS_ATTR((unused)))
+{
+ signal (SIGALRM, alarm_intr);
+ alarm(1);
+ if (!num_blocks)
+ return;
+ print_status();
+}
+
+static void *terminate_addr = NULL;
+
+static void terminate_intr(int signo EXT2FS_ATTR((unused)))
+{
+ fflush(out);
+ fprintf(stderr, "\n\nInterrupted at block %llu\n",
+ (unsigned long long) currently_testing);
+ fflush(stderr);
+ if (terminate_addr)
+ longjmp(terminate_addr,1);
+ exit(1);
+}
+
+static void capture_terminate(jmp_buf term_addr)
+{
+ terminate_addr = term_addr;
+ signal (SIGHUP, terminate_intr);
+ signal (SIGINT, terminate_intr);
+ signal (SIGPIPE, terminate_intr);
+ signal (SIGTERM, terminate_intr);
+ signal (SIGUSR1, terminate_intr);
+ signal (SIGUSR2, terminate_intr);
+}
+
+static void uncapture_terminate(void)
+{
+ terminate_addr = NULL;
+ signal (SIGHUP, SIG_DFL);
+ signal (SIGINT, SIG_DFL);
+ signal (SIGPIPE, SIG_DFL);
+ signal (SIGTERM, SIG_DFL);
+ signal (SIGUSR1, SIG_DFL);
+ signal (SIGUSR2, SIG_DFL);
+}
+
+/* Linux requires that O_DIRECT I/Os be 512-byte sector aligned */
+
+#define O_DIRECT_SIZE 512
+
+static void set_o_direct(int dev, unsigned char *buffer, size_t size,
+ ext2_loff_t offset)
+{
+#ifdef O_DIRECT
+ static int current_O_DIRECT; /* Current status of O_DIRECT flag */
+ int new_flag = O_DIRECT;
+ int flag;
+
+ if ((use_buffered_io != 0) ||
+ (((unsigned long) buffer & (sys_page_size - 1)) != 0) ||
+ ((size & (sys_page_size - 1)) != 0) ||
+ ((offset & (O_DIRECT_SIZE - 1)) != 0))
+ new_flag = 0;
+
+ if (new_flag != current_O_DIRECT) {
+ /* printf("%s O_DIRECT\n", new_flag ? "Setting" : "Clearing"); */
+ flag = fcntl(dev, F_GETFL);
+ if (flag > 0) {
+ flag = (flag & ~O_DIRECT) | new_flag;
+ if (fcntl(dev, F_SETFL, flag) < 0)
+ perror("set_o_direct");
+ }
+ current_O_DIRECT = new_flag;
+ }
+#endif
+}
+
+
+static void pattern_fill(unsigned char *buffer, unsigned int pattern,
+ size_t n)
+{
+ unsigned int i, nb;
+ unsigned char bpattern[sizeof(pattern)], *ptr;
+
+ if (pattern == (unsigned int) ~0) {
+ for (ptr = buffer; ptr < buffer + n; ptr++) {
+ (*ptr) = random() % (1 << (8 * sizeof(char)));
+ }
+ if (s_flag | v_flag)
+ fputs(_("Testing with random pattern: "), stderr);
+ } else {
+ bpattern[0] = 0;
+ for (i = 0; i < sizeof(bpattern); i++) {
+ if (pattern == 0)
+ break;
+ bpattern[i] = pattern & 0xFF;
+ pattern = pattern >> 8;
+ }
+ nb = i ? (i-1) : 0;
+ for (ptr = buffer, i = nb; ptr < buffer + n; ptr++) {
+ *ptr = bpattern[i];
+ if (i == 0)
+ i = nb;
+ else
+ i--;
+ }
+ if (s_flag | v_flag) {
+ fputs(_("Testing with pattern 0x"), stderr);
+ for (i = 0; i <= nb; i++)
+ fprintf(stderr, "%02x", buffer[i]);
+ fputs(": ", stderr);
+ }
+ }
+}
+
+/*
+ * Perform a read of a sequence of blocks; return the number of blocks
+ * successfully sequentially read.
+ */
+static int do_read (int dev, unsigned char * buffer, int try, int block_size,
+ blk_t current_block)
+{
+ long got;
+ struct timeval tv1, tv2;
+#define NANOSEC (1000000000L)
+#define MILISEC (1000L)
+
+#if 0
+ printf("do_read: block %d, try %d\n", current_block, try);
+#endif
+ set_o_direct(dev, buffer, try * block_size,
+ ((ext2_loff_t) current_block) * block_size);
+
+ if (v_flag > 1)
+ print_status();
+
+ /* Seek to the correct loc. */
+ if (ext2fs_llseek (dev, (ext2_loff_t) current_block * block_size,
+ SEEK_SET) != (ext2_loff_t) current_block * block_size)
+ com_err (program_name, errno, "%s", _("during seek"));
+
+ /* Try the read */
+ if (d_flag)
+ gettimeofday(&tv1, NULL);
+ got = read (dev, buffer, (size_t) try * block_size);
+ if (d_flag)
+ gettimeofday(&tv2, NULL);
+ if (got < 0)
+ got = 0;
+ if (got & 511)
+ fprintf(stderr, _("Weird value (%ld) in do_read\n"), got);
+ got /= block_size;
+ if (d_flag && got == try) {
+#ifdef HAVE_NANOSLEEP
+ struct timespec ts;
+ ts.tv_sec = tv2.tv_sec - tv1.tv_sec;
+ ts.tv_nsec = (tv2.tv_usec - tv1.tv_usec) * MILISEC;
+ if (ts.tv_nsec < 0) {
+ ts.tv_nsec += NANOSEC;
+ ts.tv_sec -= 1;
+ }
+ /* increase/decrease the sleep time based on d_flag value */
+ ts.tv_sec = ts.tv_sec * d_flag / 100;
+ ts.tv_nsec = ts.tv_nsec * d_flag / 100;
+ if (ts.tv_nsec > NANOSEC) {
+ ts.tv_sec += ts.tv_nsec / NANOSEC;
+ ts.tv_nsec %= NANOSEC;
+ }
+ if (ts.tv_sec || ts.tv_nsec)
+ nanosleep(&ts, NULL);
+#else
+#ifdef HAVE_USLEEP
+ struct timeval tv;
+ tv.tv_sec = tv2.tv_sec - tv1.tv_sec;
+ tv.tv_usec = tv2.tv_usec - tv1.tv_usec;
+ tv.tv_sec = tv.tv_sec * d_flag / 100;
+ tv.tv_usec = tv.tv_usec * d_flag / 100;
+ if (tv.tv_usec > 1000000) {
+ tv.tv_sec += tv.tv_usec / 1000000;
+ tv.tv_usec %= 1000000;
+ }
+ if (tv.tv_sec)
+ sleep(tv.tv_sec);
+ if (tv.tv_usec)
+ usleep(tv.tv_usec);
+#endif
+#endif
+ }
+ return got;
+}
+
+/*
+ * Perform a write of a sequence of blocks; return the number of blocks
+ * successfully sequentially written.
+ */
+static int do_write(int dev, unsigned char * buffer, int try, int block_size,
+ unsigned long current_block)
+{
+ long got;
+
+#if 0
+ printf("do_write: block %lu, try %d\n", current_block, try);
+#endif
+ set_o_direct(dev, buffer, try * block_size,
+ ((ext2_loff_t) current_block) * block_size);
+
+ if (v_flag > 1)
+ print_status();
+
+ /* Seek to the correct loc. */
+ if (ext2fs_llseek (dev, (ext2_loff_t) current_block * block_size,
+ SEEK_SET) != (ext2_loff_t) current_block * block_size)
+ com_err (program_name, errno, "%s", _("during seek"));
+
+ /* Try the write */
+ got = write (dev, buffer, (size_t) try * block_size);
+ if (got < 0)
+ got = 0;
+ if (got & 511)
+ fprintf(stderr, "Weird value (%ld) in do_write\n", got);
+ got /= block_size;
+ return got;
+}
+
+static int host_dev;
+
+static void flush_bufs(void)
+{
+ errcode_t retval;
+
+#ifdef O_DIRECT
+ if (!use_buffered_io)
+ return;
+#endif
+ retval = ext2fs_sync_device(host_dev, 1);
+ if (retval)
+ com_err(program_name, retval, "%s",
+ _("during ext2fs_sync_device"));
+}
+
+static unsigned int test_ro (int dev, blk_t last_block,
+ int block_size, blk_t first_block,
+ unsigned int blocks_at_once)
+{
+ unsigned char * blkbuf;
+ int try;
+ int got;
+ unsigned int bb_count = 0;
+ errcode_t errcode;
+ blk_t recover_block = ~0;
+
+ /* set up abend handler */
+ capture_terminate(NULL);
+
+ errcode = ext2fs_badblocks_list_iterate_begin(bb_list,&bb_iter);
+ if (errcode) {
+ com_err(program_name, errcode, "%s",
+ _("while beginning bad block list iteration"));
+ exit (1);
+ }
+ do {
+ ext2fs_badblocks_list_iterate (bb_iter, &next_bad);
+ } while (next_bad && next_bad < first_block);
+
+ if (t_flag) {
+ blkbuf = allocate_buffer(((size_t) blocks_at_once + 1) * block_size);
+ } else {
+ blkbuf = allocate_buffer((size_t) blocks_at_once * block_size);
+ }
+ if (!blkbuf)
+ {
+ com_err(program_name, ENOMEM, "%s",
+ _("while allocating buffers"));
+ exit (1);
+ }
+ if (v_flag) {
+ fprintf(stderr, _("Checking blocks %lu to %lu\n"),
+ (unsigned long)first_block,
+ (unsigned long)last_block - 1);
+ }
+ if (t_flag) {
+ fputs(_("Checking for bad blocks in read-only mode\n"), stderr);
+ pattern_fill(blkbuf + blocks_at_once * block_size,
+ t_patts[0], block_size);
+ }
+ flush_bufs();
+ try = blocks_at_once;
+ currently_testing = first_block;
+ num_blocks = last_block - 1;
+ if (!t_flag && (s_flag || v_flag))
+ fputs(_("Checking for bad blocks (read-only test): "), stderr);
+ if (s_flag && v_flag <= 1)
+ alarm_intr(SIGALRM);
+ while (currently_testing < last_block)
+ {
+ if (bb_count >= max_bb) {
+ if (s_flag || v_flag) {
+ fputs(_("Too many bad blocks, aborting test\n"), stderr);
+ }
+ break;
+ }
+ if (next_bad) {
+ if (currently_testing == next_bad) {
+ /* fprintf (out, "%lu\n", nextbad); */
+ ext2fs_badblocks_list_iterate (bb_iter, &next_bad);
+ currently_testing++;
+ continue;
+ }
+ else if (currently_testing + try > next_bad)
+ try = next_bad - currently_testing;
+ }
+ if (currently_testing + try > last_block)
+ try = last_block - currently_testing;
+ got = do_read (dev, blkbuf, try, block_size, currently_testing);
+ if (t_flag) {
+ /* test the comparison between all the
+ blocks successfully read */
+ int i;
+ for (i = 0; i < got; ++i)
+ if (memcmp (blkbuf+i*block_size,
+ blkbuf+blocks_at_once*block_size,
+ block_size))
+ bb_count += bb_output(currently_testing + i, CORRUPTION_ERROR);
+ }
+ if (got == 0 && try == 1)
+ bb_count += bb_output(currently_testing++, READ_ERROR);
+ currently_testing += got;
+ if (got != try) {
+ try = 1;
+ if (recover_block == ~0U)
+ recover_block = currently_testing - got +
+ blocks_at_once;
+ continue;
+ } else if (currently_testing == recover_block) {
+ try = blocks_at_once;
+ recover_block = ~0;
+ }
+ }
+ num_blocks = 0;
+ alarm(0);
+ if (s_flag || v_flag)
+ fputs(_(done_string), stderr);
+
+ fflush (stderr);
+ free (blkbuf);
+
+ ext2fs_badblocks_list_iterate_end(bb_iter);
+
+ uncapture_terminate();
+
+ return bb_count;
+}
+
+static unsigned int test_rw (int dev, blk_t last_block,
+ int block_size, blk_t first_block,
+ unsigned int blocks_at_once)
+{
+ unsigned char *buffer, *read_buffer;
+ const unsigned int patterns[] = {0xaa, 0x55, 0xff, 0x00};
+ const unsigned int *pattern;
+ int i, try, got, nr_pattern, pat_idx;
+ unsigned int bb_count = 0;
+ blk_t recover_block = ~0;
+
+ /* set up abend handler */
+ capture_terminate(NULL);
+
+ buffer = allocate_buffer((size_t) 2 * blocks_at_once * block_size);
+ read_buffer = buffer + blocks_at_once * block_size;
+
+ if (!buffer) {
+ com_err(program_name, ENOMEM, "%s",
+ _("while allocating buffers"));
+ exit (1);
+ }
+
+ flush_bufs();
+
+ if (v_flag) {
+ fputs(_("Checking for bad blocks in read-write mode\n"),
+ stderr);
+ fprintf(stderr, _("From block %lu to %lu\n"),
+ (unsigned long) first_block,
+ (unsigned long) last_block - 1);
+ }
+ if (t_flag) {
+ pattern = t_patts;
+ nr_pattern = t_flag;
+ } else {
+ pattern = patterns;
+ nr_pattern = sizeof(patterns) / sizeof(patterns[0]);
+ }
+ for (pat_idx = 0; pat_idx < nr_pattern; pat_idx++) {
+ pattern_fill(buffer, pattern[pat_idx],
+ blocks_at_once * block_size);
+ num_blocks = last_block - 1;
+ currently_testing = first_block;
+ if (s_flag && v_flag <= 1)
+ alarm_intr(SIGALRM);
+
+ try = blocks_at_once;
+ while (currently_testing < last_block) {
+ if (bb_count >= max_bb) {
+ if (s_flag || v_flag) {
+ fputs(_("Too many bad blocks, aborting test\n"), stderr);
+ }
+ break;
+ }
+ if (currently_testing + try > last_block)
+ try = last_block - currently_testing;
+ got = do_write(dev, buffer, try, block_size,
+ currently_testing);
+ if (v_flag > 1)
+ print_status();
+
+ if (got == 0 && try == 1)
+ bb_count += bb_output(currently_testing++, WRITE_ERROR);
+ currently_testing += got;
+ if (got != try) {
+ try = 1;
+ if (recover_block == ~0U)
+ recover_block = currently_testing -
+ got + blocks_at_once;
+ continue;
+ } else if (currently_testing == recover_block) {
+ try = blocks_at_once;
+ recover_block = ~0;
+ }
+ }
+
+ num_blocks = 0;
+ alarm (0);
+ if (s_flag | v_flag)
+ fputs(_(done_string), stderr);
+ flush_bufs();
+ if (s_flag | v_flag)
+ fputs(_("Reading and comparing: "), stderr);
+ num_blocks = last_block;
+ currently_testing = first_block;
+ if (s_flag && v_flag <= 1)
+ alarm_intr(SIGALRM);
+
+ try = blocks_at_once;
+ while (currently_testing < last_block) {
+ if (bb_count >= max_bb) {
+ if (s_flag || v_flag) {
+ fputs(_("Too many bad blocks, aborting test\n"), stderr);
+ }
+ break;
+ }
+ if (currently_testing + try > last_block)
+ try = last_block - currently_testing;
+ got = do_read (dev, read_buffer, try, block_size,
+ currently_testing);
+ if (got == 0 && try == 1)
+ bb_count += bb_output(currently_testing++, READ_ERROR);
+ currently_testing += got;
+ if (got != try) {
+ try = 1;
+ if (recover_block == ~0U)
+ recover_block = currently_testing -
+ got + blocks_at_once;
+ continue;
+ } else if (currently_testing == recover_block) {
+ try = blocks_at_once;
+ recover_block = ~0U;
+ }
+ for (i=0; i < got; i++) {
+ if (memcmp(read_buffer + i * block_size,
+ buffer + i * block_size,
+ block_size))
+ bb_count += bb_output(currently_testing+i, CORRUPTION_ERROR);
+ }
+ if (v_flag > 1)
+ print_status();
+ }
+
+ num_blocks = 0;
+ alarm (0);
+ if (s_flag | v_flag)
+ fputs(_(done_string), stderr);
+ flush_bufs();
+ }
+ uncapture_terminate();
+ free(buffer);
+ return bb_count;
+}
+
+struct saved_blk_record {
+ blk_t block;
+ int num;
+};
+
+static unsigned int test_nd (int dev, blk_t last_block,
+ int block_size, blk_t first_block,
+ unsigned int blocks_at_once)
+{
+ unsigned char *blkbuf, *save_ptr, *test_ptr, *read_ptr;
+ unsigned char *test_base, *save_base, *read_base;
+ int try, i;
+ const unsigned int patterns[] = { ~0 };
+ const unsigned int *pattern;
+ int nr_pattern, pat_idx;
+ int got, used2, written;
+ blk_t save_currently_testing;
+ struct saved_blk_record *test_record;
+ /* This is static to prevent being clobbered by the longjmp */
+ static int num_saved;
+ jmp_buf terminate_env;
+ errcode_t errcode;
+ unsigned long buf_used;
+ static unsigned int bb_count;
+ unsigned int granularity = blocks_at_once;
+ blk_t recover_block = ~0U;
+
+ bb_count = 0;
+ errcode = ext2fs_badblocks_list_iterate_begin(bb_list,&bb_iter);
+ if (errcode) {
+ com_err(program_name, errcode, "%s",
+ _("while beginning bad block list iteration"));
+ exit (1);
+ }
+ do {
+ ext2fs_badblocks_list_iterate (bb_iter, &next_bad);
+ } while (next_bad && next_bad < first_block);
+
+ blkbuf = allocate_buffer((size_t) 3 * blocks_at_once * block_size);
+ test_record = malloc(blocks_at_once * sizeof(struct saved_blk_record));
+ if (!blkbuf || !test_record) {
+ com_err(program_name, ENOMEM, "%s",
+ _("while allocating buffers"));
+ exit (1);
+ }
+
+ save_base = blkbuf;
+ test_base = blkbuf + (blocks_at_once * block_size);
+ read_base = blkbuf + (2 * blocks_at_once * block_size);
+
+ num_saved = 0;
+
+ flush_bufs();
+ if (v_flag) {
+ fputs(_("Checking for bad blocks in non-destructive read-write mode\n"), stderr);
+ fprintf (stderr, _("From block %lu to %lu\n"),
+ (unsigned long) first_block,
+ (unsigned long) last_block - 1);
+ }
+ if (s_flag || v_flag > 1) {
+ fputs(_("Checking for bad blocks (non-destructive read-write test)\n"), stderr);
+ }
+ if (setjmp(terminate_env)) {
+ /*
+ * Abnormal termination by a signal is handled here.
+ */
+ signal (SIGALRM, SIG_IGN);
+ fputs(_("\nInterrupt caught, cleaning up\n"), stderr);
+
+ save_ptr = save_base;
+ for (i=0; i < num_saved; i++) {
+ do_write(dev, save_ptr, test_record[i].num,
+ block_size, test_record[i].block);
+ save_ptr += test_record[i].num * block_size;
+ }
+ fflush (out);
+ exit(1);
+ }
+
+ /* set up abend handler */
+ capture_terminate(terminate_env);
+
+ if (t_flag) {
+ pattern = t_patts;
+ nr_pattern = t_flag;
+ } else {
+ pattern = patterns;
+ nr_pattern = sizeof(patterns) / sizeof(patterns[0]);
+ }
+ for (pat_idx = 0; pat_idx < nr_pattern; pat_idx++) {
+ pattern_fill(test_base, pattern[pat_idx],
+ blocks_at_once * block_size);
+
+ buf_used = 0;
+ bb_count = 0;
+ save_ptr = save_base;
+ test_ptr = test_base;
+ currently_testing = first_block;
+ num_blocks = last_block - 1;
+ if (s_flag && v_flag <= 1)
+ alarm_intr(SIGALRM);
+
+ while (currently_testing < last_block) {
+ if (bb_count >= max_bb) {
+ if (s_flag || v_flag) {
+ fputs(_("Too many bad blocks, aborting test\n"), stderr);
+ }
+ break;
+ }
+ got = try = granularity - buf_used;
+ if (next_bad) {
+ if (currently_testing == next_bad) {
+ /* fprintf (out, "%lu\n", nextbad); */
+ ext2fs_badblocks_list_iterate (bb_iter, &next_bad);
+ currently_testing++;
+ goto check_for_more;
+ }
+ else if (currently_testing + try > next_bad)
+ try = next_bad - currently_testing;
+ }
+ if (currently_testing + try > last_block)
+ try = last_block - currently_testing;
+ got = do_read (dev, save_ptr, try, block_size,
+ currently_testing);
+ if (got == 0) {
+ if (recover_block == ~0U)
+ recover_block = currently_testing +
+ blocks_at_once;
+ if (granularity != 1) {
+ granularity = 1;
+ continue;
+ }
+ /* First block must have been bad. */
+ bb_count += bb_output(currently_testing++, READ_ERROR);
+ goto check_for_more;
+ }
+
+ /*
+ * Note the fact that we've saved this much data
+ * *before* we overwrite it with test data
+ */
+ test_record[num_saved].block = currently_testing;
+ test_record[num_saved].num = got;
+ num_saved++;
+
+ /* Write the test data */
+ written = do_write (dev, test_ptr, got, block_size,
+ currently_testing);
+ if (written != got)
+ com_err (program_name, errno,
+ _("during test data write, block %lu"),
+ (unsigned long) currently_testing +
+ written);
+
+ buf_used += got;
+ save_ptr += got * block_size;
+ test_ptr += got * block_size;
+ currently_testing += got;
+ if (got != try) {
+ if (recover_block == ~0U)
+ recover_block = currently_testing -
+ got + blocks_at_once;
+ continue;
+ }
+
+ check_for_more:
+ /*
+ * If there's room for more blocks to be tested this
+ * around, and we're not done yet testing the disk, go
+ * back and get some more blocks.
+ */
+ if ((buf_used != granularity) &&
+ (currently_testing < last_block))
+ continue;
+
+ if (currently_testing >= recover_block) {
+ granularity = blocks_at_once;
+ recover_block = ~0;
+ }
+
+ flush_bufs();
+ save_currently_testing = currently_testing;
+
+ /*
+ * for each contiguous block that we read into the
+ * buffer (and wrote test data into afterwards), read
+ * it back (looping if necessary, to get past newly
+ * discovered unreadable blocks, of which there should
+ * be none, but with a hard drive which is unreliable,
+ * it has happened), and compare with the test data
+ * that was written; output to the bad block list if
+ * it doesn't match.
+ */
+ used2 = 0;
+ save_ptr = save_base;
+ test_ptr = test_base;
+ read_ptr = read_base;
+ try = 0;
+
+ while (1) {
+ if (try == 0) {
+ if (used2 >= num_saved)
+ break;
+ currently_testing = test_record[used2].block;
+ try = test_record[used2].num;
+ used2++;
+ }
+
+ got = do_read (dev, read_ptr, try,
+ block_size, currently_testing);
+
+ /* test the comparison between all the
+ blocks successfully read */
+ for (i = 0; i < got; ++i)
+ if (memcmp (test_ptr+i*block_size,
+ read_ptr+i*block_size, block_size))
+ bb_count += bb_output(currently_testing + i, CORRUPTION_ERROR);
+ if (got < try) {
+ bb_count += bb_output(currently_testing + got, READ_ERROR);
+ got++;
+ }
+
+ /* write back original data */
+ do_write (dev, save_ptr, got,
+ block_size, currently_testing);
+ save_ptr += got * block_size;
+
+ currently_testing += got;
+ test_ptr += got * block_size;
+ read_ptr += got * block_size;
+ try -= got;
+ }
+
+ /* empty the buffer so it can be reused */
+ num_saved = 0;
+ buf_used = 0;
+ save_ptr = save_base;
+ test_ptr = test_base;
+ currently_testing = save_currently_testing;
+ }
+ num_blocks = 0;
+ alarm(0);
+ if (s_flag || v_flag > 1)
+ fputs(_(done_string), stderr);
+
+ flush_bufs();
+ }
+ uncapture_terminate();
+ fflush(stderr);
+ free(blkbuf);
+ free(test_record);
+
+ ext2fs_badblocks_list_iterate_end(bb_iter);
+
+ return bb_count;
+}
+
+static void check_mount(char *device_name)
+{
+ errcode_t retval;
+ int mount_flags;
+
+ retval = ext2fs_check_if_mounted(device_name, &mount_flags);
+ if (retval) {
+ com_err("ext2fs_check_if_mount", retval,
+ _("while determining whether %s is mounted."),
+ device_name);
+ return;
+ }
+ if (mount_flags & EXT2_MF_MOUNTED) {
+ fprintf(stderr, _("%s is mounted; "), device_name);
+ if (force) {
+ fputs(_("badblocks forced anyway. "
+ "Hope /etc/mtab is incorrect.\n"), stderr);
+ return;
+ }
+ abort_badblocks:
+ fputs(_("it's not safe to run badblocks!\n"), stderr);
+ exit(1);
+ }
+
+ if ((mount_flags & EXT2_MF_BUSY) && !exclusive_ok) {
+ fprintf(stderr, _("%s is apparently in use by the system; "),
+ device_name);
+ if (force)
+ fputs(_("badblocks forced anyway.\n"), stderr);
+ else
+ goto abort_badblocks;
+ }
+
+}
+
+/*
+ * This function will convert a string to an unsigned long, printing
+ * an error message if it fails, and returning success or failure in err.
+ */
+static unsigned int parse_uint(const char *str, const char *descr)
+{
+ char *tmp;
+ unsigned long ret;
+
+ errno = 0;
+ ret = strtoul(str, &tmp, 0);
+ if (*tmp || errno) {
+ com_err (program_name, 0, _("invalid %s - %s"), descr, str);
+ exit (1);
+ } else if ((ret > UINT_MAX) ||
+ (ret == ULONG_MAX && errno == ERANGE)) {
+ com_err (program_name, 0, _("%s too large - %lu"), descr, ret);
+ exit (1);
+ }
+ return ret;
+}
+
+int main (int argc, char ** argv)
+{
+ int c;
+ char * device_name;
+ char * host_device_name = NULL;
+ char * input_file = NULL;
+ char * output_file = NULL;
+ FILE * in = NULL;
+ unsigned int block_size = 1024;
+ unsigned int blocks_at_once = 64;
+ blk64_t last_block, first_block;
+ int num_passes = 0;
+ int passes_clean = 0;
+ int dev;
+ errcode_t errcode;
+ unsigned int pattern;
+ unsigned int (*test_func)(int, blk_t,
+ int, blk_t,
+ unsigned int);
+ int open_flag;
+ long sysval;
+ unsigned long long inblk;
+
+ setbuf(stdout, NULL);
+ setbuf(stderr, NULL);
+#ifdef ENABLE_NLS
+ setlocale(LC_MESSAGES, "");
+ setlocale(LC_CTYPE, "");
+ bindtextdomain(NLS_CAT_NAME, LOCALEDIR);
+ textdomain(NLS_CAT_NAME);
+ set_com_err_gettext(gettext);
+#endif
+ srandom((unsigned int)time(NULL)); /* simple randomness is enough */
+ test_func = test_ro;
+
+ /* Determine the system page size if possible */
+#ifdef HAVE_SYSCONF
+#if (!defined(_SC_PAGESIZE) && defined(_SC_PAGE_SIZE))
+#define _SC_PAGESIZE _SC_PAGE_SIZE
+#endif
+#ifdef _SC_PAGESIZE
+ sysval = sysconf(_SC_PAGESIZE);
+ if (sysval > 0)
+ sys_page_size = sysval;
+#endif /* _SC_PAGESIZE */
+#endif /* HAVE_SYSCONF */
+
+ if (argc && *argv)
+ program_name = *argv;
+ else
+ usage();
+ while ((c = getopt (argc, argv, "b:d:e:fi:o:svwnc:p:h:t:BX")) != EOF) {
+ switch (c) {
+ case 'b':
+ block_size = parse_uint(optarg, "block size");
+ break;
+ case 'f':
+ force++;
+ break;
+ case 'i':
+ input_file = optarg;
+ break;
+ case 'o':
+ output_file = optarg;
+ break;
+ case 's':
+ s_flag = 1;
+ break;
+ case 'v':
+ v_flag++;
+ break;
+ case 'w':
+ if (w_flag)
+ exclusive_usage();
+ test_func = test_rw;
+ w_flag = 1;
+ break;
+ case 'n':
+ if (w_flag)
+ exclusive_usage();
+ test_func = test_nd;
+ w_flag = 2;
+ break;
+ case 'c':
+ blocks_at_once = parse_uint(optarg, "blocks at once");
+ break;
+ case 'e':
+ max_bb = parse_uint(optarg, "max bad block count");
+ if (max_bb > MAX_BAD_BLOCKS) {
+ com_err (program_name, 0,
+ _("Too big max bad blocks count %u - "
+ "maximum is %u"), max_bb,
+ MAX_BAD_BLOCKS);
+ exit (1);
+ }
+ /* 0 really means unlimited but we cannot do that much... */
+ if (max_bb == 0)
+ max_bb = MAX_BAD_BLOCKS;
+ break;
+ case 'd':
+ d_flag = parse_uint(optarg, "read delay factor");
+ break;
+ case 'p':
+ num_passes = parse_uint(optarg,
+ "number of clean passes");
+ break;
+ case 'h':
+ host_device_name = optarg;
+ break;
+ case 't':
+ if (t_flag + 1 > t_max) {
+ unsigned int *t_patts_new;
+
+ t_patts_new = realloc(t_patts, sizeof(int) *
+ (t_max + T_INC));
+ if (!t_patts_new) {
+ com_err(program_name, ENOMEM,
+ _("can't allocate memory for "
+ "test_pattern - %s"),
+ optarg);
+ exit(1);
+ }
+ t_patts = t_patts_new;
+ t_max += T_INC;
+ }
+ if (!strcmp(optarg, "r") || !strcmp(optarg,"random")) {
+ t_patts[t_flag++] = ~0;
+ } else {
+ pattern = parse_uint(optarg, "test pattern");
+ if (pattern == (unsigned int) ~0)
+ pattern = 0xffff;
+ t_patts[t_flag++] = pattern;
+ }
+ break;
+ case 'B':
+ use_buffered_io = 1;
+ break;
+ case 'X':
+ exclusive_ok++;
+ break;
+ default:
+ usage();
+ }
+ }
+ if (!w_flag) {
+ if (t_flag > 1) {
+ com_err(program_name, 0, "%s",
+ _("Maximum of one test_pattern may be "
+ "specified in read-only mode"));
+ exit(1);
+ }
+ if (t_patts && (t_patts[0] == (unsigned int) ~0)) {
+ com_err(program_name, 0, "%s",
+ _("Random test_pattern is not allowed "
+ "in read-only mode"));
+ exit(1);
+ }
+ }
+ if ((block_size == 0) || (block_size > (1 << 24)) ||
+ (block_size & (block_size - 1))) {
+ com_err(program_name, 0, _("Invalid block size: %u\n"),
+ block_size);
+ exit(1);
+ }
+ if (blocks_at_once == 0) {
+ com_err(program_name, 0, _("Invalid number of blocks: %d\n"),
+ blocks_at_once);
+ exit(1);
+ } else if (((size_t) block_size * blocks_at_once) > SIZE_MAX / 3) {
+ /* maximum usage is in test_nd() */
+ com_err(program_name, 0, _("For block size %d, number of blocks too large: %d\n"),
+ block_size, blocks_at_once);
+ exit(1);
+ }
+
+ if (optind > argc - 1)
+ usage();
+ device_name = argv[optind++];
+ if (optind > argc - 1) {
+ errcode = ext2fs_get_device_size2(device_name,
+ (int) block_size,
+ &last_block);
+ if (errcode == EXT2_ET_UNIMPLEMENTED) {
+ com_err(program_name, 0, "%s",
+ _("Couldn't determine device size; you "
+ "must specify\nthe size manually\n"));
+ exit(1);
+ }
+ if (errcode) {
+ com_err(program_name, errcode, "%s",
+ _("while trying to determine device size"));
+ exit(1);
+ }
+ } else {
+ errno = 0;
+ last_block = parse_uint(argv[optind], _("last block"));
+ last_block++;
+ optind++;
+ }
+ if (optind <= argc-1) {
+ errno = 0;
+ first_block = parse_uint(argv[optind], _("first block"));
+ } else first_block = 0;
+ if (first_block >= last_block) {
+ com_err (program_name, 0, _("invalid starting block (%llu): must be less than %llu"),
+ (unsigned long long) first_block,
+ (unsigned long long) last_block);
+ exit (1);
+ }
+ /* ext2 badblocks file can't handle large values */
+ if (last_block >> 32) {
+ com_err(program_name, EOVERFLOW,
+ _("invalid end block (%llu): must be 32-bit value"),
+ (unsigned long long) last_block);
+ exit(1);
+ }
+ if (w_flag)
+ check_mount(device_name);
+
+ gettimeofday(&time_start, 0);
+ open_flag = O_LARGEFILE | (w_flag ? O_RDWR : O_RDONLY);
+ dev = open (device_name, open_flag);
+ if (dev == -1) {
+ com_err (program_name, errno, _("while trying to open %s"),
+ device_name);
+ exit (1);
+ }
+ if (host_device_name) {
+ host_dev = open (host_device_name, open_flag);
+ if (host_dev == -1) {
+ com_err (program_name, errno,
+ _("while trying to open %s"),
+ host_device_name);
+ exit (1);
+ }
+ } else
+ host_dev = dev;
+ if (input_file) {
+ if (strcmp (input_file, "-") == 0)
+ in = stdin;
+ else {
+ in = fopen (input_file, "r");
+ if (in == NULL)
+ {
+ com_err (program_name, errno,
+ _("while trying to open %s"),
+ input_file);
+ exit (1);
+ }
+ }
+ }
+ if (output_file && strcmp (output_file, "-") != 0)
+ {
+ out = fopen (output_file, "w");
+ if (out == NULL)
+ {
+ com_err (program_name, errno,
+ _("while trying to open %s"),
+ output_file);
+ exit (1);
+ }
+ }
+ else
+ out = stdout;
+
+ errcode = ext2fs_badblocks_list_create(&bb_list,0);
+ if (errcode) {
+ com_err(program_name, errcode, "%s",
+ _("while creating in-memory bad blocks list"));
+ exit (1);
+ }
+
+ if (in) {
+ for(;;) {
+ switch (fscanf(in, "%llu\n", &inblk)) {
+ case 0:
+ com_err(program_name, 0, "%s",
+ _("input file - bad format"));
+ exit (1);
+ case EOF:
+ break;
+ default:
+ if (inblk >> 32) {
+ com_err(program_name,
+ EOVERFLOW, "%s",
+ _("while adding to in-memory "
+ "bad block list"));
+ exit(1);
+ }
+ next_bad = inblk;
+ errcode = ext2fs_badblocks_list_add(bb_list,next_bad);
+ if (errcode) {
+ com_err(program_name, errcode,
+ "%s",
+ _("while adding to in-memory "
+ "bad block list"));
+ exit (1);
+ }
+ continue;
+ }
+ break;
+ }
+
+ if (in != stdin)
+ fclose (in);
+ }
+
+ do {
+ unsigned int bb_count;
+
+ bb_count = test_func(dev, last_block, (int) block_size,
+ first_block, blocks_at_once);
+ if (bb_count)
+ passes_clean = 0;
+ else
+ ++passes_clean;
+
+ if (v_flag)
+ fprintf(stderr,
+ _("Pass completed, %u bad blocks found. (%d/%d/%d errors)\n"),
+ bb_count, num_read_errors, num_write_errors, num_corruption_errors);
+
+ } while (passes_clean < num_passes);
+
+ close (dev);
+ if (out != stdout)
+ fclose (out);
+ free(t_patts);
+ return 0;
+}
diff --git a/misc/base_device.c b/misc/base_device.c
new file mode 100644
index 0000000..814a479
--- /dev/null
+++ b/misc/base_device.c
@@ -0,0 +1,171 @@
+/*
+ * base_device.c
+ *
+ * Return the "base device" given a particular device; this is used to
+ * assure that we only fsck one partition on a particular drive at any
+ * one time. Otherwise, the disk heads will be seeking all over the
+ * place. If the base device can not be determined, return NULL.
+ *
+ * The base_device() function returns an allocated string which must
+ * be freed.
+ *
+ * Written by Theodore Ts'o, <tytso@mit.edu>
+ *
+ * Copyright (C) 2000 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ * %End-Header%
+ */
+#include "config.h"
+#include <stdio.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#if HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#include <ctype.h>
+#include <string.h>
+
+#include "fsck.h"
+
+/*
+ * Required for the uber-silly devfs /dev/ide/host1/bus2/target3/lun3
+ * pathnames.
+ */
+static const char *devfs_hier[] = {
+ "host", "bus", "target", "lun", 0
+};
+
+char *base_device(const char *device)
+{
+ char *str, *cp;
+ const char **hier, *disk;
+ int len;
+
+ str = malloc(strlen(device)+1);
+ if (!str)
+ return NULL;
+ strcpy(str, device);
+ cp = str;
+
+ /* Skip over /dev/; if it's not present, give up. */
+ if (strncmp(cp, "/dev/", 5) != 0)
+ goto errout;
+ cp += 5;
+
+ /* Skip over /dev/dsk/... */
+ if (strncmp(cp, "dsk/", 4) == 0)
+ cp += 4;
+
+ /*
+ * For md devices, we treat them all as if they were all
+ * on one disk, since we don't know how to parallelize them.
+ */
+ if (cp[0] == 'm' && cp[1] == 'd') {
+ *(cp+2) = 0;
+ return str;
+ }
+
+ /* Handle DAC 960 devices */
+ if (strncmp(cp, "rd/", 3) == 0) {
+ cp += 3;
+ if (cp[0] != 'c' || cp[2] != 'd' ||
+ !isdigit(cp[1]) || !isdigit(cp[3]))
+ goto errout;
+ *(cp+4) = 0;
+ return str;
+ }
+
+ /* Now let's handle /dev/hd* and /dev/sd* devices.... */
+ if ((cp[0] == 'h' || cp[0] == 's') && (cp[1] == 'd')) {
+ cp += 2;
+ /* If there's a single number after /dev/hd, skip it */
+ if (isdigit(*cp))
+ cp++;
+ /* What follows must be an alpha char, or give up */
+ if (!isalpha(*cp))
+ goto errout;
+ *(cp + 1) = 0;
+ return str;
+ }
+
+ /* Now let's handle devfs (ugh) names */
+ len = 0;
+ if (strncmp(cp, "ide/", 4) == 0)
+ len = 4;
+ if (strncmp(cp, "scsi/", 5) == 0)
+ len = 5;
+ if (len) {
+ cp += len;
+ /*
+ * Now we proceed down the expected devfs hierarchy.
+ * i.e., .../host1/bus2/target3/lun4/...
+ * If we don't find the expected token, followed by
+ * some number of digits at each level, abort.
+ */
+ for (hier = devfs_hier; *hier; hier++) {
+ len = strlen(*hier);
+ if (strncmp(cp, *hier, len) != 0)
+ goto errout;
+ cp += len;
+ while (*cp != '/' && *cp != 0) {
+ if (!isdigit(*cp))
+ goto errout;
+ cp++;
+ }
+ cp++;
+ }
+ *(cp - 1) = 0;
+ return str;
+ }
+
+ /* Now handle devfs /dev/disc or /dev/disk names */
+ disk = 0;
+ if (strncmp(cp, "discs/", 6) == 0)
+ disk = "disc";
+ else if (strncmp(cp, "disks/", 6) == 0)
+ disk = "disk";
+ if (disk) {
+ cp += 6;
+ if (strncmp(cp, disk, 4) != 0)
+ goto errout;
+ cp += 4;
+ while (*cp != '/' && *cp != 0) {
+ if (!isdigit(*cp))
+ goto errout;
+ cp++;
+ }
+ *cp = 0;
+ return str;
+ }
+
+errout:
+ free(str);
+ return NULL;
+}
+
+#ifdef DEBUG
+int main(int argc, char** argv)
+{
+ char *base;
+ char buf[256], *cp;
+
+ while (1) {
+ if (fgets(buf, sizeof(buf), stdin) == NULL)
+ break;
+ cp = strchr(buf, '\n');
+ if (cp)
+ *cp = 0;
+ cp = strchr(buf, '\t');
+ if (cp)
+ *cp = 0;
+ base = base_device(buf);
+ printf("%s\t%s\n", buf, base ? base : "NONE");
+ free(base);
+ }
+ exit(0);
+}
+#endif
diff --git a/misc/base_device.tst b/misc/base_device.tst
new file mode 100644
index 0000000..609a58d
--- /dev/null
+++ b/misc/base_device.tst
@@ -0,0 +1,16 @@
+/dev/hda7 /dev/hda
+/dev/sda1 /dev/sda
+/dev/hda /dev/hda
+/dev/sda /dev/sda
+/dev/dsk/hda6 /dev/dsk/hda
+/dev/dsk/sda5 /dev/dsk/sda
+/dev/md4 /dev/md
+/dev/md/4 /dev/md
+/dev/ide/host0/bus1/target2/lun3 /dev/ide/host0/bus1/target2/lun3
+/dev/ide/host0/bus1/target2/lun3/part10 /dev/ide/host0/bus1/target2/lun3
+/dev/ide/host0/bus1/target2/lun3/ /dev/ide/host0/bus1/target2/lun3
+/dev/disks/disk2/part2 /dev/disks/disk2
+/dev/disks/disk2/ /dev/disks/disk2
+/dev/disks/disk2 /dev/disks/disk2
+/dev/discs/disc1/part10 /dev/discs/disc1
+/dev/discs/disc1/ /dev/discs/disc1
diff --git a/misc/blkid.8.in b/misc/blkid.8.in
new file mode 100644
index 0000000..79be1c0
--- /dev/null
+++ b/misc/blkid.8.in
@@ -0,0 +1,164 @@
+.\" Copyright 2000 Andreas Dilger (adilger@turbolinux.com)
+.\"
+.\" This man page was created for blkid from e2fsprogs-1.25.
+.\"
+.\" This file may be copied under the terms of the GNU Public License.
+.\"
+.\" Based on uuidgen, Mon Sep 17 10:42:12 2000, Andreas Dilger
+.TH BLKID 8 "@E2FSPROGS_MONTH@ @E2FSPROGS_YEAR@" "E2fsprogs version @E2FSPROGS_VERSION@"
+.SH NAME
+blkid \- command\-line utility to locate/print block device attributes
+.SH SYNOPSIS
+.B blkid
+[
+.B \-ghlLv
+]
+[
+[
+.B \-c
+.I cachefile
+]
+.B \-w
+.I writecachefile
+]
+[
+.B \-o
+.I format
+]
+[
+.B \-s
+.I tag
+]
+[
+.B \-t
+.IR NAME = value
+]
+[
+.I device ...
+]
+.SH DESCRIPTION
+The
+.B blkid
+program is the command-line interface to working with
+.BR libblkid (3)
+library. It can determine the type of content (e.g. file system, swap)
+a block device holds, and also attributes (tokens, NAME=value pairs)
+from the content metadata (e.g. LABEL or UUID fields).
+.PP
+.B blkid
+has two main forms of operation: either searching for a device with a
+specific NAME=value pair, or displaying NAME=value pairs for one or
+more devices.
+.SH OPTIONS
+.TP
+.BI \-c " cachefile"
+Read from
+.I cachefile
+instead of reading from the default cache file
+.IR /etc/blkid.tab .
+If you want to start with a clean cache (i.e. don't report devices previously
+scanned but not necessarily available at this time), specify
+.IR /dev/null .
+.TP
+.B \-g
+Perform a garbage collection pass on the blkid cache to remove
+devices which no longer exist.
+.TP
+.B \-h
+Display a usage message and exit.
+.TP
+.B \-l
+Look up one device that matches the search parameter specified using
+the
+.B \-t
+option. If there are multiple devices that match the specified search
+parameter, then the device with the highest priority is returned, and/or
+the first device found at a given priority. Device types in order of
+decreasing priority are Device Mapper, EVMS, LVM, MD, and finally regular
+block devices. If this option is not specified,
+.B blkid
+will print all of the devices that match the search parameter.
+.TP
+.BI \-o " format"
+Display
+.BR blkid 's
+output using the specified format. The
+.I format
+parameter may be
+.I full
+(the default),
+.I value
+(only print the value of the tags),
+.I list
+(print the devices in a user-friendly format),
+or
+.I device
+(only print the device name).
+.TP
+.B \-L
+Print the devices in a user-friendly list format. This is the
+equivalent of using the option \fB-o list\fR.
+.TP
+.BI \-s " tag"
+For each (specified) device, show only the tags that match
+.IR tag .
+It is possible to specify multiple
+.B \-s
+options. If no tag is specified, then all tokens are shown for all
+(specified) devices.
+In order to just refresh the cache without showing any tokens, use
+.B "-s none"
+with no other options.
+.TP
+.BI \-t " NAME" = "value"
+Search for block devices with tokens named
+.I NAME
+that have the value
+.IR value ,
+and display any devices which are found.
+Common values for
+.I NAME
+include
+.BR TYPE ,
+.BR LABEL ,
+and
+.BR UUID .
+If there are no devices specified on the command line, all block devices
+will be searched; otherwise only the specified devices are searched.
+.TP
+.B \-v
+Display version number and exit.
+.TP
+.BI \-w " writecachefile"
+Write the device cache to
+.I writecachefile
+instead of writing it to the default cache file
+.IR /etc/blkid.tab .
+If you don't want to save the cache to the default file, specify
+.IR /dev/null.
+If not specified it will be the same file as that given by the
+.B \-c
+option.
+.TP
+.I device
+Display tokens from only the specified device. It is possible to
+give multiple
+.I device
+options on the command line. If none is given, all devices which
+appear in
+.I /proc/partitions
+are shown, if they are recognized.
+.SH "RETURN CODE"
+If the specified token was found, or if any tags were shown from (specified)
+devices, 0 is returned. If the specified token was not found, or no
+(specified) devices could be identified, an exit code of 2 is returned.
+For usage or other errors, an exit code of 4 is returned.
+.SH AUTHOR
+.B blkid
+was written by Andreas Dilger for libblkid.
+.SH AVAILABILITY
+.B blkid
+is part the e2fsprogs package since version 1.26 and is available from
+http://e2fsprogs.sourceforge.net.
+.SH "SEE ALSO"
+.BR libblkid (3)
diff --git a/misc/blkid.c b/misc/blkid.c
new file mode 100644
index 0000000..472f017
--- /dev/null
+++ b/misc/blkid.c
@@ -0,0 +1,433 @@
+/*
+ * blkid.c - User command-line interface for libblkid
+ *
+ * Copyright (C) 2001 Andreas Dilger
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ * %End-Header%
+ */
+
+#include "config.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#ifdef HAVE_TERMIOS_H
+#include <termios.h>
+#endif
+#ifdef HAVE_TERMIO_H
+#include <termio.h>
+#endif
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#else
+extern int getopt(int argc, char * const argv[], const char *optstring);
+extern char *optarg;
+extern int optind;
+#endif
+
+#define OUTPUT_VALUE_ONLY 0x0001
+#define OUTPUT_DEVICE_ONLY 0x0002
+#define OUTPUT_PRETTY_LIST 0x0004
+
+#include "ext2fs/ext2fs.h"
+#include "blkid/blkid.h"
+
+static const char *progname = "blkid";
+
+static void print_version(FILE *out)
+{
+ fprintf(out, "%s %s (%s)\n", progname, BLKID_VERSION, BLKID_DATE);
+}
+
+static void usage(int error)
+{
+ FILE *out = error ? stderr : stdout;
+
+ print_version(out);
+ fprintf(out,
+ "usage:\t%s [-c <file>] [-ghlLv] [-o format] "
+ "[-s <tag>] [-t <token>]\n [-w <file>] [dev ...]\n"
+ "\t-c\tcache file (default: /etc/blkid.tab, /dev/null = none)\n"
+ "\t-h\tprint this usage message and exit\n"
+ "\t-g\tgarbage collect the blkid cache\n"
+ "\t-s\tshow specified tag(s) (default show all tags)\n"
+ "\t-t\tfind device with a specific token (NAME=value pair)\n"
+ "\t-l\tlookup the the first device with arguments specified by -t\n"
+ "\t-v\tprint version and exit\n"
+ "\t-w\twrite cache to different file (/dev/null = no write)\n"
+ "\tdev\tspecify device(s) to probe (default: all devices)\n",
+ progname);
+ exit(error);
+}
+
+/*
+ * This function does "safe" printing. It will convert non-printable
+ * ASCII characters using '^' and M- notation.
+ */
+static void safe_print(const char *cp, int len)
+{
+ unsigned char ch;
+
+ if (len < 0)
+ len = strlen(cp);
+
+ while (len--) {
+ ch = *cp++;
+ if (ch > 128) {
+ fputs("M-", stdout);
+ ch -= 128;
+ }
+ if ((ch < 32) || (ch == 0x7f)) {
+ fputc('^', stdout);
+ ch ^= 0x40; /* ^@, ^A, ^B; ^? for DEL */
+ }
+ if (ch != '"') {
+ fputc(ch, stdout);
+ }
+ }
+}
+
+static int get_terminal_width(void)
+{
+#ifdef TIOCGSIZE
+ struct ttysize t_win;
+#endif
+#ifdef TIOCGWINSZ
+ struct winsize w_win;
+#endif
+ const char *cp;
+ int width = 80;
+
+#ifdef TIOCGSIZE
+ if (ioctl (0, TIOCGSIZE, &t_win) == 0) {
+ width = t_win.ts_cols;
+ goto got_it;
+ }
+#endif
+#ifdef TIOCGWINSZ
+ if (ioctl (0, TIOCGWINSZ, &w_win) == 0) {
+ width = w_win.ws_col;
+ goto got_it;
+ }
+#endif
+ cp = getenv("COLUMNS");
+ if (cp)
+ width = atoi(cp);
+got_it:
+ if (width > 4096)
+ return 4096; /* sanity check */
+ return width;
+}
+
+static int pretty_print_word(const char *str, int max_len,
+ int left_len, int overflow_nl)
+{
+ int len = strlen(str) + left_len;
+ int ret = 0;
+
+ fputs(str, stdout);
+ if (overflow_nl && len > max_len) {
+ fputc('\n', stdout);
+ len = 0;
+ } else if (len > max_len)
+ ret = len - max_len;
+ do {
+ fputc(' ', stdout);
+ } while (len++ < max_len);
+ return ret;
+}
+
+static void pretty_print_line(const char *device, const char *fs_type,
+ const char *label, const char *mtpt,
+ const char *uuid)
+{
+ static int device_len = 10, fs_type_len = 7;
+ static int label_len = 8, mtpt_len = 14;
+ static int term_width = -1;
+ int len, w;
+
+ if (term_width < 0) {
+ term_width = get_terminal_width();
+
+ if (term_width > 80) {
+ term_width -= 80;
+ w = term_width / 10;
+ if (w > 8)
+ w = 8;
+ term_width -= 2*w;
+ label_len += w;
+ fs_type_len += w;
+ w = term_width/2;
+ device_len += w;
+ mtpt_len +=w;
+ }
+ }
+
+ len = pretty_print_word(device, device_len, 0, 1);
+ len = pretty_print_word(fs_type, fs_type_len, len, 0);
+ len = pretty_print_word(label, label_len, len, 0);
+ len = pretty_print_word(mtpt, mtpt_len, len, 0);
+ fputs(uuid, stdout);
+ fputc('\n', stdout);
+}
+
+static void pretty_print_dev(blkid_dev dev)
+{
+ blkid_tag_iterate iter;
+ const char *type, *value, *devname;
+ const char *uuid = "", *fs_type = "", *label = "";
+ int len, mount_flags;
+ char mtpt[80];
+ errcode_t retval;
+
+ if (dev == NULL) {
+ pretty_print_line("device", "fs_type", "label",
+ "mount point", "UUID");
+ for (len=get_terminal_width()-1; len > 0; len--)
+ fputc('-', stdout);
+ fputc('\n', stdout);
+ return;
+ }
+
+ devname = blkid_dev_devname(dev);
+ if (access(devname, F_OK))
+ return;
+
+ /* Get the uuid, label, type */
+ iter = blkid_tag_iterate_begin(dev);
+ while (blkid_tag_next(iter, &type, &value) == 0) {
+ if (!strcmp(type, "UUID"))
+ uuid = value;
+ if (!strcmp(type, "TYPE"))
+ fs_type = value;
+ if (!strcmp(type, "LABEL"))
+ label = value;
+ }
+ blkid_tag_iterate_end(iter);
+
+ /* Get the mount point */
+ mtpt[0] = 0;
+ retval = ext2fs_check_mount_point(devname, &mount_flags,
+ mtpt, sizeof(mtpt));
+ if (retval == 0) {
+ if (mount_flags & EXT2_MF_MOUNTED) {
+ if (!mtpt[0])
+ strcpy(mtpt, "(mounted, mtpt unknown)");
+ } else if (mount_flags & EXT2_MF_BUSY)
+ strcpy(mtpt, "(in use)");
+ else
+ strcpy(mtpt, "(not mounted)");
+ }
+
+ pretty_print_line(devname, fs_type, label, mtpt, uuid);
+}
+
+static void print_tags(blkid_dev dev, char *show[], int numtag, int output)
+{
+ blkid_tag_iterate iter;
+ const char *type, *value;
+ int i, first = 1;
+
+ if (!dev)
+ return;
+
+ if (output & OUTPUT_PRETTY_LIST) {
+ pretty_print_dev(dev);
+ return;
+ }
+
+ if (output & OUTPUT_DEVICE_ONLY) {
+ printf("%s\n", blkid_dev_devname(dev));
+ return;
+ }
+
+ iter = blkid_tag_iterate_begin(dev);
+ while (blkid_tag_next(iter, &type, &value) == 0) {
+ if (numtag && show) {
+ for (i=0; i < numtag; i++)
+ if (!strcmp(type, show[i]))
+ break;
+ if (i >= numtag)
+ continue;
+ }
+ if (output & OUTPUT_VALUE_ONLY) {
+ fputs(value, stdout);
+ fputc('\n', stdout);
+ } else {
+ if (first) {
+ printf("%s: ", blkid_dev_devname(dev));
+ first = 0;
+ }
+ fputs(type, stdout);
+ fputs("=\"", stdout);
+ safe_print(value, -1);
+ fputs("\" ", stdout);
+ }
+ }
+ blkid_tag_iterate_end(iter);
+
+ if (!first && !(output & OUTPUT_VALUE_ONLY))
+ printf("\n");
+}
+
+int main(int argc, char **argv)
+{
+ blkid_cache cache = NULL;
+ char *devices[128] = { NULL, };
+ char *show[128] = { NULL, };
+ char *search_type = NULL, *search_value = NULL;
+ char *read = NULL;
+ char *write = NULL;
+ unsigned int numdev = 0, numtag = 0;
+ int version = 0;
+ int err = 4;
+ unsigned int i;
+ int output_format = 0;
+ int lookup = 0, gc = 0;
+ int c;
+
+ while ((c = getopt (argc, argv, "c:f:ghlLo:s:t:w:v")) != EOF)
+ switch (c) {
+ case 'c':
+ read = optarg;
+ if (!write)
+ write = read;
+ break;
+ case 'l':
+ lookup++;
+ break;
+ case 'L':
+ output_format = OUTPUT_PRETTY_LIST;
+ break;
+ case 'g':
+ gc = 1;
+ break;
+ case 'o':
+ if (!strcmp(optarg, "value"))
+ output_format = OUTPUT_VALUE_ONLY;
+ else if (!strcmp(optarg, "device"))
+ output_format = OUTPUT_DEVICE_ONLY;
+ else if (!strcmp(optarg, "list"))
+ output_format = OUTPUT_PRETTY_LIST;
+ else if (!strcmp(optarg, "full"))
+ output_format = 0;
+ else {
+ fprintf(stderr, "Invalid output format %s. "
+ "Choose from value,\n\t"
+ "device, list, or full\n", optarg);
+ exit(1);
+ }
+ break;
+ case 's':
+ if (numtag >= sizeof(show) / sizeof(*show)) {
+ fprintf(stderr, "Too many tags specified\n");
+ usage(err);
+ }
+ show[numtag++] = optarg;
+ break;
+ case 't':
+ if (search_type) {
+ fprintf(stderr, "Can only search for "
+ "one NAME=value pair\n");
+ usage(err);
+ }
+ if (blkid_parse_tag_string(optarg,
+ &search_type,
+ &search_value)) {
+ fprintf(stderr, "-t needs NAME=value pair\n");
+ usage(err);
+ }
+ break;
+ case 'v':
+ version = 1;
+ break;
+ case 'w':
+ write = optarg;
+ break;
+ case 'h':
+ err = 0;
+ /* fallthrough */
+ default:
+ usage(err);
+ }
+
+ while (optind < argc)
+ devices[numdev++] = argv[optind++];
+
+ if (version) {
+ print_version(stdout);
+ goto exit;
+ }
+
+ if (blkid_get_cache(&cache, read) < 0)
+ goto exit;
+
+ err = 2;
+ if (gc) {
+ blkid_gc_cache(cache);
+ goto exit;
+ }
+ if (output_format & OUTPUT_PRETTY_LIST)
+ pretty_print_dev(NULL);
+
+ if (lookup) {
+ blkid_dev dev;
+
+ if (!search_type) {
+ fprintf(stderr, "The lookup option requires a "
+ "search type specified using -t\n");
+ exit(1);
+ }
+ /* Load any additional devices not in the cache */
+ for (i = 0; i < numdev; i++)
+ blkid_get_dev(cache, devices[i], BLKID_DEV_NORMAL);
+
+ if ((dev = blkid_find_dev_with_tag(cache, search_type,
+ search_value))) {
+ print_tags(dev, show, numtag, output_format);
+ err = 0;
+ }
+ /* If we didn't specify a single device, show all available devices */
+ } else if (!numdev) {
+ blkid_dev_iterate iter;
+ blkid_dev dev;
+
+ blkid_probe_all(cache);
+
+ iter = blkid_dev_iterate_begin(cache);
+ blkid_dev_set_search(iter, search_type, search_value);
+ while (blkid_dev_next(iter, &dev) == 0) {
+ dev = blkid_verify(cache, dev);
+ if (!dev)
+ continue;
+ print_tags(dev, show, numtag, output_format);
+ err = 0;
+ }
+ blkid_dev_iterate_end(iter);
+ /* Add all specified devices to cache (optionally display tags) */
+ } else for (i = 0; i < numdev; i++) {
+ blkid_dev dev = blkid_get_dev(cache, devices[i],
+ BLKID_DEV_NORMAL);
+
+ if (dev) {
+ if (search_type &&
+ !blkid_dev_has_tag(dev, search_type,
+ search_value))
+ continue;
+ print_tags(dev, show, numtag, output_format);
+ err = 0;
+ }
+ }
+
+exit:
+ free(search_type);
+ free(search_value);
+ blkid_put_cache(cache);
+ return err;
+}
diff --git a/misc/chattr.1.in b/misc/chattr.1.in
new file mode 100644
index 0000000..50c54e7
--- /dev/null
+++ b/misc/chattr.1.in
@@ -0,0 +1,299 @@
+.\" -*- nroff -*-
+.TH CHATTR 1 "@E2FSPROGS_MONTH@ @E2FSPROGS_YEAR@" "E2fsprogs version @E2FSPROGS_VERSION@"
+.SH NAME
+chattr \- change file attributes on a Linux file system
+.SH SYNOPSIS
+.B chattr
+[
+.B \-RVf
+]
+[
+.B \-v
+.I version
+]
+[
+.B \-p
+.I project
+]
+[
+.I mode
+]
+.I files...
+.SH DESCRIPTION
+.B chattr
+changes the file attributes on a Linux file system.
+.PP
+The format of a symbolic
+.I mode
+is
+.BR +-= [ aAcCdDeFijmPsStTux ].
+.PP
+The operator
+.RB ' + '
+causes the selected attributes to be added to the
+existing attributes of the files;
+.RB ' - '
+causes them to be removed; and
+.RB ' = '
+causes them to be the only attributes that the files have.
+.PP
+The letters
+.RB ' aAcCdDeFijmPsStTux '
+select the new attributes for the files:
+append only
+.RB ( a ),
+no atime updates
+.RB ( A ),
+compressed
+.RB ( c ),
+no copy on write
+.RB ( C ),
+no dump
+.RB ( d ),
+synchronous directory updates
+.RB ( D ),
+extent format
+.RB ( e ),
+case-insensitive directory lookups
+.RB ( F ),
+immutable
+.RB ( i ),
+data journaling
+.RB ( j ),
+don't compress
+.RB ( m ),
+project hierarchy
+.RB ( P ),
+secure deletion
+.RB ( s ),
+synchronous updates
+.RB ( S ),
+no tail-merging
+.RB ( t ),
+top of directory hierarchy
+.RB ( T ),
+undeletable
+.RB ( u ),
+and direct access for files
+.RB ( x ).
+.PP
+The following attributes are read-only, and may be listed by
+.BR lsattr (1)
+but not modified by chattr:
+encrypted
+.RB ( E ),
+indexed directory
+.RB ( I ),
+inline data
+.RB ( N ),
+and verity
+.RB ( V ).
+.PP
+Not all flags are supported or utilized by all file systems; refer to
+file system-specific man pages such as
+.BR btrfs (5),
+.BR ext4 (5),
+.BR mkfs.f2fs (8),
+and
+.BR xfs (5)
+for more file system-specific details.
+.SH OPTIONS
+.TP
+.B \-R
+Recursively change attributes of directories and their contents.
+.TP
+.B \-V
+Be verbose with chattr's output and print the program version.
+.TP
+.B \-f
+Suppress most error messages.
+.TP
+.BI \-v " version"
+Set the file's version/generation number.
+.TP
+.BI \-p " project"
+Set the file's project number.
+.SH ATTRIBUTES
+.TP
+.B a
+A file with the 'a' attribute set can only be opened in append mode for
+writing. Only the superuser or a process possessing the
+CAP_LINUX_IMMUTABLE capability can set or clear this attribute.
+.TP
+.B A
+When a file with the 'A' attribute set is accessed, its atime record is
+not modified. This avoids a certain amount of disk I/O for laptop
+systems.
+.TP
+.B c
+A file with the 'c' attribute set is automatically compressed on the disk
+by the kernel. A read from this file returns uncompressed data. A write to
+this file compresses data before storing them on the disk. Note: please
+make sure to read the bugs and limitations section at the end of this
+document. (Note: For btrfs, If the 'c' flag is set, then the 'C' flag
+cannot be set. Also conflicts with btrfs mount option 'nodatasum')
+.TP
+.B C
+A file with the 'C' attribute set will not be subject to copy-on-write
+updates. This flag is only supported on file systems which perform
+copy-on-write. (Note: For btrfs, the 'C' flag should be
+set on new or empty files. If it is set on a file which already has
+data blocks, it is undefined when the blocks assigned to the file will
+be fully stable. If the 'C' flag is set on a directory, it will have no
+effect on the directory, but new files created in that directory will
+have the No_COW attribute set. If the 'C' flag is set, then the 'c' flag
+cannot be set.)
+.TP
+.B d
+A file with the 'd' attribute set is not a candidate for backup when the
+.BR dump (8)
+program is run.
+.TP
+.B D
+When a directory with the 'D' attribute set is modified,
+the changes are written synchronously to the disk; this is equivalent to
+the 'dirsync' mount option applied to a subset of the files.
+.TP
+.B e
+The 'e' attribute indicates that the file is using extents for mapping
+the blocks on disk. It may not be removed using
+.BR chattr (1).
+.TP
+.B E
+A file, directory, or symlink with the 'E' attribute set is encrypted by the
+file system. This attribute may not be set or cleared using
+.BR chattr (1),
+although it can be displayed by
+.BR lsattr (1).
+.TP
+.B F
+A directory with the 'F' attribute set indicates that all the path
+lookups inside that directory are made in a case-insensitive fashion.
+This attribute can only be changed in empty directories on file systems
+with the casefold feature enabled.
+.TP
+.B i
+A file with the 'i' attribute cannot be modified: it cannot be deleted or
+renamed, no link can be created to this file, most of the file's
+metadata can not be modified, and the file can not be opened in write mode.
+Only the superuser or a process possessing the CAP_LINUX_IMMUTABLE
+capability can set or clear this attribute.
+.TP
+.B I
+The 'I' attribute is used by the htree code to indicate that a directory
+is being indexed using hashed trees. It may not be set or cleared using
+.BR chattr (1),
+although it can be displayed by
+.BR lsattr (1).
+.TP
+.B j
+A file with the 'j' attribute has all of its data written to the ext3 or
+ext4 journal before being written to the file itself, if the file system
+is mounted with the "data=ordered" or "data=writeback" options and the
+file system has a journal. When the file system is mounted with the
+"data=journal" option all file data is already journalled and this
+attribute has no effect. Only the superuser or a process possessing the
+CAP_SYS_RESOURCE capability can set or clear this attribute.
+.TP
+.B m
+A file with the 'm' attribute is excluded from compression on file
+systems that support per-file compression.
+.TP
+.B N
+A file with the 'N' attribute set indicates that the file has data
+stored inline, within the inode itself. It may not be set or cleared
+using
+.BR chattr (1),
+although it can be displayed by
+.BR lsattr (1).
+.TP
+.B P
+A directory with the 'P' attribute set will enforce a hierarchical
+structure for project id's. This means that files and directories created
+in the directory will inherit the project id of the directory, rename
+operations are constrained so when a file or directory is moved into
+another directory, that the project ids must match. In addition, a
+hard link to file can only be created when the project id for the file
+and the destination directory match.
+.TP
+.B s
+When a file with the 's' attribute set is deleted, its blocks are zeroed
+and written back to the disk. Note: please make sure to read the bugs
+and limitations section at the end of this document.
+.TP
+.B S
+When a file with the 'S' attribute set is modified,
+the changes are written synchronously to the disk; this is equivalent to
+the 'sync' mount option applied to a subset of the files.
+.TP
+.B t
+A file with the 't' attribute will not have a partial block fragment at
+the end of the file merged with other files (for those file systems which
+support tail-merging). This is necessary for applications such as LILO
+which read the file system directly, and which don't understand tail-merged
+files. Note: As of this writing, the ext2, ext3, and ext4 file systems do
+not support tail-merging.
+.TP
+.B T
+A directory with the 'T' attribute will be deemed to be the top of
+directory hierarchies for the purposes of the Orlov block allocator.
+This is a hint to the block allocator used by ext3 and ext4 that the
+subdirectories under this directory are not related, and thus should be
+spread apart for allocation purposes. For example it is a very good
+idea to set the 'T' attribute on the /home directory, so that /home/john
+and /home/mary are placed into separate block groups. For directories
+where this attribute is not set, the Orlov block allocator will try to
+group subdirectories closer together where possible.
+.TP
+.B u
+When a file with the 'u' attribute set is deleted, its contents are
+saved. This allows the user to ask for its undeletion. Note: please
+make sure to read the bugs and limitations section at the end of this
+document.
+.TP
+.B x
+A file with the 'x' requests the use of direct access (dax) mode, if the
+kernel supports DAX. This can be overridden by the 'dax=never' mount
+option. For more information see the kernel documentation for dax:
+<https://www.kernel.org/doc/html/latest/filesystems/dax.html>.
+.IP
+If the attribute is set on an existing directory, it will be inherited
+by all files and subdirectories that are subsequently created in the
+directory. If an existing directory has contained some files and
+subdirectories, modifying the attribute on the parent directory doesn't
+change the attributes on these files and subdirectories.
+.TP
+.B V
+A file with the 'V' attribute set has fs-verity enabled. It cannot be
+written to, and the file system will automatically verify all data read
+from it against a cryptographic hash that covers the entire file's
+contents, e.g. via a Merkle tree. This makes it possible to efficiently
+authenticate the file. This attribute may not be set or cleared using
+.BR chattr (1),
+although it can be displayed by
+.BR lsattr (1).
+.PP
+.SH AUTHOR
+.B chattr
+was written by Remy Card <Remy.Card@linux.org>. It is currently being
+maintained by Theodore Ts'o <tytso@alum.mit.edu>.
+.SH BUGS AND LIMITATIONS
+The 'c', 's', and 'u' attributes are not honored
+by the ext2, ext3, and ext4 file systems as implemented in the current
+mainline Linux kernels.
+Setting 'a' and 'i' attributes will not affect the ability to write
+to already existing file descriptors.
+.PP
+The 'j' option is only useful for ext3 and ext4 file systems.
+.PP
+The 'D' option is only useful on Linux kernel 2.5.19 and later.
+.SH AVAILABILITY
+.B chattr
+is part of the e2fsprogs package and is available from
+http://e2fsprogs.sourceforge.net.
+.SH SEE ALSO
+.BR lsattr (1),
+.BR btrfs (5),
+.BR ext4 (5),
+.BR mkfs.f2fs (8),
+.BR xfs (5).
diff --git a/misc/chattr.c b/misc/chattr.c
new file mode 100644
index 0000000..c7382a3
--- /dev/null
+++ b/misc/chattr.c
@@ -0,0 +1,359 @@
+/*
+ * chattr.c - Change file attributes on an ext2 file system
+ *
+ * Copyright (C) 1993, 1994 Remy Card <card@masi.ibp.fr>
+ * Laboratoire MASI, Institut Blaise Pascal
+ * Universite Pierre et Marie Curie (Paris VI)
+ *
+ * This file can be redistributed under the terms of the GNU General
+ * Public License
+ */
+
+/*
+ * History:
+ * 93/10/30 - Creation
+ * 93/11/13 - Replace stat() calls by lstat() to avoid loops
+ * 94/02/27 - Integrated in Ted's distribution
+ * 98/12/29 - Ignore symlinks when working recursively (G M Sipe)
+ * 98/12/29 - Display version info only when -V specified (G M Sipe)
+ */
+
+#define _LARGEFILE64_SOURCE
+
+#include "config.h"
+#include <sys/types.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#include <sys/param.h>
+#include <sys/stat.h>
+#include "ext2fs/ext2_fs.h"
+
+#ifdef __GNUC__
+#define EXT2FS_ATTR(x) __attribute__(x)
+#else
+#define EXT2FS_ATTR(x)
+#endif
+
+#ifndef S_ISLNK /* So we can compile even with gcc-warn */
+# ifdef __S_IFLNK
+# define S_ISLNK(mode) __S_ISTYPE((mode), __S_IFLNK)
+# else
+# define S_ISLNK(mode) 0
+# endif
+#endif
+
+#include "et/com_err.h"
+#include "e2p/e2p.h"
+#include "support/nls-enable.h"
+
+#include "../version.h"
+
+static const char * program_name = "chattr";
+
+static int add;
+static int rem;
+static int set;
+static int set_version;
+
+static unsigned long version;
+
+static int set_project;
+static unsigned long project;
+
+static int recursive;
+static int verbose;
+static int silent;
+
+static unsigned long af;
+static unsigned long rf;
+static unsigned long sf;
+
+#ifdef _LFS64_LARGEFILE
+#define LSTAT lstat64
+#define STRUCT_STAT struct stat64
+#else
+#define LSTAT lstat
+#define STRUCT_STAT struct stat
+#endif
+
+static void usage(void)
+{
+ fprintf(stderr,
+ _("Usage: %s [-RVf] [-+=aAcCdDeijPsStTuFx] [-p project] [-v version] files...\n"),
+ program_name);
+ exit(1);
+}
+
+struct flags_char {
+ unsigned long flag;
+ char optchar;
+};
+
+static const struct flags_char flags_array[] = {
+ { EXT2_NOATIME_FL, 'A' },
+ { EXT2_SYNC_FL, 'S' },
+ { EXT2_DIRSYNC_FL, 'D' },
+ { EXT2_APPEND_FL, 'a' },
+ { EXT2_COMPR_FL, 'c' },
+ { EXT2_NOCOMPR_FL, 'm' },
+ { EXT2_NODUMP_FL, 'd' },
+ { EXT4_EXTENTS_FL, 'e'},
+ { EXT2_IMMUTABLE_FL, 'i' },
+ { EXT3_JOURNAL_DATA_FL, 'j' },
+ { EXT4_PROJINHERIT_FL, 'P' },
+ { EXT2_SECRM_FL, 's' },
+ { EXT2_UNRM_FL, 'u' },
+ { EXT2_NOTAIL_FL, 't' },
+ { EXT2_TOPDIR_FL, 'T' },
+ { FS_NOCOW_FL, 'C' },
+ { FS_DAX_FL, 'x' },
+ { EXT4_CASEFOLD_FL, 'F' },
+ { 0, 0 }
+};
+
+static unsigned long get_flag(char c)
+{
+ const struct flags_char *fp;
+
+ for (fp = flags_array; fp->flag != 0; fp++) {
+ if (fp->optchar == c)
+ return fp->flag;
+ }
+ return 0;
+}
+
+
+static int decode_arg (int * i, int argc, char ** argv)
+{
+ char * p;
+ char * tmp;
+ unsigned long fl;
+
+ switch (argv[*i][0])
+ {
+ case '-':
+ for (p = &argv[*i][1]; *p; p++) {
+ if (*p == 'R') {
+ recursive = 1;
+ continue;
+ }
+ if (*p == 'V') {
+ verbose = 1;
+ continue;
+ }
+ if (*p == 'f') {
+ silent = 1;
+ continue;
+ }
+ if (*p == 'p') {
+ (*i)++;
+ if (*i >= argc)
+ usage ();
+ project = strtol (argv[*i], &tmp, 0);
+ if (*tmp) {
+ com_err (program_name, 0,
+ _("bad project - %s\n"),
+ argv[*i]);
+ usage ();
+ }
+ set_project = 1;
+ continue;
+ }
+ if (*p == 'v') {
+ (*i)++;
+ if (*i >= argc)
+ usage ();
+ version = strtol (argv[*i], &tmp, 0);
+ if (*tmp) {
+ com_err (program_name, 0,
+ _("bad version - %s\n"),
+ argv[*i]);
+ usage ();
+ }
+ set_version = 1;
+ continue;
+ }
+ if ((fl = get_flag(*p)) == 0)
+ usage();
+ rf |= fl;
+ rem = 1;
+ }
+ break;
+ case '+':
+ add = 1;
+ for (p = &argv[*i][1]; *p; p++) {
+ if ((fl = get_flag(*p)) == 0)
+ usage();
+ af |= fl;
+ }
+ break;
+ case '=':
+ set = 1;
+ for (p = &argv[*i][1]; *p; p++) {
+ if ((fl = get_flag(*p)) == 0)
+ usage();
+ sf |= fl;
+ }
+ break;
+ default:
+ return EOF;
+ }
+ return 1;
+}
+
+static int chattr_dir_proc(const char *, struct dirent *, void *);
+
+static int change_attributes(const char * name)
+{
+ unsigned long flags;
+ STRUCT_STAT st;
+
+ if (LSTAT (name, &st) == -1) {
+ if (!silent)
+ com_err (program_name, errno,
+ _("while trying to stat %s"), name);
+ return -1;
+ }
+
+ if (fgetflags(name, &flags) == -1) {
+ if (!silent)
+ com_err(program_name, errno,
+ _("while reading flags on %s"), name);
+ return -1;
+ }
+ if (set) {
+ if (verbose) {
+ printf (_("Flags of %s set as "), name);
+ print_flags (stdout, sf, 0);
+ printf ("\n");
+ }
+ if (fsetflags (name, sf) == -1)
+ perror (name);
+ } else {
+ if (rem)
+ flags &= ~rf;
+ if (add)
+ flags |= af;
+ if (verbose) {
+ printf(_("Flags of %s set as "), name);
+ print_flags(stdout, flags, 0);
+ printf("\n");
+ }
+ if (!S_ISDIR(st.st_mode))
+ flags &= ~EXT2_DIRSYNC_FL;
+ if (fsetflags(name, flags) == -1) {
+ if (!silent) {
+ com_err(program_name, errno,
+ _("while setting flags on %s"),
+ name);
+ }
+ return -1;
+ }
+ }
+ if (set_version) {
+ if (verbose)
+ printf (_("Version of %s set as %lu\n"), name, version);
+ if (fsetversion (name, version) == -1) {
+ if (!silent)
+ com_err (program_name, errno,
+ _("while setting version on %s"),
+ name);
+ return -1;
+ }
+ }
+ if (set_project) {
+ if (verbose)
+ printf (_("Project of %s set as %lu\n"), name, project);
+ if (fsetproject (name, project) == -1) {
+ if (!silent)
+ com_err (program_name, errno,
+ _("while setting project on %s"),
+ name);
+ return -1;
+ }
+
+ }
+ if (S_ISDIR(st.st_mode) && recursive)
+ return iterate_on_dir (name, chattr_dir_proc, NULL);
+ return 0;
+}
+
+static int chattr_dir_proc (const char * dir_name, struct dirent * de,
+ void * private EXT2FS_ATTR((unused)))
+{
+ int ret = 0;
+
+ if (strcmp (de->d_name, ".") && strcmp (de->d_name, "..")) {
+ char *path;
+
+ path = malloc(strlen (dir_name) + 1 + strlen (de->d_name) + 1);
+ if (!path) {
+ fprintf(stderr, "%s",
+ _("Couldn't allocate path variable "
+ "in chattr_dir_proc"));
+ return -1;
+ }
+ sprintf(path, "%s/%s", dir_name, de->d_name);
+ ret = change_attributes(path);
+ free(path);
+ }
+ return ret;
+}
+
+int main (int argc, char ** argv)
+{
+ int i, j;
+ int end_arg = 0;
+ int err, retval = 0;
+
+#ifdef ENABLE_NLS
+ setlocale(LC_MESSAGES, "");
+ setlocale(LC_CTYPE, "");
+ bindtextdomain(NLS_CAT_NAME, LOCALEDIR);
+ textdomain(NLS_CAT_NAME);
+ set_com_err_gettext(gettext);
+#endif
+ if (argc && *argv)
+ program_name = *argv;
+ i = 1;
+ while (i < argc && !end_arg) {
+ /* '--' arg should end option processing */
+ if (strcmp(argv[i], "--") == 0) {
+ i++;
+ end_arg = 1;
+ } else if (decode_arg (&i, argc, argv) == EOF)
+ end_arg = 1;
+ else
+ i++;
+ }
+ if (i >= argc)
+ usage ();
+ if (set && (add || rem)) {
+ fputs(_("= is incompatible with - and +\n"), stderr);
+ exit (1);
+ }
+ if ((rf & af) != 0) {
+ fputs("Can't both set and unset same flag.\n", stderr);
+ exit (1);
+ }
+ if (!(add || rem || set || set_version || set_project )) {
+ fputs(_("Must use '-v', =, - or +\n"), stderr);
+ exit (1);
+ }
+ if (verbose)
+ fprintf (stderr, "chattr %s (%s)\n",
+ E2FSPROGS_VERSION, E2FSPROGS_DATE);
+ for (j = i; j < argc; j++) {
+ err = change_attributes (argv[j]);
+ if (err)
+ retval = 1;
+ }
+ exit(retval);
+}
diff --git a/misc/check_fuzzer.c b/misc/check_fuzzer.c
new file mode 100644
index 0000000..cee21bf
--- /dev/null
+++ b/misc/check_fuzzer.c
@@ -0,0 +1,61 @@
+/*
+ * Play with a file system image quickly to find UBSAN problems
+ *
+ * Run a file system through some of the libext2fs functions used by
+ * some fuzzer reports.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/time.h>
+
+#include <ext2fs/ext2_fs.h>
+#include <ext2fs/ext2fs.h>
+
+int main (int argc, char *argv[])
+{
+ errcode_t retval = 0;
+ ext2_filsys fs;
+ int exit_status = 1;
+
+ initialize_ext2_error_table();
+
+ if (argc != 2) {
+ fprintf(stderr, "%s: Usage <device|filesystem>\n", argv[0]);
+ exit(1);
+ }
+
+ retval = ext2fs_open(argv[1], 0, 0, 0,
+ unix_io_manager, &fs);
+ if (retval) {
+ com_err(argv[0], retval, "while trying to open '%s'",
+ argv[1]);
+ exit(1);
+ }
+
+ retval = ext2fs_read_inode_bitmap(fs);
+ if (retval) {
+ com_err(argv[0], retval, "while trying to read inode bitmaps");
+ goto errout;
+ }
+
+ retval = ext2fs_read_block_bitmap(fs);
+ if (retval) {
+ com_err(argv[0], retval, "while trying to read inode bitmaps");
+ goto errout;
+ }
+
+ retval = ext2fs_check_directory(fs, EXT2_ROOT_INO);
+ if (retval) {
+ com_err(argv[0], retval, "while trying to read inode bitmaps");
+ goto errout;
+ }
+ exit_status = 0;
+errout:
+ ext2fs_close(fs);
+ return exit_status;
+}
diff --git a/misc/create_inode.c b/misc/create_inode.c
new file mode 100644
index 0000000..a3a34cd
--- /dev/null
+++ b/misc/create_inode.c
@@ -0,0 +1,1092 @@
+/*
+ * create_inode.c --- create an inode
+ *
+ * Copyright (C) 2014 Robert Yang <liezhi.yang@windriver.com>
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU library
+ * General Public License, version 2.
+ * %End-Header%
+ */
+
+#define _FILE_OFFSET_BITS 64
+#define _LARGEFILE64_SOURCE 1
+#define _GNU_SOURCE 1
+
+#include "config.h"
+#include <time.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <limits.h> /* for PATH_MAX */
+#include <dirent.h> /* for scandir() and alphasort() */
+#if defined HAVE_SYS_XATTR_H
+#include <sys/xattr.h>
+#elif defined HAVE_ATTR_XATTR_H
+#include <attr/xattr.h>
+#endif
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+#ifdef HAVE_SYS_SYSMACROS_H
+#include <sys/sysmacros.h>
+#endif
+
+#include <ext2fs/ext2fs.h>
+#include <ext2fs/ext2_types.h>
+#include <ext2fs/fiemap.h>
+
+#include "create_inode.h"
+#include "support/nls-enable.h"
+
+/* 64KiB is the minimum blksize to best minimize system call overhead. */
+#define COPY_FILE_BUFLEN 65536
+
+static int ext2_file_type(unsigned int mode)
+{
+ if (LINUX_S_ISREG(mode))
+ return EXT2_FT_REG_FILE;
+
+ if (LINUX_S_ISDIR(mode))
+ return EXT2_FT_DIR;
+
+ if (LINUX_S_ISCHR(mode))
+ return EXT2_FT_CHRDEV;
+
+ if (LINUX_S_ISBLK(mode))
+ return EXT2_FT_BLKDEV;
+
+ if (LINUX_S_ISLNK(mode))
+ return EXT2_FT_SYMLINK;
+
+ if (LINUX_S_ISFIFO(mode))
+ return EXT2_FT_FIFO;
+
+ if (LINUX_S_ISSOCK(mode))
+ return EXT2_FT_SOCK;
+
+ return 0;
+}
+
+/* Link an inode number to a directory */
+static errcode_t add_link(ext2_filsys fs, ext2_ino_t parent_ino,
+ ext2_ino_t ino, const char *name)
+{
+ struct ext2_inode inode;
+ errcode_t retval;
+
+ retval = ext2fs_read_inode(fs, ino, &inode);
+ if (retval) {
+ com_err(__func__, retval, _("while reading inode %u"), ino);
+ return retval;
+ }
+
+ retval = ext2fs_link(fs, parent_ino, name, ino,
+ ext2_file_type(inode.i_mode));
+ if (retval == EXT2_ET_DIR_NO_SPACE) {
+ retval = ext2fs_expand_dir(fs, parent_ino);
+ if (retval) {
+ com_err(__func__, retval,
+ _("while expanding directory"));
+ return retval;
+ }
+ retval = ext2fs_link(fs, parent_ino, name, ino,
+ ext2_file_type(inode.i_mode));
+ }
+ if (retval) {
+ com_err(__func__, retval, _("while linking \"%s\""), name);
+ return retval;
+ }
+
+ inode.i_links_count++;
+
+ retval = ext2fs_write_inode(fs, ino, &inode);
+ if (retval)
+ com_err(__func__, retval, _("while writing inode %u"), ino);
+
+ return retval;
+}
+
+/* Set the uid, gid, mode and time for the inode */
+static errcode_t set_inode_extra(ext2_filsys fs, ext2_ino_t ino,
+ struct stat *st)
+{
+ errcode_t retval;
+ struct ext2_inode inode;
+
+ retval = ext2fs_read_inode(fs, ino, &inode);
+ if (retval) {
+ com_err(__func__, retval, _("while reading inode %u"), ino);
+ return retval;
+ }
+
+ inode.i_uid = st->st_uid;
+ ext2fs_set_i_uid_high(inode, st->st_uid >> 16);
+ inode.i_gid = st->st_gid;
+ ext2fs_set_i_gid_high(inode, st->st_gid >> 16);
+ inode.i_mode = (LINUX_S_IFMT & inode.i_mode) | (~S_IFMT & st->st_mode);
+ inode.i_atime = st->st_atime;
+ inode.i_mtime = st->st_mtime;
+ inode.i_ctime = st->st_ctime;
+
+ retval = ext2fs_write_inode(fs, ino, &inode);
+ if (retval)
+ com_err(__func__, retval, _("while writing inode %u"), ino);
+ return retval;
+}
+
+#ifdef HAVE_LLISTXATTR
+static errcode_t set_inode_xattr(ext2_filsys fs, ext2_ino_t ino,
+ const char *filename)
+{
+ errcode_t retval, close_retval;
+ struct ext2_xattr_handle *handle;
+ ssize_t size, value_size;
+ char *list = NULL;
+ int i;
+
+ if (no_copy_xattrs)
+ return 0;
+
+ size = llistxattr(filename, NULL, 0);
+ if (size == -1) {
+ if (errno == ENOTSUP)
+ return 0;
+ retval = errno;
+ com_err(__func__, retval, _("while listing attributes of \"%s\""),
+ filename);
+ return retval;
+ } else if (size == 0) {
+ return 0;
+ }
+
+ retval = ext2fs_xattrs_open(fs, ino, &handle);
+ if (retval) {
+ if (retval == EXT2_ET_MISSING_EA_FEATURE)
+ return 0;
+ com_err(__func__, retval, _("while opening inode %u"), ino);
+ return retval;
+ }
+
+ retval = ext2fs_xattrs_read(handle);
+ if (retval) {
+ com_err(__func__, retval,
+ _("while reading xattrs for inode %u"), ino);
+ goto out;
+ }
+
+ retval = ext2fs_get_mem(size, &list);
+ if (retval) {
+ com_err(__func__, retval, _("while allocating memory"));
+ goto out;
+ }
+
+ size = llistxattr(filename, list, size);
+ if (size == -1) {
+ retval = errno;
+ com_err(__func__, retval, _("while listing attributes of \"%s\""),
+ filename);
+ goto out;
+ }
+
+ for (i = 0; i < size; i += strlen(&list[i]) + 1) {
+ const char *name = &list[i];
+ char *value;
+
+ value_size = lgetxattr(filename, name, NULL, 0);
+ if (value_size == -1) {
+ retval = errno;
+ com_err(__func__, retval,
+ _("while reading attribute \"%s\" of \"%s\""),
+ name, filename);
+ break;
+ }
+
+ retval = ext2fs_get_mem(value_size, &value);
+ if (retval) {
+ com_err(__func__, retval, _("while allocating memory"));
+ break;
+ }
+
+ value_size = lgetxattr(filename, name, value, value_size);
+ if (value_size == -1) {
+ ext2fs_free_mem(&value);
+ retval = errno;
+ com_err(__func__, retval,
+ _("while reading attribute \"%s\" of \"%s\""),
+ name, filename);
+ break;
+ }
+
+ retval = ext2fs_xattr_set(handle, name, value, value_size);
+ ext2fs_free_mem(&value);
+ if (retval) {
+ com_err(__func__, retval,
+ _("while writing attribute \"%s\" to inode %u"),
+ name, ino);
+ break;
+ }
+
+ }
+ out:
+ ext2fs_free_mem(&list);
+ close_retval = ext2fs_xattrs_close(&handle);
+ if (close_retval) {
+ com_err(__func__, retval, _("while closing inode %u"), ino);
+ retval = retval ? retval : close_retval;
+ }
+ return retval;
+}
+#else /* HAVE_LLISTXATTR */
+static errcode_t set_inode_xattr(ext2_filsys fs EXT2FS_ATTR((unused)),
+ ext2_ino_t ino EXT2FS_ATTR((unused)),
+ const char *filename EXT2FS_ATTR((unused)))
+{
+ return 0;
+}
+#endif /* HAVE_LLISTXATTR */
+
+#ifndef _WIN32
+/* Make a special files (block and character devices), fifo's, and sockets */
+errcode_t do_mknod_internal(ext2_filsys fs, ext2_ino_t cwd, const char *name,
+ unsigned int st_mode, unsigned int st_rdev)
+{
+ ext2_ino_t ino;
+ errcode_t retval;
+ struct ext2_inode inode;
+ unsigned long devmajor, devminor, mode;
+ int filetype;
+
+ switch(st_mode & S_IFMT) {
+ case S_IFCHR:
+ mode = LINUX_S_IFCHR;
+ filetype = EXT2_FT_CHRDEV;
+ break;
+ case S_IFBLK:
+ mode = LINUX_S_IFBLK;
+ filetype = EXT2_FT_BLKDEV;
+ break;
+ case S_IFIFO:
+ mode = LINUX_S_IFIFO;
+ filetype = EXT2_FT_FIFO;
+ break;
+#ifndef _WIN32
+ case S_IFSOCK:
+ mode = LINUX_S_IFSOCK;
+ filetype = EXT2_FT_SOCK;
+ break;
+#endif
+ default:
+ return EXT2_ET_INVALID_ARGUMENT;
+ }
+
+ retval = ext2fs_new_inode(fs, cwd, 010755, 0, &ino);
+ if (retval) {
+ com_err(__func__, retval, _("while allocating inode \"%s\""),
+ name);
+ return retval;
+ }
+
+#ifdef DEBUGFS
+ printf("Allocated inode: %u\n", ino);
+#endif
+ retval = ext2fs_link(fs, cwd, name, ino, filetype);
+ if (retval == EXT2_ET_DIR_NO_SPACE) {
+ retval = ext2fs_expand_dir(fs, cwd);
+ if (retval) {
+ com_err(__func__, retval,
+ _("while expanding directory"));
+ return retval;
+ }
+ retval = ext2fs_link(fs, cwd, name, ino, filetype);
+ }
+ if (retval) {
+ com_err(name, retval, _("while creating inode \"%s\""), name);
+ return retval;
+ }
+ if (ext2fs_test_inode_bitmap2(fs->inode_map, ino))
+ com_err(__func__, 0, "Warning: inode already set");
+ ext2fs_inode_alloc_stats2(fs, ino, +1, 0);
+ memset(&inode, 0, sizeof(inode));
+ inode.i_mode = mode;
+ inode.i_atime = inode.i_ctime = inode.i_mtime =
+ fs->now ? fs->now : time(0);
+
+ if (filetype != S_IFIFO) {
+ devmajor = major(st_rdev);
+ devminor = minor(st_rdev);
+
+ if ((devmajor < 256) && (devminor < 256)) {
+ inode.i_block[0] = devmajor * 256 + devminor;
+ inode.i_block[1] = 0;
+ } else {
+ inode.i_block[0] = 0;
+ inode.i_block[1] = (devminor & 0xff) | (devmajor << 8) |
+ ((devminor & ~0xff) << 12);
+ }
+ }
+ inode.i_links_count = 1;
+
+ retval = ext2fs_write_new_inode(fs, ino, &inode);
+ if (retval)
+ com_err(__func__, retval, _("while writing inode %u"), ino);
+
+ return retval;
+}
+#endif
+
+/* Make a symlink name -> target */
+errcode_t do_symlink_internal(ext2_filsys fs, ext2_ino_t cwd, const char *name,
+ char *target, ext2_ino_t root)
+{
+ char *cp;
+ ext2_ino_t parent_ino;
+ errcode_t retval;
+
+ cp = strrchr(name, '/');
+ if (cp) {
+ *cp = 0;
+ retval = ext2fs_namei(fs, root, cwd, name, &parent_ino);
+ if (retval) {
+ com_err(name, retval, 0);
+ return retval;
+ }
+ name = cp+1;
+ } else
+ parent_ino = cwd;
+
+ retval = ext2fs_symlink(fs, parent_ino, 0, name, target);
+ if (retval == EXT2_ET_DIR_NO_SPACE) {
+ retval = ext2fs_expand_dir(fs, parent_ino);
+ if (retval) {
+ com_err("do_symlink_internal", retval,
+ _("while expanding directory"));
+ return retval;
+ }
+ retval = ext2fs_symlink(fs, parent_ino, 0, name, target);
+ }
+ if (retval)
+ com_err("ext2fs_symlink", retval,
+ _("while creating symlink \"%s\""), name);
+ return retval;
+}
+
+/* Make a directory in the fs */
+errcode_t do_mkdir_internal(ext2_filsys fs, ext2_ino_t cwd, const char *name,
+ ext2_ino_t root)
+{
+ char *cp;
+ ext2_ino_t parent_ino;
+ errcode_t retval;
+
+
+ cp = strrchr(name, '/');
+ if (cp) {
+ *cp = 0;
+ retval = ext2fs_namei(fs, root, cwd, name, &parent_ino);
+ if (retval) {
+ com_err(name, retval, _("while looking up \"%s\""),
+ name);
+ return retval;
+ }
+ name = cp+1;
+ } else
+ parent_ino = cwd;
+
+ retval = ext2fs_mkdir(fs, parent_ino, 0, name);
+ if (retval == EXT2_ET_DIR_NO_SPACE) {
+ retval = ext2fs_expand_dir(fs, parent_ino);
+ if (retval) {
+ com_err(__func__, retval,
+ _("while expanding directory"));
+ return retval;
+ }
+ retval = ext2fs_mkdir(fs, parent_ino, 0, name);
+ }
+ if (retval)
+ com_err("ext2fs_mkdir", retval,
+ _("while creating directory \"%s\""), name);
+ return retval;
+}
+
+#if !defined HAVE_PREAD64 && !defined HAVE_PREAD
+static ssize_t my_pread(int fd, void *buf, size_t count, off_t offset)
+{
+ if (lseek(fd, offset, SEEK_SET) < 0)
+ return 0;
+
+ return read(fd, buf, count);
+}
+#endif /* !defined HAVE_PREAD64 && !defined HAVE_PREAD */
+
+static errcode_t copy_file_chunk(ext2_filsys fs, int fd, ext2_file_t e2_file,
+ off_t start, off_t end, char *buf,
+ char *zerobuf)
+{
+ off_t off, bpos;
+ ssize_t got, blen;
+ unsigned int written;
+ char *ptr;
+ errcode_t err = 0;
+
+ for (off = start; off < end; off += COPY_FILE_BUFLEN) {
+#ifdef HAVE_PREAD64
+ got = pread64(fd, buf, COPY_FILE_BUFLEN, off);
+#elif HAVE_PREAD
+ got = pread(fd, buf, COPY_FILE_BUFLEN, off);
+#else
+ got = my_pread(fd, buf, COPY_FILE_BUFLEN, off);
+#endif
+ if (got < 0) {
+ err = errno;
+ goto fail;
+ }
+ for (bpos = 0, ptr = buf; bpos < got; bpos += fs->blocksize) {
+ blen = fs->blocksize;
+ if (blen > got - bpos)
+ blen = got - bpos;
+ if (memcmp(ptr, zerobuf, blen) == 0) {
+ ptr += blen;
+ continue;
+ }
+ err = ext2fs_file_llseek(e2_file, off + bpos,
+ EXT2_SEEK_SET, NULL);
+ if (err)
+ goto fail;
+ while (blen > 0) {
+ err = ext2fs_file_write(e2_file, ptr, blen,
+ &written);
+ if (err)
+ goto fail;
+ if (written == 0) {
+ err = EIO;
+ goto fail;
+ }
+ blen -= written;
+ ptr += written;
+ }
+ }
+ }
+fail:
+ return err;
+}
+
+#if defined(SEEK_DATA) && defined(SEEK_HOLE)
+static errcode_t try_lseek_copy(ext2_filsys fs, int fd, struct stat *statbuf,
+ ext2_file_t e2_file, char *buf, char *zerobuf)
+{
+ off_t data = 0, hole;
+ off_t data_blk, hole_blk;
+ errcode_t err = 0;
+
+ /* Try to use SEEK_DATA and SEEK_HOLE */
+ while (data < statbuf->st_size) {
+ data = lseek(fd, data, SEEK_DATA);
+ if (data < 0) {
+ if (errno == ENXIO)
+ break;
+ return EXT2_ET_UNIMPLEMENTED;
+ }
+ hole = lseek(fd, data, SEEK_HOLE);
+ if (hole < 0)
+ return EXT2_ET_UNIMPLEMENTED;
+
+ data_blk = data & ~(off_t)(fs->blocksize - 1);
+ hole_blk = ((hole + (off_t)(fs->blocksize - 1)) &
+ ~(off_t)(fs->blocksize - 1));
+ err = copy_file_chunk(fs, fd, e2_file, data_blk, hole_blk, buf,
+ zerobuf);
+ if (err)
+ return err;
+
+ data = hole;
+ }
+
+ return err;
+}
+#endif /* SEEK_DATA and SEEK_HOLE */
+
+#if defined(FS_IOC_FIEMAP)
+static errcode_t try_fiemap_copy(ext2_filsys fs, int fd, ext2_file_t e2_file,
+ char *buf, char *zerobuf)
+{
+#define EXTENT_MAX_COUNT 512
+ struct fiemap *fiemap_buf;
+ struct fiemap_extent *ext_buf, *ext;
+ int ext_buf_size, fie_buf_size;
+ off_t pos = 0;
+ unsigned int i;
+ errcode_t err;
+
+ ext_buf_size = EXTENT_MAX_COUNT * sizeof(struct fiemap_extent);
+ fie_buf_size = sizeof(struct fiemap) + ext_buf_size;
+
+ err = ext2fs_get_memzero(fie_buf_size, &fiemap_buf);
+ if (err)
+ return err;
+
+ ext_buf = fiemap_buf->fm_extents;
+ memset(fiemap_buf, 0, fie_buf_size);
+ fiemap_buf->fm_length = FIEMAP_MAX_OFFSET;
+ fiemap_buf->fm_flags |= FIEMAP_FLAG_SYNC;
+ fiemap_buf->fm_extent_count = EXTENT_MAX_COUNT;
+
+ do {
+ fiemap_buf->fm_start = pos;
+ memset(ext_buf, 0, ext_buf_size);
+ err = ioctl(fd, FS_IOC_FIEMAP, fiemap_buf);
+ if (err < 0 && (errno == EOPNOTSUPP || errno == ENOTTY)) {
+ err = EXT2_ET_UNIMPLEMENTED;
+ goto out;
+ } else if (err < 0) {
+ err = errno;
+ goto out;
+ } else if (fiemap_buf->fm_mapped_extents == 0)
+ goto out;
+ for (i = 0, ext = ext_buf; i < fiemap_buf->fm_mapped_extents;
+ i++, ext++) {
+ err = copy_file_chunk(fs, fd, e2_file, ext->fe_logical,
+ ext->fe_logical + ext->fe_length,
+ buf, zerobuf);
+ if (err)
+ goto out;
+ }
+
+ ext--;
+ /* Record file's logical offset this time */
+ pos = ext->fe_logical + ext->fe_length;
+ /*
+ * If fm_extents array has been filled and
+ * there are extents left, continue to cycle.
+ */
+ } while (fiemap_buf->fm_mapped_extents == EXTENT_MAX_COUNT &&
+ !(ext->fe_flags & FIEMAP_EXTENT_LAST));
+out:
+ ext2fs_free_mem(&fiemap_buf);
+ return err;
+}
+#endif /* FS_IOC_FIEMAP */
+
+static errcode_t copy_file(ext2_filsys fs, int fd, struct stat *statbuf,
+ ext2_ino_t ino)
+{
+ ext2_file_t e2_file;
+ char *buf = NULL, *zerobuf = NULL;
+ errcode_t err, close_err;
+
+ err = ext2fs_file_open(fs, ino, EXT2_FILE_WRITE, &e2_file);
+ if (err)
+ return err;
+
+ err = ext2fs_get_mem(COPY_FILE_BUFLEN, &buf);
+ if (err)
+ goto out;
+
+ err = ext2fs_get_memzero(fs->blocksize, &zerobuf);
+ if (err)
+ goto out;
+
+#if defined(SEEK_DATA) && defined(SEEK_HOLE)
+ err = try_lseek_copy(fs, fd, statbuf, e2_file, buf, zerobuf);
+ if (err != EXT2_ET_UNIMPLEMENTED)
+ goto out;
+#endif
+
+#if defined(FS_IOC_FIEMAP)
+ err = try_fiemap_copy(fs, fd, e2_file, buf, zerobuf);
+ if (err != EXT2_ET_UNIMPLEMENTED)
+ goto out;
+#endif
+
+ err = copy_file_chunk(fs, fd, e2_file, 0, statbuf->st_size, buf,
+ zerobuf);
+out:
+ ext2fs_free_mem(&zerobuf);
+ ext2fs_free_mem(&buf);
+ close_err = ext2fs_file_close(e2_file);
+ if (err == 0)
+ err = close_err;
+ return err;
+}
+
+static int is_hardlink(struct hdlinks_s *hdlinks, dev_t dev, ino_t ino)
+{
+ int i;
+
+ for (i = 0; i < hdlinks->count; i++) {
+ if (hdlinks->hdl[i].src_dev == dev &&
+ hdlinks->hdl[i].src_ino == ino)
+ return i;
+ }
+ return -1;
+}
+
+/* Copy the native file to the fs */
+errcode_t do_write_internal(ext2_filsys fs, ext2_ino_t cwd, const char *src,
+ const char *dest, ext2_ino_t root)
+{
+ int fd;
+ struct stat statbuf;
+ ext2_ino_t newfile, parent_ino;
+ errcode_t retval;
+ struct ext2_inode inode;
+ char *cp;
+
+ fd = ext2fs_open_file(src, O_RDONLY, 0);
+ if (fd < 0) {
+ retval = errno;
+ com_err(__func__, retval, _("while opening \"%s\" to copy"),
+ src);
+ return retval;
+ }
+ if (fstat(fd, &statbuf) < 0) {
+ retval = errno;
+ goto out;
+ }
+
+ cp = strrchr(dest, '/');
+ if (cp) {
+ *cp = 0;
+ retval = ext2fs_namei(fs, root, cwd, dest, &parent_ino);
+ if (retval) {
+ com_err(dest, retval, _("while looking up \"%s\""),
+ dest);
+ goto out;
+ }
+ dest = cp+1;
+ } else
+ parent_ino = cwd;
+
+ retval = ext2fs_namei(fs, root, parent_ino, dest, &newfile);
+ if (retval == 0) {
+ retval = EXT2_ET_FILE_EXISTS;
+ goto out;
+ }
+
+ retval = ext2fs_new_inode(fs, parent_ino, 010755, 0, &newfile);
+ if (retval)
+ goto out;
+#ifdef DEBUGFS
+ printf("Allocated inode: %u\n", newfile);
+#endif
+ retval = ext2fs_link(fs, parent_ino, dest, newfile, EXT2_FT_REG_FILE);
+ if (retval == EXT2_ET_DIR_NO_SPACE) {
+ retval = ext2fs_expand_dir(fs, parent_ino);
+ if (retval)
+ goto out;
+ retval = ext2fs_link(fs, parent_ino, dest, newfile,
+ EXT2_FT_REG_FILE);
+ }
+ if (retval)
+ goto out;
+ if (ext2fs_test_inode_bitmap2(fs->inode_map, newfile))
+ com_err(__func__, 0, "Warning: inode already set");
+ ext2fs_inode_alloc_stats2(fs, newfile, +1, 0);
+ memset(&inode, 0, sizeof(inode));
+ inode.i_mode = (statbuf.st_mode & ~S_IFMT) | LINUX_S_IFREG;
+ inode.i_atime = inode.i_ctime = inode.i_mtime =
+ fs->now ? fs->now : time(0);
+ inode.i_links_count = 1;
+ retval = ext2fs_inode_size_set(fs, &inode, statbuf.st_size);
+ if (retval)
+ goto out;
+ if (ext2fs_has_feature_inline_data(fs->super)) {
+ inode.i_flags |= EXT4_INLINE_DATA_FL;
+ } else if (ext2fs_has_feature_extents(fs->super)) {
+ ext2_extent_handle_t handle;
+
+ inode.i_flags &= ~EXT4_EXTENTS_FL;
+ retval = ext2fs_extent_open2(fs, newfile, &inode, &handle);
+ if (retval)
+ goto out;
+ ext2fs_extent_free(handle);
+ }
+
+ retval = ext2fs_write_new_inode(fs, newfile, &inode);
+ if (retval)
+ goto out;
+ if (inode.i_flags & EXT4_INLINE_DATA_FL) {
+ retval = ext2fs_inline_data_init(fs, newfile);
+ if (retval)
+ goto out;
+ }
+ if (LINUX_S_ISREG(inode.i_mode)) {
+ retval = copy_file(fs, fd, &statbuf, newfile);
+ if (retval)
+ goto out;
+ }
+out:
+ close(fd);
+ return retval;
+}
+
+struct file_info {
+ char *path;
+ size_t path_len;
+ size_t path_max_len;
+};
+
+static errcode_t path_append(struct file_info *target, const char *file)
+{
+ if (strlen(file) + target->path_len + 1 > target->path_max_len) {
+ void *p;
+ target->path_max_len *= 2;
+ p = realloc(target->path, target->path_max_len);
+ if (p == NULL)
+ return EXT2_ET_NO_MEMORY;
+ target->path = p;
+ }
+ target->path_len += sprintf(target->path + target->path_len, "/%s",
+ file);
+ return 0;
+}
+
+#ifdef _WIN32
+static int scandir(const char *dir_name, struct dirent ***name_list,
+ int (*filter)(const struct dirent*),
+ int (*compar)(const struct dirent**, const struct dirent**)) {
+ DIR *dir;
+ struct dirent *dent;
+ struct dirent **temp_list = NULL;
+ size_t temp_list_size = 0; // unit: num of dirent
+ size_t num_dent = 0;
+
+ dir = opendir(dir_name);
+ if (dir == NULL) {
+ return -1;
+ }
+
+ while ((dent = readdir(dir))) {
+ if (filter != NULL && !(*filter)(dent))
+ continue;
+
+ // re-allocate the list
+ if (num_dent == temp_list_size) {
+ size_t new_list_size = temp_list_size + 32;
+ struct dirent **new_list = (struct dirent**)realloc(
+ temp_list, new_list_size * sizeof(struct dirent*));
+ if (new_list == NULL)
+ goto out_err;
+ temp_list_size = new_list_size;
+ temp_list = new_list;
+ }
+ // add the copy of dirent to the list
+ temp_list[num_dent] = (struct dirent*)malloc((dent->d_reclen + 3) & ~3);
+ if (!temp_list[num_dent])
+ goto out_err;
+ memcpy(temp_list[num_dent], dent, dent->d_reclen);
+ num_dent++;
+ }
+ closedir(dir);
+
+ if (compar != NULL) {
+ qsort(temp_list, num_dent, sizeof(struct dirent*),
+ (int (*)(const void*, const void*))compar);
+ }
+ *name_list = temp_list;
+ return num_dent;
+
+out_err:
+ closedir(dir);
+ while (num_dent > 0)
+ free(temp_list[--num_dent]);
+ free(temp_list);
+ return -1;
+}
+
+static int alphasort(const struct dirent **a, const struct dirent **b) {
+ return strcoll((*a)->d_name, (*b)->d_name);
+}
+#endif
+
+/* Copy files from source_dir to fs in alphabetical order */
+static errcode_t __populate_fs(ext2_filsys fs, ext2_ino_t parent_ino,
+ const char *source_dir, ext2_ino_t root,
+ struct hdlinks_s *hdlinks,
+ struct file_info *target,
+ struct fs_ops_callbacks *fs_callbacks)
+{
+ const char *name;
+ struct dirent **dent;
+ struct stat st;
+ unsigned int save_inode;
+ ext2_ino_t ino;
+ errcode_t retval = 0;
+ int hdlink;
+ size_t cur_dir_path_len;
+ int i, num_dents;
+
+ if (chdir(source_dir) < 0) {
+ retval = errno;
+ com_err(__func__, retval,
+ _("while changing working directory to \"%s\""),
+ source_dir);
+ return retval;
+ }
+
+ num_dents = scandir(".", &dent, NULL, alphasort);
+
+ if (num_dents < 0) {
+ retval = errno;
+ com_err(__func__, retval,
+ _("while scanning directory \"%s\""), source_dir);
+ return retval;
+ }
+
+ for (i = 0; i < num_dents; free(dent[i]), i++) {
+ name = dent[i]->d_name;
+ if ((!strcmp(name, ".")) || (!strcmp(name, "..")))
+ continue;
+ if (lstat(name, &st)) {
+ retval = errno;
+ com_err(__func__, retval, _("while lstat \"%s\""),
+ name);
+ goto out;
+ }
+
+ /* Check for hardlinks */
+ save_inode = 0;
+ if (!S_ISDIR(st.st_mode) && !S_ISLNK(st.st_mode) &&
+ st.st_nlink > 1) {
+ hdlink = is_hardlink(hdlinks, st.st_dev, st.st_ino);
+ if (hdlink >= 0) {
+ retval = add_link(fs, parent_ino,
+ hdlinks->hdl[hdlink].dst_ino,
+ name);
+ if (retval) {
+ com_err(__func__, retval,
+ "while linking %s", name);
+ goto out;
+ }
+ continue;
+ } else
+ save_inode = 1;
+ }
+
+ cur_dir_path_len = target->path_len;
+ retval = path_append(target, name);
+ if (retval) {
+ com_err(__func__, retval,
+ "while appending %s", name);
+ goto out;
+ }
+
+ if (fs_callbacks && fs_callbacks->create_new_inode) {
+ retval = fs_callbacks->create_new_inode(fs,
+ target->path, name, parent_ino, root,
+ st.st_mode & S_IFMT);
+ if (retval)
+ goto out;
+ }
+
+ switch(st.st_mode & S_IFMT) {
+ case S_IFCHR:
+ case S_IFBLK:
+ case S_IFIFO:
+#ifndef _WIN32
+ case S_IFSOCK:
+ retval = do_mknod_internal(fs, parent_ino, name,
+ st.st_mode, st.st_rdev);
+ if (retval) {
+ com_err(__func__, retval,
+ _("while creating special file "
+ "\"%s\""), name);
+ goto out;
+ }
+ break;
+ case S_IFLNK: {
+ char *ln_target;
+ int read_cnt;
+
+ ln_target = malloc(st.st_size + 1);
+ if (ln_target == NULL) {
+ com_err(__func__, retval,
+ _("malloc failed"));
+ goto out;
+ }
+ read_cnt = readlink(name, ln_target,
+ st.st_size + 1);
+ if (read_cnt == -1) {
+ retval = errno;
+ com_err(__func__, retval,
+ _("while trying to read link \"%s\""),
+ name);
+ free(ln_target);
+ goto out;
+ }
+ if (read_cnt > st.st_size) {
+ com_err(__func__, retval,
+ _("symlink increased in size "
+ "between lstat() and readlink()"));
+ free(ln_target);
+ goto out;
+ }
+ ln_target[read_cnt] = '\0';
+ retval = do_symlink_internal(fs, parent_ino, name,
+ ln_target, root);
+ free(ln_target);
+ if (retval) {
+ com_err(__func__, retval,
+ _("while writing symlink\"%s\""),
+ name);
+ goto out;
+ }
+ break;
+ }
+#endif /* !_WIN32 */
+ case S_IFREG:
+ retval = do_write_internal(fs, parent_ino, name, name,
+ root);
+ if (retval) {
+ com_err(__func__, retval,
+ _("while writing file \"%s\""), name);
+ goto out;
+ }
+ break;
+ case S_IFDIR:
+ /* Don't choke on /lost+found */
+ if (parent_ino == EXT2_ROOT_INO &&
+ strcmp(name, "lost+found") == 0)
+ goto find_lnf;
+ retval = do_mkdir_internal(fs, parent_ino, name,
+ root);
+ if (retval) {
+ com_err(__func__, retval,
+ _("while making dir \"%s\""), name);
+ goto out;
+ }
+find_lnf:
+ retval = ext2fs_namei(fs, root, parent_ino,
+ name, &ino);
+ if (retval) {
+ com_err(name, retval, 0);
+ goto out;
+ }
+ /* Populate the dir recursively*/
+ retval = __populate_fs(fs, ino, name, root, hdlinks,
+ target, fs_callbacks);
+ if (retval)
+ goto out;
+ if (chdir("..")) {
+ retval = errno;
+ com_err(__func__, retval,
+ _("while changing directory"));
+ goto out;
+ }
+ break;
+ default:
+ com_err(__func__, 0,
+ _("ignoring entry \"%s\""), name);
+ }
+
+ retval = ext2fs_namei(fs, root, parent_ino, name, &ino);
+ if (retval) {
+ com_err(name, retval, _("while looking up \"%s\""),
+ name);
+ goto out;
+ }
+
+ retval = set_inode_extra(fs, ino, &st);
+ if (retval) {
+ com_err(__func__, retval,
+ _("while setting inode for \"%s\""), name);
+ goto out;
+ }
+
+ retval = set_inode_xattr(fs, ino, name);
+ if (retval) {
+ com_err(__func__, retval,
+ _("while setting xattrs for \"%s\""), name);
+ goto out;
+ }
+
+ if (fs_callbacks && fs_callbacks->end_create_new_inode) {
+ retval = fs_callbacks->end_create_new_inode(fs,
+ target->path, name, parent_ino, root,
+ st.st_mode & S_IFMT);
+ if (retval)
+ goto out;
+ }
+
+ /* Save the hardlink ino */
+ if (save_inode) {
+ /*
+ * Check whether need more memory, and we don't need
+ * free() since the lifespan will be over after the fs
+ * populated.
+ */
+ if (hdlinks->count == hdlinks->size) {
+ void *p = realloc(hdlinks->hdl,
+ (hdlinks->size + HDLINK_CNT) *
+ sizeof(struct hdlink_s));
+ if (p == NULL) {
+ retval = EXT2_ET_NO_MEMORY;
+ com_err(name, retval,
+ _("while saving inode data"));
+ goto out;
+ }
+ hdlinks->hdl = p;
+ hdlinks->size += HDLINK_CNT;
+ }
+ hdlinks->hdl[hdlinks->count].src_dev = st.st_dev;
+ hdlinks->hdl[hdlinks->count].src_ino = st.st_ino;
+ hdlinks->hdl[hdlinks->count].dst_ino = ino;
+ hdlinks->count++;
+ }
+ target->path_len = cur_dir_path_len;
+ target->path[target->path_len] = 0;
+ }
+
+out:
+ for (; i < num_dents; free(dent[i]), i++);
+ free(dent);
+ return retval;
+}
+
+errcode_t populate_fs2(ext2_filsys fs, ext2_ino_t parent_ino,
+ const char *source_dir, ext2_ino_t root,
+ struct fs_ops_callbacks *fs_callbacks)
+{
+ struct file_info file_info;
+ struct hdlinks_s hdlinks;
+ errcode_t retval;
+
+ if (!(fs->flags & EXT2_FLAG_RW)) {
+ com_err(__func__, 0, "Filesystem opened readonly");
+ return EROFS;
+ }
+
+ hdlinks.count = 0;
+ hdlinks.size = HDLINK_CNT;
+ hdlinks.hdl = realloc(NULL, hdlinks.size * sizeof(struct hdlink_s));
+ if (hdlinks.hdl == NULL) {
+ retval = errno;
+ com_err(__func__, retval, _("while allocating memory"));
+ return retval;
+ }
+
+ file_info.path_len = 0;
+ file_info.path_max_len = 255;
+ file_info.path = calloc(file_info.path_max_len, 1);
+
+ retval = set_inode_xattr(fs, root, source_dir);
+ if (retval) {
+ com_err(__func__, retval,
+ _("while copying xattrs on root directory"));
+ goto out;
+ }
+
+ retval = __populate_fs(fs, parent_ino, source_dir, root, &hdlinks,
+ &file_info, fs_callbacks);
+
+out:
+ free(file_info.path);
+ free(hdlinks.hdl);
+ return retval;
+}
+
+errcode_t populate_fs(ext2_filsys fs, ext2_ino_t parent_ino,
+ const char *source_dir, ext2_ino_t root)
+{
+ return populate_fs2(fs, parent_ino, source_dir, root, NULL);
+}
diff --git a/misc/create_inode.h b/misc/create_inode.h
new file mode 100644
index 0000000..b5eeb42
--- /dev/null
+++ b/misc/create_inode.h
@@ -0,0 +1,57 @@
+#ifndef _CREATE_INODE_H
+#define _CREATE_INODE_H
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "et/com_err.h"
+#include "e2p/e2p.h"
+#include "ext2fs/ext2fs.h"
+
+struct hdlink_s
+{
+ dev_t src_dev;
+ ino_t src_ino;
+ ext2_ino_t dst_ino;
+};
+
+struct hdlinks_s
+{
+ int count;
+ int size;
+ struct hdlink_s *hdl;
+};
+
+#define HDLINK_CNT (4)
+
+struct fs_ops_callbacks {
+ errcode_t (* create_new_inode)(ext2_filsys fs, const char *target_path,
+ const char *name, ext2_ino_t parent_ino, ext2_ino_t root,
+ mode_t mode);
+ errcode_t (* end_create_new_inode)(ext2_filsys fs,
+ const char *target_path, const char *name,
+ ext2_ino_t parent_ino, ext2_ino_t root, mode_t mode);
+};
+
+extern int no_copy_xattrs; /* this should eventually be a flag
+ passed to populate_fs3() */
+
+/* For populating the filesystem */
+extern errcode_t populate_fs(ext2_filsys fs, ext2_ino_t parent_ino,
+ const char *source_dir, ext2_ino_t root);
+extern errcode_t populate_fs2(ext2_filsys fs, ext2_ino_t parent_ino,
+ const char *source_dir, ext2_ino_t root,
+ struct fs_ops_callbacks *fs_callbacks);
+extern errcode_t do_mknod_internal(ext2_filsys fs, ext2_ino_t cwd,
+ const char *name, unsigned int st_mode,
+ unsigned int st_rdev);
+extern errcode_t do_symlink_internal(ext2_filsys fs, ext2_ino_t cwd,
+ const char *name, char *target,
+ ext2_ino_t root);
+extern errcode_t do_mkdir_internal(ext2_filsys fs, ext2_ino_t cwd,
+ const char *name, ext2_ino_t root);
+extern errcode_t do_write_internal(ext2_filsys fs, ext2_ino_t cwd,
+ const char *src, const char *dest,
+ ext2_ino_t root);
+
+#endif /* _CREATE_INODE_H */
diff --git a/misc/dumpe2fs.8.in b/misc/dumpe2fs.8.in
new file mode 100644
index 0000000..dd27804
--- /dev/null
+++ b/misc/dumpe2fs.8.in
@@ -0,0 +1,115 @@
+.\" -*- nroff -*-
+.\" Copyright 1993, 1994, 1995 by Theodore Ts'o. All Rights Reserved.
+.\" This file may be copied under the terms of the GNU Public License.
+.\"
+.TH DUMPE2FS 8 "@E2FSPROGS_MONTH@ @E2FSPROGS_YEAR@" "E2fsprogs version @E2FSPROGS_VERSION@"
+.SH NAME
+dumpe2fs \- dump ext2/ext3/ext4 file system information
+.SH SYNOPSIS
+.B dumpe2fs
+[
+.B \-bfghixV
+]
+[
+.B \-o superblock=\fIsuperblock
+]
+[
+.B \-o blocksize=\fIblocksize
+]
+.I device
+.SH DESCRIPTION
+.B dumpe2fs
+prints the super block and blocks group information for the file system
+present on
+.I device.
+.PP
+.B Note:
+When used with a mounted file system, the printed
+information may be old or inconsistent.
+.SH OPTIONS
+.TP
+.B \-b
+print the blocks which are reserved as bad in the file system.
+.TP
+.B \-o superblock=\fIsuperblock
+use the block
+.I superblock
+when examining the file system.
+This option is not usually needed except by a file system wizard who
+is examining the remains of a very badly corrupted file system.
+.TP
+.B \-o blocksize=\fIblocksize
+use blocks of
+.I blocksize
+bytes when examining the file system.
+This option is not usually needed except by a file system wizard who
+is examining the remains of a very badly corrupted file system.
+.TP
+.B \-f
+force dumpe2fs to display a file system even though it may have some
+file system feature flags which dumpe2fs may not understand (and which
+can cause some of dumpe2fs's display to be suspect).
+.TP
+.B \-g
+display the group descriptor information in a machine readable colon-separated
+value format. The fields displayed are the group number; the number of the
+first block in the group; the superblock location (or -1 if not present); the
+range of blocks used by the group descriptors (or -1 if not present); the block
+bitmap location; the inode bitmap location; and the range of blocks used by the
+inode table.
+.TP
+.B \-h
+only display the superblock information and not any of the block
+group descriptor detail information.
+.TP
+.B \-i
+display the file system data from an image file created by
+.BR e2image ,
+using
+.I device
+as the pathname to the image file.
+.TP
+.B \-m
+If the
+.B mmp
+feature is enabled on the file system, check if
+.I device
+is in use by another node, see
+.BR e2mmpstatus (8)
+for full details. If used together with the
+.B \-i
+option, only the MMP block information is printed.
+.TP
+.B \-x
+print the detailed group information block numbers in hexadecimal format
+.TP
+.B \-V
+print the version number of
+.B dumpe2fs
+and exit.
+.SH EXIT CODE
+.B dumpe2fs
+exits with a return code of 0 if the operation completed without errors.
+It will exit with a non-zero return code if there are any errors, such
+as problems reading a valid superblock, bad checksums, or if the device
+is in use by another node and
+.B -m
+is specified.
+.SH BUGS
+You may need to know the physical file system structure to understand the
+output.
+.SH AUTHOR
+.B dumpe2fs
+was written by Remy Card <Remy.Card@linux.org>. It is currently being
+maintained by Theodore Ts'o <tytso@alum.mit.edu>.
+.SH AVAILABILITY
+.B dumpe2fs
+is part of the e2fsprogs package and is available from
+http://e2fsprogs.sourceforge.net.
+.SH SEE ALSO
+.BR e2fsck (8),
+.BR e2mmpstatus (8),
+.BR mke2fs (8),
+.BR tune2fs (8).
+.BR ext4 (5)
+
diff --git a/misc/dumpe2fs.c b/misc/dumpe2fs.c
new file mode 100644
index 0000000..7c080ed
--- /dev/null
+++ b/misc/dumpe2fs.c
@@ -0,0 +1,780 @@
+/*
+ * dumpe2fs.c - List the control structures of a second
+ * extended filesystem
+ *
+ * Copyright (C) 1992, 1993, 1994 Remy Card <card@masi.ibp.fr>
+ * Laboratoire MASI, Institut Blaise Pascal
+ * Universite Pierre et Marie Curie (Paris VI)
+ *
+ * Copyright 1995, 1996, 1997 by Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ * %End-Header%
+ */
+
+/*
+ * History:
+ * 94/01/09 - Creation
+ * 94/02/27 - Ported to use the ext2fs library
+ */
+
+#include "config.h"
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#else
+extern char *optarg;
+extern int optind;
+#endif
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "ext2fs/ext2_fs.h"
+
+#include "ext2fs/ext2fs.h"
+#include "e2p/e2p.h"
+#include "ext2fs/kernel-jbd.h"
+#include <uuid/uuid.h>
+
+#include "support/nls-enable.h"
+#include "support/plausible.h"
+#include "../version.h"
+
+#define in_use(m, x) (ext2fs_test_bit ((x), (m)))
+
+static const char * program_name = "dumpe2fs";
+static char * device_name = NULL;
+static int hex_format = 0;
+static int blocks64 = 0;
+
+static void usage(void)
+{
+ fprintf(stderr, _("Usage: %s [-bfghimxV] [-o superblock=<num>] "
+ "[-o blocksize=<num>] device\n"), program_name);
+ exit(1);
+}
+
+static void print_number(unsigned long long num)
+{
+ if (hex_format) {
+ if (blocks64)
+ printf("0x%08llx", num);
+ else
+ printf("0x%04llx", num);
+ } else
+ printf("%llu", num);
+}
+
+static void print_range(unsigned long long a, unsigned long long b)
+{
+ if (hex_format) {
+ if (blocks64)
+ printf("0x%08llx-0x%08llx", a, b);
+ else
+ printf("0x%04llx-0x%04llx", a, b);
+ } else
+ printf("%llu-%llu", a, b);
+}
+
+static void print_free(unsigned long group, char * bitmap,
+ unsigned long num, unsigned long offset, int ratio)
+{
+ int p = 0;
+ unsigned long i;
+ unsigned long j;
+
+ offset /= ratio;
+ offset += group * num;
+ for (i = 0; i < num; i++)
+ if (!in_use (bitmap, i))
+ {
+ if (p)
+ printf (", ");
+ print_number((i + offset) * ratio);
+ for (j = i; j < num && !in_use (bitmap, j); j++)
+ ;
+ if (--j != i) {
+ fputc('-', stdout);
+ print_number((j + offset) * ratio);
+ i = j;
+ }
+ p = 1;
+ }
+}
+
+static void print_bg_opt(int bg_flags, int mask,
+ const char *str, int *first)
+{
+ if (bg_flags & mask) {
+ if (*first) {
+ fputs(" [", stdout);
+ *first = 0;
+ } else
+ fputs(", ", stdout);
+ fputs(str, stdout);
+ }
+}
+static void print_bg_opts(ext2_filsys fs, dgrp_t i)
+{
+ int first = 1, bg_flags = 0;
+
+ if (ext2fs_has_group_desc_csum(fs))
+ bg_flags = ext2fs_bg_flags(fs, i);
+
+ print_bg_opt(bg_flags, EXT2_BG_INODE_UNINIT, "INODE_UNINIT",
+ &first);
+ print_bg_opt(bg_flags, EXT2_BG_BLOCK_UNINIT, "BLOCK_UNINIT",
+ &first);
+ print_bg_opt(bg_flags, EXT2_BG_INODE_ZEROED, "ITABLE_ZEROED",
+ &first);
+ if (!first)
+ fputc(']', stdout);
+ fputc('\n', stdout);
+}
+
+static void print_bg_rel_offset(ext2_filsys fs, blk64_t block, int itable,
+ blk64_t first_block, blk64_t last_block)
+{
+ if ((block >= first_block) && (block <= last_block)) {
+ if (itable && block == first_block)
+ return;
+ printf(" (+%u)", (unsigned)(block - first_block));
+ } else if (ext2fs_has_feature_flex_bg(fs->super)) {
+ dgrp_t flex_grp = ext2fs_group_of_blk2(fs, block);
+ printf(" (bg #%u + %u)", flex_grp,
+ (unsigned)(block-ext2fs_group_first_block2(fs,flex_grp)));
+ }
+}
+
+static void list_desc(ext2_filsys fs, int grp_only)
+{
+ unsigned long i;
+ blk64_t first_block, last_block;
+ blk64_t super_blk, old_desc_blk, new_desc_blk;
+ char *block_bitmap=NULL, *inode_bitmap=NULL;
+ const char *units = _("blocks");
+ int inode_blocks_per_group, old_desc_blocks, reserved_gdt;
+ int block_nbytes, inode_nbytes;
+ int has_super;
+ blk64_t blk_itr = EXT2FS_B2C(fs, fs->super->s_first_data_block);
+ ext2_ino_t ino_itr = 1;
+ errcode_t retval;
+
+ if (ext2fs_has_feature_bigalloc(fs->super))
+ units = _("clusters");
+
+ block_nbytes = EXT2_CLUSTERS_PER_GROUP(fs->super) / 8;
+ inode_nbytes = EXT2_INODES_PER_GROUP(fs->super) / 8;
+
+ if (fs->block_map)
+ block_bitmap = malloc(block_nbytes);
+ if (fs->inode_map)
+ inode_bitmap = malloc(inode_nbytes);
+
+ inode_blocks_per_group = ((fs->super->s_inodes_per_group *
+ EXT2_INODE_SIZE(fs->super)) +
+ EXT2_BLOCK_SIZE(fs->super) - 1) /
+ EXT2_BLOCK_SIZE(fs->super);
+ reserved_gdt = fs->super->s_reserved_gdt_blocks;
+ fputc('\n', stdout);
+ first_block = fs->super->s_first_data_block;
+ if (ext2fs_has_feature_meta_bg(fs->super))
+ old_desc_blocks = fs->super->s_first_meta_bg;
+ else
+ old_desc_blocks = fs->desc_blocks;
+ if (grp_only)
+ printf("group:block:super:gdt:bbitmap:ibitmap:itable\n");
+ for (i = 0; i < fs->group_desc_count; i++) {
+ first_block = ext2fs_group_first_block2(fs, i);
+ last_block = ext2fs_group_last_block2(fs, i);
+
+ ext2fs_super_and_bgd_loc2(fs, i, &super_blk,
+ &old_desc_blk, &new_desc_blk, 0);
+
+ if (grp_only) {
+ printf("%lu:%llu:", i, (unsigned long long) first_block);
+ if (i == 0 || super_blk)
+ printf("%llu:", (unsigned long long) super_blk);
+ else
+ printf("-1:");
+ if (old_desc_blk) {
+ print_range(old_desc_blk,
+ old_desc_blk + old_desc_blocks - 1);
+ printf(":");
+ } else if (new_desc_blk)
+ printf("%llu:", (unsigned long long) new_desc_blk);
+ else
+ printf("-1:");
+ printf("%llu:%llu:%llu\n",
+ (unsigned long long) ext2fs_block_bitmap_loc(fs, i),
+ (unsigned long long) ext2fs_inode_bitmap_loc(fs, i),
+ (unsigned long long) ext2fs_inode_table_loc(fs, i));
+ continue;
+ }
+
+ printf(_("Group %lu: (Blocks "), i);
+ print_range(first_block, last_block);
+ fputs(")", stdout);
+ if (ext2fs_has_group_desc_csum(fs)) {
+ unsigned csum = ext2fs_bg_checksum(fs, i);
+ unsigned exp_csum = ext2fs_group_desc_csum(fs, i);
+
+ printf(_(" csum 0x%04x"), csum);
+ if (csum != exp_csum)
+ printf(_(" (EXPECTED 0x%04x)"), exp_csum);
+ }
+ print_bg_opts(fs, i);
+ has_super = ((i==0) || super_blk);
+ if (has_super) {
+ printf (_(" %s superblock at "),
+ i == 0 ? _("Primary") : _("Backup"));
+ print_number(super_blk);
+ }
+ if (old_desc_blk) {
+ printf("%s", _(", Group descriptors at "));
+ print_range(old_desc_blk,
+ old_desc_blk + old_desc_blocks - 1);
+ if (reserved_gdt) {
+ printf("%s", _("\n Reserved GDT blocks at "));
+ print_range(old_desc_blk + old_desc_blocks,
+ old_desc_blk + old_desc_blocks +
+ reserved_gdt - 1);
+ }
+ } else if (new_desc_blk) {
+ fputc(has_super ? ',' : ' ', stdout);
+ printf("%s", _(" Group descriptor at "));
+ print_number(new_desc_blk);
+ has_super++;
+ }
+ if (has_super)
+ fputc('\n', stdout);
+ fputs(_(" Block bitmap at "), stdout);
+ print_number(ext2fs_block_bitmap_loc(fs, i));
+ print_bg_rel_offset(fs, ext2fs_block_bitmap_loc(fs, i), 0,
+ first_block, last_block);
+ if (ext2fs_has_feature_metadata_csum(fs->super))
+ printf(_(", csum 0x%08x"),
+ ext2fs_block_bitmap_checksum(fs, i));
+ if (getenv("DUMPE2FS_IGNORE_80COL"))
+ fputs(_(","), stdout);
+ else
+ fputs(_("\n "), stdout);
+ fputs(_(" Inode bitmap at "), stdout);
+ print_number(ext2fs_inode_bitmap_loc(fs, i));
+ print_bg_rel_offset(fs, ext2fs_inode_bitmap_loc(fs, i), 0,
+ first_block, last_block);
+ if (ext2fs_has_feature_metadata_csum(fs->super))
+ printf(_(", csum 0x%08x"),
+ ext2fs_inode_bitmap_checksum(fs, i));
+ fputs(_("\n Inode table at "), stdout);
+ print_range(ext2fs_inode_table_loc(fs, i),
+ ext2fs_inode_table_loc(fs, i) +
+ inode_blocks_per_group - 1);
+ print_bg_rel_offset(fs, ext2fs_inode_table_loc(fs, i), 1,
+ first_block, last_block);
+ printf (_("\n %u free %s, %u free inodes, "
+ "%u directories%s"),
+ ext2fs_bg_free_blocks_count(fs, i), units,
+ ext2fs_bg_free_inodes_count(fs, i),
+ ext2fs_bg_used_dirs_count(fs, i),
+ ext2fs_bg_itable_unused(fs, i) ? "" : "\n");
+ if (ext2fs_bg_itable_unused(fs, i))
+ printf (_(", %u unused inodes\n"),
+ ext2fs_bg_itable_unused(fs, i));
+ if (block_bitmap) {
+ fputs(_(" Free blocks: "), stdout);
+ retval = ext2fs_get_block_bitmap_range2(fs->block_map,
+ blk_itr, block_nbytes << 3, block_bitmap);
+ if (retval)
+ com_err("list_desc", retval,
+ "while reading block bitmap");
+ else
+ print_free(i, block_bitmap,
+ fs->super->s_clusters_per_group,
+ fs->super->s_first_data_block,
+ EXT2FS_CLUSTER_RATIO(fs));
+ fputc('\n', stdout);
+ blk_itr += fs->super->s_clusters_per_group;
+ }
+ if (inode_bitmap) {
+ fputs(_(" Free inodes: "), stdout);
+ retval = ext2fs_get_inode_bitmap_range2(fs->inode_map,
+ ino_itr, inode_nbytes << 3, inode_bitmap);
+ if (retval)
+ com_err("list_desc", retval,
+ "while reading inode bitmap");
+ else
+ print_free(i, inode_bitmap,
+ fs->super->s_inodes_per_group,
+ 1, 1);
+ fputc('\n', stdout);
+ ino_itr += fs->super->s_inodes_per_group;
+ }
+ }
+ if (block_bitmap)
+ free(block_bitmap);
+ if (inode_bitmap)
+ free(inode_bitmap);
+}
+
+static void list_bad_blocks(ext2_filsys fs, int dump)
+{
+ badblocks_list bb_list = 0;
+ badblocks_iterate bb_iter;
+ blk_t blk;
+ errcode_t retval;
+ const char *header, *fmt;
+
+ retval = ext2fs_read_bb_inode(fs, &bb_list);
+ if (retval) {
+ com_err("ext2fs_read_bb_inode", retval, 0);
+ return;
+ }
+ retval = ext2fs_badblocks_list_iterate_begin(bb_list, &bb_iter);
+ if (retval) {
+ com_err("ext2fs_badblocks_list_iterate_begin", retval,
+ "%s", _("while printing bad block list"));
+ ext2fs_badblocks_list_free(bb_list);
+ return;
+ }
+ if (dump) {
+ header = fmt = "%u\n";
+ } else {
+ header = _("Bad blocks: %u");
+ fmt = ", %u";
+ }
+ while (ext2fs_badblocks_list_iterate(bb_iter, &blk)) {
+ printf(header ? header : fmt, blk);
+ header = 0;
+ }
+ ext2fs_badblocks_list_iterate_end(bb_iter);
+ if (!dump)
+ fputc('\n', stdout);
+ ext2fs_badblocks_list_free(bb_list);
+}
+
+static void print_inline_journal_information(ext2_filsys fs)
+{
+ journal_superblock_t *jsb;
+ struct ext2_inode inode;
+ ext2_file_t journal_file;
+ errcode_t retval;
+ ext2_ino_t ino = fs->super->s_journal_inum;
+ char buf[1024];
+ int flags;
+
+ if (fs->flags & EXT2_FLAG_IMAGE_FILE)
+ return;
+ retval = ext2fs_read_inode(fs, ino, &inode);
+ if (retval) {
+ com_err(program_name, retval, "%s",
+ _("while reading journal inode"));
+ exit(1);
+ }
+ retval = ext2fs_file_open2(fs, ino, &inode, 0, &journal_file);
+ if (retval) {
+ com_err(program_name, retval, "%s",
+ _("while opening journal inode"));
+ exit(1);
+ }
+ retval = ext2fs_file_read(journal_file, buf, sizeof(buf), 0);
+ if (retval) {
+ com_err(program_name, retval, "%s",
+ _("while reading journal super block"));
+ exit(1);
+ }
+ ext2fs_file_close(journal_file);
+ jsb = (journal_superblock_t *) buf;
+ if (be32_to_cpu(jsb->s_header.h_magic) != JBD2_MAGIC_NUMBER) {
+ fprintf(stderr, "%s",
+ _("Journal superblock magic number invalid!\n"));
+ exit(1);
+ }
+ flags = ext2fs_has_feature_fast_commit(fs->super) ?
+ E2P_LIST_JOURNAL_FLAG_FC : 0;
+ e2p_list_journal_super(stdout, buf, fs->blocksize, flags);
+}
+
+static void print_journal_information(ext2_filsys fs)
+{
+ errcode_t retval;
+ char buf[1024];
+ journal_superblock_t *jsb;
+ int flags;
+
+ /* Get the journal superblock */
+ if ((retval = io_channel_read_blk64(fs->io,
+ fs->super->s_first_data_block + 1,
+ -1024, buf))) {
+ com_err(program_name, retval, "%s",
+ _("while reading journal superblock"));
+ exit(1);
+ }
+ jsb = (journal_superblock_t *) buf;
+ if ((jsb->s_header.h_magic != (unsigned) ntohl(JBD2_MAGIC_NUMBER)) ||
+ (jsb->s_header.h_blocktype !=
+ (unsigned) ntohl(JBD2_SUPERBLOCK_V2))) {
+ com_err(program_name, 0, "%s",
+ _("Couldn't find journal superblock magic numbers"));
+ exit(1);
+ }
+ flags = ext2fs_has_feature_fast_commit(fs->super) ?
+ E2P_LIST_JOURNAL_FLAG_FC : 0;
+ e2p_list_journal_super(stdout, buf, fs->blocksize, flags);
+}
+
+static int check_mmp(ext2_filsys fs)
+{
+ int retval;
+
+ /* This won't actually start MMP on the filesystem, since fs is opened
+ * readonly, but it will do the proper activity checking for us. */
+ retval = ext2fs_mmp_start(fs);
+ if (retval) {
+ com_err(program_name, retval, _("while trying to open %s"),
+ fs->device_name);
+ if (retval == EXT2_ET_MMP_FAILED ||
+ retval == EXT2_ET_MMP_FSCK_ON ||
+ retval == EXT2_ET_MMP_CSUM_INVALID ||
+ retval == EXT2_ET_MMP_UNKNOWN_SEQ) {
+ if (fs->mmp_buf) {
+ struct mmp_struct *mmp = fs->mmp_buf;
+ time_t mmp_time = mmp->mmp_time;
+
+ fprintf(stderr,
+ "%s: MMP update by '%.*s%.*s' at %s",
+ program_name,
+ EXT2_LEN_STR(mmp->mmp_nodename),
+ EXT2_LEN_STR(mmp->mmp_bdevname),
+ ctime(&mmp_time));
+ }
+ retval = 1;
+ } else {
+ retval = 2;
+ }
+ } else {
+ printf("%s: it is safe to mount '%s', MMP is clean\n",
+ program_name, fs->device_name);
+ }
+
+ return retval;
+}
+
+static void print_mmp_block(ext2_filsys fs)
+{
+ struct mmp_struct *mmp;
+ time_t mmp_time;
+ errcode_t retval;
+
+ if (fs->mmp_buf == NULL) {
+ retval = ext2fs_get_mem(fs->blocksize, &fs->mmp_buf);
+ if (retval) {
+ com_err(program_name, retval,
+ _("failed to alloc MMP buffer\n"));
+ return;
+ }
+ }
+
+ retval = ext2fs_mmp_read(fs, fs->super->s_mmp_block, fs->mmp_buf);
+ /* this is only dumping, not checking status, so OK to skip this */
+ if (retval == EXT2_ET_OP_NOT_SUPPORTED)
+ return;
+ if (retval) {
+ com_err(program_name, retval,
+ _("reading MMP block %llu from '%s'\n"),
+ (unsigned long long) fs->super->s_mmp_block,
+ fs->device_name);
+ return;
+ }
+
+ mmp = fs->mmp_buf;
+ mmp_time = mmp->mmp_time;
+ printf("MMP_block:\n");
+ printf(" mmp_magic: 0x%x\n", mmp->mmp_magic);
+ printf(" mmp_check_interval: %d\n", mmp->mmp_check_interval);
+ printf(" mmp_sequence: %#08x\n", mmp->mmp_seq);
+ printf(" mmp_update_date: %s", ctime(&mmp_time));
+ printf(" mmp_update_time: %llu\n",
+ (unsigned long long) mmp->mmp_time);
+ printf(" mmp_node_name: %.*s\n",
+ EXT2_LEN_STR(mmp->mmp_nodename));
+ printf(" mmp_device_name: %.*s\n",
+ EXT2_LEN_STR(mmp->mmp_bdevname));
+}
+
+static void parse_extended_opts(const char *opts, blk64_t *superblock,
+ int *blocksize)
+{
+ char *buf, *token, *next, *p, *arg, *badopt = 0;
+ int len;
+ int do_usage = 0;
+
+ len = strlen(opts);
+ buf = malloc(len+1);
+ if (!buf) {
+ fprintf(stderr, "%s",
+ _("Couldn't allocate memory to parse options!\n"));
+ exit(1);
+ }
+ strcpy(buf, opts);
+ for (token = buf; token && *token; token = next) {
+ p = strchr(token, ',');
+ next = 0;
+ if (p) {
+ *p = 0;
+ next = p+1;
+ }
+ arg = strchr(token, '=');
+ if (arg) {
+ *arg = 0;
+ arg++;
+ }
+ if (strcmp(token, "superblock") == 0 ||
+ strcmp(token, "sb") == 0) {
+ if (!arg) {
+ do_usage++;
+ badopt = token;
+ continue;
+ }
+ *superblock = strtoul(arg, &p, 0);
+ if (*p) {
+ fprintf(stderr,
+ _("Invalid superblock parameter: %s\n"),
+ arg);
+ do_usage++;
+ continue;
+ }
+ } else if (strcmp(token, "blocksize") == 0 ||
+ strcmp(token, "bs") == 0) {
+ if (!arg) {
+ do_usage++;
+ badopt = token;
+ continue;
+ }
+ *blocksize = strtoul(arg, &p, 0);
+ if (*p) {
+ fprintf(stderr,
+ _("Invalid blocksize parameter: %s\n"),
+ arg);
+ do_usage++;
+ continue;
+ }
+ } else {
+ do_usage++;
+ badopt = token;
+ }
+ }
+ if (do_usage) {
+ fprintf(stderr, _("\nBad extended option(s) specified: %s\n\n"
+ "Extended options are separated by commas, "
+ "and may take an argument which\n"
+ "\tis set off by an equals ('=') sign.\n\n"
+ "Valid extended options are:\n"
+ "\tsuperblock=<superblock number>\n"
+ "\tblocksize=<blocksize>\n"),
+ badopt ? badopt : "");
+ free(buf);
+ exit(1);
+ }
+ free(buf);
+}
+
+int main (int argc, char ** argv)
+{
+ errcode_t retval;
+ errcode_t retval_csum = 0;
+ const char *error_csum = NULL;
+ ext2_filsys fs;
+ int print_badblocks = 0;
+ blk64_t use_superblock = 0;
+ int use_blocksize = 0;
+ int image_dump = 0;
+ int mmp_check = 0;
+ int mmp_info = 0;
+ int force = 0;
+ int flags;
+ int header_only = 0;
+ int c;
+ int grp_only = 0;
+
+#ifdef ENABLE_NLS
+ setlocale(LC_MESSAGES, "");
+ setlocale(LC_CTYPE, "");
+ bindtextdomain(NLS_CAT_NAME, LOCALEDIR);
+ textdomain(NLS_CAT_NAME);
+ set_com_err_gettext(gettext);
+#endif
+ add_error_table(&et_ext2_error_table);
+ if (argc && *argv) {
+ if (strrchr(*argv, '/'))
+ program_name = strrchr(*argv, '/') + 1;
+ else
+ program_name = *argv;
+
+ if (strstr(program_name, "mmpstatus") != NULL) {
+ mmp_check = 1;
+ header_only = 1;
+ }
+ } else
+ usage();
+
+ if (!mmp_check)
+ fprintf(stderr, "dumpe2fs %s (%s)\n", E2FSPROGS_VERSION,
+ E2FSPROGS_DATE);
+
+ while ((c = getopt(argc, argv, "bfghimxVo:")) != EOF) {
+ switch (c) {
+ case 'b':
+ print_badblocks++;
+ break;
+ case 'f':
+ force++;
+ break;
+ case 'g':
+ grp_only++;
+ break;
+ case 'h':
+ header_only++;
+ break;
+ case 'i':
+ if (mmp_check)
+ mmp_info++;
+ else
+ image_dump++;
+ break;
+ case 'm':
+ mmp_check++;
+ header_only++;
+ if (image_dump) {
+ mmp_info = image_dump;
+ image_dump = 0;
+ }
+ break;
+ case 'o':
+ parse_extended_opts(optarg, &use_superblock,
+ &use_blocksize);
+ break;
+ case 'V':
+ /* Print version number and exit */
+ fprintf(stderr, _("\tUsing %s\n"),
+ error_message(EXT2_ET_BASE));
+ exit(0);
+ case 'x':
+ hex_format++;
+ break;
+ default:
+ usage();
+ }
+ }
+ if (optind != argc - 1)
+ usage();
+
+ device_name = argv[optind++];
+ flags = EXT2_FLAG_JOURNAL_DEV_OK | EXT2_FLAG_SOFTSUPP_FEATURES |
+ EXT2_FLAG_64BITS | EXT2_FLAG_THREADS;
+ if (force)
+ flags |= EXT2_FLAG_FORCE;
+ if (image_dump)
+ flags |= EXT2_FLAG_IMAGE_FILE;
+ if (header_only)
+ flags |= EXT2_FLAG_SUPER_ONLY;
+try_open_again:
+ if (use_superblock && !use_blocksize) {
+ for (use_blocksize = EXT2_MIN_BLOCK_SIZE;
+ use_blocksize <= EXT2_MAX_BLOCK_SIZE;
+ use_blocksize *= 2) {
+ retval = ext2fs_open (device_name, flags,
+ use_superblock,
+ use_blocksize, unix_io_manager,
+ &fs);
+ if (!retval)
+ break;
+ }
+ } else {
+ retval = ext2fs_open(device_name, flags, use_superblock,
+ use_blocksize, unix_io_manager, &fs);
+ }
+ flags |= EXT2_FLAG_IGNORE_CSUM_ERRORS;
+ if (retval && !retval_csum) {
+ retval_csum = retval;
+ error_csum = _("while trying to open %s");
+ goto try_open_again;
+ }
+ if (retval) {
+ com_err(program_name, retval, _("while trying to open %s"),
+ device_name);
+ printf("%s", _("Couldn't find valid filesystem superblock.\n"));
+ if (retval == EXT2_ET_BAD_MAGIC)
+ check_plausibility(device_name, CHECK_FS_EXIST, NULL);
+ goto out;
+ }
+ fs->default_bitmap_type = EXT2FS_BMAP64_RBTREE;
+ if (ext2fs_has_feature_64bit(fs->super))
+ blocks64 = 1;
+ if (mmp_check) {
+ if (ext2fs_has_feature_mmp(fs->super) &&
+ fs->super->s_mmp_block != 0) {
+ if (mmp_info) {
+ print_mmp_block(fs);
+ printf(" mmp_block_number: ");
+ print_number(fs->super->s_mmp_block);
+ printf("\n");
+ } else {
+ retval = check_mmp(fs);
+ }
+ if (!retval && retval_csum)
+ retval = 2;
+ } else {
+ fprintf(stderr, _("%s: MMP feature not enabled.\n"),
+ program_name);
+ retval = 2;
+ }
+ } else if (print_badblocks) {
+ list_bad_blocks(fs, 1);
+ } else {
+ if (grp_only)
+ goto just_descriptors;
+ list_super(fs->super);
+ if (ext2fs_has_feature_journal_dev(fs->super)) {
+ print_journal_information(fs);
+
+ goto out_close;
+ }
+ if (ext2fs_has_feature_journal(fs->super) &&
+ (fs->super->s_journal_inum != 0))
+ print_inline_journal_information(fs);
+ if (ext2fs_has_feature_mmp(fs->super) &&
+ fs->super->s_mmp_block != 0)
+ print_mmp_block(fs);
+ list_bad_blocks(fs, 0);
+ if (header_only)
+ goto out_close;
+
+ fs->flags &= ~EXT2_FLAG_IGNORE_CSUM_ERRORS;
+try_bitmaps_again:
+ retval = ext2fs_read_bitmaps(fs);
+ if (retval && !retval_csum) {
+ fs->flags |= EXT2_FLAG_IGNORE_CSUM_ERRORS;
+ retval_csum = retval;
+ error_csum = _("while trying to read '%s' bitmaps\n");
+ goto try_bitmaps_again;
+ }
+just_descriptors:
+ list_desc(fs, grp_only);
+ }
+out_close:
+ if (retval_csum) {
+ com_err(program_name, retval_csum, error_csum, device_name);
+ printf("%s", _("*** Run e2fsck now!\n\n"));
+ if (!retval)
+ retval = retval_csum;
+ }
+ ext2fs_close_free(&fs);
+ remove_error_table(&et_ext2_error_table);
+out:
+ return retval;
+}
diff --git a/misc/e2freefrag.8.in b/misc/e2freefrag.8.in
new file mode 100644
index 0000000..e77bcdb
--- /dev/null
+++ b/misc/e2freefrag.8.in
@@ -0,0 +1,96 @@
+.\" -*- nroff -*-
+.TH E2FREEFRAG 8 "@E2FSPROGS_MONTH@ @E2FSPROGS_YEAR@" "E2fsprogs version @E2FSPROGS_VERSION@"
+.SH NAME
+e2freefrag \- report free space fragmentation information
+.SH SYNOPSIS
+.B e2freefrag
+[
+.B \-c chunk_kb
+]
+[
+.B \-h
+]
+.B filesys
+
+.SH DESCRIPTION
+.B e2freefrag
+is used to report free space fragmentation on ext2/3/4 file systems.
+.I filesys
+is the file system device name (e.g.
+.IR /dev/hdc1 ", " /dev/md0 ).
+The
+.B e2freefrag
+program will scan the block bitmap information to check how many free blocks
+are present as contiguous and aligned free space. The percentage of contiguous
+free blocks of size and of alignment
+.I chunk_kb
+is reported. It also displays the minimum/maximum/average free chunk size in
+the file system, along with a histogram of all free chunks. This information
+can be used to gauge the level of free space fragmentation in the file system.
+.SH OPTIONS
+.TP
+.BI \-c " chunk_kb"
+If a chunk size is specified, then
+.B e2freefrag
+will print how many free chunks of size
+.I chunk_kb
+are available in units of kilobytes (Kb). The chunk size must be a
+power of two and be larger than file system block size.
+.TP
+.B \-h
+Print the usage of the program.
+.SH EXAMPLE
+# e2freefrag /dev/vgroot/lvhome
+.br
+Device: /dev/vgroot/lvhome
+.br
+Blocksize: 4096 bytes
+.br
+Total blocks: 1504085
+.br
+Free blocks: 292995 (19.5%)
+.br
+
+Min. free extent: 4 KB
+.br
+Max. free extent: 24008 KB
+.br
+Avg. free extent: 252 KB
+.br
+
+HISTOGRAM OF FREE EXTENT SIZES:
+.br
+Extent Size Range : Free extents Free Blocks Percent
+.br
+ 4K... 8K- : 704 704 0.2%
+.br
+ 8K... 16K- : 810 1979 0.7%
+.br
+ 16K... 32K- : 843 4467 1.5%
+.br
+ 32K... 64K- : 579 6263 2.1%
+.br
+ 64K... 128K- : 493 11067 3.8%
+.br
+ 128K... 256K- : 394 18097 6.2%
+.br
+ 256K... 512K- : 281 25477 8.7%
+.br
+ 512K... 1024K- : 253 44914 15.3%
+.br
+ 1M... 2M- : 143 51897 17.7%
+.br
+ 2M... 4M- : 73 50683 17.3%
+.br
+ 4M... 8M- : 37 52417 17.9%
+.br
+ 8M... 16M- : 7 19028 6.5%
+.br
+ 16M... 32M- : 1 6002 2.0%
+.SH AUTHOR
+This version of e2freefrag was written by Rupesh Thakare, and modified by
+Andreas Dilger <adilger@sun.com>, and Kalpak Shah.
+.SH SEE ALSO
+.IR debugfs (8),
+.IR dumpe2fs (8),
+.IR e2fsck (8)
diff --git a/misc/e2freefrag.c b/misc/e2freefrag.c
new file mode 100644
index 0000000..49b6346
--- /dev/null
+++ b/misc/e2freefrag.c
@@ -0,0 +1,452 @@
+/*
+ * e2freefrag - report filesystem free-space fragmentation
+ *
+ * Copyright (C) 2009 Sun Microsystems, Inc.
+ *
+ * Author: Rupesh Thakare <rupesh@sun.com>
+ * Andreas Dilger <adilger@sun.com>
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Public
+ * License version 2.
+ * %End-Header%
+ */
+#include "config.h"
+#include <stdio.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#else
+extern char *optarg;
+extern int optind;
+#endif
+#if defined(HAVE_EXT2_IOCTLS) && !defined(DEBUGFS)
+# include <sys/ioctl.h>
+# include <sys/types.h>
+# include <sys/stat.h>
+# include <fcntl.h>
+# include <limits.h>
+#endif
+
+#include "ext2fs/ext2_fs.h"
+#include "ext2fs/ext2fs.h"
+#include "e2freefrag.h"
+
+#if defined(HAVE_EXT2_IOCTLS) && !defined(DEBUGFS)
+# ifdef HAVE_LINUX_FSMAP_H
+# include <linux/fsmap.h>
+# endif
+# include "fsmap.h"
+#endif
+
+#ifndef PATH_MAX
+#define PATH_MAX 4096
+#endif
+
+static void usage(const char *prog)
+{
+ fprintf(stderr, "usage: %s [-c chunksize in kb] [-h] "
+ "device_name\n", prog);
+#ifndef DEBUGFS
+ exit(1);
+#endif
+}
+
+static int ul_log2(unsigned long arg)
+{
+ int l = 0;
+
+ arg >>= 1;
+ while (arg) {
+ l++;
+ arg >>= 1;
+ }
+ return l;
+}
+
+static void init_chunk_info(ext2_filsys fs, struct chunk_info *info)
+{
+ int i;
+
+ info->blocksize_bits = ul_log2((unsigned long)fs->blocksize);
+ if (info->chunkbytes) {
+ info->chunkbits = ul_log2(info->chunkbytes);
+ info->blks_in_chunk = info->chunkbytes >> info->blocksize_bits;
+ } else {
+ info->chunkbits = ul_log2(DEFAULT_CHUNKSIZE);
+ info->blks_in_chunk = DEFAULT_CHUNKSIZE >> info->blocksize_bits;
+ }
+
+ info->min = ~0UL;
+ info->max = info->avg = 0;
+ info->real_free_chunks = 0;
+
+ for (i = 0; i < MAX_HIST; i++) {
+ info->histogram.fc_chunks[i] = 0;
+ info->histogram.fc_blocks[i] = 0;
+ }
+}
+
+static void update_chunk_stats(struct chunk_info *info,
+ unsigned long chunk_size)
+{
+ unsigned long idx;
+
+ idx = ul_log2(chunk_size) + 1;
+ if (idx >= MAX_HIST)
+ idx = MAX_HIST-1;
+ info->histogram.fc_chunks[idx]++;
+ info->histogram.fc_blocks[idx] += chunk_size;
+
+ if (chunk_size > info->max)
+ info->max = chunk_size;
+ if (chunk_size < info->min)
+ info->min = chunk_size;
+ info->avg += chunk_size;
+ info->real_free_chunks++;
+}
+
+static void scan_block_bitmap(ext2_filsys fs, struct chunk_info *info)
+{
+ unsigned long long blocks_count = ext2fs_blocks_count(fs->super);
+ unsigned long long chunks = (blocks_count + info->blks_in_chunk) >>
+ (info->chunkbits - info->blocksize_bits);
+ unsigned long long chunk_num;
+ unsigned long last_chunk_size = 0;
+ unsigned long long chunk_start_blk = 0;
+ int used;
+
+ for (chunk_num = 0; chunk_num < chunks; chunk_num++) {
+ unsigned long long blk, num_blks;
+ int chunk_free;
+
+ /* Last chunk may be smaller */
+ if (chunk_start_blk + info->blks_in_chunk > blocks_count)
+ num_blks = blocks_count - chunk_start_blk;
+ else
+ num_blks = info->blks_in_chunk;
+
+ chunk_free = 0;
+
+ /* Initialize starting block for first chunk correctly else
+ * there is a segfault when blocksize = 1024 in which case
+ * block_map->start = 1 */
+ for (blk = 0; blk < num_blks; blk++, chunk_start_blk++) {
+ if (chunk_num == 0 && blk == 0) {
+ blk = fs->super->s_first_data_block;
+ chunk_start_blk = blk;
+ }
+ used = ext2fs_fast_test_block_bitmap2(fs->block_map,
+ chunk_start_blk >> fs->cluster_ratio_bits);
+ if (!used) {
+ last_chunk_size++;
+ chunk_free++;
+ }
+
+ if (used && last_chunk_size != 0) {
+ update_chunk_stats(info, last_chunk_size);
+ last_chunk_size = 0;
+ }
+ }
+
+ if (chunk_free == info->blks_in_chunk)
+ info->free_chunks++;
+ }
+ if (last_chunk_size != 0)
+ update_chunk_stats(info, last_chunk_size);
+}
+
+#if defined(HAVE_EXT2_IOCTLS) && !defined(DEBUGFS)
+# define FSMAP_EXTENTS 1024
+static int scan_online(ext2_filsys fs, struct chunk_info *info,
+ blk64_t *free_blks)
+{
+ struct fsmap_head *fsmap;
+ struct fsmap *extent;
+ struct fsmap *p;
+ char mntpoint[PATH_MAX + 1];
+ errcode_t retval;
+ int mount_flags;
+ int fd;
+ int ret;
+ unsigned int i;
+
+ /* Try to open the mountpoint for a live query. */
+ retval = ext2fs_check_mount_point(fs->device_name, &mount_flags,
+ mntpoint, PATH_MAX);
+ if (retval) {
+ com_err(fs->device_name, retval, "while checking mount status");
+ return 0;
+ }
+ if (!(mount_flags & EXT2_MF_MOUNTED))
+ return 0;
+ fd = open(mntpoint, O_RDONLY);
+ if (fd < 0) {
+ com_err(mntpoint, errno, "while opening mount point");
+ return 0;
+ }
+
+ fsmap = malloc(fsmap_sizeof(FSMAP_EXTENTS));
+ if (!fsmap) {
+ com_err(fs->device_name, errno, "while allocating memory");
+ return 0;
+ }
+
+ memset(fsmap, 0, sizeof(*fsmap));
+ fsmap->fmh_count = FSMAP_EXTENTS;
+ fsmap->fmh_keys[1].fmr_device = UINT_MAX;
+ fsmap->fmh_keys[1].fmr_physical = ULLONG_MAX;
+ fsmap->fmh_keys[1].fmr_owner = ULLONG_MAX;
+ fsmap->fmh_keys[1].fmr_offset = ULLONG_MAX;
+ fsmap->fmh_keys[1].fmr_flags = UINT_MAX;
+
+ *free_blks = 0;
+ /* Fill the extent histogram with live data */
+ while (1) {
+ ret = ioctl(fd, FS_IOC_GETFSMAP, fsmap);
+ if (ret < 0) {
+ com_err(fs->device_name, errno, "while calling fsmap");
+ free(fsmap);
+ return 0;
+ }
+
+ /* No more extents to map, exit */
+ if (!fsmap->fmh_entries)
+ break;
+
+ for (i = 0, extent = fsmap->fmh_recs;
+ i < fsmap->fmh_entries;
+ i++, extent++) {
+ if (!(extent->fmr_flags & FMR_OF_SPECIAL_OWNER) ||
+ extent->fmr_owner != FMR_OWN_FREE)
+ continue;
+ update_chunk_stats(info,
+ extent->fmr_length / fs->blocksize);
+ *free_blks += (extent->fmr_length / fs->blocksize);
+ }
+
+ p = &fsmap->fmh_recs[fsmap->fmh_entries - 1];
+ if (p->fmr_flags & FMR_OF_LAST)
+ break;
+ fsmap_advance(fsmap);
+ }
+ free(fsmap);
+ return 1;
+}
+#else
+# define scan_online(fs, info, free_blks) (0)
+#endif /* HAVE_EXT2_IOCTLS */
+
+static errcode_t scan_offline(ext2_filsys fs, struct chunk_info *info,
+ blk64_t *free_blks)
+{
+ errcode_t retval;
+
+ *free_blks = ext2fs_free_blocks_count(fs->super);
+ retval = ext2fs_read_block_bitmap(fs);
+ if (retval)
+ return retval;
+ scan_block_bitmap(fs, info);
+ return 0;
+}
+
+static errcode_t dump_chunk_info(ext2_filsys fs, struct chunk_info *info,
+ FILE *f, blk64_t free_blks)
+{
+ unsigned long total_chunks;
+ const char *unitp = "KMGTPEZY";
+ int units = 10;
+ unsigned long start = 0, end;
+ int i, retval = 0;
+
+ fprintf(f, "Total blocks: %llu\nFree blocks: %llu (%0.1f%%)\n",
+ (unsigned long long) ext2fs_blocks_count(fs->super),
+ (unsigned long long) free_blks,
+ (double)free_blks * 100 /
+ ext2fs_blocks_count(fs->super));
+
+ if (info->chunkbytes) {
+ fprintf(f, "\nChunksize: %lu bytes (%u blocks)\n",
+ info->chunkbytes, info->blks_in_chunk);
+ total_chunks = (ext2fs_blocks_count(fs->super) +
+ info->blks_in_chunk) >>
+ (info->chunkbits - info->blocksize_bits);
+ fprintf(f, "Total chunks: %lu\nFree chunks: %lu (%0.1f%%)\n",
+ total_chunks, info->free_chunks,
+ (double)info->free_chunks * 100 / total_chunks);
+ }
+
+ /* Display chunk information in KB */
+ if (info->real_free_chunks) {
+ unsigned int scale = fs->blocksize >> 10;
+ info->min = info->min * scale;
+ info->max = info->max * scale;
+ info->avg = info->avg / info->real_free_chunks * scale;
+ } else {
+ info->min = 0;
+ }
+
+ fprintf(f, "\nMin. free extent: %lu KB \nMax. free extent: %lu KB\n"
+ "Avg. free extent: %lu KB\n", info->min, info->max, info->avg);
+ fprintf(f, "Num. free extent: %lu\n", info->real_free_chunks);
+
+ fprintf(f, "\nHISTOGRAM OF FREE EXTENT SIZES:\n");
+ fprintf(f, "%s : %12s %12s %7s\n", "Extent Size Range",
+ "Free extents", "Free Blocks", "Percent");
+ for (i = 0; i < MAX_HIST; i++) {
+ end = 1 << (i + info->blocksize_bits - units);
+ if (info->histogram.fc_chunks[i] != 0) {
+ char end_str[32];
+
+ sprintf(end_str, "%5lu%c-", end, *unitp);
+ if (i == MAX_HIST-1)
+ strcpy(end_str, "max ");
+ fprintf(f, "%5lu%c...%7s : %12lu %12lu %6.2f%%\n",
+ start, *unitp, end_str,
+ info->histogram.fc_chunks[i],
+ info->histogram.fc_blocks[i],
+ (double)info->histogram.fc_blocks[i] * 100 /
+ free_blks);
+ }
+ start = end;
+ if (start == 1<<10) {
+ start = 1;
+ units += 10;
+ unitp++;
+ }
+ }
+
+ return retval;
+}
+
+static void close_device(char *device_name, ext2_filsys fs)
+{
+ int retval = ext2fs_close_free(&fs);
+
+ if (retval)
+ com_err(device_name, retval, "while closing the filesystem.\n");
+}
+
+static void collect_info(ext2_filsys fs, struct chunk_info *chunk_info, FILE *f)
+{
+ unsigned int retval = 0;
+ blk64_t free_blks = 0;
+
+ fprintf(f, "Device: %s\n", fs->device_name);
+ fprintf(f, "Blocksize: %u bytes\n", fs->blocksize);
+
+ init_chunk_info(fs, chunk_info);
+ if (!scan_online(fs, chunk_info, &free_blks)) {
+ init_chunk_info(fs, chunk_info);
+ retval = scan_offline(fs, chunk_info, &free_blks);
+ }
+ if (retval) {
+ com_err(fs->device_name, retval, "while reading block bitmap");
+ close_device(fs->device_name, fs);
+ exit(1);
+ }
+
+ retval = dump_chunk_info(fs, chunk_info, f, free_blks);
+ if (retval) {
+ com_err(fs->device_name, retval, "while dumping chunk info");
+ close_device(fs->device_name, fs);
+ exit(1);
+ }
+}
+
+#ifndef DEBUGFS
+static void open_device(char *device_name, ext2_filsys *fs)
+{
+ int retval;
+ int flag = EXT2_FLAG_FORCE | EXT2_FLAG_64BITS | EXT2_FLAG_THREADS;
+
+ retval = ext2fs_open(device_name, flag, 0, 0, unix_io_manager, fs);
+ if (retval) {
+ com_err(device_name, retval, "while opening filesystem");
+ exit(1);
+ }
+ (*fs)->default_bitmap_type = EXT2FS_BMAP64_RBTREE;
+}
+#endif
+
+#ifdef DEBUGFS
+#include "debugfs.h"
+
+void do_freefrag(int argc, char **argv, int sci_idx EXT2FS_ATTR((unused)),
+ void *infop EXT2FS_ATTR((unused)))
+#else
+int main(int argc, char *argv[])
+#endif
+{
+ struct chunk_info chunk_info;
+ ext2_filsys fs = NULL;
+ char *progname;
+ char *end;
+ int c;
+
+#ifdef DEBUGFS
+ if (check_fs_open(argv[0]))
+ return;
+ reset_getopt();
+#else
+ char *device_name;
+
+ add_error_table(&et_ext2_error_table);
+#endif
+ progname = argv[0];
+ memset(&chunk_info, 0, sizeof(chunk_info));
+
+ while ((c = getopt(argc, argv, "c:h")) != EOF) {
+ switch (c) {
+ case 'c':
+ chunk_info.chunkbytes = strtoull(optarg, &end, 0);
+ if (*end != '\0') {
+ fprintf(stderr, "%s: bad chunk size '%s'\n",
+ progname, optarg);
+ usage(progname);
+ }
+ if (chunk_info.chunkbytes &
+ (chunk_info.chunkbytes - 1)) {
+ fprintf(stderr, "%s: chunk size must be a "
+ "power of 2.\n", argv[0]);
+ usage(progname);
+ }
+ chunk_info.chunkbytes *= 1024;
+ break;
+ case 'h':
+ default:
+ usage(progname);
+ break;
+ }
+ }
+
+#ifndef DEBUGFS
+ if (optind == argc) {
+ fprintf(stderr, "%s: missing device name.\n", progname);
+ usage(progname);
+ }
+
+ device_name = argv[optind];
+
+ open_device(device_name, &fs);
+#else
+ fs = current_fs;
+#endif
+
+ if (chunk_info.chunkbytes && (chunk_info.chunkbytes < fs->blocksize)) {
+ fprintf(stderr, "%s: chunksize must be greater than or equal "
+ "to filesystem blocksize.\n", progname);
+ exit(1);
+ }
+ collect_info(fs, &chunk_info, stdout);
+#ifndef DEBUGFS
+ close_device(device_name, fs);
+
+ return 0;
+#endif
+}
diff --git a/misc/e2freefrag.h b/misc/e2freefrag.h
new file mode 100644
index 0000000..80d1eef
--- /dev/null
+++ b/misc/e2freefrag.h
@@ -0,0 +1,20 @@
+#include <sys/types.h>
+
+#define DEFAULT_CHUNKSIZE (1024*1024)
+
+#define MAX_HIST 32
+struct free_chunk_histogram {
+ unsigned long fc_chunks[MAX_HIST];
+ unsigned long fc_blocks[MAX_HIST];
+};
+
+struct chunk_info {
+ unsigned long chunkbytes; /* chunk size in bytes */
+ int chunkbits; /* chunk size in bits */
+ unsigned long free_chunks; /* total free chunks of given size */
+ unsigned long real_free_chunks; /* free chunks of any size */
+ int blocksize_bits; /* fs blocksize in bits */
+ int blks_in_chunk; /* number of blocks in a chunk */
+ unsigned long min, max, avg; /* chunk size stats */
+ struct free_chunk_histogram histogram; /* histogram of all chunk sizes*/
+};
diff --git a/misc/e2fuzz.c b/misc/e2fuzz.c
new file mode 100644
index 0000000..0ceece9
--- /dev/null
+++ b/misc/e2fuzz.c
@@ -0,0 +1,372 @@
+/*
+ * e2fuzz.c -- Fuzz an ext4 image, for testing purposes.
+ *
+ * Copyright (C) 2014 Oracle.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Library
+ * General Public License, version 2.
+ * %End-Header%
+ */
+#define _XOPEN_SOURCE 600
+#define _FILE_OFFSET_BITS 64
+#define _LARGEFILE64_SOURCE 1
+#define _GNU_SOURCE 1
+
+#include "config.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <unistd.h>
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#endif
+
+#include "ext2fs/ext2_fs.h"
+#include "ext2fs/ext2fs.h"
+
+static int dryrun = 0;
+static int verbose = 0;
+static int metadata_only = 1;
+static unsigned long long user_corrupt_bytes = 0;
+static double user_corrupt_pct = 0.0;
+
+#if !defined HAVE_PWRITE64 && !defined HAVE_PWRITE
+static ssize_t my_pwrite(int fd, const void *buf, size_t count,
+ ext2_loff_t offset)
+{
+ if (ext2fs_llseek(fd, offset, SEEK_SET) < 0)
+ return 0;
+
+ return write(fd, buf, count);
+}
+#endif /* !defined HAVE_PWRITE64 && !defined HAVE_PWRITE */
+
+static int getseed(void)
+{
+ int r;
+ int fd;
+
+ fd = open("/dev/urandom", O_RDONLY);
+ if (fd < 0) {
+ perror("open");
+ exit(0);
+ }
+ if (read(fd, &r, sizeof(r)) != sizeof(r))
+ printf("Unable to read random seed!\n");
+ close(fd);
+ return r;
+}
+
+struct find_block {
+ ext2_ino_t ino;
+ ext2fs_block_bitmap bmap;
+ struct ext2_inode *inode;
+ blk64_t corrupt_blocks;
+};
+
+static int find_block_helper(ext2_filsys fs EXT2FS_ATTR((unused)),
+ blk64_t *blocknr, e2_blkcnt_t blockcnt,
+ blk64_t ref_blk EXT2FS_ATTR((unused)),
+ int ref_offset EXT2FS_ATTR((unused)),
+ void *priv_data)
+{
+ struct find_block *fb = (struct find_block *)priv_data;
+
+ if (S_ISDIR(fb->inode->i_mode) || !metadata_only || blockcnt < 0) {
+ ext2fs_mark_block_bitmap2(fb->bmap, *blocknr);
+ fb->corrupt_blocks++;
+ }
+
+ return 0;
+}
+
+static errcode_t find_metadata_blocks(ext2_filsys fs, ext2fs_block_bitmap bmap,
+ ext2_loff_t *corrupt_bytes)
+{
+ dgrp_t i;
+ blk64_t b, c;
+ ext2_inode_scan scan;
+ ext2_ino_t ino;
+ struct ext2_inode inode;
+ struct find_block fb;
+ errcode_t retval;
+
+ *corrupt_bytes = 0;
+ fb.corrupt_blocks = 0;
+
+ /* Construct bitmaps of super/descriptor blocks */
+ for (i = 0; i < fs->group_desc_count; i++) {
+ ext2fs_reserve_super_and_bgd(fs, i, bmap);
+
+ /* bitmaps and inode table */
+ b = ext2fs_block_bitmap_loc(fs, i);
+ ext2fs_mark_block_bitmap2(bmap, b);
+ fb.corrupt_blocks++;
+
+ b = ext2fs_inode_bitmap_loc(fs, i);
+ ext2fs_mark_block_bitmap2(bmap, b);
+ fb.corrupt_blocks++;
+
+ c = ext2fs_inode_table_loc(fs, i);
+ ext2fs_mark_block_bitmap_range2(bmap, c,
+ fs->inode_blocks_per_group);
+ fb.corrupt_blocks += fs->inode_blocks_per_group;
+ }
+
+ /* Scan inodes */
+ fb.bmap = bmap;
+ fb.inode = &inode;
+ memset(&inode, 0, sizeof(inode));
+ retval = ext2fs_open_inode_scan(fs, 0, &scan);
+ if (retval)
+ goto out;
+
+ retval = ext2fs_get_next_inode_full(scan, &ino, &inode, sizeof(inode));
+ if (retval)
+ goto out2;
+ while (ino) {
+ if (inode.i_links_count == 0)
+ goto next_loop;
+
+ b = ext2fs_file_acl_block(fs, &inode);
+ if (b) {
+ ext2fs_mark_block_bitmap2(bmap, b);
+ fb.corrupt_blocks++;
+ }
+
+ /*
+ * Inline data, sockets, devices, and symlinks have
+ * no blocks to iterate.
+ */
+ if ((inode.i_flags & EXT4_INLINE_DATA_FL) ||
+ S_ISLNK(inode.i_mode) || S_ISFIFO(inode.i_mode) ||
+ S_ISCHR(inode.i_mode) || S_ISBLK(inode.i_mode) ||
+ S_ISSOCK(inode.i_mode))
+ goto next_loop;
+ fb.ino = ino;
+ retval = ext2fs_block_iterate3(fs, ino, BLOCK_FLAG_READ_ONLY,
+ NULL, find_block_helper, &fb);
+ if (retval)
+ goto out2;
+next_loop:
+ retval = ext2fs_get_next_inode_full(scan, &ino, &inode,
+ sizeof(inode));
+ if (retval)
+ goto out2;
+ }
+out2:
+ ext2fs_close_inode_scan(scan);
+out:
+ if (!retval)
+ *corrupt_bytes = fb.corrupt_blocks * fs->blocksize;
+ return retval;
+}
+
+static uint64_t rand_num(uint64_t min, uint64_t max)
+{
+ uint64_t x;
+ unsigned int i;
+ uint8_t *px = (uint8_t *)&x;
+
+ for (i = 0; i < sizeof(x); i++)
+ px[i] = random();
+
+ return min + (uint64_t)((double)(max - min) *
+ (x / ((double) UINT64_MAX + 1.0)));
+}
+
+static int process_fs(const char *fsname)
+{
+ errcode_t ret;
+ int flags, fd;
+ ext2_filsys fs = NULL;
+ ext2fs_block_bitmap corrupt_map;
+ ext2_loff_t hsize, count, off, offset, corrupt_bytes, i;
+ unsigned char c;
+
+ /* If mounted rw, force dryrun mode */
+ ret = ext2fs_check_if_mounted(fsname, &flags);
+ if (ret) {
+ fprintf(stderr, "%s: failed to determine filesystem mount "
+ "state.\n", fsname);
+ return 1;
+ }
+
+ if (!dryrun && (flags & EXT2_MF_MOUNTED) &&
+ !(flags & EXT2_MF_READONLY)) {
+ fprintf(stderr, "%s: is mounted rw, performing dry run.\n",
+ fsname);
+ dryrun = 1;
+ }
+
+ /* Ensure the fs is clean and does not have errors */
+ ret = ext2fs_open(fsname, EXT2_FLAG_64BITS | EXT2_FLAG_THREADS,
+ 0, 0, unix_io_manager, &fs);
+ if (ret) {
+ fprintf(stderr, "%s: failed to open filesystem.\n",
+ fsname);
+ return 1;
+ }
+
+ if ((fs->super->s_state & EXT2_ERROR_FS)) {
+ fprintf(stderr, "%s: errors detected, run fsck.\n",
+ fsname);
+ goto fail;
+ }
+
+ if (!dryrun && (fs->super->s_state & EXT2_VALID_FS) == 0) {
+ fprintf(stderr, "%s: unclean shutdown, performing dry run.\n",
+ fsname);
+ dryrun = 1;
+ }
+
+ /* Construct a bitmap of whatever we're corrupting */
+ if (!metadata_only) {
+ /* Load block bitmap */
+ ret = ext2fs_read_block_bitmap(fs);
+ if (ret) {
+ fprintf(stderr, "%s: error while reading block bitmap\n",
+ fsname);
+ goto fail;
+ }
+ corrupt_map = fs->block_map;
+ corrupt_bytes = (ext2fs_blocks_count(fs->super) -
+ ext2fs_free_blocks_count(fs->super)) *
+ fs->blocksize;
+ } else {
+ ret = ext2fs_allocate_block_bitmap(fs, "metadata block map",
+ &corrupt_map);
+ if (ret) {
+ fprintf(stderr, "%s: unable to create block bitmap\n",
+ fsname);
+ goto fail;
+ }
+
+ /* Iterate everything... */
+ ret = find_metadata_blocks(fs, corrupt_map, &corrupt_bytes);
+ if (ret) {
+ fprintf(stderr, "%s: while finding metadata\n",
+ fsname);
+ goto fail;
+ }
+ }
+
+ /* Run around corrupting things */
+ fd = open(fsname, O_RDWR);
+ if (fd < 0) {
+ perror(fsname);
+ goto fail;
+ }
+ srandom(getseed());
+ hsize = fs->blocksize * ext2fs_blocks_count(fs->super);
+ if (user_corrupt_bytes > 0)
+ count = user_corrupt_bytes;
+ else if (user_corrupt_pct > 0.0)
+ count = user_corrupt_pct * corrupt_bytes / 100;
+ else
+ count = rand_num(0, corrupt_bytes / 100);
+ offset = 4096; /* never corrupt superblock */
+ for (i = 0; i < count; i++) {
+ do
+ off = rand_num(offset, hsize);
+ while (!ext2fs_test_block_bitmap2(corrupt_map,
+ off / fs->blocksize));
+ c = rand() % 256;
+ if ((rand() % 2) && c < 128)
+ c |= 0x80;
+ if (verbose)
+ printf("Corrupting byte %lld in block %lld to 0x%x\n",
+ off % fs->blocksize,
+ off / fs->blocksize, c);
+ if (dryrun)
+ continue;
+#ifdef HAVE_PWRITE64
+ if (pwrite64(fd, &c, sizeof(c), off) != sizeof(c)) {
+ perror(fsname);
+ goto fail3;
+ }
+#elif HAVE_PWRITE
+ if (pwrite(fd, &c, sizeof(c), off) != sizeof(c)) {
+ perror(fsname);
+ goto fail3;
+ }
+#else
+ if (my_pwrite(fd, &c, sizeof(c), off) != sizeof(c)) {
+ perror(fsname);
+ goto fail3;
+ }
+#endif
+ }
+ close(fd);
+
+ /* Clean up */
+ ret = ext2fs_close_free(&fs);
+ if (ret) {
+ fprintf(stderr, "%s: error while closing filesystem\n",
+ fsname);
+ return 1;
+ }
+
+ return 0;
+fail3:
+ close(fd);
+ if (corrupt_map != fs->block_map)
+ ext2fs_free_block_bitmap(corrupt_map);
+fail:
+ ext2fs_close_free(&fs);
+ return 1;
+}
+
+static void print_help(const char *progname)
+{
+ printf("Usage: %s OPTIONS device\n", progname);
+ printf("-b: Corrupt this many bytes.\n");
+ printf("-d: Fuzz data blocks too.\n");
+ printf("-n: Dry run only.\n");
+ printf("-v: Verbose output.\n");
+ exit(0);
+}
+
+int main(int argc, char *argv[])
+{
+ int c;
+
+ while ((c = getopt(argc, argv, "b:dnv")) != -1) {
+ switch (c) {
+ case 'b':
+ if (optarg[strlen(optarg) - 1] == '%') {
+ user_corrupt_pct = strtod(optarg, NULL);
+ if (user_corrupt_pct > 100 ||
+ user_corrupt_pct < 0) {
+ fprintf(stderr, "%s: Invalid percentage.\n",
+ optarg);
+ return 1;
+ }
+ } else
+ user_corrupt_bytes = strtoull(optarg, NULL, 0);
+ if (errno) {
+ perror(optarg);
+ return 1;
+ }
+ break;
+ case 'd':
+ metadata_only = 0;
+ break;
+ case 'n':
+ dryrun = 1;
+ break;
+ case 'v':
+ verbose = 1;
+ break;
+ default:
+ print_help(argv[0]);
+ }
+ }
+
+ for (c = optind; c < argc; c++)
+ if (process_fs(argv[c]))
+ return 1;
+ return 0;
+}
diff --git a/misc/e2fuzz.sh b/misc/e2fuzz.sh
new file mode 100755
index 0000000..389f2ca
--- /dev/null
+++ b/misc/e2fuzz.sh
@@ -0,0 +1,327 @@
+#!/bin/bash
+
+# Test harness to fuzz a filesystem over and over...
+# Copyright (C) 2014 Oracle.
+
+DIR=/tmp
+PASSES=10000
+SZ=32m
+SCRIPT_DIR="$(dirname "$0")"
+FEATURES="has_journal,extent,huge_file,flex_bg,uninit_bg,dir_nlink,extra_isize,64bit,metadata_csum,bigalloc,sparse_super2,inline_data"
+BLK_SZ=4096
+INODE_SZ=256
+EXTENDED_OPTS="discard"
+EXTENDED_FSCK_OPTIONS=""
+RUN_FSCK=1
+OVERRIDE_PATH=1
+HAS_FUSE2FS=0
+USE_FUSE2FS=0
+MAX_FSCK=10
+SRCDIR=/etc
+test -x "${SCRIPT_DIR}/fuse2fs" && HAS_FUSE2FS=1
+
+print_help() {
+ echo "Usage: $0 OPTIONS"
+ echo "-b: FS block size is this. (${BLK_SZ})"
+ echo "-B: Corrupt this many bytes per run."
+ echo "-d: Create test files in this directory. (${DIR})"
+ echo "-E: Extended mke2fs options."
+ echo "-f: Do not run e2fsck after each pass."
+ echo "-F: Extended e2fsck options."
+ echo "-I: Create inodes of this size. (${INODE_SZ})"
+ echo "-n: Run this many passes. (${PASSES})"
+ echo "-O: Create FS with these features."
+ echo "-p: Use system's mke2fs/e2fsck/tune2fs tools."
+ echo "-s: Create FS images of this size. (${SZ})"
+ echo "-S: Copy files from this dir. (${SRCDIR})"
+ echo "-x: Run e2fsck at most this many times. (${MAX_FSCK})"
+ test "${HAS_FUSE2FS}" -gt 0 && echo "-u: Use fuse2fs instead of the kernel."
+ exit 0
+}
+
+GETOPT="d:n:s:O:I:b:B:E:F:fpx:S:"
+test "${HAS_FUSE2FS}" && GETOPT="${GETOPT}u"
+
+while getopts "${GETOPT}" opt; do
+ case "${opt}" in
+ "B")
+ E2FUZZ_ARGS="${E2FUZZ_ARGS} -b ${OPTARG}"
+ ;;
+ "d")
+ DIR="${OPTARG}"
+ ;;
+ "n")
+ PASSES="${OPTARG}"
+ ;;
+ "s")
+ SZ="${OPTARG}"
+ ;;
+ "O")
+ FEATURES="${FEATURES},${OPTARG}"
+ ;;
+ "I")
+ INODE_SZ="${OPTARG}"
+ ;;
+ "b")
+ BLK_SZ="${OPTARG}"
+ ;;
+ "E")
+ EXTENDED_OPTS="${OPTARG}"
+ ;;
+ "F")
+ EXTENDED_FSCK_OPTS="-E ${OPTARG}"
+ ;;
+ "f")
+ RUN_FSCK=0
+ ;;
+ "p")
+ OVERRIDE_PATH=0
+ ;;
+ "u")
+ USE_FUSE2FS=1
+ ;;
+ "x")
+ MAX_FSCK="${OPTARG}"
+ ;;
+ "S")
+ SRCDIR="${OPTARG}"
+ ;;
+ *)
+ print_help
+ ;;
+ esac
+done
+
+if [ "${OVERRIDE_PATH}" -gt 0 ]; then
+ PATH="${SCRIPT_DIR}:${SCRIPT_DIR}/../e2fsck/:${PATH}"
+ export PATH
+fi
+
+TESTDIR="${DIR}/tests/"
+TESTMNT="${DIR}/mnt/"
+BASE_IMG="${DIR}/e2fuzz.img"
+
+cat > /tmp/mke2fs.conf << ENDL
+[defaults]
+ base_features = ${FEATURES}
+ default_mntopts = acl,user_xattr,block_validity
+ enable_periodic_fsck = 0
+ blocksize = ${BLK_SZ}
+ inode_size = ${INODE_SZ}
+ inode_ratio = 4096
+ cluster_size = $((BLK_SZ * 2))
+ options = ${EXTENDED_OPTS}
+ENDL
+MKE2FS_CONFIG=/tmp/mke2fs.conf
+export MKE2FS_CONFIG
+
+# Set up FS image
+echo "+ create fs image"
+umount "${TESTDIR}"
+umount "${TESTMNT}"
+rm -rf "${TESTDIR}"
+rm -rf "${TESTMNT}"
+mkdir -p "${TESTDIR}"
+mkdir -p "${TESTMNT}"
+rm -rf "${BASE_IMG}"
+truncate -s "${SZ}" "${BASE_IMG}"
+mke2fs -F -v "${BASE_IMG}"
+if [ $? -ne 0 ]; then
+ exit $?
+fi
+
+# Populate FS image
+echo "+ populate fs image"
+modprobe loop
+mount "${BASE_IMG}" "${TESTMNT}" -o loop
+if [ $? -ne 0 ]; then
+ exit $?
+fi
+SRC_SZ="$(du -ks "${SRCDIR}" | awk '{print $1}')"
+FS_SZ="$(( $(stat -f "${TESTMNT}" -c '%a * %S') / 1024 ))"
+NR="$(( (FS_SZ * 4 / 10) / SRC_SZ ))"
+if [ "${NR}" -lt 1 ]; then
+ NR=1
+fi
+echo "+ make ${NR} copies"
+seq 1 "${NR}" | while read nr; do
+ cp -pRdu "${SRCDIR}" "${TESTMNT}/test.${nr}" 2> /dev/null
+done
+umount "${TESTMNT}"
+e2fsck -fn "${BASE_IMG}"
+if [ $? -ne 0 ]; then
+ echo "fsck failed??"
+ exit 1
+fi
+
+# Run tests
+echo "+ run test"
+ret=0
+seq 1 "${PASSES}" | while read pass; do
+ echo "+ pass ${pass}"
+ PASS_IMG="${TESTDIR}/e2fuzz-${pass}.img"
+ FSCK_IMG="${TESTDIR}/e2fuzz-${pass}.fsck"
+ FUZZ_LOG="${TESTDIR}/e2fuzz-${pass}.fuzz.log"
+ OPS_LOG="${TESTDIR}/e2fuzz-${pass}.ops.log"
+
+ echo "++ corrupt image"
+ cp "${BASE_IMG}" "${PASS_IMG}"
+ if [ $? -ne 0 ]; then
+ exit $?
+ fi
+ tune2fs -L "e2fuzz-${pass}" "${PASS_IMG}"
+ e2fuzz -v "${PASS_IMG}" ${E2FUZZ_ARGS} > "${FUZZ_LOG}"
+ if [ $? -ne 0 ]; then
+ exit $?
+ fi
+
+ echo "++ mount image"
+ if [ "${USE_FUSE2FS}" -gt 0 ]; then
+ "${SCRIPT_DIR}/fuse2fs" "${PASS_IMG}" "${TESTMNT}"
+ res=$?
+ else
+ mount "${PASS_IMG}" "${TESTMNT}" -o loop
+ res=$?
+ fi
+
+ if [ "${res}" -eq 0 ]; then
+ echo "+++ ls -laR"
+ ls -laR "${TESTMNT}/test.1/" > /dev/null 2> "${OPS_LOG}"
+
+ echo "+++ cat files"
+ find "${TESTMNT}/test.1/" -type f -size -1048576k -print0 | xargs -0 cat > /dev/null 2>> "${OPS_LOG}"
+
+ echo "+++ expand"
+ find "${TESTMNT}/" -type f 2> /dev/null | head -n 50000 | while read f; do
+ attr -l "$f" > /dev/null 2>> "${OPS_LOG}"
+ if [ -f "$f" -a -w "$f" ]; then
+ dd if=/dev/zero bs="${BLK_SZ}" count=1 >> "$f" 2>> "${OPS_LOG}"
+ fi
+ mv "$f" "$f.longer" > /dev/null 2>> "${OPS_LOG}"
+ done
+ sync
+
+ echo "+++ create files"
+ cp -pRdu "${SRCDIR}" "${TESTMNT}/test.moo" 2>> "${OPS_LOG}"
+ sync
+
+ echo "+++ remove files"
+ rm -rf "${TESTMNT}/test.moo" 2>> "${OPS_LOG}"
+
+ umount "${TESTMNT}"
+ res=$?
+ if [ "${res}" -ne 0 ]; then
+ ret=1
+ break
+ fi
+ sync
+ test "${USE_FUSE2FS}" -gt 0 && sleep 2
+ fi
+ if [ "${RUN_FSCK}" -gt 0 ]; then
+ cp "${PASS_IMG}" "${FSCK_IMG}"
+ pass_img_sz="$(stat -c '%s' "${PASS_IMG}")"
+
+ seq 1 "${MAX_FSCK}" | while read fsck_pass; do
+ echo "++ fsck pass ${fsck_pass}: $(which e2fsck) -fy ${FSCK_IMG} ${EXTENDED_FSCK_OPTS}"
+ FSCK_LOG="${TESTDIR}/e2fuzz-${pass}-${fsck_pass}.log"
+ e2fsck -fy "${FSCK_IMG}" ${EXTENDED_FSCK_OPTS} > "${FSCK_LOG}" 2>&1
+ res=$?
+ echo "++ fsck returns ${res}"
+ if [ "${res}" -eq 0 ]; then
+ exit 0
+ elif [ "${fsck_pass}" -eq "${MAX_FSCK}" ]; then
+ echo "++ fsck did not fix in ${MAX_FSCK} passes."
+ exit 1
+ fi
+ if [ "${res}" -gt 0 -a \
+ "$(grep 'Memory allocation failed' "${FSCK_LOG}" | wc -l)" -gt 0 ]; then
+ echo "++ Ran out of memory, get more RAM"
+ exit 0
+ fi
+ if [ "${res}" -gt 0 -a \
+ "$(grep 'Could not allocate block' "${FSCK_LOG}" | wc -l)" -gt 0 -a \
+ "$(dumpe2fs -h "${FSCK_IMG}" | grep '^Free blocks:' | awk '{print $3}')0" -eq 0 ]; then
+ echo "++ Ran out of space, get a bigger image"
+ exit 0
+ fi
+ if [ "${fsck_pass}" -gt 1 ]; then
+ diff -u "${TESTDIR}/e2fuzz-${pass}-$((fsck_pass - 1)).log" "${FSCK_LOG}"
+ if [ $? -eq 0 ]; then
+ echo "++ fsck makes no progress"
+ exit 2
+ fi
+ fi
+
+ fsck_img_sz="$(stat -c '%s' "${FSCK_IMG}")"
+ if [ "${fsck_img_sz}" -ne "${pass_img_sz}" ]; then
+ echo "++ fsck image size changed"
+ exit 3
+ fi
+ done
+ fsck_loop_ret=$?
+ if [ "${fsck_loop_ret}" -gt 0 ]; then
+ break;
+ fi
+ fi
+
+ echo "+++ check fs for round 2"
+ FSCK_LOG="${TESTDIR}/e2fuzz-${pass}-round2.log"
+ e2fsck -fn "${FSCK_IMG}" ${EXTENDED_FSCK_OPTS} >> "${FSCK_LOG}" 2>&1
+ res=$?
+ if [ "${res}" -ne 0 ]; then
+ echo "++++ fsck failed."
+ exit 1
+ fi
+
+ echo "++ mount image (2)"
+ mount "${FSCK_IMG}" "${TESTMNT}" -o loop
+ res=$?
+
+ if [ "${res}" -eq 0 ]; then
+ echo "+++ ls -laR (2)"
+ ls -laR "${TESTMNT}/test.1/" > /dev/null 2> "${OPS_LOG}"
+
+ echo "+++ cat files (2)"
+ find "${TESTMNT}/test.1/" -type f -size -1048576k -print0 | xargs -0 cat > /dev/null 2>> "${OPS_LOG}"
+
+ echo "+++ expand (2)"
+ find "${TESTMNT}/" -type f 2> /dev/null | head -n 50000 | while read f; do
+ attr -l "$f" > /dev/null 2>> "${OPS_LOG}"
+ if [ -f "$f" -a -w "$f" ]; then
+ dd if=/dev/zero bs="${BLK_SZ}" count=1 >> "$f" 2>> "${OPS_LOG}"
+ fi
+ mv "$f" "$f.longer" > /dev/null 2>> "${OPS_LOG}"
+ done
+ sync
+
+ echo "+++ create files (2)"
+ cp -pRdu "${SRCDIR}" "${TESTMNT}/test.moo" 2>> "${OPS_LOG}"
+ sync
+
+ echo "+++ remove files (2)"
+ rm -rf "${TESTMNT}/test.moo" 2>> "${OPS_LOG}"
+
+ umount "${TESTMNT}"
+ res=$?
+ if [ "${res}" -ne 0 ]; then
+ ret=1
+ break
+ fi
+ sync
+ test "${USE_FUSE2FS}" -gt 0 && sleep 2
+
+ echo "+++ check fs (2)"
+ e2fsck -fn "${FSCK_IMG}" >> "${FSCK_LOG}" 2>&1
+ res=$?
+ if [ "${res}" -ne 0 ]; then
+ echo "++ fsck failed."
+ exit 1
+ fi
+ else
+ echo "++ mount(2) failed with ${res}"
+ exit 1
+ fi
+ rm -rf "${FSCK_IMG}" "${PASS_IMG}" "${FUZZ_LOG}" "${TESTDIR}"/e2fuzz*.log
+done
+
+exit $ret
diff --git a/misc/e2image.8.in b/misc/e2image.8.in
new file mode 100644
index 0000000..90ea0c2
--- /dev/null
+++ b/misc/e2image.8.in
@@ -0,0 +1,335 @@
+.\" -*- nroff -*-
+.\" Copyright 2001 by Theodore Ts'o. All Rights Reserved.
+.\" This file may be copied under the terms of the GNU Public License.
+.\"
+.TH E2IMAGE 8 "@E2FSPROGS_MONTH@ @E2FSPROGS_YEAR@" "E2fsprogs version @E2FSPROGS_VERSION@"
+.SH NAME
+e2image \- Save critical ext2/ext3/ext4 file system metadata to a file
+
+.SH SYNOPSIS
+.B e2image
+.RB [ \-r | \-Q " [" \-af ]]
+[
+.B \-b
+.I superblock
+]
+[
+.B \-B
+.I blocksize
+]
+[
+.B \-cnps
+]
+[
+.B \-o
+.I src_offset
+]
+[
+.B \-O
+.I dest_offset
+]
+.I device
+.I image-file
+.br
+.B e2image
+.B \-I
+.I device
+.I image-file
+
+.SH DESCRIPTION
+The
+.B e2image
+program will save critical ext2, ext3, or ext4 file system metadata located on
+.I device
+to a file specified by
+.IR image-file .
+The image file may be examined by
+.B dumpe2fs
+and
+.BR debugfs ,
+by using the
+.B \-i
+option to those programs. This can assist an expert in recovering
+catastrophically corrupted file systems.
+.PP
+It is a very good idea to create image files for all file systems on a
+system and save the partition layout (which can be generated using the
+.B fdisk \-l
+command) at regular intervals --- at boot time, and/or every week or so.
+The image file should be stored on some file system other than
+the file system whose data it contains, to ensure that this data is
+accessible in the case where the file system has been badly damaged.
+.PP
+To save disk space,
+.B e2image
+creates the image file as a sparse file, or in QCOW2 format. Hence, if
+the sparse image file needs to be copied to another location, it should
+either be compressed first or copied using the
+.B \-\-sparse=always
+option to the GNU version of
+.BR cp (1).
+This does not apply to the QCOW2 image, which is not sparse.
+.PP
+The size of an ext2 image file depends primarily on the size of the
+file systems and how many inodes are in use. For a typical 10 Gigabyte
+file system, with 200,000 inodes in use out of 1.2 million inodes, the image
+file will be approximately 35 Megabytes; a 4 Gigabyte file system with 15,000
+inodes in use out of 550,000 inodes will result in a 3 Megabyte image file.
+Image files tend to be quite compressible; an image file taking up 32 Megabytes
+of space on disk will generally compress down to 3 or 4 Megabytes.
+.PP
+If
+.I image-file
+is
+.BR \- ,
+then the output of
+.B e2image
+will be sent to standard output, so that the output can be piped to
+another program, such as
+.BR gzip (1).
+(Note that this is currently only supported when
+creating a raw image file using the
+.B \-r
+option, since the process of creating a normal image file, or QCOW2
+image currently
+requires random access to the file, which cannot be done using a
+pipe.
+
+.SH OPTIONS
+.TP
+.B \-a
+Include file data in the image file. Normally
+.B e2image
+only includes fs metadata, not regular file data. This option will
+produce an image that is suitable to use to clone the entire FS or
+for backup purposes. Note that this option only works with the
+raw
+.RI ( \-r )
+or QCOW2
+.RI ( \-Q )
+formats. In conjunction with the
+.B \-r
+option it is possible to clone all and only the used blocks of one
+file system to another device/image file.
+.TP
+.BI \-b " superblock"
+Get image from partition with broken primary superblock by using
+the superblock located at file system block number
+.IR superblock .
+The partition is copied as-is including broken primary superblock.
+.TP
+.BI \-B " blocksize"
+Set the file system blocksize in bytes. Normally,
+.B e2image
+will search for the superblock at various different block sizes in an
+attempt to find the appropriate blocksize. This search can be fooled in
+some cases. This option forces e2fsck to only try locating the superblock
+with a particular blocksize. If the superblock is not found, e2image will
+terminate with a fatal error.
+.TP
+.BI \-c
+Compare each block to be copied from the source
+.I device
+to the corresponding block in the target
+.IR image-file .
+If both are already the same, the write will be skipped. This is
+useful if the file system is being cloned to a flash-based storage device
+(where reads are very fast and where it is desirable to avoid unnecessary
+writes to reduce write wear on the device).
+.TP
+.B \-f
+Override the read-only requirement for the source file system when saving
+the image file using the
+.B \-r
+and
+.B \-Q
+options. Normally, if the source file system is in use, the resulting image
+file is very likely not going to be useful. In some cases where the source
+file system is in constant use this may be better than no image at all.
+.TP
+.B \-I
+install the metadata stored in the image file back to the device.
+It can be used to restore the file system metadata back to the device
+in emergency situations.
+.PP
+.B WARNING!!!!
+The
+.B \-I
+option should only be used as a desperation measure when other
+alternatives have failed. If the file system has changed since the image
+file was created, data
+.B will
+be lost. In general, you should make another full image backup of the
+file system first, in case you wish to try other recovery strategies afterward.
+.TP
+.B \-n
+Cause all image writes to be skipped, and instead only print the block
+numbers that would have been written.
+.TP
+.BI \-o " src_offset"
+Specify offset of the image to be read from the start of the source
+.I device
+in bytes. See
+.B OFFSETS
+for more details.
+.TP
+.BI \-O " tgt_offset"
+Specify offset of the image to be written from the start of the target
+.I image-file
+in bytes. See
+.B OFFSETS
+for more details.
+.TP
+.B \-p
+Show progress of image-file creation.
+.TP
+.B \-Q
+Create a QCOW2-format image file instead of a normal image file, suitable
+for use by virtual machine images, and other tools that can use the
+.B .qcow
+image format. See
+.B QCOW2 IMAGE FILES
+below for details.
+.TP
+.B \-r
+Create a raw image file instead of a normal image file. See
+.B RAW IMAGE FILES
+below for details.
+.TP
+.B \-s
+Scramble directory entries and zero out unused portions of the directory
+blocks in the written image file to avoid revealing information about
+the contents of the file system. However, this will prevent analysis of
+problems related to hash-tree indexed directories.
+
+.SH RAW IMAGE FILES
+The
+.B \-r
+option will create a raw image file, which differs
+from a normal image file in two ways. First, the file system metadata is
+placed in the same relative offset within
+.I image-file
+as it is in the
+.I device
+so that
+.BR debugfs (8),
+.BR dumpe2fs (8),
+.BR e2fsck (8),
+.BR losetup (8),
+etc. and can be run directly on the raw image file. In order to minimize
+the amount of disk space consumed by the raw image file, it is
+created as a sparse file. (Beware of copying or
+compressing/decompressing this file with utilities that don't understand
+how to create sparse files; the file will become as large as the
+file system itself!) Secondly, the raw image file also includes indirect
+blocks and directory blocks, which the standard image file does not have.
+.PP
+Raw image files are sometimes used when sending file systems to the maintainer
+as part of bug reports to e2fsprogs. When used in this capacity, the
+recommended command is as follows (replace
+.B hda1
+with the appropriate device for your system):
+.PP
+.br
+ \fBe2image \-r /dev/hda1 \- | bzip2 > hda1.e2i.bz2\fR
+.PP
+This will only send the metadata information, without any data blocks.
+However, the filenames in the directory blocks can still reveal
+information about the contents of the file system that the bug reporter
+may wish to keep confidential. To address this concern, the
+.B \-s
+option can be specified to scramble the filenames in the image.
+.PP
+Note that this will work even if you substitute
+.B /dev/hda1
+for another raw
+disk image, or QCOW2 image previously created by
+.BR e2image .
+
+.SH QCOW2 IMAGE FILES
+The
+.B \-Q
+option will create a QCOW2 image file instead of a normal, or raw image file.
+A QCOW2 image contains all the information the raw image does, however unlike
+the raw image it is not sparse. The QCOW2 image minimize the amount of space
+used by the image by storing it in special format which packs data closely
+together, hence avoiding holes while still minimizing size.
+.PP
+In order to send file system to the maintainer as a part of bug report to
+e2fsprogs, use following commands (replace
+.B hda1
+with the appropriate device for your system):
+.PP
+.br
+\ \fBe2image \-Q /dev/hda1 hda1.qcow2\fR
+.br
+\ \fBbzip2 -z hda1.qcow2\fR
+.PP
+This will only send the metadata information, without any data blocks.
+As described for
+.B RAW IMAGE FILES
+the
+.B \-s
+option can be specified to scramble the file system names in the image.
+.PP
+Note that the QCOW2 image created by
+.B e2image
+is a regular QCOW2 image and can be processed by tools aware of QCOW2 format
+such as for example
+.BR qemu-img .
+.PP
+You can convert a .qcow2 image into a raw image with:
+.PP
+.br
+\ \fBe2image \-r hda1.qcow2 hda1.raw\fR
+.br
+.PP
+This can be useful to write a QCOW2 image containing all data to a
+sparse image file where it can be loop mounted, or to a disk partition.
+Note that this may not work with QCOW2 images not generated by e2image.
+
+.SH OFFSETS
+Normally a file system starts at the beginning of a partition, and
+.B e2image
+is run on the partition. When working with image files, you don't
+have the option of using the partition device, so you can specify
+the offset where the file system starts directly with the
+.B \-o
+option. Similarly the
+.B \-O
+option specifies the offset that should be seeked to in the destination
+before writing the file system.
+.PP
+For example, if you have a
+.B dd
+image of a whole hard drive that contains an ext2 fs in a partition
+starting at 1 MiB, you can clone that image to a block device with:
+.PP
+.br
+\ \fBe2image \-aro 1048576 img /dev/sda1\fR
+.br
+.PP
+Or you can clone a file system from a block device into an image file,
+leaving room in the first MiB for a partition table with:
+.PP
+.br
+\ \fBe2image -arO 1048576 /dev/sda1 img\fR
+.br
+.PP
+If you specify at least one offset, and only one file, an in-place
+move will be performed, allowing you to safely move the file system
+from one offset to another.
+
+.SH AUTHOR
+.B e2image
+was written by Theodore Ts'o (tytso@mit.edu).
+
+.SH AVAILABILITY
+.B e2image
+is part of the e2fsprogs package and is available from
+http://e2fsprogs.sourceforge.net.
+
+.SH SEE ALSO
+.BR dumpe2fs (8),
+.BR debugfs (8)
+.BR e2fsck (8)
diff --git a/misc/e2image.c b/misc/e2image.c
new file mode 100644
index 0000000..1ae0300
--- /dev/null
+++ b/misc/e2image.c
@@ -0,0 +1,1745 @@
+/*
+ * e2image.c --- Program which writes an image file backing up
+ * critical metadata for the filesystem.
+ *
+ * Copyright 2000, 2001 by Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ * %End-Header%
+ */
+
+#ifndef _LARGEFILE_SOURCE
+#define _LARGEFILE_SOURCE
+#endif
+#ifndef _LARGEFILE64_SOURCE
+#define _LARGEFILE64_SOURCE
+#endif
+
+#include "config.h"
+#include <fcntl.h>
+#include <grp.h>
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#else
+extern char *optarg;
+extern int optind;
+#endif
+#include <pwd.h>
+#include <stdio.h>
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <assert.h>
+#include <signal.h>
+
+#include "ext2fs/ext2_fs.h"
+#include "ext2fs/ext2fs.h"
+#include "ext2fs/ext2fsP.h"
+#include "et/com_err.h"
+#include "uuid/uuid.h"
+#include "e2p/e2p.h"
+#include "ext2fs/e2image.h"
+#include "ext2fs/qcow2.h"
+
+#include "support/nls-enable.h"
+#include "support/plausible.h"
+#include "support/quotaio.h"
+#include "../version.h"
+
+#define QCOW_OFLAG_COPIED (1ULL << 63)
+#define NO_BLK ((blk64_t) -1)
+
+/* Image types */
+#define E2IMAGE_RAW 1
+#define E2IMAGE_QCOW2 2
+
+/* Image flags */
+#define E2IMAGE_INSTALL_FLAG 1
+#define E2IMAGE_SCRAMBLE_FLAG 2
+#define E2IMAGE_IS_QCOW2_FLAG 4
+#define E2IMAGE_CHECK_ZERO_FLAG 8
+
+static const char * program_name = "e2image";
+static char * device_name = NULL;
+static char all_data;
+static char output_is_blk;
+static char nop_flag;
+/* writing to blk device: don't skip zeroed blocks */
+static blk64_t source_offset, dest_offset;
+static char move_mode;
+static char show_progress;
+static char *check_buf;
+static int skipped_blocks;
+
+static blk64_t align_offset(blk64_t offset, unsigned int n)
+{
+ return (offset + n - 1) & ~((blk64_t) n - 1);
+}
+
+static int get_bits_from_size(size_t size)
+{
+ int res = 0;
+
+ if (size == 0)
+ return -1;
+
+ while (size != 1) {
+ /* Not a power of two */
+ if (size & 1)
+ return -1;
+
+ size >>= 1;
+ res++;
+ }
+ return res;
+}
+
+static void usage(void)
+{
+ fprintf(stderr, _("Usage: %s [ -r|-Q ] [ -f ] [ -b superblock ] [ -B blocksize ] "
+ "device image-file\n"),
+ program_name);
+ fprintf(stderr, _(" %s -I device image-file\n"), program_name);
+ fprintf(stderr, _(" %s -ra [ -cfnp ] [ -o src_offset ] "
+ "[ -O dest_offset ] src_fs [ dest_fs ]\n"),
+ program_name);
+ exit (1);
+}
+
+static ext2_loff_t seek_relative(int fd, int offset)
+{
+ ext2_loff_t ret = ext2fs_llseek(fd, offset, SEEK_CUR);
+ if (ret < 0) {
+ perror("seek_relative");
+ exit(1);
+ }
+ return ret;
+}
+
+static ext2_loff_t seek_set(int fd, ext2_loff_t offset)
+{
+ ext2_loff_t ret = ext2fs_llseek(fd, offset, SEEK_SET);
+ if (ret < 0) {
+ perror("seek_set");
+ exit(1);
+ }
+ return ret;
+}
+
+/*
+ * Returns true if the block we are about to write is identical to
+ * what is already on the disk.
+ */
+static int check_block(int fd, void *buf, void *cbuf, int blocksize)
+{
+ char *cp = cbuf;
+ int count = blocksize, ret;
+
+ if (cbuf == NULL)
+ return 0;
+
+ while (count > 0) {
+ ret = read(fd, cp, count);
+ if (ret < 0) {
+ perror("check_block");
+ exit(1);
+ }
+ count -= ret;
+ cp += ret;
+ }
+ ret = memcmp(buf, cbuf, blocksize);
+ seek_relative(fd, -blocksize);
+ return (ret == 0) ? 1 : 0;
+}
+
+static void generic_write(int fd, void *buf, int blocksize, blk64_t block)
+{
+ int count, free_buf = 0;
+ errcode_t err;
+
+ if (!blocksize)
+ return;
+
+ if (!buf) {
+ free_buf = 1;
+ err = ext2fs_get_arrayzero(1, blocksize, &buf);
+ if (err) {
+ com_err(program_name, err, "%s",
+ _("while allocating buffer"));
+ exit(1);
+ }
+ }
+ if (nop_flag) {
+ printf(_("Writing block %llu\n"), (unsigned long long) block);
+ if (fd != 1)
+ seek_relative(fd, blocksize);
+ goto free_and_return;
+ }
+ count = write(fd, buf, blocksize);
+ if (count != blocksize) {
+ if (count == -1)
+ err = errno;
+ else
+ err = 0;
+
+ if (block)
+ com_err(program_name, err,
+ _("error writing block %llu"),
+ (unsigned long long) block);
+ else
+ com_err(program_name, err, "%s",
+ _("error in generic_write()"));
+
+ exit(1);
+ }
+free_and_return:
+ if (free_buf)
+ ext2fs_free_mem(&buf);
+}
+
+static void write_header(int fd, void *hdr, int hdr_size, int wrt_size)
+{
+ char *header_buf;
+ int ret;
+
+ /* Sanity check */
+ if (hdr_size > wrt_size) {
+ fprintf(stderr, "%s",
+ _("Error: header size is bigger than wrt_size\n"));
+ }
+
+ ret = ext2fs_get_mem(wrt_size, &header_buf);
+ if (ret) {
+ fputs(_("Couldn't allocate header buffer\n"), stderr);
+ exit(1);
+ }
+
+ seek_set(fd, 0);
+ memset(header_buf, 0, wrt_size);
+
+ if (hdr)
+ memcpy(header_buf, hdr, hdr_size);
+
+ generic_write(fd, header_buf, wrt_size, NO_BLK);
+
+ ext2fs_free_mem(&header_buf);
+}
+
+static void write_image_file(ext2_filsys fs, int fd)
+{
+ struct ext2_image_hdr hdr;
+ struct stat st;
+ errcode_t retval;
+
+ write_header(fd, NULL, sizeof(struct ext2_image_hdr), fs->blocksize);
+ memset(&hdr, 0, sizeof(struct ext2_image_hdr));
+
+ hdr.offset_super = ext2fs_cpu_to_le32(seek_relative(fd, 0));
+ retval = ext2fs_image_super_write(fs, fd, 0);
+ if (retval) {
+ com_err(program_name, retval, "%s",
+ _("while writing superblock"));
+ exit(1);
+ }
+
+ hdr.offset_inode = ext2fs_cpu_to_le32(seek_relative(fd, 0));
+ retval = ext2fs_image_inode_write(fs, fd,
+ (fd != 1) ? IMAGER_FLAG_SPARSEWRITE : 0);
+ if (retval) {
+ com_err(program_name, retval, "%s",
+ _("while writing inode table"));
+ exit(1);
+ }
+
+ hdr.offset_blockmap = ext2fs_cpu_to_le32(seek_relative(fd, 0));
+ retval = ext2fs_image_bitmap_write(fs, fd, 0);
+ if (retval) {
+ com_err(program_name, retval, "%s",
+ _("while writing block bitmap"));
+ exit(1);
+ }
+
+ hdr.offset_inodemap = ext2fs_cpu_to_le32(seek_relative(fd, 0));
+ retval = ext2fs_image_bitmap_write(fs, fd, IMAGER_FLAG_INODEMAP);
+ if (retval) {
+ com_err(program_name, retval, "%s",
+ _("while writing inode bitmap"));
+ exit(1);
+ }
+
+ hdr.magic_number = ext2fs_cpu_to_le32(EXT2_ET_MAGIC_E2IMAGE);
+ strcpy(hdr.magic_descriptor, "Ext2 Image 1.0");
+ gethostname(hdr.fs_hostname, sizeof(hdr.fs_hostname));
+ strncpy(hdr.fs_device_name, device_name, sizeof(hdr.fs_device_name)-1);
+ hdr.fs_device_name[sizeof(hdr.fs_device_name) - 1] = 0;
+ hdr.fs_blocksize = ext2fs_cpu_to_le32(fs->blocksize);
+
+ if (stat(device_name, &st) == 0)
+ hdr.fs_device = ext2fs_cpu_to_le32(st.st_rdev);
+
+ if (fstat(fd, &st) == 0) {
+ hdr.image_device = ext2fs_cpu_to_le32(st.st_dev);
+ hdr.image_inode = ext2fs_cpu_to_le32(st.st_ino);
+ }
+ memcpy(hdr.fs_uuid, fs->super->s_uuid, sizeof(hdr.fs_uuid));
+
+ hdr.image_time = ext2fs_cpu_to_le32(time(0));
+ write_header(fd, &hdr, sizeof(struct ext2_image_hdr), fs->blocksize);
+}
+
+/*
+ * These set of functions are used to write a RAW image file.
+ */
+static ext2fs_block_bitmap meta_block_map;
+static ext2fs_block_bitmap scramble_block_map; /* Directory blocks to be scrambled */
+static blk64_t meta_blocks_count;
+
+struct process_block_struct {
+ ext2_ino_t ino;
+ int is_dir;
+};
+
+/*
+ * These subroutines short circuits ext2fs_get_blocks and
+ * ext2fs_check_directory; we use them since we already have the inode
+ * structure, so there's no point in letting the ext2fs library read
+ * the inode again.
+ */
+static ext2_ino_t stashed_ino = 0;
+static struct ext2_inode *stashed_inode;
+
+static errcode_t meta_get_blocks(ext2_filsys fs EXT2FS_ATTR((unused)),
+ ext2_ino_t ino,
+ blk_t *blocks)
+{
+ int i;
+
+ if ((ino != stashed_ino) || !stashed_inode)
+ return EXT2_ET_CALLBACK_NOTHANDLED;
+
+ for (i=0; i < EXT2_N_BLOCKS; i++)
+ blocks[i] = stashed_inode->i_block[i];
+ return 0;
+}
+
+static errcode_t meta_check_directory(ext2_filsys fs EXT2FS_ATTR((unused)),
+ ext2_ino_t ino)
+{
+ if ((ino != stashed_ino) || !stashed_inode)
+ return EXT2_ET_CALLBACK_NOTHANDLED;
+
+ if (!LINUX_S_ISDIR(stashed_inode->i_mode))
+ return EXT2_ET_NO_DIRECTORY;
+ return 0;
+}
+
+static errcode_t meta_read_inode(ext2_filsys fs EXT2FS_ATTR((unused)),
+ ext2_ino_t ino,
+ struct ext2_inode *inode)
+{
+ if ((ino != stashed_ino) || !stashed_inode)
+ return EXT2_ET_CALLBACK_NOTHANDLED;
+ *inode = *stashed_inode;
+ return 0;
+}
+
+static void use_inode_shortcuts(ext2_filsys fs, int use_shortcuts)
+{
+ if (use_shortcuts) {
+ fs->get_blocks = meta_get_blocks;
+ fs->check_directory = meta_check_directory;
+ fs->read_inode = meta_read_inode;
+ stashed_ino = 0;
+ } else {
+ fs->get_blocks = 0;
+ fs->check_directory = 0;
+ fs->read_inode = 0;
+ }
+}
+
+static int process_dir_block(ext2_filsys fs EXT2FS_ATTR((unused)),
+ blk64_t *block_nr,
+ e2_blkcnt_t blockcnt EXT2FS_ATTR((unused)),
+ blk64_t ref_block EXT2FS_ATTR((unused)),
+ int ref_offset EXT2FS_ATTR((unused)),
+ void *priv_data EXT2FS_ATTR((unused)))
+{
+ struct process_block_struct *p;
+
+ p = (struct process_block_struct *) priv_data;
+
+ ext2fs_mark_block_bitmap2(meta_block_map, *block_nr);
+ meta_blocks_count++;
+ if (scramble_block_map && p->is_dir && blockcnt >= 0)
+ ext2fs_mark_block_bitmap2(scramble_block_map, *block_nr);
+ return 0;
+}
+
+static int process_file_block(ext2_filsys fs EXT2FS_ATTR((unused)),
+ blk64_t *block_nr,
+ e2_blkcnt_t blockcnt,
+ blk64_t ref_block EXT2FS_ATTR((unused)),
+ int ref_offset EXT2FS_ATTR((unused)),
+ void *priv_data EXT2FS_ATTR((unused)))
+{
+ if (blockcnt < 0 || all_data) {
+ ext2fs_mark_block_bitmap2(meta_block_map, *block_nr);
+ meta_blocks_count++;
+ }
+ return 0;
+}
+
+static void mark_table_blocks(ext2_filsys fs)
+{
+ blk64_t first_block, b;
+ unsigned int i,j;
+
+ first_block = fs->super->s_first_data_block;
+ /*
+ * Mark primary superblock
+ */
+ ext2fs_mark_block_bitmap2(meta_block_map, first_block);
+ meta_blocks_count++;
+
+ /*
+ * Mark the primary superblock descriptors
+ */
+ for (j = 0; j < fs->desc_blocks; j++) {
+ ext2fs_mark_block_bitmap2(meta_block_map,
+ ext2fs_descriptor_block_loc2(fs, first_block, j));
+ }
+ meta_blocks_count += fs->desc_blocks;
+
+ /*
+ * Mark MMP block
+ */
+ if (fs->super->s_feature_incompat & EXT4_FEATURE_INCOMPAT_MMP) {
+ ext2fs_mark_block_bitmap2(meta_block_map, fs->super->s_mmp_block);
+ meta_blocks_count++;
+ }
+
+ for (i = 0; i < fs->group_desc_count; i++) {
+ /*
+ * Mark the blocks used for the inode table
+ */
+ if ((output_is_blk ||
+ !ext2fs_bg_flags_test(fs, i, EXT2_BG_INODE_UNINIT)) &&
+ ext2fs_inode_table_loc(fs, i)) {
+ unsigned int end = (unsigned) fs->inode_blocks_per_group;
+ /* skip unused blocks */
+ if (!output_is_blk && ext2fs_has_group_desc_csum(fs))
+ end -= (ext2fs_bg_itable_unused(fs, i) /
+ EXT2_INODES_PER_BLOCK(fs->super));
+ for (j = 0, b = ext2fs_inode_table_loc(fs, i);
+ j < end;
+ j++, b++) {
+ ext2fs_mark_block_bitmap2(meta_block_map, b);
+ meta_blocks_count++;
+ }
+ }
+
+ /*
+ * Mark block used for the block bitmap
+ */
+ if (!ext2fs_bg_flags_test(fs, i, EXT2_BG_BLOCK_UNINIT) &&
+ ext2fs_block_bitmap_loc(fs, i)) {
+ ext2fs_mark_block_bitmap2(meta_block_map,
+ ext2fs_block_bitmap_loc(fs, i));
+ meta_blocks_count++;
+ }
+
+ /*
+ * Mark block used for the inode bitmap
+ */
+ if (!ext2fs_bg_flags_test(fs, i, EXT2_BG_INODE_UNINIT) &&
+ ext2fs_inode_bitmap_loc(fs, i)) {
+ ext2fs_mark_block_bitmap2(meta_block_map,
+ ext2fs_inode_bitmap_loc(fs, i));
+ meta_blocks_count++;
+ }
+ }
+}
+
+/*
+ * This function returns 1 if the specified block is all zeros
+ */
+static int check_zero_block(char *buf, int blocksize)
+{
+ char *cp = buf;
+ int left = blocksize;
+
+ if (output_is_blk)
+ return 0;
+ while (left > 0) {
+ if (*cp++)
+ return 0;
+ left--;
+ }
+ return 1;
+}
+
+static int name_id[256];
+
+#define EXT4_MAX_REC_LEN ((1<<16)-1)
+
+static void scramble_dir_block(ext2_filsys fs, blk64_t blk, char *buf)
+{
+ char *p, *end, *cp;
+ struct ext2_dir_entry_2 *dirent;
+ unsigned int rec_len;
+ int id, len;
+
+ end = buf + fs->blocksize;
+ for (p = buf; p < end-8; p += rec_len) {
+ dirent = (struct ext2_dir_entry_2 *) p;
+ rec_len = dirent->rec_len;
+#ifdef WORDS_BIGENDIAN
+ rec_len = ext2fs_swab16(rec_len);
+#endif
+ if (rec_len == EXT4_MAX_REC_LEN || rec_len == 0)
+ rec_len = fs->blocksize;
+ else
+ rec_len = (rec_len & 65532) | ((rec_len & 3) << 16);
+#if 0
+ printf("rec_len = %d, name_len = %d\n", rec_len, dirent->name_len);
+#endif
+ if (rec_len < 8 || (rec_len % 4) ||
+ (p+rec_len > end)) {
+ printf(_("Corrupt directory block %llu: "
+ "bad rec_len (%d)\n"),
+ (unsigned long long) blk, rec_len);
+ rec_len = end - p;
+ (void) ext2fs_set_rec_len(fs, rec_len,
+ (struct ext2_dir_entry *) dirent);
+#ifdef WORDS_BIGENDIAN
+ dirent->rec_len = ext2fs_swab16(dirent->rec_len);
+#endif
+ continue;
+ }
+ if (dirent->name_len + 8U > rec_len) {
+ printf(_("Corrupt directory block %llu: "
+ "bad name_len (%d)\n"),
+ (unsigned long long) blk, dirent->name_len);
+ dirent->name_len = rec_len - 8;
+ continue;
+ }
+ cp = p+8;
+ len = rec_len - dirent->name_len - 8;
+ if (len > 0)
+ memset(cp+dirent->name_len, 0, len);
+ if (dirent->name_len==1 && cp[0] == '.')
+ continue;
+ if (dirent->name_len==2 && cp[0] == '.' && cp[1] == '.')
+ continue;
+
+ memset(cp, 'A', dirent->name_len);
+ len = dirent->name_len;
+ id = name_id[len]++;
+ while ((len > 0) && (id > 0)) {
+ *cp += id % 26;
+ id = id / 26;
+ cp++;
+ len--;
+ }
+ }
+}
+
+static char got_sigint;
+
+static void sigint_handler(int unused EXT2FS_ATTR((unused)))
+{
+ got_sigint = 1;
+ signal (SIGINT, SIG_DFL);
+}
+
+#define calc_percent(a, b) ((int) ((100.0 * (((float) (a)) / \
+ ((float) (b)))) + 0.5))
+#define calc_rate(t, b, d) (((float)(t) / ((float)(1024 * 1024) / (b))) / (d))
+
+static int print_progress(blk64_t num, blk64_t total)
+{
+ return fprintf(stderr, _("%llu / %llu blocks (%d%%)"),
+ (unsigned long long) num,
+ (unsigned long long) total,
+ calc_percent(num, total));
+}
+
+static void output_meta_data_blocks(ext2_filsys fs, int fd, int flags)
+{
+ errcode_t retval;
+ blk64_t blk;
+ char *buf, *zero_buf;
+ int sparse = 0;
+ blk64_t start = 0;
+ blk64_t distance = 0;
+ blk64_t end = ext2fs_blocks_count(fs->super);
+ time_t last_update = 0;
+ time_t start_time = 0;
+ blk64_t total_written = 0;
+ int bscount = 0;
+
+ retval = ext2fs_get_mem(fs->blocksize, &buf);
+ if (retval) {
+ com_err(program_name, retval, "%s",
+ _("while allocating buffer"));
+ exit(1);
+ }
+ retval = ext2fs_get_memzero(fs->blocksize, &zero_buf);
+ if (retval) {
+ com_err(program_name, retval, "%s",
+ _("while allocating buffer"));
+ exit(1);
+ }
+ if (show_progress) {
+ fprintf(stderr, "%s", _("Copying "));
+ bscount = print_progress(total_written, meta_blocks_count);
+ fflush(stderr);
+ last_update = time(NULL);
+ start_time = time(NULL);
+ }
+ /* when doing an in place move to the right, you can't start
+ at the beginning or you will overwrite data, so instead
+ divide the fs up into distance size chunks and write them
+ in reverse. */
+ if (move_mode && dest_offset > source_offset) {
+ distance = (dest_offset - source_offset) / fs->blocksize;
+ if (distance < ext2fs_blocks_count(fs->super))
+ start = ext2fs_blocks_count(fs->super) - distance;
+ }
+ if (move_mode)
+ signal (SIGINT, sigint_handler);
+more_blocks:
+ if (distance)
+ seek_set(fd, (start * fs->blocksize) + dest_offset);
+ for (blk = start; blk < end; blk++) {
+ if (got_sigint) {
+ if (distance) {
+ /* moving to the right */
+ if (distance >= ext2fs_blocks_count(fs->super)||
+ start == ext2fs_blocks_count(fs->super) -
+ distance)
+ kill(getpid(), SIGINT);
+ } else {
+ /* moving to the left */
+ if (blk < (source_offset - dest_offset) /
+ fs->blocksize)
+ kill(getpid(), SIGINT);
+ }
+ if (show_progress)
+ fputc('\r', stderr);
+ fprintf(stderr, "%s",
+ _("Stopping now will destroy the filesystem, "
+ "interrupt again if you are sure\n"));
+ if (show_progress) {
+ fprintf(stderr, "%s", _("Copying "));
+ bscount = print_progress(total_written,
+ meta_blocks_count);
+ fflush(stderr);
+ }
+
+ got_sigint = 0;
+ }
+ if (show_progress && last_update != time(NULL)) {
+ time_t duration;
+ last_update = time(NULL);
+ while (bscount--)
+ fputc('\b', stderr);
+ bscount = print_progress(total_written,
+ meta_blocks_count);
+ duration = time(NULL) - start_time;
+ if (duration > 5 && total_written) {
+ time_t est = (duration * meta_blocks_count /
+ total_written) - duration;
+ char buff[30];
+ strftime(buff, 30, "%T", gmtime(&est));
+ bscount +=
+ fprintf(stderr,
+ _(" %s remaining at %.2f MB/s"),
+ buff, calc_rate(total_written,
+ fs->blocksize,
+ duration));
+ }
+ fflush (stderr);
+ }
+ if ((blk >= fs->super->s_first_data_block) &&
+ ext2fs_test_block_bitmap2(meta_block_map, blk)) {
+ retval = io_channel_read_blk64(fs->io, blk, 1, buf);
+ if (retval) {
+ com_err(program_name, retval,
+ _("error reading block %llu"),
+ (unsigned long long) blk);
+ }
+ total_written++;
+ if (scramble_block_map &&
+ ext2fs_test_block_bitmap2(scramble_block_map, blk))
+ scramble_dir_block(fs, blk, buf);
+ if ((flags & E2IMAGE_CHECK_ZERO_FLAG) &&
+ check_zero_block(buf, fs->blocksize))
+ goto sparse_write;
+ if (sparse)
+ seek_relative(fd, sparse);
+ sparse = 0;
+ if (check_block(fd, buf, check_buf, fs->blocksize)) {
+ seek_relative(fd, fs->blocksize);
+ skipped_blocks++;
+ } else
+ generic_write(fd, buf, fs->blocksize, blk);
+ } else {
+ sparse_write:
+ if (fd == 1) {
+ if (!nop_flag)
+ generic_write(fd, zero_buf,
+ fs->blocksize, blk);
+ continue;
+ }
+ sparse += fs->blocksize;
+ if (sparse > 1024*1024) {
+ seek_relative(fd, 1024*1024);
+ sparse -= 1024*1024;
+ }
+ }
+ }
+ if (distance && start) {
+ if (start < distance) {
+ end = start;
+ start = 0;
+ } else {
+ end -= distance;
+ start -= distance;
+ if (end < distance) {
+ /* past overlap, do rest in one go */
+ end = start;
+ start = 0;
+ }
+ }
+ sparse = 0;
+ goto more_blocks;
+ }
+ signal (SIGINT, SIG_DFL);
+ if (show_progress) {
+ time_t duration = time(NULL) - start_time;
+ char buff[30];
+ fputc('\r', stderr);
+ strftime(buff, 30, "%T", gmtime(&duration));
+ fprintf(stderr, _("Copied %llu / %llu blocks (%d%%) in %s "),
+ (unsigned long long) total_written,
+ (unsigned long long) meta_blocks_count,
+ calc_percent(total_written, meta_blocks_count), buff);
+ if (duration)
+ fprintf(stderr, _("at %.2f MB/s"),
+ calc_rate(total_written, fs->blocksize, duration));
+ fputs(" \n", stderr);
+ }
+#ifdef HAVE_FTRUNCATE64
+ if (sparse) {
+ ext2_loff_t offset;
+ if (distance)
+ offset = seek_set(fd,
+ fs->blocksize * ext2fs_blocks_count(fs->super) + dest_offset);
+ else
+ offset = seek_relative(fd, sparse);
+
+ if (ftruncate64(fd, offset) < 0) {
+ seek_relative(fd, -1);
+ generic_write(fd, zero_buf, 1, NO_BLK);
+ }
+ }
+#else
+ if (sparse && !distance) {
+ seek_relative(fd, sparse-1);
+ generic_write(fd, zero_buf, 1, NO_BLK);
+ }
+#endif
+ ext2fs_free_mem(&zero_buf);
+ ext2fs_free_mem(&buf);
+}
+
+static void init_l1_table(struct ext2_qcow2_image *image)
+{
+ __u64 *l1_table;
+ errcode_t ret;
+
+ ret = ext2fs_get_arrayzero(image->l1_size, sizeof(__u64), &l1_table);
+ if (ret) {
+ com_err(program_name, ret, "%s",
+ _("while allocating l1 table"));
+ exit(1);
+ }
+
+ image->l1_table = l1_table;
+}
+
+static void init_l2_cache(struct ext2_qcow2_image *image)
+{
+ unsigned int count, i;
+ struct ext2_qcow2_l2_cache *cache;
+ struct ext2_qcow2_l2_table *table;
+ errcode_t ret;
+
+ ret = ext2fs_get_arrayzero(1, sizeof(struct ext2_qcow2_l2_cache),
+ &cache);
+ if (ret)
+ goto alloc_err;
+
+ count = (image->l1_size > L2_CACHE_PREALLOC) ? L2_CACHE_PREALLOC :
+ image->l1_size;
+
+ cache->count = count;
+ cache->free = count;
+ cache->next_offset = image->l2_offset;
+
+ for (i = 0; i < count; i++) {
+ ret = ext2fs_get_arrayzero(1,
+ sizeof(struct ext2_qcow2_l2_table), &table);
+ if (ret)
+ goto alloc_err;
+
+ ret = ext2fs_get_arrayzero(image->l2_size,
+ sizeof(__u64), &table->data);
+ if (ret)
+ goto alloc_err;
+
+ table->next = cache->free_head;
+ cache->free_head = table;
+ }
+
+ image->l2_cache = cache;
+ return;
+
+alloc_err:
+ com_err(program_name, ret, "%s", _("while allocating l2 cache"));
+ exit(1);
+}
+
+static void put_l2_cache(struct ext2_qcow2_image *image)
+{
+ struct ext2_qcow2_l2_cache *cache = image->l2_cache;
+ struct ext2_qcow2_l2_table *tmp, *table;
+
+ if (!cache)
+ return;
+
+ table = cache->free_head;
+ cache->free_head = NULL;
+again:
+ while (table) {
+ tmp = table;
+ table = table->next;
+ ext2fs_free_mem(&tmp->data);
+ ext2fs_free_mem(&tmp);
+ }
+
+ if (cache->free != cache->count) {
+ fprintf(stderr, "%s", _("Warning: There are still tables in "
+ "the cache while putting the cache, "
+ "data will be lost so the image may "
+ "not be valid.\n"));
+ table = cache->used_head;
+ cache->used_head = NULL;
+ goto again;
+ }
+
+ ext2fs_free_mem(&cache);
+}
+
+static int init_refcount(struct ext2_qcow2_image *img, blk64_t table_offset)
+{
+ struct ext2_qcow2_refcount *ref;
+ blk64_t table_clusters;
+ errcode_t ret;
+
+ ref = &(img->refcount);
+
+ /*
+ * One refcount block addresses 2048 clusters, one refcount table
+ * addresses cluster/sizeof(__u64) refcount blocks, and we need
+ * to address meta_blocks_count clusters + qcow2 metadata clusters
+ * in the worst case.
+ */
+ table_clusters = meta_blocks_count + (table_offset >>
+ img->cluster_bits);
+ table_clusters >>= (img->cluster_bits + 6 - 1);
+ table_clusters = (table_clusters == 0) ? 1 : table_clusters;
+
+ ref->refcount_table_offset = table_offset;
+ ref->refcount_table_clusters = table_clusters;
+ ref->refcount_table_index = 0;
+ ref->refcount_block_index = 0;
+
+ /* Allocate refcount table */
+ ret = ext2fs_get_arrayzero(ref->refcount_table_clusters,
+ img->cluster_size, &ref->refcount_table);
+ if (ret)
+ return ret;
+
+ /* Allocate refcount block */
+ ret = ext2fs_get_arrayzero(1, img->cluster_size, &ref->refcount_block);
+ if (ret)
+ ext2fs_free_mem(&ref->refcount_table);
+
+ return ret;
+}
+
+static errcode_t initialize_qcow2_image(int fd, ext2_filsys fs,
+ struct ext2_qcow2_image *image)
+{
+ struct ext2_qcow2_hdr *header;
+ blk64_t total_size, offset;
+ int shift, l2_bits, header_size, l1_size, ret;
+ int cluster_bits = get_bits_from_size(fs->blocksize);
+ struct ext2_super_block *sb = fs->super;
+
+ /* Sbould never happen, but just in case... */
+ if (cluster_bits < 0)
+ return EXT2_FILSYS_CORRUPTED;
+
+ /* Allocate header */
+ ret = ext2fs_get_memzero(sizeof(struct ext2_qcow2_hdr), &header);
+ if (ret)
+ return ret;
+
+ total_size = ext2fs_blocks_count(sb) << cluster_bits;
+ image->cluster_size = fs->blocksize;
+ image->l2_size = 1 << (cluster_bits - 3);
+ image->cluster_bits = cluster_bits;
+ image->fd = fd;
+
+ header->magic = ext2fs_cpu_to_be32(QCOW_MAGIC);
+ header->version = ext2fs_cpu_to_be32(QCOW_VERSION);
+ header->size = ext2fs_cpu_to_be64(total_size);
+ header->cluster_bits = ext2fs_cpu_to_be32(cluster_bits);
+
+ header_size = (sizeof(struct ext2_qcow2_hdr) + 7) & ~7;
+ offset = align_offset(header_size, image->cluster_size);
+
+ header->l1_table_offset = ext2fs_cpu_to_be64(offset);
+ image->l1_offset = offset;
+
+ l2_bits = cluster_bits - 3;
+ shift = cluster_bits + l2_bits;
+ l1_size = ((total_size + (1LL << shift) - 1) >> shift);
+ header->l1_size = ext2fs_cpu_to_be32(l1_size);
+ image->l1_size = l1_size;
+
+ /* Make space for L1 table */
+ offset += align_offset(l1_size * sizeof(blk64_t), image->cluster_size);
+
+ /* Initialize refcounting */
+ ret = init_refcount(image, offset);
+ if (ret) {
+ ext2fs_free_mem(&header);
+ return ret;
+ }
+ header->refcount_table_offset = ext2fs_cpu_to_be64(offset);
+ header->refcount_table_clusters =
+ ext2fs_cpu_to_be32(image->refcount.refcount_table_clusters);
+ offset += image->cluster_size;
+ offset += (blk64_t) image->refcount.refcount_table_clusters <<
+ image->cluster_bits;
+
+ /* Make space for L2 tables */
+ image->l2_offset = offset;
+ offset += image->cluster_size;
+
+ /* Make space for first refcount block */
+ image->refcount.refcount_block_offset = offset;
+
+ image->hdr = header;
+ /* Initialize l1 and l2 tables */
+ init_l1_table(image);
+ init_l2_cache(image);
+
+ return 0;
+}
+
+static void free_qcow2_image(struct ext2_qcow2_image *img)
+{
+ if (!img)
+ return;
+
+ if (img->hdr)
+ ext2fs_free_mem(&img->hdr);
+
+ if (img->l1_table)
+ ext2fs_free_mem(&img->l1_table);
+
+ if (img->refcount.refcount_table)
+ ext2fs_free_mem(&img->refcount.refcount_table);
+ if (img->refcount.refcount_block)
+ ext2fs_free_mem(&img->refcount.refcount_block);
+
+ put_l2_cache(img);
+
+ ext2fs_free_mem(&img);
+}
+
+/**
+ * Put table from used list (used_head) into free list (free_head).
+ * l2_table is used to return pointer to the next used table (used_head).
+ */
+static void put_used_table(struct ext2_qcow2_image *img,
+ struct ext2_qcow2_l2_table **l2_table)
+{
+ struct ext2_qcow2_l2_cache *cache = img->l2_cache;
+ struct ext2_qcow2_l2_table *table;
+
+ table = cache->used_head;
+ cache->used_head = table->next;
+
+ assert(table);
+ if (!table->next)
+ cache->used_tail = NULL;
+
+ /* Clean the table for case we will need to use it again */
+ memset(table->data, 0, img->cluster_size);
+ table->next = cache->free_head;
+ cache->free_head = table;
+
+ cache->free++;
+
+ *l2_table = cache->used_head;
+}
+
+static void flush_l2_cache(struct ext2_qcow2_image *image)
+{
+ blk64_t seek = 0;
+ ext2_loff_t offset;
+ struct ext2_qcow2_l2_cache *cache = image->l2_cache;
+ struct ext2_qcow2_l2_table *table = cache->used_head;
+ int fd = image->fd;
+
+ /* Store current position */
+ offset = seek_relative(fd, 0);
+
+ assert(table);
+ while (cache->free < cache->count) {
+ if (seek != table->offset) {
+ seek_set(fd, table->offset);
+ seek = table->offset;
+ }
+
+ generic_write(fd, (char *)table->data, image->cluster_size,
+ NO_BLK);
+ put_used_table(image, &table);
+ seek += image->cluster_size;
+ }
+
+ /* Restore previous position */
+ seek_set(fd, offset);
+}
+
+/**
+ * Get first free table (from free_head) and put it into tail of used list
+ * (to used_tail).
+ * l2_table is used to return pointer to moved table.
+ * Returns 1 if the cache is full, 0 otherwise.
+ */
+static void get_free_table(struct ext2_qcow2_image *image,
+ struct ext2_qcow2_l2_table **l2_table)
+{
+ struct ext2_qcow2_l2_table *table;
+ struct ext2_qcow2_l2_cache *cache = image->l2_cache;
+
+ if (0 == cache->free)
+ flush_l2_cache(image);
+
+ table = cache->free_head;
+ assert(table);
+ cache->free_head = table->next;
+
+ if (cache->used_tail)
+ cache->used_tail->next = table;
+ else
+ /* First item in the used list */
+ cache->used_head = table;
+
+ cache->used_tail = table;
+ cache->free--;
+
+ *l2_table = table;
+}
+
+static int add_l2_item(struct ext2_qcow2_image *img, blk64_t blk,
+ blk64_t data, blk64_t next)
+{
+ struct ext2_qcow2_l2_cache *cache = img->l2_cache;
+ struct ext2_qcow2_l2_table *table = cache->used_tail;
+ blk64_t l1_index = blk / img->l2_size;
+ blk64_t l2_index = blk & (img->l2_size - 1);
+ int ret = 0;
+
+ /*
+ * Need to create new table if it does not exist,
+ * or if it is full
+ */
+ if (!table || (table->l1_index != l1_index)) {
+ get_free_table(img, &table);
+ table->l1_index = l1_index;
+ table->offset = cache->next_offset;
+ cache->next_offset = next;
+ img->l1_table[l1_index] =
+ ext2fs_cpu_to_be64(table->offset | QCOW_OFLAG_COPIED);
+ ret++;
+ }
+
+ table->data[l2_index] = ext2fs_cpu_to_be64(data | QCOW_OFLAG_COPIED);
+ return ret;
+}
+
+static int update_refcount(int fd, struct ext2_qcow2_image *img,
+ blk64_t offset, blk64_t rfblk_pos)
+{
+ struct ext2_qcow2_refcount *ref;
+ __u32 table_index;
+ int ret = 0;
+
+ ref = &(img->refcount);
+ table_index = offset >> (2 * img->cluster_bits - 1);
+
+ /*
+ * Need to create new refcount block when the offset addresses
+ * another item in the refcount table
+ */
+ if (table_index != ref->refcount_table_index) {
+
+ seek_set(fd, ref->refcount_block_offset);
+
+ generic_write(fd, (char *)ref->refcount_block,
+ img->cluster_size, NO_BLK);
+ memset(ref->refcount_block, 0, img->cluster_size);
+
+ ref->refcount_table[ref->refcount_table_index] =
+ ext2fs_cpu_to_be64(ref->refcount_block_offset);
+ ref->refcount_block_offset = rfblk_pos;
+ ref->refcount_block_index = 0;
+ ref->refcount_table_index = table_index;
+ ret++;
+ }
+
+ /*
+ * We are relying on the fact that we are creating the qcow2
+ * image sequentially, hence we will always allocate refcount
+ * block items sequentially.
+ */
+ ref->refcount_block[ref->refcount_block_index] = ext2fs_cpu_to_be16(1);
+ ref->refcount_block_index++;
+ return ret;
+}
+
+static int sync_refcount(int fd, struct ext2_qcow2_image *img)
+{
+ struct ext2_qcow2_refcount *ref;
+
+ ref = &(img->refcount);
+
+ ref->refcount_table[ref->refcount_table_index] =
+ ext2fs_cpu_to_be64(ref->refcount_block_offset);
+ seek_set(fd, ref->refcount_table_offset);
+ generic_write(fd, (char *)ref->refcount_table,
+ ref->refcount_table_clusters << img->cluster_bits, NO_BLK);
+
+ seek_set(fd, ref->refcount_block_offset);
+ generic_write(fd, (char *)ref->refcount_block, img->cluster_size,
+ NO_BLK);
+ return 0;
+}
+
+static void output_qcow2_meta_data_blocks(ext2_filsys fs, int fd)
+{
+ errcode_t retval;
+ blk64_t blk, offset, size, end;
+ char *buf;
+ struct ext2_qcow2_image *img;
+ unsigned int header_size;
+
+ /* allocate struct ext2_qcow2_image */
+ retval = ext2fs_get_mem(sizeof(struct ext2_qcow2_image), &img);
+ if (retval) {
+ com_err(program_name, retval, "%s",
+ _("while allocating ext2_qcow2_image"));
+ exit(1);
+ }
+
+ retval = initialize_qcow2_image(fd, fs, img);
+ if (retval) {
+ com_err(program_name, retval, "%s",
+ _("while initializing ext2_qcow2_image"));
+ exit(1);
+ }
+ header_size = align_offset(sizeof(struct ext2_qcow2_hdr),
+ img->cluster_size);
+ write_header(fd, img->hdr, sizeof(struct ext2_qcow2_hdr), header_size);
+
+ /* Refcount all qcow2 related metadata up to refcount_block_offset */
+ end = img->refcount.refcount_block_offset;
+ seek_set(fd, end);
+ blk = end + img->cluster_size;
+ for (offset = 0; offset <= end; offset += img->cluster_size) {
+ if (update_refcount(fd, img, offset, blk)) {
+ blk += img->cluster_size;
+ /*
+ * If we create new refcount block, we need to refcount
+ * it as well.
+ */
+ end += img->cluster_size;
+ }
+ }
+ seek_set(fd, offset);
+
+ retval = ext2fs_get_mem(fs->blocksize, &buf);
+ if (retval) {
+ com_err(program_name, retval, "%s",
+ _("while allocating buffer"));
+ exit(1);
+ }
+ /* Write qcow2 data blocks */
+ for (blk = 0; blk < ext2fs_blocks_count(fs->super); blk++) {
+ if ((blk >= fs->super->s_first_data_block) &&
+ ext2fs_test_block_bitmap2(meta_block_map, blk)) {
+ retval = io_channel_read_blk64(fs->io, blk, 1, buf);
+ if (retval) {
+ com_err(program_name, retval,
+ _("error reading block %llu"),
+ (unsigned long long) blk);
+ continue;
+ }
+ if (scramble_block_map &&
+ ext2fs_test_block_bitmap2(scramble_block_map, blk))
+ scramble_dir_block(fs, blk, buf);
+ if (check_zero_block(buf, fs->blocksize))
+ continue;
+
+ if (update_refcount(fd, img, offset, offset)) {
+ /* Make space for another refcount block */
+ offset += img->cluster_size;
+ seek_set(fd, offset);
+ /*
+ * We have created the new refcount block, this
+ * means that we need to refcount it as well.
+ * So the previous update_refcount refcounted
+ * the block itself and now we are going to
+ * create refcount for data. New refcount
+ * block should not be created!
+ */
+ if (update_refcount(fd, img, offset, offset)) {
+ fprintf(stderr, "%s",
+ _("Programming error: multiple "
+ "sequential refcount blocks "
+ "created!\n"));
+ exit(1);
+ }
+ }
+
+ generic_write(fd, buf, fs->blocksize, blk);
+
+ if (add_l2_item(img, blk, offset,
+ offset + img->cluster_size)) {
+ offset += img->cluster_size;
+ if (update_refcount(fd, img, offset,
+ offset + img->cluster_size)) {
+ offset += img->cluster_size;
+ if (update_refcount(fd, img, offset,
+ offset)) {
+ fprintf(stderr, "%s",
+ _("Programming error: multiple sequential refcount "
+ "blocks created!\n"));
+ exit(1);
+ }
+ }
+ offset += img->cluster_size;
+ seek_set(fd, offset);
+ continue;
+ }
+
+ offset += img->cluster_size;
+ }
+ }
+ (void) update_refcount(fd, img, offset, offset);
+ flush_l2_cache(img);
+ sync_refcount(fd, img);
+
+ /* Write l1_table*/
+ seek_set(fd, img->l1_offset);
+ size = img->l1_size * sizeof(__u64);
+ generic_write(fd, (char *)img->l1_table, size, NO_BLK);
+
+ ext2fs_free_mem(&buf);
+ free_qcow2_image(img);
+}
+
+static void write_raw_image_file(ext2_filsys fs, int fd, int type, int flags,
+ blk64_t superblock)
+{
+ struct process_block_struct pb;
+ struct ext2_inode inode;
+ ext2_inode_scan scan;
+ ext2_ino_t ino;
+ errcode_t retval;
+ char * block_buf;
+
+ meta_blocks_count = 0;
+ retval = ext2fs_allocate_block_bitmap(fs, _("in-use block map"),
+ &meta_block_map);
+ if (retval) {
+ com_err(program_name, retval, "%s",
+ _("while allocating block bitmap"));
+ exit(1);
+ }
+
+ if (flags & E2IMAGE_SCRAMBLE_FLAG) {
+ retval = ext2fs_allocate_block_bitmap(fs, "scramble block map",
+ &scramble_block_map);
+ if (retval) {
+ com_err(program_name, retval, "%s",
+ _("while allocating scramble block bitmap"));
+ exit(1);
+ }
+ }
+
+ if (superblock) {
+ unsigned int j;
+
+ ext2fs_mark_block_bitmap2(meta_block_map, superblock);
+ meta_blocks_count++;
+
+ /*
+ * Mark the backup superblock descriptors
+ */
+ for (j = 0; j < fs->desc_blocks; j++) {
+ ext2fs_mark_block_bitmap2(meta_block_map,
+ ext2fs_descriptor_block_loc2(fs, superblock, j));
+ }
+ meta_blocks_count += fs->desc_blocks;
+ }
+
+ mark_table_blocks(fs);
+ if (show_progress)
+ fprintf(stderr, "%s", _("Scanning inodes...\n"));
+
+ retval = ext2fs_open_inode_scan(fs, 0, &scan);
+ if (retval) {
+ com_err(program_name, retval, "%s",
+ _("while opening inode scan"));
+ exit(1);
+ }
+
+ retval = ext2fs_get_mem(fs->blocksize * 3, &block_buf);
+ if (retval) {
+ com_err(program_name, 0, "%s",
+ _("Can't allocate block buffer"));
+ exit(1);
+ }
+
+ use_inode_shortcuts(fs, 1);
+ stashed_inode = &inode;
+ while (1) {
+ retval = ext2fs_get_next_inode(scan, &ino, &inode);
+ if (retval == EXT2_ET_BAD_BLOCK_IN_INODE_TABLE)
+ continue;
+ if (retval) {
+ com_err(program_name, retval, "%s",
+ _("while getting next inode"));
+ exit(1);
+ }
+ if (ino == 0)
+ break;
+ if (!inode.i_links_count)
+ continue;
+ if (ext2fs_file_acl_block(fs, &inode)) {
+ ext2fs_mark_block_bitmap2(meta_block_map,
+ ext2fs_file_acl_block(fs, &inode));
+ meta_blocks_count++;
+ }
+ if (!ext2fs_inode_has_valid_blocks2(fs, &inode))
+ continue;
+
+ stashed_ino = ino;
+ pb.ino = ino;
+ pb.is_dir = LINUX_S_ISDIR(inode.i_mode);
+ if (LINUX_S_ISDIR(inode.i_mode) ||
+ LINUX_S_ISLNK(inode.i_mode) ||
+ ino == fs->super->s_journal_inum ||
+ ino == quota_type2inum(USRQUOTA, fs->super) ||
+ ino == quota_type2inum(GRPQUOTA, fs->super) ||
+ ino == quota_type2inum(PRJQUOTA, fs->super) ||
+ ino == fs->super->s_orphan_file_inum) {
+ retval = ext2fs_block_iterate3(fs, ino,
+ BLOCK_FLAG_READ_ONLY, block_buf,
+ process_dir_block, &pb);
+ if (retval) {
+ com_err(program_name, retval,
+ _("while iterating over inode %u"),
+ ino);
+ exit(1);
+ }
+ } else {
+ if ((inode.i_flags & EXT4_EXTENTS_FL) ||
+ inode.i_block[EXT2_IND_BLOCK] ||
+ inode.i_block[EXT2_DIND_BLOCK] ||
+ inode.i_block[EXT2_TIND_BLOCK] || all_data) {
+ retval = ext2fs_block_iterate3(fs,
+ ino, BLOCK_FLAG_READ_ONLY, block_buf,
+ process_file_block, &pb);
+ if (retval) {
+ com_err(program_name, retval,
+ _("while iterating over inode %u"), ino);
+ exit(1);
+ }
+ }
+ }
+ }
+ use_inode_shortcuts(fs, 0);
+
+ if (type & E2IMAGE_QCOW2)
+ output_qcow2_meta_data_blocks(fs, fd);
+ else
+ output_meta_data_blocks(fs, fd, flags);
+
+ ext2fs_free_mem(&block_buf);
+ ext2fs_close_inode_scan(scan);
+ ext2fs_free_block_bitmap(meta_block_map);
+ if (type & E2IMAGE_SCRAMBLE_FLAG)
+ ext2fs_free_block_bitmap(scramble_block_map);
+}
+
+static void install_image(char *device, char *image_fn, int type)
+{
+ errcode_t retval;
+ ext2_filsys fs;
+ int open_flag = EXT2_FLAG_IMAGE_FILE | EXT2_FLAG_64BITS |
+ EXT2_FLAG_IGNORE_CSUM_ERRORS;
+ int fd = 0;
+ io_manager io_ptr;
+ io_channel io;
+
+ if (type) {
+ com_err(program_name, 0, "%s",
+ _("Raw and qcow2 images cannot be installed"));
+ exit(1);
+ }
+
+#ifdef CONFIG_TESTIO_DEBUG
+ if (getenv("TEST_IO_FLAGS") || getenv("TEST_IO_BLOCK")) {
+ io_ptr = test_io_manager;
+ test_io_backing_manager = unix_io_manager;
+ } else
+#endif
+ io_ptr = unix_io_manager;
+
+ retval = ext2fs_open (image_fn, open_flag, 0, 0,
+ io_ptr, &fs);
+ if (retval) {
+ com_err(program_name, retval, _("while trying to open %s"),
+ image_fn);
+ exit(1);
+ }
+
+ retval = ext2fs_read_bitmaps (fs);
+ if (retval) {
+ com_err(program_name, retval, "%s", _("error reading bitmaps"));
+ exit(1);
+ }
+
+ fd = ext2fs_open_file(image_fn, O_RDONLY, 0);
+ if (fd < 0) {
+ perror(image_fn);
+ exit(1);
+ }
+
+ retval = io_ptr->open(device, IO_FLAG_RW, &io);
+ if (retval) {
+ com_err(device, 0, "%s", _("while opening device file"));
+ exit(1);
+ }
+
+ ext2fs_rewrite_to_io(fs, io);
+
+ seek_set(fd, ext2fs_le32_to_cpu(fs->image_header->offset_inode));
+
+ retval = ext2fs_image_inode_read(fs, fd, 0);
+ if (retval) {
+ com_err(image_fn, 0, "%s",
+ _("while restoring the image table"));
+ exit(1);
+ }
+
+ close(fd);
+ ext2fs_close_free(&fs);
+}
+
+static struct ext2_qcow2_hdr *check_qcow2_image(int *fd, char *name)
+{
+
+ *fd = ext2fs_open_file(name, O_RDONLY, 0600);
+ if (*fd < 0)
+ return NULL;
+
+ return qcow2_read_header(*fd);
+}
+
+int main (int argc, char ** argv)
+{
+ int c;
+ errcode_t retval;
+ ext2_filsys fs;
+ char *image_fn, offset_opt[64];
+ struct ext2_qcow2_hdr *header = NULL;
+ int open_flag = EXT2_FLAG_64BITS | EXT2_FLAG_THREADS |
+ EXT2_FLAG_IGNORE_CSUM_ERRORS;
+ int img_type = 0;
+ int flags = 0;
+ int mount_flags = 0;
+ int qcow2_fd = 0;
+ int fd = 0;
+ int ret = 0;
+ int ignore_rw_mount = 0;
+ int check = 0;
+ struct stat st;
+ blk64_t superblock = 0;
+ int blocksize = 0;
+
+#ifdef ENABLE_NLS
+ setlocale(LC_MESSAGES, "");
+ setlocale(LC_CTYPE, "");
+ bindtextdomain(NLS_CAT_NAME, LOCALEDIR);
+ textdomain(NLS_CAT_NAME);
+ set_com_err_gettext(gettext);
+#endif
+ fprintf (stderr, "e2image %s (%s)\n", E2FSPROGS_VERSION,
+ E2FSPROGS_DATE);
+ if (argc && *argv)
+ program_name = *argv;
+ else
+ usage();
+ add_error_table(&et_ext2_error_table);
+ while ((c = getopt(argc, argv, "b:B:nrsIQafo:O:pc")) != EOF)
+ switch (c) {
+ case 'b':
+ superblock = strtoull(optarg, NULL, 0);
+ break;
+ case 'B':
+ blocksize = strtoul(optarg, NULL, 0);
+ break;
+ case 'I':
+ flags |= E2IMAGE_INSTALL_FLAG;
+ break;
+ case 'Q':
+ if (img_type)
+ usage();
+ img_type |= E2IMAGE_QCOW2;
+ break;
+ case 'r':
+ if (img_type)
+ usage();
+ img_type |= E2IMAGE_RAW;
+ break;
+ case 's':
+ flags |= E2IMAGE_SCRAMBLE_FLAG;
+ break;
+ case 'a':
+ all_data = 1;
+ break;
+ case 'f':
+ ignore_rw_mount = 1;
+ break;
+ case 'n':
+ nop_flag = 1;
+ break;
+ case 'o':
+ source_offset = strtoull(optarg, NULL, 0);
+ break;
+ case 'O':
+ dest_offset = strtoull(optarg, NULL, 0);
+ break;
+ case 'p':
+ show_progress = 1;
+ break;
+ case 'c':
+ check = 1;
+ break;
+ default:
+ usage();
+ }
+ if (optind == argc - 1 &&
+ (source_offset || dest_offset))
+ move_mode = 1;
+ else if (optind != argc - 2 )
+ usage();
+
+ if (all_data && !img_type) {
+ com_err(program_name, 0, "%s", _("-a option can only be used "
+ "with raw or QCOW2 images."));
+ exit(1);
+ }
+ if (superblock && !img_type) {
+ com_err(program_name, 0, "%s", _("-b option can only be used "
+ "with raw or QCOW2 images."));
+ exit(1);
+ }
+ if ((source_offset || dest_offset) && img_type != E2IMAGE_RAW) {
+ com_err(program_name, 0, "%s",
+ _("Offsets are only allowed with raw images."));
+ exit(1);
+ }
+ if (move_mode && img_type != E2IMAGE_RAW) {
+ com_err(program_name, 0, "%s",
+ _("Move mode is only allowed with raw images."));
+ exit(1);
+ }
+ if (move_mode && !all_data) {
+ com_err(program_name, 0, "%s",
+ _("Move mode requires all data mode."));
+ exit(1);
+ }
+ device_name = argv[optind];
+ if (move_mode)
+ image_fn = device_name;
+ else image_fn = argv[optind+1];
+
+ retval = ext2fs_check_if_mounted(device_name, &mount_flags);
+ if (retval) {
+ com_err(program_name, retval, "%s", _("checking if mounted"));
+ exit(1);
+ }
+
+ if (img_type && !ignore_rw_mount &&
+ (mount_flags & EXT2_MF_MOUNTED) &&
+ !(mount_flags & EXT2_MF_READONLY)) {
+ fprintf(stderr, "%s", _("\nRunning e2image on a R/W mounted "
+ "filesystem can result in an\n"
+ "inconsistent image which will not be useful "
+ "for debugging purposes.\n"
+ "Use -f option if you really want to do that.\n"));
+ exit(1);
+ }
+
+ if (flags & E2IMAGE_INSTALL_FLAG) {
+ install_image(device_name, image_fn, img_type);
+ exit (0);
+ }
+
+ if (img_type & E2IMAGE_RAW) {
+ header = check_qcow2_image(&qcow2_fd, device_name);
+ if (header) {
+ flags |= E2IMAGE_IS_QCOW2_FLAG;
+ goto skip_device;
+ }
+ }
+ sprintf(offset_opt, "offset=%llu", (unsigned long long) source_offset);
+ retval = ext2fs_open2(device_name, offset_opt, open_flag,
+ superblock, blocksize, unix_io_manager, &fs);
+ if (retval) {
+ com_err (program_name, retval, _("while trying to open %s"),
+ device_name);
+ fputs(_("Couldn't find valid filesystem superblock.\n"), stdout);
+ if (retval == EXT2_ET_BAD_MAGIC)
+ check_plausibility(device_name, CHECK_FS_EXIST, NULL);
+ exit(1);
+ }
+
+skip_device:
+ if (strcmp(image_fn, "-") == 0)
+ fd = 1;
+ else {
+ int o_flags = O_CREAT|O_RDWR;
+
+ if (img_type != E2IMAGE_RAW)
+ o_flags |= O_TRUNC;
+ if (access(image_fn, F_OK) != 0)
+ flags |= E2IMAGE_CHECK_ZERO_FLAG;
+ fd = ext2fs_open_file(image_fn, o_flags, 0600);
+ if (fd < 0) {
+ com_err(program_name, errno,
+ _("while trying to open %s"), image_fn);
+ exit(1);
+ }
+ }
+ if (dest_offset)
+ seek_set(fd, dest_offset);
+
+ if ((img_type & E2IMAGE_QCOW2) && (fd == 1)) {
+ com_err(program_name, 0, "%s",
+ _("QCOW2 image can not be written to the stdout!\n"));
+ exit(1);
+ }
+ if (fd != 1) {
+ if (fstat(fd, &st)) {
+ com_err(program_name, 0, "%s",
+ _("Can not stat output\n"));
+ exit(1);
+ }
+ if (ext2fsP_is_disk_device(st.st_mode))
+ output_is_blk = 1;
+ }
+ if (flags & E2IMAGE_IS_QCOW2_FLAG) {
+ ret = qcow2_write_raw_image(qcow2_fd, fd, header);
+ if (ret) {
+ if (ret == -QCOW_COMPRESSED)
+ fprintf(stderr, _("Image (%s) is compressed\n"),
+ image_fn);
+ else if (ret == -QCOW_ENCRYPTED)
+ fprintf(stderr, _("Image (%s) is encrypted\n"),
+ image_fn);
+ else if (ret == -QCOW_CORRUPTED)
+ fprintf(stderr, _("Image (%s) is corrupted\n"),
+ image_fn);
+ else
+ com_err(program_name, ret,
+ _("while trying to convert qcow2 image"
+ " (%s) into raw image (%s)"),
+ image_fn, device_name);
+ ret = 1;
+ }
+ goto out;
+ }
+
+ if (check) {
+ if (img_type != E2IMAGE_RAW) {
+ fprintf(stderr, "%s", _("The -c option only supported "
+ "in raw mode\n"));
+ exit(1);
+ }
+ if (fd == 1) {
+ fprintf(stderr, "%s", _("The -c option not supported "
+ "when writing to stdout\n"));
+ exit(1);
+ }
+ retval = ext2fs_get_mem(fs->blocksize, &check_buf);
+ if (retval) {
+ com_err(program_name, retval, "%s",
+ _("while allocating check_buf"));
+ exit(1);
+ }
+ }
+ if (show_progress && (img_type != E2IMAGE_RAW)) {
+ fprintf(stderr, "%s",
+ _("The -p option only supported in raw mode\n"));
+ exit(1);
+ }
+ if (img_type)
+ write_raw_image_file(fs, fd, img_type, flags, superblock);
+ else
+ write_image_file(fs, fd);
+
+ ext2fs_close_free(&fs);
+ if (check)
+ printf(_("%d blocks already contained the data to be copied\n"),
+ skipped_blocks);
+
+out:
+ if (header)
+ free(header);
+ if (qcow2_fd)
+ close(qcow2_fd);
+ remove_error_table(&et_ext2_error_table);
+ return ret;
+}
diff --git a/misc/e2initrd_helper.c b/misc/e2initrd_helper.c
new file mode 100644
index 0000000..b39fe15
--- /dev/null
+++ b/misc/e2initrd_helper.c
@@ -0,0 +1,398 @@
+/*
+ * e2initrd_helper.c - Get the filesystem table
+ *
+ * Copyright 2004 by Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ * %End-Header%
+ */
+
+#include "config.h"
+#include <stdio.h>
+#include <unistd.h>
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <utime.h>
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#else
+extern int optind;
+extern char *optarg;
+#endif
+
+#include "ext2fs/ext2_fs.h"
+#include "ext2fs/ext2fs.h"
+#include "blkid/blkid.h"
+#include "support/nls-enable.h"
+#include "support/devname.h"
+
+#include "../version.h"
+
+static const char * program_name = "e2initrd_helper";
+static char * device_name;
+static int open_flag;
+static int root_type;
+static blkid_cache cache = NULL;
+
+struct mem_file {
+ char *buf;
+ int size;
+ int ptr;
+};
+
+struct fs_info {
+ char *device;
+ char *mountpt;
+ char *type;
+ char *opts;
+ int freq;
+ int passno;
+ int flags;
+ struct fs_info *next;
+};
+
+static void usage(void)
+{
+ fprintf(stderr,
+ _("Usage: %s -r device\n"), program_name);
+ exit (1);
+}
+
+static errcode_t get_file(ext2_filsys fs, const char * filename,
+ struct mem_file *ret_file)
+{
+ errcode_t retval;
+ char *buf;
+ ext2_file_t e2_file = NULL;
+ unsigned int got;
+ struct ext2_inode inode;
+ ext2_ino_t ino;
+
+ ret_file->buf = 0;
+ ret_file->size = 0;
+ ret_file->ptr = 0;
+
+ retval = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO,
+ filename, &ino);
+ if (retval)
+ return retval;
+
+ retval = ext2fs_read_inode(fs, ino, &inode);
+ if (retval)
+ return retval;
+
+ if (inode.i_size_high || (inode.i_size > 65536))
+ return EFBIG;
+
+ buf = malloc(inode.i_size + 1);
+ if (!buf)
+ return ENOMEM;
+ memset(buf, 0, inode.i_size+1);
+
+ retval = ext2fs_file_open(fs, ino, 0, &e2_file);
+ if (retval)
+ goto errout;
+
+ retval = ext2fs_file_read(e2_file, buf, inode.i_size, &got);
+ if (retval)
+ goto errout;
+
+ retval = ext2fs_file_close(e2_file);
+ if (retval)
+ goto errout;
+
+ ret_file->buf = buf;
+ ret_file->size = (int) got;
+ return 0;
+
+errout:
+ free(buf);
+ if (e2_file)
+ ext2fs_file_close(e2_file);
+ return retval;
+}
+
+static char *get_line(struct mem_file *file)
+{
+ char *cp, *ret;
+ int s = 0;
+
+ cp = file->buf + file->ptr;
+ while (*cp && *cp != '\n') {
+ cp++;
+ s++;
+ }
+ ret = malloc(s+1);
+ if (!ret)
+ return 0;
+ ret[s]=0;
+ memcpy(ret, file->buf + file->ptr, s);
+ while (*cp && (*cp == '\n' || *cp == '\r')) {
+ cp++;
+ s++;
+ }
+ file->ptr += s;
+ return ret;
+}
+
+static int mem_file_eof(struct mem_file *file)
+{
+ return (file->ptr >= file->size);
+}
+
+/*
+ * fstab parsing code
+ */
+static char *string_copy(const char *s)
+{
+ char *ret;
+
+ if (!s)
+ return 0;
+ ret = malloc(strlen(s)+1);
+ if (ret)
+ strcpy(ret, s);
+ return ret;
+}
+
+static char *skip_over_blank(char *cp)
+{
+ while (*cp && isspace(*cp))
+ cp++;
+ return cp;
+}
+
+static char *skip_over_word(char *cp)
+{
+ while (*cp && !isspace(*cp))
+ cp++;
+ return cp;
+}
+
+static char *parse_word(char **buf)
+{
+ char *word, *next;
+
+ word = *buf;
+ if (*word == 0)
+ return 0;
+
+ word = skip_over_blank(word);
+ next = skip_over_word(word);
+ if (*next)
+ *next++ = 0;
+ *buf = next;
+ return word;
+}
+
+static void parse_escape(char *word)
+{
+ char *p, *q;
+ int ac, i;
+
+ if (!word)
+ return;
+
+ for (p = word, q = word; *p; p++, q++) {
+ *q = *p;
+ if (*p != '\\')
+ continue;
+ if (*++p == 0)
+ break;
+ if (*p == 't') {
+ *q = '\t';
+ continue;
+ }
+ if (*p == 'n') {
+ *q = '\n';
+ continue;
+ }
+ if (!isdigit(*p)) {
+ *q = *p;
+ continue;
+ }
+ ac = 0;
+ for (i = 0; i < 3; i++, p++) {
+ if (!isdigit(*p))
+ break;
+ ac = (ac * 8) + (*p - '0');
+ }
+ *q = ac;
+ p--;
+ }
+ *q = 0;
+}
+
+static int parse_fstab_line(char *line, struct fs_info *fs)
+{
+ char *dev, *device, *mntpnt, *type, *opts, *freq, *passno, *cp;
+
+ if ((cp = strchr(line, '#')))
+ *cp = 0; /* Ignore everything after the comment char */
+ cp = line;
+
+ device = parse_word(&cp);
+ mntpnt = parse_word(&cp);
+ type = parse_word(&cp);
+ opts = parse_word(&cp);
+ freq = parse_word(&cp);
+ passno = parse_word(&cp);
+
+ if (!device)
+ return -1; /* Allow blank lines */
+
+ if (!mntpnt || !type)
+ return -1;
+
+ parse_escape(device);
+ parse_escape(mntpnt);
+ parse_escape(type);
+ parse_escape(opts);
+ parse_escape(freq);
+ parse_escape(passno);
+
+ dev = get_devname(cache, device, NULL);
+ if (dev)
+ device = dev;
+
+ if (strchr(type, ','))
+ type = 0;
+
+ fs->device = string_copy(device);
+ fs->mountpt = string_copy(mntpnt);
+ fs->type = string_copy(type);
+ fs->opts = string_copy(opts ? opts : "");
+ fs->freq = freq ? atoi(freq) : -1;
+ fs->passno = passno ? atoi(passno) : -1;
+ fs->flags = 0;
+ fs->next = NULL;
+
+ free(dev);
+
+ return 0;
+}
+
+static void free_fstab_line(struct fs_info *fs)
+{
+ if (fs->device)
+ fs->device = 0;
+ if (fs->mountpt)
+ fs->mountpt = 0;
+ if (fs->type)
+ fs->type = 0;
+ if (fs->opts)
+ fs->opts = 0;
+ memset(fs, 0, sizeof(struct fs_info));
+}
+
+
+static void PRS(int argc, char **argv)
+{
+ int c;
+
+#ifdef ENABLE_NLS
+ setlocale(LC_MESSAGES, "");
+ setlocale(LC_CTYPE, "");
+ bindtextdomain(NLS_CAT_NAME, LOCALEDIR);
+ textdomain(NLS_CAT_NAME);
+ set_com_err_gettext(gettext);
+#endif
+
+ while ((c = getopt(argc, argv, "rv")) != EOF) {
+ switch (c) {
+ case 'r':
+ root_type++;
+ break;
+
+ case 'v':
+ printf("%s %s (%s)\n", program_name,
+ E2FSPROGS_VERSION, E2FSPROGS_DATE);
+ break;
+ default:
+ usage();
+ }
+ }
+ if (optind < argc - 1 || optind == argc)
+ usage();
+ device_name = get_devname(NULL, argv[optind], NULL);
+ if (!device_name) {
+ com_err(program_name, 0, _("Unable to resolve '%s'"),
+ argv[optind]);
+ exit(1);
+ }
+}
+
+static void get_root_type(ext2_filsys fs)
+{
+ errcode_t retval;
+ struct mem_file file;
+ char *buf;
+ struct fs_info fs_info;
+ int ret;
+
+ retval = get_file(fs, "/etc/fstab", &file);
+ if (retval) {
+ com_err(program_name, retval, "couldn't open /etc/fstab");
+ exit(1);
+ }
+
+ while (!mem_file_eof(&file)) {
+ buf = get_line(&file);
+ if (!buf)
+ continue;
+
+ ret = parse_fstab_line(buf, &fs_info);
+ if (ret < 0)
+ goto next_line;
+
+ if (!strcmp(fs_info.mountpt, "/"))
+ printf("%s\n", fs_info.type);
+
+ free_fstab_line(&fs_info);
+
+ next_line:
+ free(buf);
+ }
+}
+
+
+int main (int argc, char ** argv)
+{
+ errcode_t retval;
+ ext2_filsys fs;
+ io_manager io_ptr;
+
+ add_error_table(&et_ext2_error_table);
+
+ blkid_get_cache(&cache, NULL);
+ PRS(argc, argv);
+
+#ifdef CONFIG_TESTIO_DEBUG
+ if (getenv("TEST_IO_FLAGS") || getenv("TEST_IO_BLOCK")) {
+ io_ptr = test_io_manager;
+ test_io_backing_manager = unix_io_manager;
+ } else
+#endif
+ io_ptr = unix_io_manager;
+ retval = ext2fs_open (device_name, open_flag, 0, 0, io_ptr, &fs);
+ if (retval)
+ exit(1);
+
+ if (root_type)
+ get_root_type(fs);
+
+ remove_error_table(&et_ext2_error_table);
+ return (ext2fs_close (fs) ? 1 : 0);
+}
diff --git a/misc/e2label.8.in b/misc/e2label.8.in
new file mode 100644
index 0000000..fa5294c
--- /dev/null
+++ b/misc/e2label.8.in
@@ -0,0 +1,58 @@
+.\" -*- nroff -*-
+.\" Copyright 1993, 1994, 1995 by Theodore Ts'o. All Rights Reserved.
+.\" This file may be copied under the terms of the GNU Public License.
+.\"
+.TH E2LABEL 8 "@E2FSPROGS_MONTH@ @E2FSPROGS_YEAR@" "E2fsprogs version @E2FSPROGS_VERSION@"
+.SH NAME
+e2label \- Change the label on an ext2/ext3/ext4 file system
+.SH SYNOPSIS
+.B e2label
+.I device
+[
+.I volume-label
+]
+.SH DESCRIPTION
+.B e2label
+will display or change the volume label on the ext2, ext3, or ext4
+file system located on
+.I device.
+.PP
+If the optional argument
+.I volume-label
+is not present,
+.B e2label
+will simply display the current volume label.
+.PP
+If the optional argument
+.I volume-label
+is present, then
+.B e2label
+will set the volume label to be
+.IR volume-label .
+Ext2 volume labels can be at most 16 characters long; if
+.I volume-label
+is longer than 16 characters,
+.B e2label
+will truncate it and print a warning message. For other file systems that
+support online label manipulation and are mounted
+.B e2label
+will work as well, but it will not attempt to truncate the
+.I volume-label
+at all.
+.PP
+It is also possible to set the volume label using the
+.B \-L
+option of
+.BR tune2fs (8).
+.PP
+.SH AUTHOR
+.B e2label
+was written by Theodore Ts'o (tytso@mit.edu).
+.SH AVAILABILITY
+.B e2label
+is part of the e2fsprogs package and is available from
+http://e2fsprogs.sourceforge.net.
+.SH SEE ALSO
+.BR mke2fs (8),
+.BR tune2fs (8)
+
diff --git a/misc/e2label.c b/misc/e2label.c
new file mode 100644
index 0000000..910ccc7
--- /dev/null
+++ b/misc/e2label.c
@@ -0,0 +1,121 @@
+/*
+ * e2label.c - Print or change the volume label on an ext2 fs
+ *
+ * Written by Andries Brouwer (aeb@cwi.nl), 970714
+ *
+ * Copyright 1997, 1998 by Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ * %End-Header%
+ */
+
+#include "config.h"
+#include <stdio.h>
+#include <string.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <termios.h>
+#include <time.h>
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#else
+extern char *optarg;
+extern int optind;
+#endif
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#include "support/nls-enable.h"
+
+#define EXT2_SUPER_MAGIC 0xEF53
+
+#define VOLNAMSZ 16
+
+struct ext2_super_block {
+ char s_dummy0[56];
+ unsigned char s_magic[2];
+ char s_dummy1[62];
+ char s_volume_name[VOLNAMSZ];
+ char s_last_mounted[64];
+ char s_dummy2[824];
+} sb;
+
+static int open_e2fs (char *dev, int mode)
+{
+ int fd;
+
+ fd = open(dev, mode);
+ if (fd < 0) {
+ perror(dev);
+ fprintf (stderr, _("e2label: cannot open %s\n"), dev);
+ exit(1);
+ }
+ if (lseek(fd, 1024, SEEK_SET) != 1024) {
+ perror(dev);
+ fprintf (stderr, _("e2label: cannot seek to superblock\n"));
+ exit(1);
+ }
+ if (read(fd, (char *) &sb, sizeof(sb)) != sizeof(sb)) {
+ perror(dev);
+ fprintf (stderr, _("e2label: error reading superblock\n"));
+ exit(1);
+ }
+ if (sb.s_magic[0] + 256*sb.s_magic[1] != EXT2_SUPER_MAGIC) {
+ fprintf (stderr, _("e2label: not an ext2 filesystem\n"));
+ exit(1);
+ }
+
+ return fd;
+}
+
+static void print_label (char *dev)
+{
+ char label[VOLNAMSZ+1];
+
+ open_e2fs (dev, O_RDONLY);
+ snprintf(label, sizeof(label), "%.*s", EXT2_LEN_STR(sb.s_volume_name));
+ label[VOLNAMSZ] = 0;
+ printf("%s\n", label);
+}
+
+static void change_label (char *dev, char *label)
+{
+ int fd;
+
+ fd = open_e2fs(dev, O_RDWR);
+ memset(sb.s_volume_name, 0, VOLNAMSZ);
+ strncpy(sb.s_volume_name, label, VOLNAMSZ);
+ if (strlen(label) > VOLNAMSZ)
+ fprintf(stderr, _("Warning: label too long, truncating.\n"));
+ if (lseek(fd, 1024, SEEK_SET) != 1024) {
+ perror(dev);
+ fprintf (stderr, _("e2label: cannot seek to superblock again\n"));
+ exit(1);
+ }
+ if (write(fd, (char *) &sb, sizeof(sb)) != sizeof(sb)) {
+ perror(dev);
+ fprintf (stderr, _("e2label: error writing superblock\n"));
+ exit(1);
+ }
+}
+
+int main (int argc, char ** argv)
+{
+ if (argc == 2)
+ print_label(argv[1]);
+ else if (argc == 3)
+ change_label(argv[1], argv[2]);
+ else {
+ fprintf(stderr, _("Usage: e2label device [newlabel]\n"));
+ exit(1);
+ }
+ return 0;
+}
diff --git a/misc/e2mmpstatus.8.in b/misc/e2mmpstatus.8.in
new file mode 100644
index 0000000..c7ed929
--- /dev/null
+++ b/misc/e2mmpstatus.8.in
@@ -0,0 +1,59 @@
+.\" -*- nroff -*-
+.\" This file may be copied under the terms of the GNU Public License.
+.\"
+.TH E2MMPSTATUS 8 "@E2FSPROGS_MONTH@ @E2FSPROGS_YEAR@" "E2fsprogs version @E2FSPROGS_VERSION@"
+.SH NAME
+e2mmpstatus \- Check MMP status of an ext4 file system
+.SH SYNOPSIS
+.BR e2mmpstatus " [" \-i ]
+.RI < file system >
+.SH OPTIONS
+.TP
+.B \-i
+prints out the MMP information rather than check it.
+.SH DESCRIPTION
+.B e2mmpstatus
+is used to check Multiple-Mount Protection (MMP) status of an ext4
+file system with the
+.B mmp
+feature enabled. The specified
+.I file system
+can be a device name (e.g.
+.IR /dev/hdc1 ", " /dev/sdb2 ),
+or an ext4 file system label or UUID, for example
+.B UUID=8868abf6-88c5-4a83-98b8-bfc24057f7bd
+or
+.BR LABEL=root .
+By default, the
+.B e2mmpstatus
+program checks whether it is safe to mount the file system without taking
+the risk of mounting it more than once.
+.PP
+MMP (multiple-mount protection) is a feature that adds protection against
+the file system being modified simultaneously by more than one node.
+It is NOT safe to mount a file system when one of the following conditions
+is true:
+.br
+ 1. e2fsck is running on the file system.
+.br
+ 2. the file system is in use by another node.
+.br
+ 3. The MMP block is corrupted or cannot be read for some reason.
+.br
+The
+.B e2mmpstatus
+program might wait for some time to see whether the MMP block is being
+updated by any node during this period. The time taken depends on how
+frequently the MMP block is being written by the other node.
+.SH EXIT CODE
+The exit code returned by
+.B e2mmpstatus
+is 0 when it is safe to mount the file system, 1 when the MMP block shows
+the file system is in use on another node and it is NOT safe to mount
+the file system, and 2 if some other failure occurred that prevents the
+check from properly detecting the current MMP status.
+.SH SEE ALSO
+.BR dumpe2fs (8),
+.BR e2fsck (8),
+.BR fstab (5),
+.BR fsck (8),
diff --git a/misc/e2undo.8.in b/misc/e2undo.8.in
new file mode 100644
index 0000000..30253a4
--- /dev/null
+++ b/misc/e2undo.8.in
@@ -0,0 +1,84 @@
+.\" -*- nroff -*-
+.\" Copyright 2008 by Theodore Ts'o. All Rights Reserved.
+.\" This file may be copied under the terms of the GNU Public License.
+.\"
+.TH E2UNDO 8 "@E2FSPROGS_MONTH@ @E2FSPROGS_YEAR@" "E2fsprogs version @E2FSPROGS_VERSION@"
+.SH NAME
+e2undo \- Replay an undo log for an ext2/ext3/ext4 file system
+.SH SYNOPSIS
+.B e2undo
+[
+.B \-f
+]
+[
+.B \-h
+]
+[
+.B \-n
+]
+[
+.B \-o
+.I offset
+]
+[
+.B \-v
+]
+[
+.B \-z
+.I undo_file
+]
+.I undo_log device
+.SH DESCRIPTION
+.B e2undo
+will replay the undo log
+.I undo_log
+for an ext2/ext3/ext4 file system found on
+.IR device .
+This can be
+used to undo a failed operation by an e2fsprogs program.
+.SH OPTIONS
+.TP
+.B \-f
+Normally,
+.B e2undo
+will check the file system superblock to make sure the undo log matches
+with the file system on the device. If they do not match,
+.B e2undo
+will refuse to apply the undo log as a safety mechanism. The
+.B \-f
+option disables this safety mechanism.
+.TP
+.B \-h
+Display a usage message.
+.TP
+.B \-n
+Dry-run; do not actually write blocks back to the file system.
+.TP
+.BI \-o " offset"
+Specify the file system's
+.I offset
+(in bytes) from the beginning of the device or file.
+.TP
+.B \-v
+Report which block we're currently replaying.
+.TP
+.BI \-z " undo_file"
+Before overwriting a file system block, write the old contents of the block to
+an undo file. This undo file can be used with e2undo(8) to restore the old
+contents of the file system should something go wrong. If the empty string is
+passed as the undo_file argument, the undo file will be written to a file named
+e2undo-\fIdevice\fR.e2undo in the directory specified via the
+\fIE2FSPROGS_UNDO_DIR\fR environment variable.
+
+WARNING: The undo file cannot be used to recover from a power or system crash.
+.SH AUTHOR
+.B e2undo
+was written by Aneesh Kumar K.V. (aneesh.kumar@linux.vnet.ibm.com)
+.SH AVAILABILITY
+.B e2undo
+is part of the e2fsprogs package and is available from
+http://e2fsprogs.sourceforge.net.
+.SH SEE ALSO
+.BR mke2fs (8),
+.BR tune2fs (8)
+
diff --git a/misc/e2undo.c b/misc/e2undo.c
new file mode 100644
index 0000000..bc78fb2
--- /dev/null
+++ b/misc/e2undo.c
@@ -0,0 +1,658 @@
+/*
+ * e2undo.c - Replay an undo log onto an ext2/3/4 filesystem
+ *
+ * Copyright IBM Corporation, 2007
+ * Author Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ * %End-Header%
+ */
+
+#include "config.h"
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#endif
+#include <fcntl.h>
+#if HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#include <unistd.h>
+#include <libgen.h>
+#include "ext2fs/ext2fs.h"
+#include "support/nls-enable.h"
+
+#undef DEBUG
+
+#ifdef DEBUG
+# define dbg_printf(f, a...) do {printf(f, ## a); fflush(stdout); } while (0)
+#else
+# define dbg_printf(f, a...)
+#endif
+
+/*
+ * Undo file format: The file is cut up into undo_header.block_size blocks.
+ * The first block contains the header.
+ * The second block contains the superblock.
+ * There is then a repeating series of blocks as follows:
+ * A key block, which contains undo_keys to map the following data blocks.
+ * Data blocks
+ * (Note that there are pointers to the first key block and the sb, so this
+ * order isn't strictly necessary.)
+ */
+#define E2UNDO_MAGIC "E2UNDO02"
+#define KEYBLOCK_MAGIC 0xCADECADE
+
+#define E2UNDO_STATE_FINISHED 0x1 /* undo file is complete */
+
+#define E2UNDO_MIN_BLOCK_SIZE 1024 /* undo blocks are no less than 1KB */
+#define E2UNDO_MAX_BLOCK_SIZE 1048576 /* undo blocks are no more than 1MB */
+
+struct undo_header {
+ char magic[8]; /* "E2UNDO02" */
+ __le64 num_keys; /* how many keys? */
+ __le64 super_offset; /* where in the file is the superblock copy? */
+ __le64 key_offset; /* where do the key/data block chunks start? */
+ __le32 block_size; /* block size of the undo file */
+ __le32 fs_block_size; /* block size of the target device */
+ __le32 sb_crc; /* crc32c of the superblock */
+ __le32 state; /* e2undo state flags */
+ __le32 f_compat; /* compatible features (none so far) */
+ __le32 f_incompat; /* incompatible features (none so far) */
+ __le32 f_rocompat; /* ro compatible features (none so far) */
+ __le32 pad32; /* padding for fs_offset */
+ __le64 fs_offset; /* filesystem offset */
+ __u8 padding[436]; /* padding */
+ __le32 header_crc; /* crc32c of the header (but not this field) */
+};
+
+#define E2UNDO_MAX_EXTENT_BLOCKS 512 /* max extent size, in blocks */
+
+struct undo_key {
+ __le64 fsblk; /* where in the fs does the block go */
+ __le32 blk_crc; /* crc32c of the block */
+ __le32 size; /* how many bytes in this block? */
+};
+
+struct undo_key_block {
+ __le32 magic; /* KEYBLOCK_MAGIC number */
+ __le32 crc; /* block checksum */
+ __le64 reserved; /* zero */
+#if __GNUC_PREREQ (4, 8)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wpedantic"
+#endif
+ struct undo_key keys[0]; /* keys, which come immediately after */
+#if __GNUC_PREREQ (4, 8)
+#pragma GCC diagnostic pop
+#endif
+};
+
+struct undo_key_info {
+ blk64_t fsblk;
+ blk64_t fileblk;
+ __u32 blk_crc;
+ unsigned int size;
+};
+
+struct undo_context {
+ struct undo_header hdr;
+ io_channel undo_file;
+ unsigned int blocksize, fs_blocksize;
+ blk64_t super_block;
+ size_t num_keys;
+ struct undo_key_info *keys;
+};
+#define KEYS_PER_BLOCK(d) (((d)->blocksize / sizeof(struct undo_key)) - 1)
+
+#define E2UNDO_FEATURE_COMPAT_FS_OFFSET 0x1 /* the filesystem offset */
+
+static inline int e2undo_has_feature_fs_offset(struct undo_header *header) {
+ return ext2fs_le32_to_cpu(header->f_compat) &
+ E2UNDO_FEATURE_COMPAT_FS_OFFSET;
+}
+
+static char *prg_name;
+static char *undo_file;
+
+static void usage(void)
+{
+ fprintf(stderr,
+ _("Usage: %s [-f] [-h] [-n] [-o offset] [-v] [-z undo_file] <transaction file> <filesystem>\n"), prg_name);
+ exit(1);
+}
+
+static void dump_header(struct undo_header *hdr)
+{
+ printf("nr keys:\t%llu\n",
+ (unsigned long long) ext2fs_le64_to_cpu(hdr->num_keys));
+ printf("super block:\t%llu\n",
+ (unsigned long long) ext2fs_le64_to_cpu(hdr->super_offset));
+ printf("key block:\t%llu\n",
+ (unsigned long long) ext2fs_le64_to_cpu(hdr->key_offset));
+ printf("block size:\t%u\n", ext2fs_le32_to_cpu(hdr->block_size));
+ printf("fs block size:\t%u\n", ext2fs_le32_to_cpu(hdr->fs_block_size));
+ printf("super crc:\t0x%x\n", ext2fs_le32_to_cpu(hdr->sb_crc));
+ printf("state:\t\t0x%x\n", ext2fs_le32_to_cpu(hdr->state));
+ printf("compat:\t\t0x%x\n", ext2fs_le32_to_cpu(hdr->f_compat));
+ printf("incompat:\t0x%x\n", ext2fs_le32_to_cpu(hdr->f_incompat));
+ printf("rocompat:\t0x%x\n", ext2fs_le32_to_cpu(hdr->f_rocompat));
+ if (e2undo_has_feature_fs_offset(hdr))
+ printf("fs offset:\t%llu\n",
+ (unsigned long long) ext2fs_le64_to_cpu(hdr->fs_offset));
+ printf("header crc:\t0x%x\n", ext2fs_le32_to_cpu(hdr->header_crc));
+}
+
+static void print_undo_mismatch(struct ext2_super_block *fs_super,
+ struct ext2_super_block *undo_super)
+{
+ printf("%s",
+ _("The file system superblock doesn't match the undo file.\n"));
+ if (memcmp(fs_super->s_uuid, undo_super->s_uuid,
+ sizeof(fs_super->s_uuid)))
+ printf("%s", _("UUID does not match.\n"));
+ if (fs_super->s_mtime != undo_super->s_mtime)
+ printf("%s", _("Last mount time does not match.\n"));
+ if (fs_super->s_wtime != undo_super->s_wtime)
+ printf("%s", _("Last write time does not match.\n"));
+ if (fs_super->s_kbytes_written != undo_super->s_kbytes_written)
+ printf("%s", _("Lifetime write counter does not match.\n"));
+}
+
+static int check_filesystem(struct undo_context *ctx, io_channel channel)
+{
+ struct ext2_super_block super, *sb;
+ char *buf;
+ __u32 sb_crc;
+ errcode_t retval;
+
+ io_channel_set_blksize(channel, SUPERBLOCK_OFFSET);
+ retval = io_channel_read_blk64(channel, 1, -SUPERBLOCK_SIZE, &super);
+ if (retval) {
+ com_err(prg_name, retval,
+ "%s", _("while reading filesystem superblock."));
+ return retval;
+ }
+
+ /*
+ * Compare the FS and the undo file superblock so that we can't apply
+ * e2undo "patches" out of order.
+ */
+ retval = ext2fs_get_mem(ctx->blocksize, &buf);
+ if (retval) {
+ com_err(prg_name, retval, "%s", _("while allocating memory"));
+ return retval;
+ }
+ retval = io_channel_read_blk64(ctx->undo_file, ctx->super_block,
+ -SUPERBLOCK_SIZE, buf);
+ if (retval) {
+ com_err(prg_name, retval, "%s", _("while fetching superblock"));
+ goto out;
+ }
+ sb = (struct ext2_super_block *)buf;
+ sb->s_magic = ~sb->s_magic;
+ if (memcmp(&super, buf, sizeof(super))) {
+ print_undo_mismatch(&super, (struct ext2_super_block *)buf);
+ retval = -1;
+ goto out;
+ }
+ sb_crc = ext2fs_crc32c_le(~0, (unsigned char *)buf, SUPERBLOCK_SIZE);
+ if (ext2fs_le32_to_cpu(ctx->hdr.sb_crc) != sb_crc) {
+ fprintf(stderr,
+ _("Undo file superblock checksum doesn't match.\n"));
+ retval = -1;
+ goto out;
+ }
+
+out:
+ ext2fs_free_mem(&buf);
+ return retval;
+}
+
+static int key_compare(const void *a, const void *b)
+{
+ const struct undo_key_info *ka, *kb;
+
+ ka = a;
+ kb = b;
+ return ka->fsblk - kb->fsblk;
+}
+
+static int e2undo_setup_tdb(const char *name, io_manager *io_ptr)
+{
+ errcode_t retval = 0;
+ const char *tdb_dir;
+ char *tdb_file = NULL;
+ char *dev_name, *tmp_name;
+
+ /* (re)open a specific undo file */
+ if (undo_file && undo_file[0] != 0) {
+ retval = set_undo_io_backing_manager(*io_ptr);
+ if (retval)
+ goto err;
+ *io_ptr = undo_io_manager;
+ retval = set_undo_io_backup_file(undo_file);
+ if (retval)
+ goto err;
+ printf(_("Overwriting existing filesystem; this can be undone "
+ "using the command:\n"
+ " e2undo %s %s\n\n"),
+ undo_file, name);
+ return retval;
+ }
+
+ /*
+ * Configuration via a conf file would be
+ * nice
+ */
+ tdb_dir = getenv("E2FSPROGS_UNDO_DIR");
+ if (!tdb_dir)
+ tdb_dir = "/var/lib/e2fsprogs";
+
+ if (!strcmp(tdb_dir, "none") || (tdb_dir[0] == 0) ||
+ access(tdb_dir, W_OK))
+ return 0;
+
+ tmp_name = strdup(name);
+ if (!tmp_name)
+ goto errout;
+ dev_name = basename(tmp_name);
+ tdb_file = malloc(strlen(tdb_dir) + 8 + strlen(dev_name) + 7 + 1);
+ if (!tdb_file) {
+ free(tmp_name);
+ goto errout;
+ }
+ sprintf(tdb_file, "%s/e2undo-%s.e2undo", tdb_dir, dev_name);
+ free(tmp_name);
+
+ if ((unlink(tdb_file) < 0) && (errno != ENOENT)) {
+ retval = errno;
+ com_err(prg_name, retval,
+ _("while trying to delete %s"), tdb_file);
+ goto errout;
+ }
+
+ retval = set_undo_io_backing_manager(*io_ptr);
+ if (retval)
+ goto errout;
+ *io_ptr = undo_io_manager;
+ retval = set_undo_io_backup_file(tdb_file);
+ if (retval)
+ goto errout;
+ printf(_("Overwriting existing filesystem; this can be undone "
+ "using the command:\n"
+ " e2undo %s %s\n\n"),
+ tdb_file, name);
+
+ free(tdb_file);
+ return 0;
+errout:
+ free(tdb_file);
+err:
+ com_err(prg_name, retval, "while trying to setup undo file\n");
+ return retval;
+}
+
+int main(int argc, char *argv[])
+{
+ int c, force = 0, dry_run = 0, verbose = 0, dump = 0;
+ io_channel channel;
+ errcode_t retval;
+ int mount_flags, csum_error = 0, io_error = 0;
+ size_t i, keys_per_block;
+ char *device_name, *tdb_file;
+ io_manager manager = unix_io_manager;
+ struct undo_context undo_ctx;
+ char *buf;
+ struct undo_key_block *keyb;
+ struct undo_key *dkey;
+ struct undo_key_info *ikey;
+ __u32 key_crc, blk_crc, hdr_crc;
+ blk64_t lblk;
+ ext2_filsys fs;
+ __u64 offset = 0;
+ char opt_offset_string[40] = { 0 };
+
+#ifdef ENABLE_NLS
+ setlocale(LC_MESSAGES, "");
+ setlocale(LC_CTYPE, "");
+ bindtextdomain(NLS_CAT_NAME, LOCALEDIR);
+ textdomain(NLS_CAT_NAME);
+ set_com_err_gettext(gettext);
+#endif
+ add_error_table(&et_ext2_error_table);
+
+ prg_name = argv[0];
+ while ((c = getopt(argc, argv, "fhno:vz:")) != EOF) {
+ switch (c) {
+ case 'f':
+ force = 1;
+ break;
+ case 'h':
+ dump = 1;
+ break;
+ case 'n':
+ dry_run = 1;
+ break;
+ case 'o':
+ offset = strtoull(optarg, &buf, 0);
+ if (*buf) {
+ com_err(prg_name, 0,
+ _("illegal offset - %s"), optarg);
+ exit(1);
+ }
+ /* used to indicate that an offset was specified */
+ opt_offset_string[0] = 1;
+ break;
+ case 'v':
+ verbose = 1;
+ break;
+ case 'z':
+ undo_file = optarg;
+ break;
+ default:
+ usage();
+ }
+ }
+
+ if (argc != optind + 2)
+ usage();
+
+ tdb_file = argv[optind];
+ device_name = argv[optind+1];
+
+ if (undo_file && strcmp(tdb_file, undo_file) == 0) {
+ printf(_("Will not write to an undo file while replaying it.\n"));
+ exit(1);
+ }
+
+ /* Interpret the undo file */
+ retval = manager->open(tdb_file, IO_FLAG_EXCLUSIVE,
+ &undo_ctx.undo_file);
+ if (retval) {
+ com_err(prg_name, errno,
+ _("while opening undo file `%s'\n"), tdb_file);
+ exit(1);
+ }
+ retval = io_channel_read_blk64(undo_ctx.undo_file, 0,
+ -(int)sizeof(undo_ctx.hdr),
+ &undo_ctx.hdr);
+ if (retval) {
+ com_err(prg_name, retval, _("while reading undo file"));
+ exit(1);
+ }
+ if (memcmp(undo_ctx.hdr.magic, E2UNDO_MAGIC,
+ sizeof(undo_ctx.hdr.magic))) {
+ fprintf(stderr, _("%s: Not an undo file.\n"), tdb_file);
+ exit(1);
+ }
+ if (dump) {
+ dump_header(&undo_ctx.hdr);
+ exit(1);
+ }
+ hdr_crc = ext2fs_crc32c_le(~0, (unsigned char *)&undo_ctx.hdr,
+ sizeof(struct undo_header) -
+ sizeof(__u32));
+ if (!force && ext2fs_le32_to_cpu(undo_ctx.hdr.header_crc) != hdr_crc) {
+ fprintf(stderr, _("%s: Header checksum doesn't match.\n"),
+ tdb_file);
+ exit(1);
+ }
+ undo_ctx.blocksize = ext2fs_le32_to_cpu(undo_ctx.hdr.block_size);
+ undo_ctx.fs_blocksize = ext2fs_le32_to_cpu(undo_ctx.hdr.fs_block_size);
+ if (undo_ctx.blocksize == 0 || undo_ctx.fs_blocksize == 0) {
+ fprintf(stderr, _("%s: Corrupt undo file header.\n"), tdb_file);
+ exit(1);
+ }
+ if (!force && undo_ctx.blocksize > E2UNDO_MAX_BLOCK_SIZE) {
+ fprintf(stderr, _("%s: Undo block size too large.\n"),
+ tdb_file);
+ exit(1);
+ }
+ if (!force && undo_ctx.blocksize < E2UNDO_MIN_BLOCK_SIZE) {
+ fprintf(stderr, _("%s: Undo block size too small.\n"),
+ tdb_file);
+ exit(1);
+ }
+ undo_ctx.super_block = ext2fs_le64_to_cpu(undo_ctx.hdr.super_offset);
+ undo_ctx.num_keys = ext2fs_le64_to_cpu(undo_ctx.hdr.num_keys);
+ io_channel_set_blksize(undo_ctx.undo_file, undo_ctx.blocksize);
+ /*
+ * Do not compare undo_ctx.hdr.f_compat with the available compatible
+ * features set, because a "missing" compatible feature should
+ * not cause any problems.
+ */
+ if (!force && (undo_ctx.hdr.f_incompat || undo_ctx.hdr.f_rocompat)) {
+ fprintf(stderr, _("%s: Unknown undo file feature set.\n"),
+ tdb_file);
+ exit(1);
+ }
+
+ /* open the fs */
+ retval = ext2fs_check_if_mounted(device_name, &mount_flags);
+ if (retval) {
+ com_err(prg_name, retval, _("Error while determining whether "
+ "%s is mounted."), device_name);
+ exit(1);
+ }
+
+ if (mount_flags & EXT2_MF_MOUNTED) {
+ com_err(prg_name, retval, "%s", _("e2undo should only be run "
+ "on unmounted filesystems"));
+ exit(1);
+ }
+
+ if (undo_file) {
+ retval = e2undo_setup_tdb(device_name, &manager);
+ if (retval)
+ exit(1);
+ }
+
+ retval = manager->open(device_name,
+ IO_FLAG_EXCLUSIVE | (dry_run ? 0 : IO_FLAG_RW),
+ &channel);
+ if (retval) {
+ com_err(prg_name, retval,
+ _("while opening `%s'"), device_name);
+ exit(1);
+ }
+
+ if (*opt_offset_string || e2undo_has_feature_fs_offset(&undo_ctx.hdr)) {
+ if (!*opt_offset_string)
+ offset = ext2fs_le64_to_cpu(undo_ctx.hdr.fs_offset);
+ retval = snprintf(opt_offset_string, sizeof(opt_offset_string),
+ "offset=%llu", (unsigned long long) offset);
+ if ((size_t) retval >= sizeof(opt_offset_string)) {
+ /* should not happen... */
+ com_err(prg_name, 0, _("specified offset is too large"));
+ exit(1);
+ }
+ io_channel_set_options(channel, opt_offset_string);
+ }
+
+ if (!force && check_filesystem(&undo_ctx, channel))
+ exit(1);
+
+ /* prepare to read keys */
+ retval = ext2fs_get_mem(sizeof(struct undo_key_info) * undo_ctx.num_keys,
+ &undo_ctx.keys);
+ if (retval) {
+ com_err(prg_name, retval, "%s", _("while allocating memory"));
+ exit(1);
+ }
+ ikey = undo_ctx.keys;
+ retval = ext2fs_get_mem(undo_ctx.blocksize, &keyb);
+ if (retval) {
+ com_err(prg_name, retval, "%s", _("while allocating memory"));
+ exit(1);
+ }
+ retval = ext2fs_get_mem(E2UNDO_MAX_EXTENT_BLOCKS * undo_ctx.blocksize,
+ &buf);
+ if (retval) {
+ com_err(prg_name, retval, "%s", _("while allocating memory"));
+ exit(1);
+ }
+
+ /* load keys */
+ keys_per_block = KEYS_PER_BLOCK(&undo_ctx);
+ lblk = ext2fs_le64_to_cpu(undo_ctx.hdr.key_offset);
+ dbg_printf("nr_keys=%lu, kpb=%zu, blksz=%u\n",
+ undo_ctx.num_keys, keys_per_block, undo_ctx.blocksize);
+ for (i = 0; i < undo_ctx.num_keys; i += keys_per_block) {
+ size_t j, max_j;
+ __le32 crc;
+
+ retval = io_channel_read_blk64(undo_ctx.undo_file,
+ lblk, 1, keyb);
+ if (retval) {
+ com_err(prg_name, retval, "%s", _("while reading keys"));
+ if (force) {
+ io_error = 1;
+ undo_ctx.num_keys = i - 1;
+ break;
+ }
+ exit(1);
+ }
+
+ /* check keys */
+ if (!force &&
+ ext2fs_le32_to_cpu(keyb->magic) != KEYBLOCK_MAGIC) {
+ fprintf(stderr, _("%s: wrong key magic at %llu\n"),
+ tdb_file, (unsigned long long) lblk);
+ exit(1);
+ }
+ crc = keyb->crc;
+ keyb->crc = 0;
+ key_crc = ext2fs_crc32c_le(~0, (unsigned char *)keyb,
+ undo_ctx.blocksize);
+ if (!force && ext2fs_le32_to_cpu(crc) != key_crc) {
+ fprintf(stderr,
+ _("%s: key block checksum error at %llu.\n"),
+ tdb_file, (unsigned long long) lblk);
+ exit(1);
+ }
+
+ /* load keys from key block */
+ lblk++;
+ max_j = undo_ctx.num_keys - i;
+ if (max_j > keys_per_block)
+ max_j = keys_per_block;
+ for (j = 0, dkey = keyb->keys;
+ j < max_j;
+ j++, ikey++, dkey++) {
+ ikey->fsblk = ext2fs_le64_to_cpu(dkey->fsblk);
+ ikey->fileblk = lblk;
+ ikey->blk_crc = ext2fs_le32_to_cpu(dkey->blk_crc);
+ ikey->size = ext2fs_le32_to_cpu(dkey->size);
+ lblk += (ikey->size + undo_ctx.blocksize - 1) /
+ undo_ctx.blocksize;
+
+ if (E2UNDO_MAX_EXTENT_BLOCKS * undo_ctx.blocksize <
+ ikey->size) {
+ com_err(prg_name, retval,
+ _("%s: block %llu is too long."),
+ tdb_file,
+ (unsigned long long) ikey->fsblk);
+ exit(1);
+ }
+
+ /* check each block's crc */
+ retval = io_channel_read_blk64(undo_ctx.undo_file,
+ ikey->fileblk,
+ -(int)ikey->size,
+ buf);
+ if (retval) {
+ com_err(prg_name, retval,
+ _("while fetching block %llu."),
+ (unsigned long long) ikey->fileblk);
+ if (!force)
+ exit(1);
+ io_error = 1;
+ continue;
+ }
+
+ blk_crc = ext2fs_crc32c_le(~0, (unsigned char *)buf,
+ ikey->size);
+ if (blk_crc != ikey->blk_crc) {
+ fprintf(stderr,
+ _("checksum error in filesystem block "
+ "%llu (undo blk %llu)\n"),
+ (unsigned long long) ikey->fsblk,
+ (unsigned long long) ikey->fileblk);
+ if (!force)
+ exit(1);
+ csum_error = 1;
+ }
+ }
+ }
+ ext2fs_free_mem(&keyb);
+
+ /* sort keys in fs block order */
+ qsort(undo_ctx.keys, undo_ctx.num_keys, sizeof(struct undo_key_info),
+ key_compare);
+
+ /* replay */
+ io_channel_set_blksize(channel, undo_ctx.fs_blocksize);
+ for (i = 0, ikey = undo_ctx.keys; i < undo_ctx.num_keys; i++, ikey++) {
+ retval = io_channel_read_blk64(undo_ctx.undo_file,
+ ikey->fileblk,
+ -(int)ikey->size,
+ buf);
+ if (retval) {
+ com_err(prg_name, retval,
+ _("while fetching block %llu."),
+ (unsigned long long) ikey->fileblk);
+ io_error = 1;
+ continue;
+ }
+
+ if (verbose)
+ printf("Replayed block of size %u from %llu to %llu\n",
+ ikey->size, (unsigned long long) ikey->fileblk,
+ (unsigned long long) ikey->fsblk);
+ if (dry_run)
+ continue;
+ retval = io_channel_write_blk64(channel, ikey->fsblk,
+ -(int)ikey->size, buf);
+ if (retval) {
+ com_err(prg_name, retval,
+ _("while writing block %llu."),
+ (unsigned long long) ikey->fsblk);
+ io_error = 1;
+ }
+ }
+
+ if (csum_error)
+ fprintf(stderr, _("Undo file corruption; run e2fsck NOW!\n"));
+ if (io_error)
+ fprintf(stderr, _("IO error during replay; run e2fsck NOW!\n"));
+ if (!(ext2fs_le32_to_cpu(undo_ctx.hdr.state) & E2UNDO_STATE_FINISHED)) {
+ force = 1;
+ fprintf(stderr, _("Incomplete undo record; run e2fsck.\n"));
+ }
+ ext2fs_free_mem(&buf);
+ ext2fs_free_mem(&undo_ctx.keys);
+ io_channel_close(channel);
+
+ /* If there were problems, try to force a fsck */
+ if (!dry_run && (force || csum_error || io_error)) {
+ retval = ext2fs_open2(device_name, NULL,
+ EXT2_FLAG_RW | EXT2_FLAG_64BITS, 0, 0,
+ manager, &fs);
+ if (retval)
+ goto out;
+ fs->super->s_state &= ~EXT2_VALID_FS;
+ if (csum_error || io_error)
+ fs->super->s_state |= EXT2_ERROR_FS;
+ ext2fs_mark_super_dirty(fs);
+ ext2fs_close_free(&fs);
+ }
+
+out:
+ io_channel_close(undo_ctx.undo_file);
+
+ return csum_error;
+}
diff --git a/misc/e4crypt.8.in b/misc/e4crypt.8.in
new file mode 100644
index 0000000..97bbcc9
--- /dev/null
+++ b/misc/e4crypt.8.in
@@ -0,0 +1,74 @@
+.TH E4CRYPT 8 "@E2FSPROGS_MONTH@ @E2FSPROGS_YEAR@" "E2fsprogs version @E2FSPROGS_VERSION@"
+.SH NAME
+e4crypt \- ext4 file system encryption utility
+.SH SYNOPSIS
+.B e4crypt add_key -S \fR[\fB -k \fIkeyring\fR ] [\fB-v\fR] [\fB-q\fR] \fR[\fB -p \fIpad\fR ] [ \fIpath\fR ... ]
+.br
+.B e4crypt new_session
+.br
+.B e4crypt get_policy \fIpath\fR ...
+.br
+.B e4crypt set_policy \fR[\fB -p \fIpad\fR ] \fIpolicy path\fR ...
+.SH DESCRIPTION
+.B e4crypt
+performs encryption management for ext4 file systems.
+.SH COMMANDS
+.TP
+.B e4crypt add_key \fR[\fB-vq\fR] [\fB-S\fI salt\fR ] [\fB-k \fIkeyring\fR ] [\fB -p \fIpad\fR ] [ \fIpath\fR ... ]
+Prompts the user for a passphrase and inserts it into the specified
+keyring. If no keyring is specified, e4crypt will use the session
+keyring if it exists or the user session keyring if it does not.
+.IP
+The
+.I salt
+argument is interpreted in a number of different ways, depending on how
+its prefix value. If the first two characters are "s:", then the rest
+of the argument will be used as an text string and used as the salt
+value. If the first two characters are "0x", then the rest of the
+argument will be parsed as a hex string as used as the salt. If the
+first characters are "f:" then the rest of the argument will be
+interpreted as a filename from which the salt value will be read. If
+the string begins with a '/' character, it will similarly be treated as
+filename. Finally, if the
+.I salt
+argument can be parsed as a valid UUID, then the UUID value will be used
+as a salt value.
+.IP
+The
+.I keyring
+argument specifies the keyring to which the key should be added.
+.IP
+The
+.I pad
+value specifies the number of bytes of padding will be added to
+directory names for obfuscation purposes. Valid
+.I pad
+values are 4, 8, 16, and 32.
+.IP
+If one or more directory paths are specified, e4crypt will try to
+set the policy of those directories to use the key just added by the
+.B add_key
+command. If a salt was explicitly specified, then it will be used
+to derive the encryption key of those directories. Otherwise a
+directory-specific default salt will be used.
+.TP
+.B e4crypt get_policy \fIpath\fR ...
+Print the policy for the directories specified on the command line.
+.TP
+.B e4crypt new_session
+Give the invoking process (typically a shell) a new session keyring,
+discarding its old session keyring.
+.TP
+.B e4crypt set_policy \fR[\fB -p \fIpad\fR ] \fIpolicy path\fR ...
+Sets the policy for the directories specified on the command line.
+All directories must be empty to set the policy; if the directory
+already has a policy established, e4crypt will validate that the
+policy matches what was specified. A policy is an encryption key
+identifier consisting of 16 hexadecimal characters.
+.SH AUTHOR
+Written by Michael Halcrow <mhalcrow@google.com>, Ildar Muslukhov
+<muslukhovi@gmail.com>, and Theodore Ts'o <tytso@mit.edu>
+.SH SEE ALSO
+.BR keyctl (1),
+.BR mke2fs (8),
+.BR mount (8).
diff --git a/misc/e4crypt.c b/misc/e4crypt.c
new file mode 100644
index 0000000..67d25d8
--- /dev/null
+++ b/misc/e4crypt.c
@@ -0,0 +1,893 @@
+/*
+ * e4crypt.c - ext4 encryption management utility
+ *
+ * Copyright (c) 2014 Google, Inc.
+ * SHA512 implementation from libtomcrypt.
+ *
+ * Authors: Michael Halcrow <mhalcrow@google.com>,
+ * Ildar Muslukhov <ildarm@google.com>
+ */
+
+#ifndef _LARGEFILE_SOURCE
+#define _LARGEFILE_SOURCE
+#endif
+
+#ifndef _LARGEFILE64_SOURCE
+#define _LARGEFILE64_SOURCE
+#endif
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include "config.h"
+#include <assert.h>
+#include <errno.h>
+#include <getopt.h>
+#include <dirent.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <mntent.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <unistd.h>
+#include <signal.h>
+#if !defined(HAVE_ADD_KEY) || !defined(HAVE_KEYCTL)
+#include <sys/syscall.h>
+#endif
+#ifdef HAVE_SYS_KEY_H
+#include <sys/key.h>
+#endif
+
+#include "ext2fs/ext2_fs.h"
+#include "ext2fs/ext2fs.h"
+#include "uuid/uuid.h"
+
+/* special process keyring shortcut IDs */
+#define KEY_SPEC_THREAD_KEYRING -1
+#define KEY_SPEC_PROCESS_KEYRING -2
+#define KEY_SPEC_SESSION_KEYRING -3
+#define KEY_SPEC_USER_KEYRING -4
+#define KEY_SPEC_USER_SESSION_KEYRING -5
+#define KEY_SPEC_GROUP_KEYRING -6
+
+#define KEYCTL_GET_KEYRING_ID 0
+#define KEYCTL_JOIN_SESSION_KEYRING 1
+#define KEYCTL_DESCRIBE 6
+#define KEYCTL_SEARCH 10
+#define KEYCTL_SESSION_TO_PARENT 18
+
+typedef __s32 key_serial_t;
+
+#define EXT4_KEY_REF_STR_BUF_SIZE ((EXT4_KEY_DESCRIPTOR_SIZE * 2) + 1)
+
+#ifndef EXT4_IOC_GET_ENCRYPTION_PWSALT
+#define EXT4_IOC_GET_ENCRYPTION_PWSALT _IOW('f', 20, __u8[16])
+#endif
+
+#define OPT_VERBOSE 0x0001
+#define OPT_QUIET 0x0002
+
+int options;
+
+#ifndef HAVE_KEYCTL
+static long keyctl(int cmd, ...)
+{
+ va_list va;
+ unsigned long arg2, arg3, arg4, arg5;
+
+ va_start(va, cmd);
+ arg2 = va_arg(va, unsigned long);
+ arg3 = va_arg(va, unsigned long);
+ arg4 = va_arg(va, unsigned long);
+ arg5 = va_arg(va, unsigned long);
+ va_end(va);
+ return syscall(__NR_keyctl, cmd, arg2, arg3, arg4, arg5);
+}
+#endif
+
+#ifndef HAVE_ADD_KEY
+static key_serial_t add_key(const char *type, const char *description,
+ const void *payload, size_t plen,
+ key_serial_t keyring)
+{
+ return syscall(__NR_add_key, type, description, payload,
+ plen, keyring);
+}
+#endif
+
+static const unsigned char *hexchars = (const unsigned char *) "0123456789abcdef";
+static const size_t hexchars_size = 16;
+
+#define SHA512_LENGTH 64
+#define EXT2FS_KEY_TYPE_LOGON "logon"
+#define EXT2FS_KEY_DESC_PREFIX "ext4:"
+#define EXT2FS_KEY_DESC_PREFIX_SIZE 5
+
+#define EXT4_IOC_SET_ENCRYPTION_POLICY _IOR('f', 19, struct ext4_encryption_policy)
+#define EXT4_IOC_GET_ENCRYPTION_POLICY _IOW('f', 21, struct ext4_encryption_policy)
+
+static int int_log2(int arg)
+{
+ int l = 0;
+
+ arg >>= 1;
+ while (arg) {
+ l++;
+ arg >>= 1;
+ }
+ return l;
+}
+
+static void validate_paths(int argc, char *argv[], int path_start_index)
+{
+ int x;
+ int valid = 1;
+ struct stat st;
+
+ for (x = path_start_index; x < argc; x++) {
+ int ret = access(argv[x], W_OK);
+ if (ret) {
+ invalid:
+ perror(argv[x]);
+ valid = 0;
+ continue;
+ }
+ ret = stat(argv[x], &st);
+ if (ret < 0)
+ goto invalid;
+ if (!S_ISDIR(st.st_mode)) {
+ fprintf(stderr, "%s is not a directory\n", argv[x]);
+ goto invalid;
+ }
+ }
+ if (!valid)
+ exit(1);
+}
+
+static int hex2byte(const char *hex, size_t hex_size, unsigned char *bytes,
+ size_t bytes_size)
+{
+ size_t x;
+ unsigned char *h, *l;
+
+ if (hex_size % 2)
+ return -EINVAL;
+ for (x = 0; x < hex_size; x += 2) {
+ h = memchr(hexchars, hex[x], hexchars_size);
+ if (!h)
+ return -EINVAL;
+ l = memchr(hexchars, hex[x + 1], hexchars_size);
+ if (!l)
+ return -EINVAL;
+ if ((x >> 1) >= bytes_size)
+ return -EINVAL;
+ bytes[x >> 1] = (((unsigned char)(h - hexchars) << 4) +
+ (unsigned char)(l - hexchars));
+ }
+ return 0;
+}
+
+/*
+ * Salt handling
+ */
+struct salt {
+ unsigned char *salt;
+ char key_ref_str[EXT4_KEY_REF_STR_BUF_SIZE];
+ unsigned char key_desc[EXT4_KEY_DESCRIPTOR_SIZE];
+ unsigned char key[EXT4_MAX_KEY_SIZE];
+ size_t salt_len;
+};
+struct salt *salt_list;
+unsigned num_salt;
+unsigned max_salt;
+char in_passphrase[EXT4_MAX_PASSPHRASE_SIZE];
+
+static struct salt *find_by_salt(unsigned char *salt, size_t salt_len)
+{
+ unsigned int i;
+ struct salt *p;
+
+ for (i = 0, p = salt_list; i < num_salt; i++, p++)
+ if ((p->salt_len == salt_len) &&
+ !memcmp(p->salt, salt, salt_len))
+ return p;
+ return NULL;
+}
+
+static void add_salt(unsigned char *salt, size_t salt_len)
+{
+ if (find_by_salt(salt, salt_len))
+ return;
+ if (num_salt >= max_salt) {
+ max_salt = num_salt + 10;
+ salt_list = realloc(salt_list, max_salt * sizeof(struct salt));
+ if (!salt_list) {
+ fprintf(stderr, "Couldn't allocate salt list\n");
+ exit(1);
+ }
+ }
+ salt_list[num_salt].salt = salt;
+ salt_list[num_salt].salt_len = salt_len;
+ num_salt++;
+}
+
+static void clear_secrets(void)
+{
+ if (salt_list) {
+ memset(salt_list, 0, sizeof(struct salt) * max_salt);
+ free(salt_list);
+ salt_list = NULL;
+ }
+ memset(in_passphrase, 0, sizeof(in_passphrase));
+}
+
+static void die_signal_handler(int signum EXT2FS_ATTR((unused)),
+ siginfo_t *siginfo EXT2FS_ATTR((unused)),
+ void *context EXT2FS_ATTR((unused)))
+{
+ clear_secrets();
+ exit(-1);
+}
+
+static void sigcatcher_setup(void)
+{
+ struct sigaction sa;
+
+ memset(&sa, 0, sizeof(struct sigaction));
+ sa.sa_sigaction = die_signal_handler;
+ sa.sa_flags = SA_SIGINFO;
+
+ sigaction(SIGHUP, &sa, 0);
+ sigaction(SIGINT, &sa, 0);
+ sigaction(SIGQUIT, &sa, 0);
+ sigaction(SIGFPE, &sa, 0);
+ sigaction(SIGILL, &sa, 0);
+ sigaction(SIGBUS, &sa, 0);
+ sigaction(SIGSEGV, &sa, 0);
+ sigaction(SIGABRT, &sa, 0);
+ sigaction(SIGPIPE, &sa, 0);
+ sigaction(SIGALRM, &sa, 0);
+ sigaction(SIGTERM, &sa, 0);
+ sigaction(SIGUSR1, &sa, 0);
+ sigaction(SIGUSR2, &sa, 0);
+ sigaction(SIGPOLL, &sa, 0);
+ sigaction(SIGPROF, &sa, 0);
+ sigaction(SIGSYS, &sa, 0);
+ sigaction(SIGTRAP, &sa, 0);
+ sigaction(SIGVTALRM, &sa, 0);
+ sigaction(SIGXCPU, &sa, 0);
+ sigaction(SIGXFSZ, &sa, 0);
+}
+
+
+#define PARSE_FLAGS_NOTSUPP_OK 0x0001
+#define PARSE_FLAGS_FORCE_FN 0x0002
+
+static void parse_salt(char *salt_str, int flags)
+{
+ unsigned char buf[EXT4_MAX_SALT_SIZE];
+ char *cp = salt_str;
+ unsigned char *salt_buf;
+ int fd, ret, salt_len = 0;
+
+ if (flags & PARSE_FLAGS_FORCE_FN)
+ goto salt_from_filename;
+ if (strncmp(cp, "s:", 2) == 0) {
+ cp += 2;
+ salt_len = strlen(cp);
+ if (salt_len >= EXT4_MAX_SALT_SIZE)
+ goto invalid_salt;
+ strncpy((char *) buf, cp, sizeof(buf));
+ } else if (cp[0] == '/') {
+ salt_from_filename:
+ fd = open(cp, O_RDONLY | O_DIRECTORY);
+ if (fd == -1 && errno == ENOTDIR)
+ fd = open(cp, O_RDONLY);
+ if (fd == -1) {
+ perror(cp);
+ exit(1);
+ }
+ ret = ioctl(fd, EXT4_IOC_GET_ENCRYPTION_PWSALT, &buf);
+ close(fd);
+ if (ret < 0) {
+ if (flags & PARSE_FLAGS_NOTSUPP_OK)
+ return;
+ perror("EXT4_IOC_GET_ENCRYPTION_PWSALT");
+ exit(1);
+ }
+ if (options & OPT_VERBOSE) {
+ char tmp[80];
+ uuid_unparse(buf, tmp);
+ printf("%s has pw salt %s\n", cp, tmp);
+ }
+ salt_len = 16;
+ } else if (strncmp(cp, "f:", 2) == 0) {
+ cp += 2;
+ goto salt_from_filename;
+ } else if (strncmp(cp, "0x", 2) == 0) {
+ unsigned char *h, *l;
+
+ cp += 2;
+ if (strlen(cp) & 1)
+ goto invalid_salt;
+ while (*cp) {
+ if (salt_len >= EXT4_MAX_SALT_SIZE)
+ goto invalid_salt;
+ h = memchr(hexchars, *cp++, hexchars_size);
+ l = memchr(hexchars, *cp++, hexchars_size);
+ if (!h || !l)
+ goto invalid_salt;
+ buf[salt_len++] =
+ (((unsigned char)(h - hexchars) << 4) +
+ (unsigned char)(l - hexchars));
+ }
+ } else if (uuid_parse(cp, buf) == 0) {
+ salt_len = 16;
+ } else {
+ invalid_salt:
+ fprintf(stderr, "Invalid salt: %s\n", salt_str);
+ exit(1);
+ }
+ salt_buf = malloc(salt_len);
+ if (!salt_buf) {
+ fprintf(stderr, "Couldn't allocate salt\n");
+ exit(1);
+ }
+ memcpy(salt_buf, buf, salt_len);
+ add_salt(salt_buf, salt_len);
+}
+
+static void set_policy(struct salt *set_salt, int pad,
+ int argc, char *argv[], int path_start_index)
+{
+ struct salt *salt;
+ struct ext4_encryption_policy policy;
+ uuid_t uu;
+ int fd;
+ int x;
+ int rc;
+
+ if ((pad != 4) && (pad != 8) &&
+ (pad != 16) && (pad != 32)) {
+ fprintf(stderr, "Invalid padding %d\n", pad);
+ exit(1);
+ }
+
+ for (x = path_start_index; x < argc; x++) {
+ fd = open(argv[x], O_DIRECTORY);
+ if (fd == -1) {
+ perror(argv[x]);
+ exit(1);
+ }
+ if (set_salt)
+ salt = set_salt;
+ else {
+ if (ioctl(fd, EXT4_IOC_GET_ENCRYPTION_PWSALT,
+ &uu) < 0) {
+ perror("EXT4_IOC_GET_ENCRYPTION_PWSALT");
+ exit(1);
+ }
+ salt = find_by_salt(uu, sizeof(uu));
+ if (!salt) {
+ fprintf(stderr, "Couldn't find salt!?!\n");
+ exit(1);
+ }
+ }
+ policy.version = 0;
+ policy.contents_encryption_mode =
+ EXT4_ENCRYPTION_MODE_AES_256_XTS;
+ policy.filenames_encryption_mode =
+ EXT4_ENCRYPTION_MODE_AES_256_CTS;
+ policy.flags = int_log2(pad >> 2);
+ memcpy(policy.master_key_descriptor, salt->key_desc,
+ EXT4_KEY_DESCRIPTOR_SIZE);
+ rc = ioctl(fd, EXT4_IOC_SET_ENCRYPTION_POLICY, &policy);
+ close(fd);
+ if (rc) {
+ printf("Error [%s] setting policy.\nThe key descriptor "
+ "[%s] may not match the existing encryption "
+ "context for directory [%s].\n",
+ strerror(errno), salt->key_ref_str, argv[x]);
+ continue;
+ }
+ printf("Key with descriptor [%s] applied to %s.\n",
+ salt->key_ref_str, argv[x]);
+ }
+}
+
+static void pbkdf2_sha512(const char *passphrase, struct salt *salt,
+ unsigned int count,
+ unsigned char derived_key[EXT4_MAX_KEY_SIZE])
+{
+ size_t passphrase_size = strlen(passphrase);
+ unsigned char buf[SHA512_LENGTH + EXT4_MAX_PASSPHRASE_SIZE] = {0};
+ unsigned char tempbuf[SHA512_LENGTH] = {0};
+ char final[SHA512_LENGTH] = {0};
+ unsigned char saltbuf[EXT4_MAX_SALT_SIZE + EXT4_MAX_PASSPHRASE_SIZE] = {0};
+ int actual_buf_len = SHA512_LENGTH + passphrase_size;
+ int actual_saltbuf_len = EXT4_MAX_SALT_SIZE + passphrase_size;
+ unsigned int x, y;
+ __u32 *final_u32 = (__u32 *)final;
+ __u32 *temp_u32 = (__u32 *)tempbuf;
+
+ if (passphrase_size > EXT4_MAX_PASSPHRASE_SIZE) {
+ printf("Passphrase size is %zd; max is %d.\n", passphrase_size,
+ EXT4_MAX_PASSPHRASE_SIZE);
+ exit(1);
+ }
+ if (salt->salt_len > EXT4_MAX_SALT_SIZE) {
+ printf("Salt size is %zd; max is %d.\n", salt->salt_len,
+ EXT4_MAX_SALT_SIZE);
+ exit(1);
+ }
+ assert(EXT4_MAX_KEY_SIZE <= SHA512_LENGTH);
+
+ memcpy(saltbuf, salt->salt, salt->salt_len);
+ memcpy(&saltbuf[EXT4_MAX_SALT_SIZE], passphrase, passphrase_size);
+
+ memcpy(&buf[SHA512_LENGTH], passphrase, passphrase_size);
+
+ for (x = 0; x < count; ++x) {
+ if (x == 0) {
+ ext2fs_sha512(saltbuf, actual_saltbuf_len, tempbuf);
+ } else {
+ /*
+ * buf: [previous hash || passphrase]
+ */
+ memcpy(buf, tempbuf, SHA512_LENGTH);
+ ext2fs_sha512(buf, actual_buf_len, tempbuf);
+ }
+ for (y = 0; y < (sizeof(final) / sizeof(*final_u32)); ++y)
+ final_u32[y] = final_u32[y] ^ temp_u32[y];
+ }
+ memcpy(derived_key, final, EXT4_MAX_KEY_SIZE);
+}
+
+static int disable_echo(struct termios *saved_settings)
+{
+ struct termios current_settings;
+ int rc = 0;
+
+ rc = tcgetattr(0, &current_settings);
+ if (rc)
+ return rc;
+ *saved_settings = current_settings;
+ current_settings.c_lflag &= ~ECHO;
+ rc = tcsetattr(0, TCSANOW, &current_settings);
+
+ return rc;
+}
+
+static void get_passphrase(char *passphrase, int len)
+{
+ char *p;
+ struct termios current_settings;
+
+ assert(len > 0);
+ disable_echo(&current_settings);
+ p = fgets(passphrase, len, stdin);
+ tcsetattr(0, TCSANOW, &current_settings);
+ printf("\n");
+ if (!p) {
+ printf("Aborting.\n");
+ exit(1);
+ }
+ p = strrchr(passphrase, '\n');
+ if (!p)
+ p = passphrase + len - 1;
+ *p = '\0';
+}
+
+struct keyring_map {
+ char name[4];
+ size_t name_len;
+ int code;
+};
+
+static const struct keyring_map keyrings[] = {
+ {"@us", 3, KEY_SPEC_USER_SESSION_KEYRING},
+ {"@u", 2, KEY_SPEC_USER_KEYRING},
+ {"@s", 2, KEY_SPEC_SESSION_KEYRING},
+ {"@g", 2, KEY_SPEC_GROUP_KEYRING},
+ {"@p", 2, KEY_SPEC_PROCESS_KEYRING},
+ {"@t", 2, KEY_SPEC_THREAD_KEYRING},
+};
+
+static int get_keyring_id(const char *keyring)
+{
+ unsigned int x;
+ char *end;
+
+ /*
+ * If no keyring is specified, by default use either the user
+ * session keyring or the session keyring. Fetching the
+ * session keyring will return the user session keyring if no
+ * session keyring has been set.
+ */
+ if (keyring == NULL)
+ return KEY_SPEC_SESSION_KEYRING;
+ for (x = 0; x < (sizeof(keyrings) / sizeof(keyrings[0])); ++x) {
+ if (strcmp(keyring, keyrings[x].name) == 0) {
+ return keyrings[x].code;
+ }
+ }
+ x = strtoul(keyring, &end, 10);
+ if (*end == '\0') {
+ if (keyctl(KEYCTL_DESCRIBE, x, NULL, 0) < 0)
+ return 0;
+ return x;
+ }
+ return 0;
+}
+
+static void generate_key_ref_str(struct salt *salt)
+{
+ unsigned char key_ref1[SHA512_LENGTH];
+ unsigned char key_ref2[SHA512_LENGTH];
+ int x;
+
+ ext2fs_sha512(salt->key, EXT4_MAX_KEY_SIZE, key_ref1);
+ ext2fs_sha512(key_ref1, SHA512_LENGTH, key_ref2);
+ memcpy(salt->key_desc, key_ref2, EXT4_KEY_DESCRIPTOR_SIZE);
+ for (x = 0; x < EXT4_KEY_DESCRIPTOR_SIZE; ++x) {
+ sprintf(&salt->key_ref_str[x * 2], "%02x",
+ salt->key_desc[x]);
+ }
+ salt->key_ref_str[EXT4_KEY_REF_STR_BUF_SIZE - 1] = '\0';
+}
+
+static void insert_key_into_keyring(const char *keyring, struct salt *salt)
+{
+ int keyring_id = get_keyring_id(keyring);
+ struct ext4_encryption_key key;
+ char key_ref_full[EXT2FS_KEY_DESC_PREFIX_SIZE +
+ EXT4_KEY_REF_STR_BUF_SIZE];
+ int rc;
+
+ if (keyring_id == 0) {
+ printf("Invalid keyring [%s].\n", keyring);
+ exit(1);
+ }
+ sprintf(key_ref_full, "%s%s", EXT2FS_KEY_DESC_PREFIX,
+ salt->key_ref_str);
+ rc = keyctl(KEYCTL_SEARCH, keyring_id, EXT2FS_KEY_TYPE_LOGON,
+ key_ref_full, 0);
+ if (rc != -1) {
+ if ((options & OPT_QUIET) == 0)
+ printf("Key with descriptor [%s] already exists\n",
+ salt->key_ref_str);
+ return;
+ } else if ((rc == -1) && (errno != ENOKEY)) {
+ printf("keyctl_search failed: %s\n", strerror(errno));
+ if (errno == EINVAL)
+ printf("Keyring [%s] is not available.\n", keyring);
+ exit(1);
+ }
+ key.mode = EXT4_ENCRYPTION_MODE_AES_256_XTS;
+ memcpy(key.raw, salt->key, EXT4_MAX_KEY_SIZE);
+ key.size = EXT4_MAX_KEY_SIZE;
+
+ /*
+ * We need to do this instead of simply adding the key to
+ * KEY_SPEC_SESSION_KEYRING since trying to add a key to a
+ * session keyring that does not yet exist will cause the
+ * kernel to create a session keyring --- which will then get
+ * garbage collected as soon as e4crypt exits.
+ *
+ * The fact that the keyctl system call and the add_key system
+ * call treats KEY_SPEC_SESSION_KEYRING differently when a
+ * session keyring does not exist is very unfortunate and
+ * confusing, but so it goes...
+ */
+ if (keyring_id == KEY_SPEC_SESSION_KEYRING) {
+ keyring_id = keyctl(KEYCTL_GET_KEYRING_ID, keyring_id, 0);
+ if (keyring_id < 0) {
+ printf("Error getting session keyring ID: %s\n",
+ strerror(errno));
+ exit(1);
+ }
+ }
+ rc = add_key(EXT2FS_KEY_TYPE_LOGON, key_ref_full, (void *)&key,
+ sizeof(key), keyring_id);
+ if (rc == -1) {
+ if (errno == EDQUOT) {
+ printf("Error adding key to keyring; quota exceeded\n");
+ } else {
+ printf("Error adding key with key descriptor [%s]: "
+ "%s\n", salt->key_ref_str, strerror(errno));
+ }
+ exit(1);
+ } else {
+ if ((options & OPT_QUIET) == 0)
+ printf("Added key with descriptor [%s]\n",
+ salt->key_ref_str);
+ }
+}
+
+static void get_default_salts(void)
+{
+ FILE *f = setmntent("/etc/mtab", "r");
+ struct mntent *mnt;
+
+ while (f && ((mnt = getmntent(f)) != NULL)) {
+ if (strcmp(mnt->mnt_type, "ext4") ||
+ access(mnt->mnt_dir, R_OK))
+ continue;
+ parse_salt(mnt->mnt_dir, PARSE_FLAGS_NOTSUPP_OK);
+ }
+ endmntent(f);
+}
+
+/* Functions which implement user commands */
+
+struct cmd_desc {
+ const char *cmd_name;
+ void (*cmd_func)(int, char **, const struct cmd_desc *);
+ const char *cmd_desc;
+ const char *cmd_help;
+ int cmd_flags;
+};
+
+#define CMD_HIDDEN 0x0001
+
+static void do_help(int argc, char **argv, const struct cmd_desc *cmd);
+
+#define add_key_desc "adds a key to the user's keyring"
+#define add_key_help \
+"e4crypt add_key -S salt [ -k keyring ] [-v] [-q] [ -p pad ] [ path ... ]\n\n" \
+"Prompts the user for a passphrase and inserts it into the specified\n" \
+"keyring. If no keyring is specified, e4crypt will use the session\n" \
+"keyring if it exists or the user session keyring if it does not.\n\n" \
+"If one or more directory paths are specified, e4crypt will try to\n" \
+"set the policy of those directories to use the key just entered by\n" \
+"the user.\n"
+
+static void do_add_key(int argc, char **argv, const struct cmd_desc *cmd)
+{
+ struct salt *salt;
+ bool explicit_salt = false;
+ char *keyring = NULL;
+ int i, opt, pad = 4;
+ unsigned j;
+
+ while ((opt = getopt(argc, argv, "k:S:p:vq")) != -1) {
+ switch (opt) {
+ case 'k':
+ /* Specify a keyring. */
+ keyring = optarg;
+ break;
+ case 'p':
+ pad = atoi(optarg);
+ break;
+ case 'S':
+ if (explicit_salt) {
+ fputs("May only provide -S once\n", stderr);
+ exit(1);
+ }
+ /* Salt value for passphrase. */
+ parse_salt(optarg, 0);
+ explicit_salt = true;
+ break;
+ case 'v':
+ options |= OPT_VERBOSE;
+ break;
+ case 'q':
+ options |= OPT_QUIET;
+ break;
+ default:
+ case '?':
+ if (opt != '?')
+ fprintf(stderr, "Unrecognized option: %c\n",
+ opt);
+ fputs("USAGE:\n ", stderr);
+ fputs(cmd->cmd_help, stderr);
+ exit(1);
+ }
+ }
+ if (num_salt == 0)
+ get_default_salts();
+ if (num_salt == 0) {
+ fprintf(stderr, "No salt values available\n");
+ exit(1);
+ }
+ validate_paths(argc, argv, optind);
+ if (!explicit_salt)
+ for (i = optind; i < argc; i++)
+ parse_salt(argv[i], PARSE_FLAGS_FORCE_FN);
+ printf("Enter passphrase (echo disabled): ");
+ get_passphrase(in_passphrase, sizeof(in_passphrase));
+ for (j = 0, salt = salt_list; j < num_salt; j++, salt++) {
+ pbkdf2_sha512(in_passphrase, salt,
+ EXT4_PBKDF2_ITERATIONS, salt->key);
+ generate_key_ref_str(salt);
+ insert_key_into_keyring(keyring, salt);
+ }
+ if (optind != argc) {
+ salt = explicit_salt ? salt_list : NULL;
+ set_policy(salt, pad, argc, argv, optind);
+ }
+ clear_secrets();
+ exit(0);
+}
+
+#define set_policy_desc "sets a policy for directories"
+#define set_policy_help \
+"e4crypt set_policy [ -p pad ] policy path ... \n\n" \
+"Sets the policy for the directories specified on the command line.\n" \
+"All directories must be empty to set the policy; if the directory\n" \
+"already has a policy established, e4crypt will validate that it the\n" \
+"policy matches what was specified. A policy is an encryption key\n" \
+"identifier consisting of 16 hexadecimal characters.\n"
+
+static void do_set_policy(int argc, char **argv, const struct cmd_desc *cmd)
+{
+ struct salt saltbuf;
+ int c, pad = 4;
+
+ while ((c = getopt (argc, argv, "p:")) != EOF) {
+ switch (c) {
+ case 'p':
+ pad = atoi(optarg);
+ break;
+ }
+ }
+
+ if (argc < optind + 2) {
+ fprintf(stderr, "Missing required argument(s).\n\n");
+ fputs("USAGE:\n ", stderr);
+ fputs(cmd->cmd_help, stderr);
+ exit(1);
+ }
+
+ if ((strlen(argv[optind]) != (EXT4_KEY_DESCRIPTOR_SIZE * 2)) ||
+ hex2byte(argv[optind], (EXT4_KEY_DESCRIPTOR_SIZE * 2),
+ saltbuf.key_desc, EXT4_KEY_DESCRIPTOR_SIZE)) {
+ printf("Invalid key descriptor [%s]. Valid characters "
+ "are 0-9 and a-f, lower case. "
+ "Length must be %d.\n",
+ argv[optind], (EXT4_KEY_DESCRIPTOR_SIZE * 2));
+ exit(1);
+ }
+ validate_paths(argc, argv, optind+1);
+ strcpy(saltbuf.key_ref_str, argv[optind]);
+ set_policy(&saltbuf, pad, argc, argv, optind+1);
+ exit(0);
+}
+
+#define get_policy_desc "get the encryption for directories"
+#define get_policy_help \
+"e4crypt get_policy path ... \n\n" \
+"Gets the policy for the directories specified on the command line.\n"
+
+static void do_get_policy(int argc, char **argv, const struct cmd_desc *cmd)
+{
+ struct ext4_encryption_policy policy;
+ int i, j, fd, rc;
+
+ if (argc < 2) {
+ fprintf(stderr, "Missing required argument(s).\n\n");
+ fputs("USAGE:\n ", stderr);
+ fputs(cmd->cmd_help, stderr);
+ exit(1);
+ }
+
+ for (i = 1; i < argc; i++) {
+ fd = open(argv[i], O_RDONLY);
+ if (fd == -1) {
+ perror(argv[i]);
+ exit(1);
+ }
+ rc = ioctl(fd, EXT4_IOC_GET_ENCRYPTION_POLICY, &policy);
+ close(fd);
+ if (rc) {
+ printf("Error getting policy for %s: %s\n",
+ argv[i], strerror(errno));
+ continue;
+ }
+ printf("%s: ", argv[i]);
+ for (j = 0; j < EXT4_KEY_DESCRIPTOR_SIZE; j++) {
+ printf("%02x", (unsigned char) policy.master_key_descriptor[j]);
+ }
+ fputc('\n', stdout);
+ }
+ exit(0);
+}
+
+#define new_session_desc "give the invoking process a new session keyring"
+#define new_session_help \
+"e4crypt new_session\n\n" \
+"Give the invoking process (typically a shell) a new session keyring,\n" \
+"discarding its old session keyring.\n"
+
+static void do_new_session(int argc, char **argv EXT2FS_ATTR((unused)),
+ const struct cmd_desc *cmd)
+{
+ long keyid, ret;
+
+ if (argc > 1) {
+ fputs("Excess arguments\n\n", stderr);
+ fputs(cmd->cmd_help, stderr);
+ exit(1);
+ }
+ keyid = keyctl(KEYCTL_JOIN_SESSION_KEYRING, NULL);
+ if (keyid < 0) {
+ perror("KEYCTL_JOIN_SESSION_KEYRING");
+ exit(1);
+ }
+ ret = keyctl(KEYCTL_SESSION_TO_PARENT, NULL);
+ if (ret < 0) {
+ perror("KEYCTL_SESSION_TO_PARENT");
+ exit(1);
+ }
+ printf("Switched invoking process to new session keyring %ld\n", keyid);
+ exit(0);
+}
+
+#define CMD(name) { #name, do_##name, name##_desc, name##_help, 0 }
+#define _CMD(name) { #name, do_##name, NULL, NULL, CMD_HIDDEN }
+
+const struct cmd_desc cmd_list[] = {
+ _CMD(help),
+ CMD(add_key),
+ CMD(get_policy),
+ CMD(new_session),
+ CMD(set_policy),
+ { NULL, NULL, NULL, NULL, 0 }
+};
+
+static void do_help(int argc, char **argv,
+ const struct cmd_desc *cmd EXT2FS_ATTR((unused)))
+{
+ const struct cmd_desc *p;
+
+ if (argc > 1) {
+ for (p = cmd_list; p->cmd_name; p++) {
+ if (p->cmd_flags & CMD_HIDDEN)
+ continue;
+ if (strcmp(p->cmd_name, argv[1]) == 0) {
+ putc('\n', stdout);
+ fputs("USAGE:\n ", stdout);
+ fputs(p->cmd_help, stdout);
+ exit(0);
+ }
+ }
+ printf("Unknown command: %s\n\n", argv[1]);
+ }
+
+ fputs("Available commands:\n", stdout);
+ for (p = cmd_list; p->cmd_name; p++) {
+ if (p->cmd_flags & CMD_HIDDEN)
+ continue;
+ printf(" %-20s %s\n", p->cmd_name, p->cmd_desc);
+ }
+ printf("\nTo get more information on a command, "
+ "type 'e4crypt help cmd'\n");
+ exit(0);
+}
+
+int main(int argc, char *argv[])
+{
+ const struct cmd_desc *cmd;
+
+ if (argc < 2)
+ do_help(argc, argv, cmd_list);
+
+ sigcatcher_setup();
+ for (cmd = cmd_list; cmd->cmd_name; cmd++) {
+ if (strcmp(cmd->cmd_name, argv[1]) == 0) {
+ cmd->cmd_func(argc-1, argv+1, cmd);
+ exit(0);
+ }
+ }
+ printf("Unknown command: %s\n\n", argv[1]);
+ do_help(1, argv, cmd_list);
+ return 0;
+}
diff --git a/misc/e4defrag.8.in b/misc/e4defrag.8.in
new file mode 100644
index 0000000..53d7f17
--- /dev/null
+++ b/misc/e4defrag.8.in
@@ -0,0 +1,90 @@
+.TH E4DEFRAG 8 "May 2009" "e4defrag version 2.0"
+.SH NAME
+e4defrag \- online defragmenter for ext4 file system
+.SH SYNOPSIS
+.B e4defrag
+[
+.B \-c
+]
+[
+.B \-v
+]
+.I target
+\&...
+.SH DESCRIPTION
+.B e4defrag
+reduces fragmentation of extent based file. The file targeted by
+.B e4defrag
+is created on ext4 file system made with "-O extent" option (see
+.BR mke2fs (8)).
+The targeted file gets more contiguous blocks and improves the file access
+speed.
+.PP
+.I target
+is a regular file, a directory, or a device that is mounted as ext4 file system.
+If
+.I target
+is a directory,
+.B e4defrag
+reduces fragmentation of all files in it. If
+.I target
+is a device,
+.B e4defrag
+gets the mount point of it and reduces fragmentation of all files in this mount
+point.
+.SH OPTIONS
+.TP
+.B \-c
+Get a current fragmentation count and an ideal fragmentation count, and
+calculate fragmentation score based on them. By seeing this score, we can
+determine whether we should execute
+.B e4defrag
+to
+.IR target .
+When used with
+.B \-v
+option, the current fragmentation count and the ideal fragmentation count are
+printed for each file.
+.IP
+Also this option outputs the average data size in one extent. If you see it,
+you'll find the file has ideal extents or not. Note that the maximum extent
+size is 131072KB in ext4 file system (if block size is 4KB).
+.IP
+If this option is specified,
+.I target
+is never defragmented.
+.TP
+.B \-v
+Print error messages and the fragmentation count before and after defrag for
+each file.
+.SH NOTES
+.B e4defrag
+does not support swap file, files in lost+found directory, and files allocated
+in indirect blocks. When
+.I target
+is a device or a mount point,
+.B e4defrag
+doesn't defragment files in mount point of other device.
+.PP
+It is safe to run e4defrag on a file while it is actively in use by another
+application. Since the contents of file blocks are copied using the
+page cache, this can result in a performance slowdown to both e4defrag
+and the application due to contention over the system's memory and disk
+bandwidth.
+.PP
+If the file system's free space is fragmented, or if there is
+insufficient free space available, e4defrag may not be able
+to improve the file's fragmentation.
+.PP
+Non-privileged users can execute
+.B e4defrag
+to their own file, but the score is not printed if
+.B \-c
+option is specified. Therefore, it is desirable to be executed by root user.
+.SH AUTHOR
+Written by Akira Fujita <a-fujita@rs.jp.nec.com> and Takashi Sato
+<t-sato@yk.jp.nec.com>.
+.SH SEE ALSO
+.BR mke2fs (8),
+.BR mount (8).
+
diff --git a/misc/e4defrag.c b/misc/e4defrag.c
new file mode 100644
index 0000000..e3011d7
--- /dev/null
+++ b/misc/e4defrag.c
@@ -0,0 +1,2035 @@
+/*
+ * e4defrag.c - ext4 filesystem defragmenter
+ *
+ * Copyright (C) 2009 NEC Software Tohoku, Ltd.
+ *
+ * Author: Akira Fujita <a-fujita@rs.jp.nec.com>
+ * Takashi Sato <t-sato@yk.jp.nec.com>
+ */
+
+#ifndef _LARGEFILE_SOURCE
+#define _LARGEFILE_SOURCE
+#endif
+
+#ifndef _LARGEFILE64_SOURCE
+#define _LARGEFILE64_SOURCE
+#endif
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include "config.h"
+#include <ctype.h>
+#include <dirent.h>
+#include <endian.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <ftw.h>
+#include <limits.h>
+#include <mntent.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <ext2fs/ext2_types.h>
+#include <ext2fs/ext2fs.h>
+#include <sys/ioctl.h>
+#include <ext2fs/fiemap.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/statfs.h>
+#include <sys/vfs.h>
+
+#include "../version.h"
+
+/* A relatively new ioctl interface ... */
+#ifndef EXT4_IOC_MOVE_EXT
+#define EXT4_IOC_MOVE_EXT _IOWR('f', 15, struct move_extent)
+#endif
+
+/* Macro functions */
+#define PRINT_ERR_MSG(msg) fprintf(stderr, "%s\n", (msg))
+#define IN_FTW_PRINT_ERR_MSG(msg) \
+ fprintf(stderr, "\t%s\t\t[ NG ]\n", (msg))
+#define PRINT_FILE_NAME(file) fprintf(stderr, " \"%s\"\n", (file))
+#define PRINT_ERR_MSG_WITH_ERRNO(msg) \
+ fprintf(stderr, "\t%s:%s\t[ NG ]\n", (msg), strerror(errno))
+#define STATISTIC_ERR_MSG(msg) \
+ fprintf(stderr, "\t%s\n", (msg))
+#define STATISTIC_ERR_MSG_WITH_ERRNO(msg) \
+ fprintf(stderr, "\t%s:%s\n", (msg), strerror(errno))
+#define min(x, y) (((x) > (y)) ? (y) : (x))
+#define CALC_SCORE(ratio) \
+ ((ratio) > 10 ? (80 + 20 * (ratio) / 100) : (8 * (ratio)))
+/* Wrap up the free function */
+#define FREE(tmp) \
+ do { \
+ if ((tmp) != NULL) \
+ free(tmp); \
+ } while (0) \
+/* Insert list2 after list1 */
+#define insert(list1, list2) \
+ do { \
+ list2->next = list1->next; \
+ list1->next->prev = list2; \
+ list2->prev = list1; \
+ list1->next = list2; \
+ } while (0)
+
+/* To delete unused warning */
+#ifdef __GNUC__
+#define EXT2FS_ATTR(x) __attribute__(x)
+#else
+#define EXT2FS_ATTR(x)
+#endif
+
+/* The mode of defrag */
+#define DETAIL 0x01
+#define STATISTIC 0x02
+
+#define DEVNAME 0
+#define DIRNAME 1
+#define FILENAME 2
+
+#define FTW_OPEN_FD 2000
+
+#define FS_EXT4 "ext4"
+#define ROOT_UID 0
+
+#define BOUND_SCORE 55
+#define SHOW_FRAG_FILES 5
+
+/* Magic number for ext4 */
+#define EXT4_SUPER_MAGIC 0xEF53
+
+/* Definition of flex_bg */
+#define EXT4_FEATURE_INCOMPAT_FLEX_BG 0x0200
+
+/* The following macro is used for ioctl FS_IOC_FIEMAP
+ * EXTENT_MAX_COUNT: the maximum number of extents for exchanging between
+ * kernel-space and user-space per ioctl
+ */
+#define EXTENT_MAX_COUNT 512
+
+/* The following macros are error message */
+#define MSG_USAGE \
+"Usage : e4defrag [-v] file...| directory...| device...\n\
+ : e4defrag -c file...| directory...| device...\n"
+
+#define NGMSG_EXT4 "Filesystem is not ext4 filesystem"
+#define NGMSG_FILE_EXTENT "Failed to get file extents"
+#define NGMSG_FILE_INFO "Failed to get file information"
+#define NGMSG_FILE_OPEN "Failed to open"
+#define NGMSG_FILE_UNREG "File is not regular file"
+#define NGMSG_LOST_FOUND "Can not process \"lost+found\""
+
+/* Data type for filesystem-wide blocks number */
+typedef unsigned long long ext4_fsblk_t;
+
+struct fiemap_extent_data {
+ __u64 len; /* blocks count */
+ __u64 logical; /* start logical block number */
+ ext4_fsblk_t physical; /* start physical block number */
+};
+
+struct fiemap_extent_list {
+ struct fiemap_extent_list *prev;
+ struct fiemap_extent_list *next;
+ struct fiemap_extent_data data; /* extent belong to file */
+};
+
+struct fiemap_extent_group {
+ struct fiemap_extent_group *prev;
+ struct fiemap_extent_group *next;
+ __u64 len; /* length of this continuous region */
+ struct fiemap_extent_list *start; /* start ext */
+ struct fiemap_extent_list *end; /* end ext */
+};
+
+struct move_extent {
+ __s32 reserved; /* original file descriptor */
+ __u32 donor_fd; /* donor file descriptor */
+ __u64 orig_start; /* logical start offset in block for orig */
+ __u64 donor_start; /* logical start offset in block for donor */
+ __u64 len; /* block length to be moved */
+ __u64 moved_len; /* moved block length */
+};
+
+struct frag_statistic_ino {
+ int now_count; /* the file's extents count of before defrag */
+ int best_count; /* the best file's extents count */
+ __u64 size_per_ext; /* size(KB) per extent */
+ float ratio; /* the ratio of fragmentation */
+ char msg_buffer[PATH_MAX + 1]; /* pathname of the file */
+};
+
+static char lost_found_dir[PATH_MAX + 1];
+static int block_size;
+static int extents_before_defrag;
+static int extents_after_defrag;
+static int mode_flag;
+static unsigned int current_uid;
+static unsigned int defraged_file_count;
+static unsigned int frag_files_before_defrag;
+static unsigned int frag_files_after_defrag;
+static unsigned int regular_count;
+static unsigned int succeed_cnt;
+static unsigned int total_count;
+static __u8 log_groups_per_flex;
+static __u32 blocks_per_group;
+static __u32 feature_incompat;
+static ext4_fsblk_t files_block_count;
+static struct frag_statistic_ino frag_rank[SHOW_FRAG_FILES];
+
+
+/*
+ * We prefer posix_fadvise64 when available, as it allows 64bit offset on
+ * 32bit systems
+ */
+#if defined(HAVE_POSIX_FADVISE64)
+#define posix_fadvise posix_fadvise64
+#elif defined(HAVE_FADVISE64)
+#define posix_fadvise fadvise64
+#elif !defined(HAVE_POSIX_FADVISE)
+#error posix_fadvise not available!
+#endif
+
+/*
+ * get_mount_point() - Get device's mount point.
+ *
+ * @devname: the device's name.
+ * @mount_point: the mount point.
+ * @dir_path_len: the length of directory.
+ */
+static int get_mount_point(const char *devname, char *mount_point,
+ int dir_path_len)
+{
+ /* Refer to /etc/mtab */
+ const char *mtab = MOUNTED;
+ FILE *fp = NULL;
+ struct mntent *mnt = NULL;
+ struct stat64 sb;
+
+ if (stat64(devname, &sb) < 0) {
+ perror(NGMSG_FILE_INFO);
+ PRINT_FILE_NAME(devname);
+ return -1;
+ }
+
+ fp = setmntent(mtab, "r");
+ if (fp == NULL) {
+ perror("Couldn't access /etc/mtab");
+ return -1;
+ }
+
+ while ((mnt = getmntent(fp)) != NULL) {
+ struct stat64 ms;
+
+ /*
+ * To handle device symlinks, we see if the
+ * device number matches, not the name
+ */
+ if (stat64(mnt->mnt_fsname, &ms) < 0)
+ continue;
+ if (sb.st_rdev != ms.st_rdev)
+ continue;
+
+ endmntent(fp);
+ if (strcmp(mnt->mnt_type, FS_EXT4) == 0) {
+ strncpy(mount_point, mnt->mnt_dir,
+ dir_path_len);
+ return 0;
+ }
+ PRINT_ERR_MSG(NGMSG_EXT4);
+ return -1;
+ }
+ endmntent(fp);
+ PRINT_ERR_MSG("Filesystem is not mounted");
+ return -1;
+}
+
+/*
+ * is_ext4() - Whether on an ext4 filesystem.
+ *
+ * @file: the file's name.
+ */
+static int is_ext4(const char *file, char devname[PATH_MAX + 1])
+{
+ int maxlen = 0;
+ int len, ret;
+ int type_is_ext4 = 0;
+ FILE *fp = NULL;
+ /* Refer to /etc/mtab */
+ const char *mtab = MOUNTED;
+ char file_path[PATH_MAX + 1];
+ struct mntent *mnt = NULL;
+ struct statfs64 fsbuf;
+
+ /* Get full path */
+ if (realpath(file, file_path) == NULL) {
+ perror("Couldn't get full path");
+ PRINT_FILE_NAME(file);
+ return -1;
+ }
+
+ if (statfs64(file_path, &fsbuf) < 0) {
+ perror("Failed to get filesystem information");
+ PRINT_FILE_NAME(file);
+ return -1;
+ }
+
+ if (fsbuf.f_type != EXT4_SUPER_MAGIC) {
+ PRINT_ERR_MSG(NGMSG_EXT4);
+ return -1;
+ }
+
+ fp = setmntent(mtab, "r");
+ if (fp == NULL) {
+ perror("Couldn't access /etc/mtab");
+ return -1;
+ }
+
+ while ((mnt = getmntent(fp)) != NULL) {
+ if (mnt->mnt_fsname[0] != '/')
+ continue;
+ len = strlen(mnt->mnt_dir);
+ ret = memcmp(file_path, mnt->mnt_dir, len);
+ if (ret != 0)
+ continue;
+
+ if (maxlen >= len)
+ continue;
+
+ maxlen = len;
+
+ type_is_ext4 = !strcmp(mnt->mnt_type, FS_EXT4);
+ strncpy(lost_found_dir, mnt->mnt_dir, PATH_MAX);
+ strncpy(devname, mnt->mnt_fsname, PATH_MAX);
+ }
+
+ endmntent(fp);
+ if (type_is_ext4)
+ return 0;
+ PRINT_ERR_MSG(NGMSG_EXT4);
+ return -1;
+}
+
+/*
+ * calc_entry_counts() - Calculate file counts.
+ *
+ * @file: file name.
+ * @buf: file info.
+ * @flag: file type.
+ * @ftwbuf: the pointer of a struct FTW.
+ */
+static int calc_entry_counts(const char *file EXT2FS_ATTR((unused)),
+ const struct stat64 *buf, int flag EXT2FS_ATTR((unused)),
+ struct FTW *ftwbuf EXT2FS_ATTR((unused)))
+{
+ if (S_ISREG(buf->st_mode))
+ regular_count++;
+
+ total_count++;
+
+ return 0;
+}
+
+/*
+ * page_in_core() - Get information on whether pages are in core.
+ *
+ * @fd: defrag target file's descriptor.
+ * @defrag_data: data used for defrag.
+ * @vec: page state array.
+ * @page_num: page number.
+ */
+static int page_in_core(int fd, struct move_extent defrag_data,
+ unsigned char **vec, unsigned int *page_num)
+{
+ long pagesize;
+ void *page = NULL;
+ ext2_loff_t offset, end_offset, length;
+
+ if (vec == NULL || *vec != NULL)
+ return -1;
+
+ pagesize = sysconf(_SC_PAGESIZE);
+ if (pagesize < 0)
+ return -1;
+ /* In mmap, offset should be a multiple of the page size */
+ offset = (ext2_loff_t)defrag_data.orig_start * block_size;
+ length = (ext2_loff_t)defrag_data.len * block_size;
+ end_offset = offset + length;
+ /* Round the offset down to the nearest multiple of pagesize */
+ offset = (offset / pagesize) * pagesize;
+ length = end_offset - offset;
+
+ page = mmap(NULL, length, PROT_READ, MAP_SHARED, fd, offset);
+ if (page == MAP_FAILED)
+ return -1;
+
+ *page_num = 0;
+ *page_num = (length + pagesize - 1) / pagesize;
+ *vec = (unsigned char *)calloc(*page_num, 1);
+ if (*vec == NULL) {
+ munmap(page, length);
+ return -1;
+ }
+
+ /* Get information on whether pages are in core */
+ if (mincore(page, (size_t)length, *vec) == -1 ||
+ munmap(page, length) == -1) {
+ FREE(*vec);
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * defrag_fadvise() - Predeclare an access pattern for file data.
+ *
+ * @fd: defrag target file's descriptor.
+ * @defrag_data: data used for defrag.
+ * @vec: page state array.
+ * @page_num: page number.
+ */
+static int defrag_fadvise(int fd, struct move_extent defrag_data,
+ unsigned char *vec, unsigned int page_num)
+{
+ int flag = 1;
+ long pagesize = sysconf(_SC_PAGESIZE);
+ int fadvise_flag = POSIX_FADV_DONTNEED;
+ int sync_flag = SYNC_FILE_RANGE_WAIT_BEFORE |
+ SYNC_FILE_RANGE_WRITE |
+ SYNC_FILE_RANGE_WAIT_AFTER;
+ unsigned int i;
+ ext2_loff_t offset;
+
+ if (pagesize < 1)
+ return -1;
+
+ offset = (ext2_loff_t)defrag_data.orig_start * block_size;
+ offset = (offset / pagesize) * pagesize;
+
+#ifdef HAVE_SYNC_FILE_RANGE
+ /* Sync file for fadvise process */
+ if (sync_file_range(fd, offset,
+ (ext2_loff_t)pagesize * page_num, sync_flag) < 0)
+ return -1;
+#endif
+
+ /* Try to release buffer cache which this process used,
+ * then other process can use the released buffer
+ */
+ for (i = 0; i < page_num; i++) {
+ if ((vec[i] & 0x1) == 0) {
+ offset += pagesize;
+ continue;
+ }
+ if ((errno = posix_fadvise(fd, offset,
+ pagesize, fadvise_flag)) != 0) {
+ if ((mode_flag & DETAIL) && flag) {
+ perror("\tFailed to fadvise");
+ flag = 0;
+ }
+ }
+ offset += pagesize;
+ }
+
+ return 0;
+}
+
+/*
+ * check_free_size() - Check if there's enough disk space.
+ *
+ * @fd: defrag target file's descriptor.
+ * @file: file name.
+ * @blk_count: file blocks.
+ */
+static int check_free_size(int fd, const char *file, ext4_fsblk_t blk_count)
+{
+ ext4_fsblk_t free_blk_count;
+ struct statfs64 fsbuf;
+
+ if (fstatfs64(fd, &fsbuf) < 0) {
+ if (mode_flag & DETAIL) {
+ PRINT_FILE_NAME(file);
+ PRINT_ERR_MSG_WITH_ERRNO(
+ "Failed to get filesystem information");
+ }
+ return -1;
+ }
+
+ /* Compute free space for root and normal user separately */
+ if (current_uid == ROOT_UID)
+ free_blk_count = fsbuf.f_bfree;
+ else
+ free_blk_count = fsbuf.f_bavail;
+
+ if (free_blk_count >= blk_count)
+ return 0;
+
+ return -ENOSPC;
+}
+
+/*
+ * file_frag_count() - Get file fragment count.
+ *
+ * @fd: defrag target file's descriptor.
+ */
+static int file_frag_count(int fd)
+{
+ int ret;
+ struct fiemap fiemap_buf;
+
+ /* When fm_extent_count is 0,
+ * ioctl just get file fragment count.
+ */
+ memset(&fiemap_buf, 0, sizeof(struct fiemap));
+ fiemap_buf.fm_start = 0;
+ fiemap_buf.fm_length = FIEMAP_MAX_OFFSET;
+ fiemap_buf.fm_flags |= FIEMAP_FLAG_SYNC;
+
+ ret = ioctl(fd, FS_IOC_FIEMAP, &fiemap_buf);
+ if (ret < 0)
+ return ret;
+
+ return fiemap_buf.fm_mapped_extents;
+}
+
+/*
+ * file_check() - Check file's attributes.
+ *
+ * @fd: defrag target file's descriptor.
+ * @buf: a pointer of the struct stat64.
+ * @file: file name.
+ * @extents: file extents.
+ * @blk_count: file blocks.
+ */
+static int file_check(int fd, const struct stat64 *buf, const char *file,
+ int extents, ext4_fsblk_t blk_count)
+{
+ int ret;
+ struct flock lock;
+
+ /* Write-lock check is more reliable */
+ lock.l_type = F_WRLCK;
+ lock.l_start = 0;
+ lock.l_whence = SEEK_SET;
+ lock.l_len = 0;
+
+ /* Free space */
+ ret = check_free_size(fd, file, blk_count);
+ if (ret < 0) {
+ if ((mode_flag & DETAIL) && ret == -ENOSPC) {
+ printf("\033[79;0H\033[K[%u/%u] \"%s\"\t\t"
+ " extents: %d -> %d\n", defraged_file_count,
+ total_count, file, extents, extents);
+ IN_FTW_PRINT_ERR_MSG(
+ "Defrag size is larger than filesystem's free space");
+ }
+ return -1;
+ }
+
+ /* Access authority */
+ if (current_uid != ROOT_UID &&
+ buf->st_uid != current_uid) {
+ if (mode_flag & DETAIL) {
+ printf("\033[79;0H\033[K[%u/%u] \"%s\"\t\t"
+ " extents: %d -> %d\n", defraged_file_count,
+ total_count, file, extents, extents);
+ IN_FTW_PRINT_ERR_MSG(
+ "File is not current user's file"
+ " or current user is not root");
+ }
+ return -1;
+ }
+
+ /* Lock status */
+ if (fcntl(fd, F_GETLK, &lock) < 0) {
+ if (mode_flag & DETAIL) {
+ PRINT_FILE_NAME(file);
+ PRINT_ERR_MSG_WITH_ERRNO(
+ "Failed to get lock information");
+ }
+ return -1;
+ } else if (lock.l_type != F_UNLCK) {
+ if (mode_flag & DETAIL) {
+ PRINT_FILE_NAME(file);
+ IN_FTW_PRINT_ERR_MSG("File has been locked");
+ }
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * insert_extent_by_logical() - Sequentially insert extent by logical.
+ *
+ * @ext_list_head: the head of logical extent list.
+ * @ext: the extent element which will be inserted.
+ */
+static int insert_extent_by_logical(struct fiemap_extent_list **ext_list_head,
+ struct fiemap_extent_list *ext)
+{
+ struct fiemap_extent_list *ext_list_tmp = *ext_list_head;
+
+ if (ext == NULL)
+ goto out;
+
+ /* First element */
+ if (*ext_list_head == NULL) {
+ (*ext_list_head) = ext;
+ (*ext_list_head)->prev = *ext_list_head;
+ (*ext_list_head)->next = *ext_list_head;
+ return 0;
+ }
+
+ if (ext->data.logical <= ext_list_tmp->data.logical) {
+ /* Insert before head */
+ if (ext_list_tmp->data.logical <
+ ext->data.logical + ext->data.len)
+ /* Overlap */
+ goto out;
+ /* Adjust head */
+ *ext_list_head = ext;
+ } else {
+ /* Insert into the middle or last of the list */
+ do {
+ if (ext->data.logical < ext_list_tmp->data.logical)
+ break;
+ ext_list_tmp = ext_list_tmp->next;
+ } while (ext_list_tmp != (*ext_list_head));
+ if (ext->data.logical <
+ ext_list_tmp->prev->data.logical +
+ ext_list_tmp->prev->data.len)
+ /* Overlap */
+ goto out;
+
+ if (ext_list_tmp != *ext_list_head &&
+ ext_list_tmp->data.logical <
+ ext->data.logical + ext->data.len)
+ /* Overlap */
+ goto out;
+ }
+ ext_list_tmp = ext_list_tmp->prev;
+ /* Insert "ext" after "ext_list_tmp" */
+ insert(ext_list_tmp, ext);
+ return 0;
+out:
+ errno = EINVAL;
+ return -1;
+}
+
+/*
+ * insert_extent_by_physical() - Sequentially insert extent by physical.
+ *
+ * @ext_list_head: the head of physical extent list.
+ * @ext: the extent element which will be inserted.
+ */
+static int insert_extent_by_physical(struct fiemap_extent_list **ext_list_head,
+ struct fiemap_extent_list *ext)
+{
+ struct fiemap_extent_list *ext_list_tmp = *ext_list_head;
+
+ if (ext == NULL)
+ goto out;
+
+ /* First element */
+ if (*ext_list_head == NULL) {
+ (*ext_list_head) = ext;
+ (*ext_list_head)->prev = *ext_list_head;
+ (*ext_list_head)->next = *ext_list_head;
+ return 0;
+ }
+
+ if (ext->data.physical <= ext_list_tmp->data.physical) {
+ /* Insert before head */
+ if (ext_list_tmp->data.physical <
+ ext->data.physical + ext->data.len)
+ /* Overlap */
+ goto out;
+ /* Adjust head */
+ *ext_list_head = ext;
+ } else {
+ /* Insert into the middle or last of the list */
+ do {
+ if (ext->data.physical < ext_list_tmp->data.physical)
+ break;
+ ext_list_tmp = ext_list_tmp->next;
+ } while (ext_list_tmp != (*ext_list_head));
+ if (ext->data.physical <
+ ext_list_tmp->prev->data.physical +
+ ext_list_tmp->prev->data.len)
+ /* Overlap */
+ goto out;
+
+ if (ext_list_tmp != *ext_list_head &&
+ ext_list_tmp->data.physical <
+ ext->data.physical + ext->data.len)
+ /* Overlap */
+ goto out;
+ }
+ ext_list_tmp = ext_list_tmp->prev;
+ /* Insert "ext" after "ext_list_tmp" */
+ insert(ext_list_tmp, ext);
+ return 0;
+out:
+ errno = EINVAL;
+ return -1;
+}
+
+/*
+ * insert_exts_group() - Insert a exts_group.
+ *
+ * @ext_group_head: the head of a exts_group list.
+ * @exts_group: the exts_group element which will be inserted.
+ */
+static int insert_exts_group(struct fiemap_extent_group **ext_group_head,
+ struct fiemap_extent_group *exts_group)
+{
+ struct fiemap_extent_group *ext_group_tmp = NULL;
+
+ if (exts_group == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* Initialize list */
+ if (*ext_group_head == NULL) {
+ (*ext_group_head) = exts_group;
+ (*ext_group_head)->prev = *ext_group_head;
+ (*ext_group_head)->next = *ext_group_head;
+ return 0;
+ }
+
+ ext_group_tmp = (*ext_group_head)->prev;
+ insert(ext_group_tmp, exts_group);
+
+ return 0;
+}
+
+/*
+ * join_extents() - Find continuous region(exts_group).
+ *
+ * @ext_list_head: the head of the extent list.
+ * @ext_group_head: the head of the target exts_group list.
+ */
+static int join_extents(struct fiemap_extent_list *ext_list_head,
+ struct fiemap_extent_group **ext_group_head)
+{
+ __u64 len = ext_list_head->data.len;
+ struct fiemap_extent_list *ext_list_start = ext_list_head;
+ struct fiemap_extent_list *ext_list_tmp = ext_list_head->next;
+
+ do {
+ struct fiemap_extent_group *ext_group_tmp = NULL;
+
+ /* This extent and previous extent are not continuous,
+ * so, all previous extents are treated as an extent group.
+ */
+ if ((ext_list_tmp->prev->data.logical +
+ ext_list_tmp->prev->data.len)
+ != ext_list_tmp->data.logical) {
+ ext_group_tmp =
+ malloc(sizeof(struct fiemap_extent_group));
+ if (ext_group_tmp == NULL)
+ return -1;
+
+ memset(ext_group_tmp, 0,
+ sizeof(struct fiemap_extent_group));
+ ext_group_tmp->len = len;
+ ext_group_tmp->start = ext_list_start;
+ ext_group_tmp->end = ext_list_tmp->prev;
+
+ if (insert_exts_group(ext_group_head,
+ ext_group_tmp) < 0) {
+ FREE(ext_group_tmp);
+ return -1;
+ }
+ ext_list_start = ext_list_tmp;
+ len = ext_list_tmp->data.len;
+ ext_list_tmp = ext_list_tmp->next;
+ continue;
+ }
+
+ /* This extent and previous extent are continuous,
+ * so, they belong to the same extent group, and we check
+ * if the next extent belongs to the same extent group.
+ */
+ len += ext_list_tmp->data.len;
+ ext_list_tmp = ext_list_tmp->next;
+ } while (ext_list_tmp != ext_list_head->next);
+
+ return 0;
+}
+
+/*
+ * get_file_extents() - Get file's extent list.
+ *
+ * @fd: defrag target file's descriptor.
+ * @ext_list_head: the head of the extent list.
+ */
+static int get_file_extents(int fd, struct fiemap_extent_list **ext_list_head)
+{
+ __u32 i;
+ int ret;
+ int ext_buf_size, fie_buf_size;
+ __u64 pos = 0;
+ struct fiemap *fiemap_buf = NULL;
+ struct fiemap_extent *ext_buf = NULL;
+ struct fiemap_extent_list *ext_list = NULL;
+
+ /* Convert units, in bytes.
+ * Be careful : now, physical block number in extent is 48bit,
+ * and the maximum blocksize for ext4 is 4K(12bit),
+ * so there is no overflow, but in future it may be changed.
+ */
+
+ /* Alloc space for fiemap */
+ ext_buf_size = EXTENT_MAX_COUNT * sizeof(struct fiemap_extent);
+ fie_buf_size = sizeof(struct fiemap) + ext_buf_size;
+
+ fiemap_buf = malloc(fie_buf_size);
+ if (fiemap_buf == NULL)
+ return -1;
+
+ ext_buf = fiemap_buf->fm_extents;
+ memset(fiemap_buf, 0, fie_buf_size);
+ fiemap_buf->fm_length = FIEMAP_MAX_OFFSET;
+ fiemap_buf->fm_flags |= FIEMAP_FLAG_SYNC;
+ fiemap_buf->fm_extent_count = EXTENT_MAX_COUNT;
+
+ do {
+ fiemap_buf->fm_start = pos;
+ memset(ext_buf, 0, ext_buf_size);
+ ret = ioctl(fd, FS_IOC_FIEMAP, fiemap_buf);
+ if (ret < 0 || fiemap_buf->fm_mapped_extents == 0)
+ goto out;
+ for (i = 0; i < fiemap_buf->fm_mapped_extents; i++) {
+ ext_list = NULL;
+ ext_list = malloc(sizeof(struct fiemap_extent_list));
+ if (ext_list == NULL)
+ goto out;
+
+ ext_list->data.physical = ext_buf[i].fe_physical
+ / block_size;
+ ext_list->data.logical = ext_buf[i].fe_logical
+ / block_size;
+ ext_list->data.len = ext_buf[i].fe_length
+ / block_size;
+
+ ret = insert_extent_by_physical(
+ ext_list_head, ext_list);
+ if (ret < 0) {
+ FREE(ext_list);
+ goto out;
+ }
+ }
+ /* Record file's logical offset this time */
+ pos = ext_buf[EXTENT_MAX_COUNT-1].fe_logical +
+ ext_buf[EXTENT_MAX_COUNT-1].fe_length;
+ /*
+ * If fm_extents array has been filled and
+ * there are extents left, continue to cycle.
+ */
+ } while (fiemap_buf->fm_mapped_extents
+ == EXTENT_MAX_COUNT &&
+ !(ext_buf[EXTENT_MAX_COUNT-1].fe_flags
+ & FIEMAP_EXTENT_LAST));
+
+ FREE(fiemap_buf);
+ return 0;
+out:
+ FREE(fiemap_buf);
+ return -1;
+}
+
+/*
+ * get_logical_count() - Get the file logical extents count.
+ *
+ * @logical_list_head: the head of the logical extent list.
+ */
+static int get_logical_count(struct fiemap_extent_list *logical_list_head)
+{
+ int ret = 0;
+ struct fiemap_extent_list *ext_list_tmp = logical_list_head;
+
+ do {
+ ret++;
+ ext_list_tmp = ext_list_tmp->next;
+ } while (ext_list_tmp != logical_list_head);
+
+ return ret;
+}
+
+/*
+ * get_physical_count() - Get the file physical extents count.
+ *
+ * @physical_list_head: the head of the physical extent list.
+ */
+static int get_physical_count(struct fiemap_extent_list *physical_list_head)
+{
+ int ret = 0;
+ struct fiemap_extent_list *ext_list_tmp = physical_list_head;
+
+ do {
+ if ((ext_list_tmp->data.physical + ext_list_tmp->data.len)
+ != ext_list_tmp->next->data.physical ||
+ (ext_list_tmp->data.logical + ext_list_tmp->data.len)
+ != ext_list_tmp->next->data.logical) {
+ /* This extent and next extent are not continuous. */
+ ret++;
+ }
+
+ ext_list_tmp = ext_list_tmp->next;
+ } while (ext_list_tmp != physical_list_head);
+
+ return ret;
+}
+
+/*
+ * change_physical_to_logical() - Change list from physical to logical.
+ *
+ * @physical_list_head: the head of physical extent list.
+ * @logical_list_head: the head of logical extent list.
+ */
+static int change_physical_to_logical(
+ struct fiemap_extent_list **physical_list_head,
+ struct fiemap_extent_list **logical_list_head)
+{
+ int ret;
+ struct fiemap_extent_list *ext_list_tmp = *physical_list_head;
+ struct fiemap_extent_list *ext_list_next = ext_list_tmp->next;
+
+ while (1) {
+ if (ext_list_tmp == ext_list_next) {
+ ret = insert_extent_by_logical(
+ logical_list_head, ext_list_tmp);
+ if (ret < 0)
+ return -1;
+
+ *physical_list_head = NULL;
+ break;
+ }
+
+ ext_list_tmp->prev->next = ext_list_tmp->next;
+ ext_list_tmp->next->prev = ext_list_tmp->prev;
+ *physical_list_head = ext_list_next;
+
+ ret = insert_extent_by_logical(
+ logical_list_head, ext_list_tmp);
+ if (ret < 0) {
+ FREE(ext_list_tmp);
+ return -1;
+ }
+ ext_list_tmp = ext_list_next;
+ ext_list_next = ext_list_next->next;
+ }
+
+ return 0;
+}
+
+/* get_file_blocks() - Get total file blocks.
+ *
+ * @ext_list_head: the extent list head of the target file
+ */
+static ext4_fsblk_t get_file_blocks(struct fiemap_extent_list *ext_list_head)
+{
+ ext4_fsblk_t blk_count = 0;
+ struct fiemap_extent_list *ext_list_tmp = ext_list_head;
+
+ do {
+ blk_count += ext_list_tmp->data.len;
+ ext_list_tmp = ext_list_tmp->next;
+ } while (ext_list_tmp != ext_list_head);
+
+ return blk_count;
+}
+
+/*
+ * free_ext() - Free the extent list.
+ *
+ * @ext_list_head: the extent list head of which will be free.
+ */
+static void free_ext(struct fiemap_extent_list *ext_list_head)
+{
+ struct fiemap_extent_list *ext_list_tmp = NULL;
+
+ if (ext_list_head == NULL)
+ return;
+
+ while (ext_list_head->next != ext_list_head) {
+ ext_list_tmp = ext_list_head;
+ ext_list_head->prev->next = ext_list_head->next;
+ ext_list_head->next->prev = ext_list_head->prev;
+ ext_list_head = ext_list_head->next;
+ free(ext_list_tmp);
+ }
+ free(ext_list_head);
+}
+
+/*
+ * free_exts_group() - Free the exts_group.
+ *
+ * @*ext_group_head: the exts_group list head which will be free.
+ */
+static void free_exts_group(struct fiemap_extent_group *ext_group_head)
+{
+ struct fiemap_extent_group *ext_group_tmp = NULL;
+
+ if (ext_group_head == NULL)
+ return;
+
+ while (ext_group_head->next != ext_group_head) {
+ ext_group_tmp = ext_group_head;
+ ext_group_head->prev->next = ext_group_head->next;
+ ext_group_head->next->prev = ext_group_head->prev;
+ ext_group_head = ext_group_head->next;
+ free(ext_group_tmp);
+ }
+ free(ext_group_head);
+}
+
+/*
+ * get_best_count() - Get the file best extents count.
+ *
+ * @block_count: the file's physical block count.
+ */
+static int get_best_count(ext4_fsblk_t block_count)
+{
+ int ret;
+ unsigned int flex_bg_num;
+
+ if (blocks_per_group == 0)
+ return 1;
+
+ if (feature_incompat & EXT4_FEATURE_INCOMPAT_FLEX_BG) {
+ flex_bg_num = 1U << log_groups_per_flex;
+ ret = ((block_count - 1) /
+ ((ext4_fsblk_t)blocks_per_group *
+ flex_bg_num)) + 1;
+ } else
+ ret = ((block_count - 1) / blocks_per_group) + 1;
+
+ return ret;
+}
+
+
+/*
+ * file_statistic() - Get statistic info of the file's fragments.
+ *
+ * @file: the file's name.
+ * @buf: the pointer of the struct stat64.
+ * @flag: file type.
+ * @ftwbuf: the pointer of a struct FTW.
+ */
+static int file_statistic(const char *file, const struct stat64 *buf,
+ int flag EXT2FS_ATTR((unused)),
+ struct FTW *ftwbuf EXT2FS_ATTR((unused)))
+{
+ int fd;
+ int ret;
+ int now_ext_count, best_ext_count = 0, physical_ext_count;
+ int i, j;
+ __u64 size_per_ext = 0;
+ float ratio = 0.0;
+ ext4_fsblk_t blk_count = 0;
+ char msg_buffer[PATH_MAX + 48];
+ struct fiemap_extent_list *physical_list_head = NULL;
+ struct fiemap_extent_list *logical_list_head = NULL;
+
+ defraged_file_count++;
+ if (defraged_file_count > total_count)
+ total_count = defraged_file_count;
+
+ if (mode_flag & DETAIL) {
+ if (total_count == 1 && regular_count == 1)
+ printf("<File>\n");
+ else {
+ printf("[%u/%u]", defraged_file_count, total_count);
+ fflush(stdout);
+ }
+ }
+ if (lost_found_dir[0] != '\0' &&
+ !memcmp(file, lost_found_dir, strnlen(lost_found_dir, PATH_MAX))) {
+ if (mode_flag & DETAIL) {
+ PRINT_FILE_NAME(file);
+ STATISTIC_ERR_MSG(NGMSG_LOST_FOUND);
+ }
+ return 0;
+ }
+
+ if (!S_ISREG(buf->st_mode)) {
+ if (mode_flag & DETAIL) {
+ PRINT_FILE_NAME(file);
+ STATISTIC_ERR_MSG(NGMSG_FILE_UNREG);
+ }
+ return 0;
+ }
+
+ /* Access authority */
+ if (current_uid != ROOT_UID &&
+ buf->st_uid != current_uid) {
+ if (mode_flag & DETAIL) {
+ PRINT_FILE_NAME(file);
+ STATISTIC_ERR_MSG(
+ "File is not current user's file"
+ " or current user is not root");
+ }
+ return 0;
+ }
+
+ /* Empty file */
+ if (buf->st_size == 0) {
+ if (mode_flag & DETAIL) {
+ PRINT_FILE_NAME(file);
+ STATISTIC_ERR_MSG("File size is 0");
+ }
+ return 0;
+ }
+
+ /* Has no blocks */
+ if (buf->st_blocks == 0) {
+ if (mode_flag & DETAIL) {
+ PRINT_FILE_NAME(file);
+ STATISTIC_ERR_MSG("File has no blocks");
+ }
+ return 0;
+ }
+
+ fd = open64(file, O_RDONLY);
+ if (fd < 0) {
+ if (mode_flag & DETAIL) {
+ PRINT_FILE_NAME(file);
+ STATISTIC_ERR_MSG_WITH_ERRNO(NGMSG_FILE_OPEN);
+ }
+ return 0;
+ }
+
+ /* Get file's physical extents */
+ ret = get_file_extents(fd, &physical_list_head);
+ if (ret < 0) {
+ if (mode_flag & DETAIL) {
+ PRINT_FILE_NAME(file);
+ STATISTIC_ERR_MSG_WITH_ERRNO(NGMSG_FILE_EXTENT);
+ }
+ goto out;
+ }
+
+ /* Get the count of file's continuous physical region */
+ physical_ext_count = get_physical_count(physical_list_head);
+
+ /* Change list from physical to logical */
+ ret = change_physical_to_logical(&physical_list_head,
+ &logical_list_head);
+ if (ret < 0) {
+ if (mode_flag & DETAIL) {
+ PRINT_FILE_NAME(file);
+ STATISTIC_ERR_MSG_WITH_ERRNO(NGMSG_FILE_EXTENT);
+ }
+ goto out;
+ }
+
+ /* Count file fragments before defrag */
+ now_ext_count = get_logical_count(logical_list_head);
+
+ if (current_uid == ROOT_UID) {
+ /* Calculate the size per extent */
+ blk_count = get_file_blocks(logical_list_head);
+
+ best_ext_count = get_best_count(blk_count);
+
+ /* e4defrag rounds size_per_ext up to a block size boundary */
+ size_per_ext = blk_count * (buf->st_blksize / 1024) /
+ now_ext_count;
+
+ ratio = (float)(physical_ext_count - best_ext_count) * 100 /
+ blk_count;
+
+ extents_before_defrag += now_ext_count;
+ extents_after_defrag += best_ext_count;
+ files_block_count += blk_count;
+ }
+
+ if (total_count == 1 && regular_count == 1) {
+ /* File only */
+ if (mode_flag & DETAIL) {
+ int count = 0;
+ struct fiemap_extent_list *ext_list_tmp =
+ logical_list_head;
+
+ /* Print extents info */
+ do {
+ count++;
+ printf("[ext %d]:\tstart %llu:\tlogical "
+ "%llu:\tlen %llu\n", count,
+ (unsigned long long)
+ ext_list_tmp->data.physical,
+ (unsigned long long)
+ ext_list_tmp->data.logical,
+ (unsigned long long)
+ ext_list_tmp->data.len);
+ ext_list_tmp = ext_list_tmp->next;
+ } while (ext_list_tmp != logical_list_head);
+
+ } else {
+ printf("%-40s%10s/%-10s%9s\n",
+ "<File>", "now", "best", "size/ext");
+ if (current_uid == ROOT_UID) {
+ if (strlen(file) > 40)
+ printf("%s\n%50d/%-10d%6llu KB\n",
+ file, now_ext_count,
+ best_ext_count,
+ (unsigned long long) size_per_ext);
+ else
+ printf("%-40s%10d/%-10d%6llu KB\n",
+ file, now_ext_count,
+ best_ext_count,
+ (unsigned long long) size_per_ext);
+ } else {
+ if (strlen(file) > 40)
+ printf("%s\n%50d/%-10s%7s\n",
+ file, now_ext_count,
+ "-", "-");
+ else
+ printf("%-40s%10d/%-10s%7s\n",
+ file, now_ext_count,
+ "-", "-");
+ }
+ }
+ succeed_cnt++;
+ goto out;
+ }
+
+ if (mode_flag & DETAIL) {
+ /* Print statistic info */
+ sprintf(msg_buffer, "[%u/%u]%.*s",
+ defraged_file_count, total_count,
+ PATH_MAX, file);
+ if (current_uid == ROOT_UID) {
+ if (strlen(msg_buffer) > 40)
+ printf("\033[79;0H\033[K%s\n"
+ "%50d/%-10d%6llu KB\n",
+ msg_buffer, now_ext_count,
+ best_ext_count,
+ (unsigned long long) size_per_ext);
+ else
+ printf("\033[79;0H\033[K%-40s"
+ "%10d/%-10d%6llu KB\n",
+ msg_buffer, now_ext_count,
+ best_ext_count,
+ (unsigned long long) size_per_ext);
+ } else {
+ if (strlen(msg_buffer) > 40)
+ printf("\033[79;0H\033[K%s\n%50d/%-10s%7s\n",
+ msg_buffer, now_ext_count,
+ "-", "-");
+ else
+ printf("\033[79;0H\033[K%-40s%10d/%-10s%7s\n",
+ msg_buffer, now_ext_count,
+ "-", "-");
+ }
+ }
+
+ for (i = 0; i < SHOW_FRAG_FILES; i++) {
+ if (ratio >= frag_rank[i].ratio) {
+ for (j = SHOW_FRAG_FILES - 1; j > i; j--) {
+ memset(&frag_rank[j], 0,
+ sizeof(struct frag_statistic_ino));
+ strncpy(frag_rank[j].msg_buffer,
+ frag_rank[j - 1].msg_buffer,
+ strnlen(frag_rank[j - 1].msg_buffer,
+ PATH_MAX));
+ frag_rank[j].now_count =
+ frag_rank[j - 1].now_count;
+ frag_rank[j].best_count =
+ frag_rank[j - 1].best_count;
+ frag_rank[j].size_per_ext =
+ frag_rank[j - 1].size_per_ext;
+ frag_rank[j].ratio =
+ frag_rank[j - 1].ratio;
+ }
+ memset(&frag_rank[i], 0,
+ sizeof(struct frag_statistic_ino));
+ strncpy(frag_rank[i].msg_buffer, file,
+ strnlen(file, PATH_MAX));
+ frag_rank[i].now_count = now_ext_count;
+ frag_rank[i].best_count = best_ext_count;
+ frag_rank[i].size_per_ext = size_per_ext;
+ frag_rank[i].ratio = ratio;
+ break;
+ }
+ }
+
+ succeed_cnt++;
+
+out:
+ close(fd);
+ free_ext(physical_list_head);
+ free_ext(logical_list_head);
+ return 0;
+}
+
+/*
+ * print_progress - Print defrag progress
+ *
+ * @file: file name.
+ * @start: logical offset for defrag target file
+ * @file_size: defrag target filesize
+ */
+static void print_progress(const char *file, ext2_loff_t start,
+ ext2_loff_t file_size)
+{
+ int percent = (start * 100) / file_size;
+ printf("\033[79;0H\033[K[%u/%u]%s:\t%3d%%",
+ defraged_file_count, total_count, file, min(percent, 100));
+ fflush(stdout);
+
+ return;
+}
+
+/*
+ * call_defrag() - Execute the defrag program.
+ *
+ * @fd: target file descriptor.
+ * @donor_fd: donor file descriptor.
+ * @file: target file name.
+ * @buf: pointer of the struct stat64.
+ * @ext_list_head: head of the extent list.
+ */
+static int call_defrag(int fd, int donor_fd, const char *file,
+ const struct stat64 *buf, struct fiemap_extent_list *ext_list_head)
+{
+ ext2_loff_t start = 0;
+ unsigned int page_num;
+ unsigned char *vec = NULL;
+ int defraged_ret = 0;
+ int ret;
+ struct move_extent move_data;
+ struct fiemap_extent_list *ext_list_tmp = NULL;
+
+ memset(&move_data, 0, sizeof(struct move_extent));
+ move_data.donor_fd = donor_fd;
+
+ /* Print defrag progress */
+ print_progress(file, start, buf->st_size);
+
+ ext_list_tmp = ext_list_head;
+ do {
+ move_data.orig_start = ext_list_tmp->data.logical;
+ /* Logical offset of orig and donor should be same */
+ move_data.donor_start = move_data.orig_start;
+ move_data.len = ext_list_tmp->data.len;
+ move_data.moved_len = 0;
+
+ ret = page_in_core(fd, move_data, &vec, &page_num);
+ if (ret < 0) {
+ if (mode_flag & DETAIL) {
+ printf("\n");
+ PRINT_ERR_MSG_WITH_ERRNO(
+ "Failed to get file map");
+ } else {
+ printf("\t[ NG ]\n");
+ }
+ return -1;
+ }
+
+ /* EXT4_IOC_MOVE_EXT */
+ defraged_ret =
+ ioctl(fd, EXT4_IOC_MOVE_EXT, &move_data);
+
+ /* Free pages */
+ ret = defrag_fadvise(fd, move_data, vec, page_num);
+ if (vec) {
+ free(vec);
+ vec = NULL;
+ }
+ if (ret < 0) {
+ if (mode_flag & DETAIL) {
+ printf("\n");
+ PRINT_ERR_MSG_WITH_ERRNO(
+ "Failed to free page");
+ } else {
+ printf("\t[ NG ]\n");
+ }
+ return -1;
+ }
+
+ if (defraged_ret < 0) {
+ if (mode_flag & DETAIL) {
+ printf("\n");
+ PRINT_ERR_MSG_WITH_ERRNO(
+ "Failed to defrag with "
+ "EXT4_IOC_MOVE_EXT ioctl");
+ if (errno == ENOTTY)
+ printf("\tAt least 2.6.31-rc1 of "
+ "vanilla kernel is required\n");
+ } else {
+ printf("\t[ NG ]\n");
+ }
+ return -1;
+ }
+ /* Adjust logical offset for next ioctl */
+ move_data.orig_start += move_data.moved_len;
+ move_data.donor_start = move_data.orig_start;
+
+ start = move_data.orig_start * buf->st_blksize;
+
+ /* Print defrag progress */
+ print_progress(file, start, buf->st_size);
+
+ /* End of file */
+ if (start >= buf->st_size)
+ break;
+
+ ext_list_tmp = ext_list_tmp->next;
+ } while (ext_list_tmp != ext_list_head);
+
+ return 0;
+}
+
+/*
+ * file_defrag() - Check file attributes and call ioctl to defrag.
+ *
+ * @file: the file's name.
+ * @buf: the pointer of the struct stat64.
+ * @flag: file type.
+ * @ftwbuf: the pointer of a struct FTW.
+ */
+static int file_defrag(const char *file, const struct stat64 *buf,
+ int flag EXT2FS_ATTR((unused)),
+ struct FTW *ftwbuf EXT2FS_ATTR((unused)))
+{
+ int fd;
+ int donor_fd = -1;
+ int ret;
+ int best;
+ int file_frags_start, file_frags_end;
+ int orig_physical_cnt, donor_physical_cnt = 0;
+ char tmp_inode_name[PATH_MAX + 8];
+ ext4_fsblk_t blk_count = 0;
+ struct fiemap_extent_list *orig_list_physical = NULL;
+ struct fiemap_extent_list *orig_list_logical = NULL;
+ struct fiemap_extent_list *donor_list_physical = NULL;
+ struct fiemap_extent_list *donor_list_logical = NULL;
+ struct fiemap_extent_group *orig_group_head = NULL;
+ struct fiemap_extent_group *orig_group_tmp = NULL;
+
+ defraged_file_count++;
+ if (defraged_file_count > total_count)
+ total_count = defraged_file_count;
+
+ if (mode_flag & DETAIL) {
+ printf("[%u/%u]", defraged_file_count, total_count);
+ fflush(stdout);
+ }
+
+ if (lost_found_dir[0] != '\0' &&
+ !memcmp(file, lost_found_dir, strnlen(lost_found_dir, PATH_MAX))) {
+ if (mode_flag & DETAIL) {
+ PRINT_FILE_NAME(file);
+ IN_FTW_PRINT_ERR_MSG(NGMSG_LOST_FOUND);
+ }
+ return 0;
+ }
+
+ if (!S_ISREG(buf->st_mode)) {
+ if (mode_flag & DETAIL) {
+ PRINT_FILE_NAME(file);
+ IN_FTW_PRINT_ERR_MSG(NGMSG_FILE_UNREG);
+ }
+ return 0;
+ }
+
+ /* Empty file */
+ if (buf->st_size == 0) {
+ if (mode_flag & DETAIL) {
+ PRINT_FILE_NAME(file);
+ IN_FTW_PRINT_ERR_MSG("File size is 0");
+ }
+ return 0;
+ }
+
+ /* Has no blocks */
+ if (buf->st_blocks == 0) {
+ if (mode_flag & DETAIL) {
+ PRINT_FILE_NAME(file);
+ STATISTIC_ERR_MSG("File has no blocks");
+ }
+ return 0;
+ }
+
+ fd = open64(file, O_RDWR);
+ if (fd < 0) {
+ if (mode_flag & DETAIL) {
+ PRINT_FILE_NAME(file);
+ PRINT_ERR_MSG_WITH_ERRNO(NGMSG_FILE_OPEN);
+ }
+ return 0;
+ }
+
+ /* Get file's extents */
+ ret = get_file_extents(fd, &orig_list_physical);
+ if (ret < 0) {
+ if (mode_flag & DETAIL) {
+ PRINT_FILE_NAME(file);
+ PRINT_ERR_MSG_WITH_ERRNO(NGMSG_FILE_EXTENT);
+ }
+ goto out;
+ }
+
+ /* Get the count of file's continuous physical region */
+ orig_physical_cnt = get_physical_count(orig_list_physical);
+
+ /* Change list from physical to logical */
+ ret = change_physical_to_logical(&orig_list_physical,
+ &orig_list_logical);
+ if (ret < 0) {
+ if (mode_flag & DETAIL) {
+ PRINT_FILE_NAME(file);
+ PRINT_ERR_MSG_WITH_ERRNO(NGMSG_FILE_EXTENT);
+ }
+ goto out;
+ }
+
+ /* Count file fragments before defrag */
+ file_frags_start = get_logical_count(orig_list_logical);
+
+ blk_count = get_file_blocks(orig_list_logical);
+ if (file_check(fd, buf, file, file_frags_start, blk_count) < 0)
+ goto out;
+
+ if (fsync(fd) < 0) {
+ if (mode_flag & DETAIL) {
+ PRINT_FILE_NAME(file);
+ PRINT_ERR_MSG_WITH_ERRNO("Failed to sync(fsync)");
+ }
+ goto out;
+ }
+
+ best = get_best_count(blk_count);
+
+ if (file_frags_start <= best)
+ goto check_improvement;
+
+ /* Combine extents to group */
+ ret = join_extents(orig_list_logical, &orig_group_head);
+ if (ret < 0) {
+ if (mode_flag & DETAIL) {
+ PRINT_FILE_NAME(file);
+ PRINT_ERR_MSG_WITH_ERRNO(NGMSG_FILE_EXTENT);
+ }
+ goto out;
+ }
+
+ /* Create donor inode */
+ memset(tmp_inode_name, 0, PATH_MAX + 8);
+ sprintf(tmp_inode_name, "%.*s.defrag",
+ (int)strnlen(file, PATH_MAX), file);
+ donor_fd = open64(tmp_inode_name, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR);
+ if (donor_fd < 0) {
+ if (mode_flag & DETAIL) {
+ PRINT_FILE_NAME(file);
+ if (errno == EEXIST)
+ PRINT_ERR_MSG_WITH_ERRNO(
+ "File is being defraged by other program");
+ else
+ PRINT_ERR_MSG_WITH_ERRNO(NGMSG_FILE_OPEN);
+ }
+ goto out;
+ }
+
+ /* Unlink donor inode */
+ ret = unlink(tmp_inode_name);
+ if (ret < 0) {
+ if (mode_flag & DETAIL) {
+ PRINT_FILE_NAME(file);
+ PRINT_ERR_MSG_WITH_ERRNO("Failed to unlink");
+ }
+ goto out;
+ }
+
+ /* Allocate space for donor inode */
+ orig_group_tmp = orig_group_head;
+ do {
+ ret = fallocate(donor_fd, 0,
+ (ext2_loff_t)orig_group_tmp->start->data.logical * block_size,
+ (ext2_loff_t)orig_group_tmp->len * block_size);
+ if (ret < 0) {
+ if (mode_flag & DETAIL) {
+ PRINT_FILE_NAME(file);
+ PRINT_ERR_MSG_WITH_ERRNO("Failed to fallocate");
+ }
+ goto out;
+ }
+
+ orig_group_tmp = orig_group_tmp->next;
+ } while (orig_group_tmp != orig_group_head);
+
+ /* Get donor inode's extents */
+ ret = get_file_extents(donor_fd, &donor_list_physical);
+ if (ret < 0) {
+ if (mode_flag & DETAIL) {
+ PRINT_FILE_NAME(file);
+ PRINT_ERR_MSG_WITH_ERRNO(NGMSG_FILE_EXTENT);
+ }
+ goto out;
+ }
+
+ /* Calculate donor inode's continuous physical region */
+ donor_physical_cnt = get_physical_count(donor_list_physical);
+
+ /* Change donor extent list from physical to logical */
+ ret = change_physical_to_logical(&donor_list_physical,
+ &donor_list_logical);
+ if (ret < 0) {
+ if (mode_flag & DETAIL) {
+ PRINT_FILE_NAME(file);
+ PRINT_ERR_MSG_WITH_ERRNO(NGMSG_FILE_EXTENT);
+ }
+ goto out;
+ }
+
+check_improvement:
+ if (mode_flag & DETAIL) {
+ if (file_frags_start != 1)
+ frag_files_before_defrag++;
+
+ extents_before_defrag += file_frags_start;
+ }
+
+ if (file_frags_start <= best ||
+ orig_physical_cnt <= donor_physical_cnt) {
+ printf("\033[79;0H\033[K[%u/%u]%s:\t%3d%%",
+ defraged_file_count, total_count, file, 100);
+ if (mode_flag & DETAIL)
+ printf(" extents: %d -> %d",
+ file_frags_start, file_frags_start);
+
+ printf("\t[ OK ]\n");
+ succeed_cnt++;
+
+ if (file_frags_start != 1)
+ frag_files_after_defrag++;
+
+ extents_after_defrag += file_frags_start;
+ goto out;
+ }
+
+ /* Defrag the file */
+ ret = call_defrag(fd, donor_fd, file, buf, donor_list_logical);
+
+ /* Count file fragments after defrag and print extents info */
+ if (mode_flag & DETAIL) {
+ file_frags_end = file_frag_count(fd);
+ if (file_frags_end < 0) {
+ printf("\n");
+ PRINT_ERR_MSG_WITH_ERRNO(NGMSG_FILE_INFO);
+ goto out;
+ }
+
+ if (file_frags_end != 1)
+ frag_files_after_defrag++;
+
+ extents_after_defrag += file_frags_end;
+
+ if (ret < 0)
+ goto out;
+
+ printf(" extents: %d -> %d",
+ file_frags_start, file_frags_end);
+ fflush(stdout);
+ }
+
+ if (ret < 0)
+ goto out;
+
+ printf("\t[ OK ]\n");
+ fflush(stdout);
+ succeed_cnt++;
+
+out:
+ close(fd);
+ if (donor_fd != -1)
+ close(donor_fd);
+ free_ext(orig_list_physical);
+ free_ext(orig_list_logical);
+ free_ext(donor_list_physical);
+ free_exts_group(orig_group_head);
+ return 0;
+}
+
+/*
+ * main() - Ext4 online defrag.
+ *
+ * @argc: the number of parameter.
+ * @argv[]: the pointer array of parameter.
+ */
+int main(int argc, char *argv[])
+{
+ int opt;
+ int i, j, ret = 0;
+ int flags = FTW_PHYS | FTW_MOUNT;
+ int arg_type = -1;
+ int mount_dir_len = 0;
+ int success_flag = 0;
+ char dir_name[PATH_MAX + 1];
+ char dev_name[PATH_MAX + 1];
+ struct stat64 buf;
+ ext2_filsys fs = NULL;
+
+ printf("e4defrag %s (%s)\n", E2FSPROGS_VERSION, E2FSPROGS_DATE);
+
+ /* Parse arguments */
+ if (argc == 1)
+ goto out;
+
+ while ((opt = getopt(argc, argv, "vc")) != EOF) {
+ switch (opt) {
+ case 'v':
+ mode_flag |= DETAIL;
+ break;
+ case 'c':
+ mode_flag |= STATISTIC;
+ break;
+ default:
+ goto out;
+ }
+ }
+
+ if (argc == optind)
+ goto out;
+
+ current_uid = getuid();
+
+ /* Main process */
+ for (i = optind; i < argc; i++) {
+ succeed_cnt = 0;
+ regular_count = 0;
+ total_count = 0;
+ frag_files_before_defrag = 0;
+ frag_files_after_defrag = 0;
+ extents_before_defrag = 0;
+ extents_after_defrag = 0;
+ defraged_file_count = 0;
+ files_block_count = 0;
+ blocks_per_group = 0;
+ feature_incompat = 0;
+ log_groups_per_flex = 0;
+
+ memset(dir_name, 0, PATH_MAX + 1);
+ memset(dev_name, 0, PATH_MAX + 1);
+ memset(lost_found_dir, 0, PATH_MAX + 1);
+ memset(frag_rank, 0,
+ sizeof(struct frag_statistic_ino) * SHOW_FRAG_FILES);
+
+ if ((mode_flag & STATISTIC) && i > optind)
+ printf("\n");
+
+#if BYTE_ORDER != BIG_ENDIAN && BYTE_ORDER != LITTLE_ENDIAN
+ PRINT_ERR_MSG("Endian's type is not big/little endian");
+ PRINT_FILE_NAME(argv[i]);
+ continue;
+#endif
+
+ if (lstat64(argv[i], &buf) < 0) {
+ perror(NGMSG_FILE_INFO);
+ PRINT_FILE_NAME(argv[i]);
+ continue;
+ }
+
+ /* Handle i.e. lvm device symlinks */
+ if (S_ISLNK(buf.st_mode)) {
+ struct stat64 buf2;
+
+ if (stat64(argv[i], &buf2) == 0 &&
+ S_ISBLK(buf2.st_mode))
+ buf = buf2;
+ }
+
+ if (S_ISBLK(buf.st_mode)) {
+ /* Block device */
+ strncpy(dev_name, argv[i], strnlen(argv[i], PATH_MAX));
+ if (get_mount_point(argv[i], dir_name, PATH_MAX) < 0)
+ continue;
+ if (lstat64(dir_name, &buf) < 0) {
+ perror(NGMSG_FILE_INFO);
+ PRINT_FILE_NAME(argv[i]);
+ continue;
+ }
+ arg_type = DEVNAME;
+ if (!(mode_flag & STATISTIC))
+ printf("ext4 defragmentation for device(%s)\n",
+ argv[i]);
+ } else if (S_ISDIR(buf.st_mode)) {
+ /* Directory */
+ if (access(argv[i], R_OK) < 0) {
+ perror(argv[i]);
+ continue;
+ }
+ arg_type = DIRNAME;
+ strncpy(dir_name, argv[i], strnlen(argv[i], PATH_MAX));
+ } else if (S_ISREG(buf.st_mode)) {
+ /* Regular file */
+ arg_type = FILENAME;
+ } else {
+ /* Irregular file */
+ PRINT_ERR_MSG(NGMSG_FILE_UNREG);
+ PRINT_FILE_NAME(argv[i]);
+ continue;
+ }
+
+ /* Set blocksize */
+ block_size = buf.st_blksize;
+
+ /* For device case,
+ * filesystem type checked in get_mount_point()
+ */
+ if (arg_type == FILENAME || arg_type == DIRNAME) {
+ if (is_ext4(argv[i], dev_name) < 0)
+ continue;
+ if (realpath(argv[i], dir_name) == NULL) {
+ perror("Couldn't get full path");
+ PRINT_FILE_NAME(argv[i]);
+ continue;
+ }
+ }
+
+ if (current_uid == ROOT_UID) {
+ /* Get super block info */
+ ret = ext2fs_open(dev_name, EXT2_FLAG_64BITS, 0,
+ block_size, unix_io_manager, &fs);
+ if (ret) {
+ if (mode_flag & DETAIL)
+ fprintf(stderr,
+ "Warning: couldn't get file "
+ "system details for %s: %s\n",
+ dev_name, error_message(ret));
+ } else {
+ blocks_per_group = fs->super->s_blocks_per_group;
+ feature_incompat = fs->super->s_feature_incompat;
+ log_groups_per_flex = fs->super->s_log_groups_per_flex;
+ ext2fs_close_free(&fs);
+ }
+ }
+
+ switch (arg_type) {
+
+ case DIRNAME:
+ if (!(mode_flag & STATISTIC))
+ printf("ext4 defragmentation "
+ "for directory(%s)\n", argv[i]);
+
+ mount_dir_len = strnlen(lost_found_dir, PATH_MAX);
+
+ strncat(lost_found_dir, "/lost+found",
+ PATH_MAX - strnlen(lost_found_dir, PATH_MAX));
+
+ /* Not the case("e4defrag mount_point_dir") */
+ if (dir_name[mount_dir_len] != '\0') {
+ /*
+ * "e4defrag mount_point_dir/lost+found"
+ * or "e4defrag mount_point_dir/lost+found/"
+ */
+ if (strncmp(lost_found_dir, dir_name,
+ strnlen(lost_found_dir,
+ PATH_MAX)) == 0 &&
+ (dir_name[strnlen(lost_found_dir,
+ PATH_MAX)] == '\0' ||
+ dir_name[strnlen(lost_found_dir,
+ PATH_MAX)] == '/')) {
+ PRINT_ERR_MSG(NGMSG_LOST_FOUND);
+ PRINT_FILE_NAME(argv[i]);
+ continue;
+ }
+
+ /* "e4defrag mount_point_dir/else_dir" */
+ memset(lost_found_dir, 0, PATH_MAX + 1);
+ }
+ /* fall through */
+ case DEVNAME:
+ if (arg_type == DEVNAME) {
+ strcpy(lost_found_dir, dir_name);
+ strncat(lost_found_dir, "/lost+found/",
+ PATH_MAX - strlen(lost_found_dir));
+ }
+
+ nftw64(dir_name, calc_entry_counts, FTW_OPEN_FD, flags);
+
+ if (mode_flag & STATISTIC) {
+ if (mode_flag & DETAIL)
+ printf("%-40s%10s/%-10s%9s\n",
+ "<File>", "now", "best", "size/ext");
+
+ if (!(mode_flag & DETAIL) &&
+ current_uid != ROOT_UID) {
+ printf(" Done.\n");
+ success_flag = 1;
+ continue;
+ }
+
+ nftw64(dir_name, file_statistic,
+ FTW_OPEN_FD, flags);
+
+ if (succeed_cnt != 0 &&
+ current_uid == ROOT_UID) {
+ if (mode_flag & DETAIL)
+ printf("\n");
+ printf("%-40s%10s/%-10s%9s\n",
+ "<Fragmented files>", "now",
+ "best", "size/ext");
+ for (j = 0; j < SHOW_FRAG_FILES; j++) {
+ if (strlen(frag_rank[j].
+ msg_buffer) > 37) {
+ printf("%d. %s\n%50d/"
+ "%-10d%6llu KB\n",
+ j + 1,
+ frag_rank[j].msg_buffer,
+ frag_rank[j].now_count,
+ frag_rank[j].best_count,
+ (unsigned long long)
+ frag_rank[j].
+ size_per_ext);
+ } else if (strlen(frag_rank[j].
+ msg_buffer) > 0) {
+ printf("%d. %-37s%10d/"
+ "%-10d%6llu KB\n",
+ j + 1,
+ frag_rank[j].msg_buffer,
+ frag_rank[j].now_count,
+ frag_rank[j].best_count,
+ (unsigned long long)
+ frag_rank[j].
+ size_per_ext);
+ } else
+ break;
+ }
+ }
+ break;
+ }
+ /* File tree walk */
+ nftw64(dir_name, file_defrag, FTW_OPEN_FD, flags);
+ printf("\n\tSuccess:\t\t\t[ %u/%u ]\n", succeed_cnt,
+ total_count);
+ printf("\tFailure:\t\t\t[ %u/%u ]\n",
+ total_count - succeed_cnt, total_count);
+ if (mode_flag & DETAIL) {
+ printf("\tTotal extents:\t\t\t%4d->%d\n",
+ extents_before_defrag,
+ extents_after_defrag);
+ printf("\tFragmented percentage:\t\t"
+ "%3llu%%->%llu%%\n",
+ !regular_count ? 0 :
+ ((unsigned long long)
+ frag_files_before_defrag * 100) /
+ regular_count,
+ !regular_count ? 0 :
+ ((unsigned long long)
+ frag_files_after_defrag * 100) /
+ regular_count);
+ }
+ break;
+ case FILENAME:
+ total_count = 1;
+ regular_count = 1;
+ strncat(lost_found_dir, "/lost+found/",
+ PATH_MAX - strnlen(lost_found_dir,
+ PATH_MAX));
+ if (strncmp(lost_found_dir, dir_name,
+ strnlen(lost_found_dir,
+ PATH_MAX)) == 0) {
+ PRINT_ERR_MSG(NGMSG_LOST_FOUND);
+ PRINT_FILE_NAME(argv[i]);
+ continue;
+ }
+
+ if (mode_flag & STATISTIC) {
+ file_statistic(argv[i], &buf, FTW_F, NULL);
+ break;
+ } else
+ printf("ext4 defragmentation for %s\n",
+ argv[i]);
+ /* Defrag single file process */
+ file_defrag(argv[i], &buf, FTW_F, NULL);
+ if (succeed_cnt != 0)
+ printf(" Success:\t\t\t[1/1]\n");
+ else
+ printf(" Success:\t\t\t[0/1]\n");
+
+ break;
+ }
+
+ if (succeed_cnt != 0)
+ success_flag = 1;
+ if (mode_flag & STATISTIC) {
+ if (current_uid != ROOT_UID) {
+ printf(" Done.\n");
+ continue;
+ }
+
+ if (!succeed_cnt) {
+ if (mode_flag & DETAIL)
+ printf("\n");
+
+ if (arg_type == DEVNAME)
+ printf(" In this device(%s), "
+ "none can be defragmented.\n", argv[i]);
+ else if (arg_type == DIRNAME)
+ printf(" In this directory(%s), "
+ "none can be defragmented.\n", argv[i]);
+ else
+ printf(" This file(%s) "
+ "can't be defragmented.\n", argv[i]);
+ } else {
+ float files_ratio = 0.0;
+ float score = 0.0;
+ __u64 size_per_ext = files_block_count *
+ (buf.st_blksize / 1024) /
+ extents_before_defrag;
+ files_ratio = (float)(extents_before_defrag -
+ extents_after_defrag) *
+ 100 / files_block_count;
+ score = CALC_SCORE(files_ratio);
+ printf("\n Total/best extents\t\t\t\t%d/%d\n"
+ " Average size per extent"
+ "\t\t\t%llu KB\n"
+ " Fragmentation score\t\t\t\t%.0f\n",
+ extents_before_defrag,
+ extents_after_defrag,
+ (unsigned long long) size_per_ext, score);
+ printf(" [0-30 no problem:"
+ " 31-55 a little bit fragmented:"
+ " 56- needs defrag]\n");
+
+ if (arg_type == DEVNAME)
+ printf(" This device (%s) ", argv[i]);
+ else if (arg_type == DIRNAME)
+ printf(" This directory (%s) ",
+ argv[i]);
+ else
+ printf(" This file (%s) ", argv[i]);
+
+ if (score > BOUND_SCORE)
+ printf("needs defragmentation.\n");
+ else
+ printf("does not need "
+ "defragmentation.\n");
+ }
+ printf(" Done.\n");
+ }
+
+ }
+
+ if (success_flag)
+ return 0;
+
+ exit(1);
+
+out:
+ printf(MSG_USAGE);
+ exit(1);
+}
+
diff --git a/misc/ext4.5.in b/misc/ext4.5.in
new file mode 100644
index 0000000..c835a34
--- /dev/null
+++ b/misc/ext4.5.in
@@ -0,0 +1,815 @@
+.\" -*- nroff -*-
+.\" Copyright 1993, 1994, 1995 by Theodore Ts'o. All Rights Reserved.
+.\" This file may be copied under the terms of the GNU Public License.
+.\"
+.TH EXT4 5 "@E2FSPROGS_MONTH@ @E2FSPROGS_YEAR@" "E2fsprogs version @E2FSPROGS_VERSION@"
+.SH NAME
+ext2 \- the second extended file system
+.br
+ext3 \- the third extended file system
+.br
+ext4 \- the fourth extended file system
+.SH DESCRIPTION
+The second, third, and fourth extended file systems, or ext2, ext3, and
+ext4 as they are commonly known, are Linux file systems that have
+historically been the default file system for many Linux distributions.
+They are general purpose file systems that have been designed for
+extensibility and backwards compatibility. In particular, file systems
+previously intended for use with the ext2 and ext3 file systems can be
+mounted using the ext4 file system driver, and indeed in many modern
+Linux distributions, the ext4 file system driver has been configured
+to handle mount requests for ext2 and ext3 file systems.
+.SH FILE SYSTEM FEATURES
+A file system formatted for ext2, ext3, or ext4 can have some
+collection of the following file system feature flags enabled. Some of
+these features are not supported by all implementations of the ext2,
+ext3, and ext4 file system drivers, depending on Linux kernel version in
+use. On other operating systems, such as the GNU/HURD or FreeBSD, only
+a very restrictive set of file system features may be supported in their
+implementations of ext2.
+.TP
+.B 64bit
+.br
+Enables the file system to be larger than 2^32 blocks. This feature is set
+automatically, as needed, but it can be useful to specify this feature
+explicitly if the file system might need to be resized larger than 2^32
+blocks, even if it was smaller than that threshold when it was
+originally created. Note that some older kernels and older versions
+of e2fsprogs will not support file systems with this ext4 feature enabled.
+.TP
+.B bigalloc
+.br
+This ext4 feature enables clustered block allocation, so that the unit of
+allocation is a power of two number of blocks. That is, each bit in the
+what had traditionally been known as the block allocation bitmap now
+indicates whether a cluster is in use or not, where a cluster is by
+default composed of 16 blocks. This feature can decrease the time
+spent on doing block allocation and brings smaller fragmentation, especially
+for large files. The size can be specified using the
+.B mke2fs \-C
+option.
+.IP
+.B Warning:
+The bigalloc feature is still under development, and may not be fully
+supported with your kernel or may have various bugs. Please see the web
+page http://ext4.wiki.kernel.org/index.php/Bigalloc for details.
+May clash with delayed allocation (see
+.B nodelalloc
+mount option).
+.IP
+This feature requires that the
+.B extent
+feature be enabled.
+.TP
+.B casefold
+.br
+This ext4 feature provides file system level character encoding support
+for directories with the casefold (+F) flag enabled. This feature is
+name-preserving on the disk, but it allows applications to lookup for a
+file in the file system using an encoding equivalent version of the file
+name.
+.TP
+.B dir_index
+.br
+Use hashed b-trees to speed up name lookups in large directories. This
+feature is supported by ext3 and ext4 file systems, and is ignored by
+ext2 file systems.
+.TP
+.B dir_nlink
+.br
+Normally, ext4 allows an inode to have no more than 65,000 hard links.
+This applies to regular files as well as directories, which means that
+there can be no more than 64,998 subdirectories in a directory (because
+each of the '.' and '..' entries, as well as the directory entry for the
+directory in its parent directory counts as a hard link). This feature
+lifts this limit by causing ext4 to use a link count of 1 to indicate
+that the number of hard links to a directory is not known when the link
+count might exceed the maximum count limit.
+.TP
+.B ea_inode
+.br
+Normally, a file's extended attributes and associated metadata must fit within
+the inode or the inode's associated extended attribute block. This feature
+allows the value of each extended attribute to be placed in the data blocks of a
+separate inode if necessary, increasing the limit on the size and number of
+extended attributes per file.
+.TP
+.B encrypt
+.br
+Enables support for file-system level encryption of data blocks and file
+names. The inode metadata (timestamps, file size, user/group ownership,
+etc.) is
+.I not
+encrypted.
+.IP
+This feature is most useful on file systems with multiple users, or
+where not all files should be encrypted. In many use cases, especially
+on single-user systems, encryption at the block device layer using
+dm-crypt may provide much better security.
+.TP
+.B ext_attr
+.br
+This feature enables the use of extended attributes. This feature is
+supported by ext2, ext3, and ext4.
+.TP
+.B extent
+.br
+This ext4 feature allows the mapping of logical block numbers for a
+particular inode to physical blocks on the storage device to be stored
+using an extent tree, which is a more efficient data structure than the
+traditional indirect block scheme used by the ext2 and ext3 file
+systems. The use of the extent tree decreases metadata block overhead,
+improves file system performance, and decreases the needed to run
+.BR e2fsck (8)
+on the file system.
+(Note: both
+.B extent
+and
+.B extents
+are accepted as valid names for this feature for
+historical/backwards compatibility reasons.)
+.TP
+.B extra_isize
+.br
+This ext4 feature reserves a specific amount of space in each inode for
+extended metadata such as nanosecond timestamps and file creation time,
+even if the current kernel does not currently need to reserve this much
+space. Without this feature, the kernel will reserve the amount of
+space for features it currently needs, and the rest may be
+consumed by extended attributes.
+
+For this feature to be useful the inode size must be 256 bytes in size
+or larger.
+.TP
+.B filetype
+.br
+This feature enables the storage of file type information in directory
+entries. This feature is supported by ext2, ext3, and ext4.
+.TP
+.B flex_bg
+.br
+This ext4 feature allows the per-block group metadata (allocation
+bitmaps
+and inode tables)
+to be placed anywhere on the storage media. In addition,
+.B mke2fs
+will place the per-block group metadata together starting at the first
+block group of each "flex_bg group". The size of the flex_bg group
+can be specified using the
+.B \-G
+option.
+.TP
+.B has_journal
+.br
+Create a journal to ensure file system consistency even across unclean
+shutdowns. Setting the file system feature is equivalent to using the
+.B \-j
+option with
+.BR mke2fs " or " tune2fs.
+This feature is supported by ext3 and ext4, and ignored by the
+ext2 file system driver.
+.TP
+.B huge_file
+.br
+This ext4 feature allows files to be larger than 2 terabytes in size.
+.TP
+.B inline_data
+Allow data to be stored in the inode and extended attribute area.
+.TP
+.B journal_dev
+.br
+This feature is enabled on the superblock found on an external journal
+device. The block size for the external journal must be the same as the
+file system which uses it.
+.IP
+The external journal device can be used by a file system by specifying
+the
+.B \-J
+.BR device= <external-device>
+option to
+.BR mke2fs (8)
+or
+.BR tune2fs(8) .
+.TP
+.B large_dir
+.br
+This feature increases the limit on the number of files per directory by
+raising the maximum size of directories and, for hashed b-tree directories (see
+.BR dir_index ),
+the maximum height of the hashed b-tree used to store the directory entries.
+.TP
+.B large_file
+.br
+This feature flag is set automatically by modern kernels when a file
+larger than 2 gigabytes is created. Very old kernels could not
+handle large files, so this feature flag was used to prohibit those
+kernels from mounting file systems that they could not understand.
+.TP
+.B metadata_csum
+.br
+This ext4 feature enables metadata checksumming. This feature stores
+checksums for all of the file system metadata (superblock, group
+descriptor blocks, inode and block bitmaps, directories, and
+extent tree blocks). The checksum algorithm used for the metadata
+blocks is different than the one used for group descriptors with the
+.B uninit_bg
+feature. These two features are incompatible and
+.B metadata_csum
+will be used preferentially instead of
+.BR uninit_bg .
+.TP
+.B metadata_csum_seed
+.br
+This feature allows the file system to store the metadata checksum seed in the
+superblock, which allows the administrator to change the UUID of a file system
+using the
+.B metadata_csum
+feature while it is mounted.
+.TP
+.B meta_bg
+.br
+This ext4 feature allows file systems to be resized on-line without explicitly
+needing to reserve space for growth in the size of the block group
+descriptors. This scheme is also used to resize file systems which are
+larger than 2^32 blocks. It is not recommended that this feature be set
+when a file system is created, since this alternate method of storing
+the block group descriptors will slow down the time needed to mount the
+file system, and newer kernels can automatically set this feature as
+necessary when doing an online resize and no more reserved space is
+available in the resize inode.
+.TP
+.B mmp
+.br
+This ext4 feature provides multiple mount protection (MMP). MMP helps to
+protect the file system from being multiply mounted and is useful in
+shared storage environments.
+.TP
+.B project
+.br
+This ext4 feature provides project quota support. With this feature,
+the project ID of inode will be managed when the file system is mounted.
+.TP
+.B quota
+.br
+Create quota inodes (inode #3 for userquota and inode
+#4 for group quota) and set them in the superblock.
+With this feature, the quotas will be enabled
+automatically when the file system is mounted.
+.IP
+Causes the quota files (i.e., user.quota and
+group.quota which existed
+in the older quota design) to be hidden inodes.
+.TP
+.B resize_inode
+.br
+This file system feature indicates that space has been reserved so that
+the block group descriptor table can be extended while resizing a mounted
+file system. The online resize operation
+is carried out by the kernel, triggered by
+.BR resize2fs (8).
+By default
+.B mke2fs
+will attempt to reserve enough space so that the
+file system may grow to 1024 times its initial size. This can be changed
+using the
+.B resize
+extended option.
+.IP
+This feature requires that the
+.B sparse_super
+or
+.B sparse_super2
+feature be enabled.
+.TP
+.B sparse_super
+.br
+This file system feature is set on all modern ext2, ext3, and ext4 file
+systems. It indicates that backup copies of the superblock and block
+group descriptors are present only in a few block groups, not all of
+them.
+.TP
+.B sparse_super2
+.br
+This feature indicates that there will only be at most two backup
+superblocks and block group descriptors. The block groups used to store
+the backup superblock(s) and blockgroup descriptor(s) are stored in the
+superblock, but typically, one will be located at the beginning of block
+group #1, and one in the last block group in the file system. This
+feature is essentially a more extreme version of sparse_super and is
+designed to allow a much larger percentage of the disk to have
+contiguous blocks available for data files.
+.TP
+.B stable_inodes
+.br
+Marks the file system's inode numbers and UUID as stable.
+.BR resize2fs (8)
+will not allow shrinking a file system with this feature, nor
+will
+.BR tune2fs (8)
+allow changing its UUID. This feature allows the use of specialized encryption
+settings that make use of the inode numbers and UUID. Note that the
+.B encrypt
+feature still needs to be enabled separately.
+.B stable_inodes
+is a "compat" feature, so old kernels will allow it.
+.TP
+.B uninit_bg
+.br
+This ext4 file system feature indicates that the block group descriptors
+will be protected using checksums, making it safe for
+.BR mke2fs (8)
+to create a file system without initializing all of the block groups.
+The kernel will keep a high watermark of unused inodes, and initialize
+inode tables and blocks lazily. This feature speeds up the time to check
+the file system using
+.BR e2fsck (8),
+and it also speeds up the time required for
+.BR mke2fs (8)
+to create the file system.
+.TP
+.B verity
+.br
+Enables support for verity protected files. Verity files are readonly,
+and their data is transparently verified against a Merkle tree hidden
+past the end of the file. Using the Merkle tree's root hash, a verity
+file can be efficiently authenticated, independent of the file's size.
+.IP
+This feature is most useful for authenticating important read-only files
+on read-write file systems. If the file system itself is read-only,
+then using dm-verity to authenticate the entire block device may provide
+much better security.
+.SH MOUNT OPTIONS
+This section describes mount options which are specific to ext2, ext3,
+and ext4. Other generic mount options may be used as well; see
+.BR mount (8)
+for details.
+.SH "Mount options for ext2"
+The `ext2' file system is the standard Linux file system.
+Since Linux 2.5.46, for most mount options the default
+is determined by the file system superblock. Set them with
+.BR tune2fs (8).
+.TP
+.BR acl | noacl
+Support POSIX Access Control Lists (or not). See the
+.BR acl (5)
+manual page.
+.TP
+.BR bsddf | minixdf
+Set the behavior for the
+.I statfs
+system call. The
+.B minixdf
+behavior is to return in the
+.I f_blocks
+field the total number of blocks of the file system, while the
+.B bsddf
+behavior (which is the default) is to subtract the overhead blocks
+used by the ext2 file system and not available for file storage. Thus
+.sp 1
+% mount /k \-o minixdf; df /k; umount /k
+.TS
+tab(#);
+l2 l2 r2 l2 l2 l
+l c r c c l.
+File System#1024-blocks#Used#Available#Capacity#Mounted on
+/dev/sda6#2630655#86954#2412169#3%#/k
+.TE
+.sp 1
+% mount /k \-o bsddf; df /k; umount /k
+.TS
+tab(#);
+l2 l2 r2 l2 l2 l
+l c r c c l.
+File System#1024-blocks#Used#Available#Capacity#Mounted on
+/dev/sda6#2543714#13#2412169#0%#/k
+.TE
+.sp 1
+(Note that this example shows that one can add command line options
+to the options given in
+.IR /etc/fstab .)
+.TP
+.BR check=none " or " nocheck
+No checking is done at mount time. This is the default. This is fast.
+It is wise to invoke
+.BR e2fsck (8)
+every now and then, e.g.\& at boot time. The non-default behavior is unsupported
+(check=normal and check=strict options have been removed). Note that these mount options
+don't have to be supported if ext4 kernel driver is used for ext2 and ext3 file systems.
+.TP
+.B debug
+Print debugging info upon each (re)mount.
+.TP
+.BR errors= { continue | remount-ro | panic }
+Define the behavior when an error is encountered.
+(Either ignore errors and just mark the file system erroneous and continue,
+or remount the file system read-only, or panic and halt the system.)
+The default is set in the file system superblock, and can be
+changed using
+.BR tune2fs (8).
+.TP
+.BR grpid | bsdgroups " and " nogrpid | sysvgroups
+These options define what group id a newly created file gets.
+When
+.B grpid
+is set, it takes the group id of the directory in which it is created;
+otherwise (the default) it takes the fsgid of the current process, unless
+the directory has the setgid bit set, in which case it takes the gid
+from the parent directory, and also gets the setgid bit set
+if it is a directory itself.
+.TP
+.BR grpquota | noquota | quota | usrquota
+The usrquota (same as quota) mount option enables user quota support on the
+file system. grpquota enables group quotas support. You need the quota utilities
+to actually enable and manage the quota system.
+.TP
+.B nouid32
+Disables 32-bit UIDs and GIDs. This is for interoperability with older
+kernels which only store and expect 16-bit values.
+.TP
+.BR oldalloc " or " orlov
+Use old allocator or Orlov allocator for new inodes. Orlov is default.
+.TP
+\fBresgid=\fP\,\fIn\fP and \fBresuid=\fP\,\fIn\fP
+The ext2 file system reserves a certain percentage of the available
+space (by default 5%, see
+.BR mke2fs (8)
+and
+.BR tune2fs (8)).
+These options determine who can use the reserved blocks.
+(Roughly: whoever has the specified uid, or belongs to the specified group.)
+.TP
+.BI sb= n
+Instead of using the normal superblock, use an alternative superblock
+specified by
+.IR n .
+This option is normally used when the primary superblock has been
+corrupted. The location of backup superblocks is dependent on the
+file system's blocksize, the number of blocks per group, and features
+such as
+.BR sparse_super .
+.IP
+Additional backup superblocks can be determined by using the
+.B mke2fs
+program using the
+.B \-n
+option to print out where the superblocks exist, supposing
+.B mke2fs
+is supplied with arguments that are consistent with the file system's layout
+(e.g. blocksize, blocks per group,
+.BR sparse_super ,
+etc.).
+.IP
+The block number here uses 1\ k units. Thus, if you want to use logical
+block 32768 on a file system with 4\ k blocks, use "sb=131072".
+.TP
+.BR user_xattr | nouser_xattr
+Support "user." extended attributes (or not).
+
+
+.SH "Mount options for ext3"
+The ext3 file system is a version of the ext2 file system which has been
+enhanced with journaling. It supports the same options as ext2 as
+well as the following additions:
+.TP
+.BR journal_dev=devnum / journal_path=path
+When the external journal device's major/minor numbers
+have changed, these options allow the user to specify
+the new journal location. The journal device is
+identified either through its new major/minor numbers encoded
+in devnum, or via a path to the device.
+.TP
+.BR norecovery / noload
+Don't load the journal on mounting. Note that
+if the file system was not unmounted cleanly,
+skipping the journal replay will lead to the
+file system containing inconsistencies that can
+lead to any number of problems.
+.TP
+.BR data= { journal | ordered | writeback }
+Specifies the journaling mode for file data. Metadata is always journaled.
+To use modes other than
+.B ordered
+on the root file system, pass the mode to the kernel as boot parameter, e.g.\&
+.IR rootflags=data=journal .
+.RS
+.TP
+.B journal
+All data is committed into the journal prior to being written into the
+main file system.
+.TP
+.B ordered
+This is the default mode. All data is forced directly out to the main file
+system prior to its metadata being committed to the journal.
+.TP
+.B writeback
+Data ordering is not preserved \(en data may be written into the main
+file system after its metadata has been committed to the journal.
+This is rumoured to be the highest-throughput option. It guarantees
+internal file system integrity, however it can allow old data to appear
+in files after a crash and journal recovery.
+.RE
+.TP
+.B data_err=ignore
+Just print an error message if an error occurs in a file data buffer in
+ordered mode.
+.TP
+.B data_err=abort
+Abort the journal if an error occurs in a file data buffer in ordered mode.
+.TP
+.BR barrier=0 " / " barrier=1 "
+This disables / enables the use of write barriers in the jbd code. barrier=0
+disables, barrier=1 enables (default). This also requires an IO stack which can
+support barriers, and if jbd gets an error on a barrier write, it will disable
+barriers again with a warning. Write barriers enforce proper on-disk ordering
+of journal commits, making volatile disk write caches safe to use, at some
+performance penalty. If your disks are battery-backed in one way or another,
+disabling barriers may safely improve performance.
+.TP
+.BI commit= nrsec
+Start a journal commit every
+.I nrsec
+seconds. The default value is 5 seconds. Zero means default.
+.TP
+.B user_xattr
+Enable Extended User Attributes. See the
+.BR attr (5)
+manual page.
+.TP
+.BR jqfmt= { vfsold | vfsv0 | vfsv1 }
+Apart from the old quota system (as in ext2, jqfmt=vfsold aka version 1 quota)
+ext3 also supports journaled quotas (version 2 quota). jqfmt=vfsv0 or
+jqfmt=vfsv1 enables journaled quotas. Journaled quotas have the advantage that
+even after a crash no quota check is required. When the
+.B quota
+file system feature is enabled, journaled quotas are used automatically, and
+this mount option is ignored.
+.TP
+.BR usrjquota=aquota.user | grpjquota=aquota.group
+For journaled quotas (jqfmt=vfsv0 or jqfmt=vfsv1), the mount options
+usrjquota=aquota.user and grpjquota=aquota.group are required to tell the
+quota system which quota database files to use. When the
+.B quota
+file system feature is enabled, journaled quotas are used automatically, and
+this mount option is ignored.
+
+.SH "Mount options for ext4"
+The ext4 file system is an advanced level of the ext3 file system which
+incorporates scalability and reliability enhancements for supporting large
+file system.
+
+The options
+.B journal_dev, journal_path, norecovery, noload, data, commit, orlov,
+.B oldalloc, [no]user_xattr, [no]acl, bsddf, minixdf, debug, errors,
+.B data_err, grpid, bsdgroups, nogrpid, sysvgroups, resgid, resuid, sb,
+.B quota, noquota, nouid32, grpquota, usrquota, usrjquota, grpjquota,
+.B and jqfmt are backwardly compatible with ext3 or ext2.
+.TP
+.B journal_checksum | nojournal_checksum
+The journal_checksum option enables checksumming of the journal transactions.
+This will allow the recovery code in e2fsck and the kernel to detect corruption
+in the kernel. It is a compatible change and will be ignored by older kernels.
+.TP
+.B journal_async_commit
+Commit block can be written to disk without waiting for descriptor blocks. If
+enabled older kernels cannot mount the device.
+This will enable 'journal_checksum' internally.
+.TP
+.BR barrier=0 " / " barrier=1 " / " barrier " / " nobarrier
+These mount options have the same effect as in ext3. The mount options
+"barrier" and "nobarrier" are added for consistency with other ext4 mount
+options.
+
+The ext4 file system enables write barriers by default.
+.TP
+.BI inode_readahead_blks= n
+This tuning parameter controls the maximum number of inode table blocks that
+ext4's inode table readahead algorithm will pre-read into the buffer cache.
+The value must be a power of 2. The default value is 32 blocks.
+.TP
+.BI stripe= n
+Number of file system blocks that mballoc will try to use for allocation size
+and alignment. For RAID5/6 systems this should be the number of data disks *
+RAID chunk size in file system blocks.
+.TP
+.B delalloc
+Deferring block allocation until write-out time.
+.TP
+.B nodelalloc
+Disable delayed allocation. Blocks are allocated when data is copied from user
+to page cache.
+.TP
+.BI max_batch_time= usec
+Maximum amount of time ext4 should wait for additional file system operations to
+be batch together with a synchronous write operation. Since a synchronous
+write operation is going to force a commit and then a wait for the I/O
+complete, it doesn't cost much, and can be a huge throughput win, we wait for a
+small amount of time to see if any other transactions can piggyback on the
+synchronous write. The algorithm used is designed to automatically tune for
+the speed of the disk, by measuring the amount of time (on average) that it
+takes to finish committing a transaction. Call this time the "commit time".
+If the time that the transaction has been running is less than the commit time,
+ext4 will try sleeping for the commit time to see if other operations will join
+the transaction. The commit time is capped by the max_batch_time, which
+defaults to 15000\ \[mc]s (15\ ms). This optimization can be turned off entirely by
+setting max_batch_time to 0.
+.TP
+.BI min_batch_time= usec
+This parameter sets the commit time (as described above) to be at least
+min_batch_time. It defaults to zero microseconds. Increasing this parameter
+may improve the throughput of multi-threaded, synchronous workloads on very
+fast disks, at the cost of increasing latency.
+.TP
+.BI journal_ioprio= prio
+The I/O priority (from 0 to 7, where 0 is the highest priority) which should be
+used for I/O operations submitted by kjournald2 during a commit operation.
+This defaults to 3, which is a slightly higher priority than the default I/O
+priority.
+.TP
+.B abort
+Simulate the effects of calling ext4_abort() for
+debugging purposes. This is normally used while
+remounting a file system which is already mounted.
+.TP
+.BR auto_da_alloc | noauto_da_alloc
+Many broken applications don't use fsync() when
+replacing existing files via patterns such as
+
+fd = open("foo.new")/write(fd,...)/close(fd)/ rename("foo.new", "foo")
+
+or worse yet
+
+fd = open("foo", O_TRUNC)/write(fd,...)/close(fd).
+
+If auto_da_alloc is enabled, ext4 will detect the replace-via-rename and
+replace-via-truncate patterns and force that any delayed allocation blocks are
+allocated such that at the next journal commit, in the default data=ordered
+mode, the data blocks of the new file are forced to disk before the rename()
+operation is committed. This provides roughly the same level of guarantees as
+ext3, and avoids the "zero-length" problem that can happen when a system
+crashes before the delayed allocation blocks are forced to disk.
+.TP
+.B noinit_itable
+Do not initialize any uninitialized inode table blocks in the background. This
+feature may be used by installation CD's so that the install process can
+complete as quickly as possible; the inode table initialization process would
+then be deferred until the next time the file system is mounted.
+.TP
+.B init_itable=n
+The lazy itable init code will wait n times the number of milliseconds it took
+to zero out the previous block group's inode table. This minimizes the impact on
+system performance while the file system's inode table is being initialized.
+.TP
+.BR discard / nodiscard
+Controls whether ext4 should issue discard/TRIM commands to the underlying
+block device when blocks are freed. This is useful for SSD devices and
+sparse/thinly-provisioned LUNs, but it is off by default until sufficient
+testing has been done.
+.TP
+.BR block_validity / noblock_validity
+This option enables/disables the in-kernel facility for tracking
+file system metadata blocks within internal data structures. This allows multi-\c
+block allocator and other routines to quickly locate extents which might
+overlap with file system metadata blocks. This option is intended for debugging
+purposes and since it negatively affects the performance, it is off by default.
+.TP
+.BR dioread_lock / dioread_nolock
+Controls whether or not ext4 should use the DIO read locking. If the
+dioread_nolock option is specified ext4 will allocate uninitialized extent
+before buffer write and convert the extent to initialized after IO completes.
+This approach allows ext4 code to avoid using inode mutex, which improves
+scalability on high speed storages. However this does not work with data
+journaling and dioread_nolock option will be ignored with kernel warning.
+Note that dioread_nolock code path is only used for extent-based files.
+Because of the restrictions this options comprises it is off by default
+(e.g.\& dioread_lock).
+.TP
+.B max_dir_size_kb=n
+This limits the size of the directories so that any attempt to expand them
+beyond the specified limit in kilobytes will cause an ENOSPC error. This is
+useful in memory-constrained environments, where a very large directory can
+cause severe performance problems or even provoke the Out Of Memory killer. (For
+example, if there is only 512\ MB memory available, a 176\ MB directory may
+seriously cramp the system's style.)
+.TP
+.B i_version
+Enable 64-bit inode version support. This option is off by default.
+.TP
+.B nombcache
+This option disables use of mbcache for extended attribute deduplication. On
+systems where extended attributes are rarely or never shared between files,
+use of mbcache for deduplication adds unnecessary computational overhead.
+.TP
+.B prjquota
+The prjquota mount option enables project quota support on the file system.
+You need the quota utilities to actually enable and manage the quota system.
+This mount option requires the
+.B project
+file system feature.
+
+.SH FILE ATTRIBUTES
+The ext2, ext3, and ext4 file systems support setting the following file
+attributes on Linux systems using the
+.BR chattr (1)
+utility:
+.sp
+.BR a " - append only"
+.sp
+.BR A " - no atime updates"
+.sp
+.BR d " - no dump"
+.sp
+.BR D " - synchronous directory updates"
+.sp
+.BR i " - immutable"
+.sp
+.BR S " - synchronous updates"
+.sp
+.BR u " - undeletable"
+.sp
+In addition, the ext3 and ext4 file systems support the following flag:
+.sp
+.BR j " - data journaling"
+.sp
+Finally, the ext4 file system also supports the following flag:
+.sp
+.BR e " - extents format"
+.sp
+For descriptions of these attribute flags, please refer to the
+.BR chattr (1)
+man page.
+.SH KERNEL SUPPORT
+This section lists the file system driver (e.g., ext2, ext3, ext4) and
+upstream kernel version where a particular file system feature was
+supported. Note that in some cases the feature was present in earlier
+kernel versions, but there were known, serious bugs. In other cases the
+feature may still be considered in an experimental state. Finally, note
+that some distributions may have backported features into older kernels;
+in particular the kernel versions in certain "enterprise distributions"
+can be extremely misleading.
+.IP "\fBfiletype\fR" 2in
+ext2, 2.2.0
+.IP "\fBsparse_super\fR" 2in
+ext2, 2.2.0
+.IP "\fBlarge_file\fR" 2in
+ext2, 2.2.0
+.IP "\fBhas_journal\fR" 2in
+ext3, 2.4.15
+.IP "\fBext_attr\fR" 2in
+ext2/ext3, 2.6.0
+.IP "\fBdir_index\fR" 2in
+ext3, 2.6.0
+.IP "\fBresize_inode\fR" 2in
+ext3, 2.6.10 (online resizing)
+.IP "\fB64bit\fR" 2in
+ext4, 2.6.28
+.IP "\fBdir_nlink\fR" 2in
+ext4, 2.6.28
+.IP "\fBextent\fR" 2in
+ext4, 2.6.28
+.IP "\fBextra_isize\fR" 2in
+ext4, 2.6.28
+.IP "\fBflex_bg\fR" 2in
+ext4, 2.6.28
+.IP "\fBhuge_file\fR" 2in
+ext4, 2.6.28
+.IP "\fBmeta_bg\fR" 2in
+ext4, 2.6.28
+.IP "\fBuninit_bg\fR" 2in
+ext4, 2.6.28
+.IP "\fBmmp\fR" 2in
+ext4, 3.0
+.IP "\fBbigalloc\fR" 2in
+ext4, 3.2
+.IP "\fBquota\fR" 2in
+ext4, 3.6
+.IP "\fBinline_data\fR" 2in
+ext4, 3.8
+.IP "\fBsparse_super2\fR" 2in
+ext4, 3.16
+.IP "\fBmetadata_csum\fR" 2in
+ext4, 3.18
+.IP "\fBencrypt\fR" 2in
+ext4, 4.1
+.IP "\fBmetadata_csum_seed\fR" 2i
+ext4, 4.4
+.IP "\fBproject\fR" 2i
+ext4, 4.5
+.IP "\fBea_inode\fR" 2i
+ext4, 4.13
+.IP "\fBlarge_dir\fR" 2i
+ext4, 4.13
+.IP "\fBcasefold\fR" 2i
+ext4, 5.2
+.IP "\fBverity\fR" 2i
+ext4, 5.4
+.IP "\fBstable_inodes\fR" 2i
+ext4, 5.5
+.SH SEE ALSO
+.BR mke2fs (8),
+.BR mke2fs.conf (5),
+.BR e2fsck (8),
+.BR dumpe2fs (8),
+.BR tune2fs (8),
+.BR debugfs (8),
+.BR mount (8),
+.BR chattr (1)
diff --git a/misc/filefrag.8.in b/misc/filefrag.8.in
new file mode 100644
index 0000000..6d56c13
--- /dev/null
+++ b/misc/filefrag.8.in
@@ -0,0 +1,77 @@
+.\" -*- nroff -*-
+.TH FILEFRAG 8 "@E2FSPROGS_MONTH@ @E2FSPROGS_YEAR@" "E2fsprogs version @E2FSPROGS_VERSION@"
+.SH NAME
+filefrag \- report on file fragmentation
+.SH SYNOPSIS
+.B filefrag
+[
+.BI \-b blocksize
+]
+[
+.B \-BeEkPsvVxX
+]
+[
+.I files...
+]
+.SH DESCRIPTION
+.B filefrag
+reports on how badly fragmented a particular file might be. It makes
+allowances for indirect blocks for ext2 and ext3 file systems, but can be
+used on files for any file system.
+.PP
+The
+.B filefrag
+program initially attempts to get the
+extent information using FIEMAP ioctl which is more efficient and faster.
+If FIEMAP is not supported then filefrag will fall back to using FIBMAP.
+.SH OPTIONS
+.TP
+.B \-B
+Force the use of the older FIBMAP ioctl instead of the FIEMAP ioctl for
+testing purposes.
+.TP
+.BI \-b blocksize
+Use
+.I blocksize
+in bytes, or with [KMG] suffix, up to 1GB for output instead of the
+file system blocksize. For compatibility with earlier versions of
+.BR filefrag ,
+if
+.I blocksize
+is unspecified it defaults to 1024 bytes. Since
+.I blocksize
+is an optional argument, it must be added without any space after
+.BR -b .
+.TP
+.B \-e
+Print output in extent format, even for block-mapped files.
+.TP
+.B \-E
+Display the contents of ext4's extent status cache. This feature is not
+supported on all kernels, and is only supported on ext4 file systems.
+.TP
+.B \-k
+Use 1024\-byte blocksize for output (identical to '\-b1024').
+.TP
+.B -P
+Pre-load the ext4 extent status cache for the file. This is not
+supported on all kernels, and is only supported on ext4 file systems.
+.TP
+.B \-s
+Sync the file before requesting the mapping.
+.TP
+.B \-v
+Be verbose when checking for file fragmentation.
+.TP
+.B \-V
+Print version number of program and library. If given twice, also
+print the FIEMAP flags that are understood by the current version.
+.TP
+.B \-x
+Display mapping of extended attributes.
+.TP
+.B \-X
+Display extent block numbers in hexadecimal format.
+.SH AUTHOR
+.B filefrag
+was written by Theodore Ts'o <tytso@mit.edu>.
diff --git a/misc/filefrag.c b/misc/filefrag.c
new file mode 100644
index 0000000..eaaa90a
--- /dev/null
+++ b/misc/filefrag.c
@@ -0,0 +1,695 @@
+/*
+ * filefrag.c -- report if a particular file is fragmented
+ *
+ * Copyright 2003 by Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ * %End-Header%
+ */
+
+#include "config.h"
+#ifndef __linux__
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+int main(void) {
+ fputs("This program is only supported on Linux!\n", stderr);
+ exit(EXIT_FAILURE);
+}
+#else
+#ifndef _LARGEFILE_SOURCE
+#define _LARGEFILE_SOURCE
+#endif
+#ifndef _LARGEFILE64_SOURCE
+#define _LARGEFILE64_SOURCE
+#endif
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <time.h>
+#include <fcntl.h>
+#include <errno.h>
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#else
+extern char *optarg;
+extern int optind;
+#endif
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/vfs.h>
+#include <sys/ioctl.h>
+#ifdef HAVE_LINUX_FD_H
+#include <linux/fd.h>
+#endif
+#include <ext2fs/ext2fs.h>
+#include <ext2fs/ext2_types.h>
+#include <ext2fs/fiemap.h>
+#include "../version.h"
+
+int verbose = 0;
+unsigned int blocksize; /* Use specified blocksize (default 1kB) */
+int sync_file = 0; /* fsync file before getting the mapping */
+int precache_file = 0; /* precache the file before getting the mapping */
+int xattr_map = 0; /* get xattr mapping */
+int force_bmap; /* force use of FIBMAP instead of FIEMAP */
+int force_extent; /* print output in extent format always */
+int use_extent_cache; /* Use extent cache */
+int logical_width = 8;
+int physical_width = 10;
+const char *ext_fmt = "%4d: %*llu..%*llu: %*llu..%*llu: %6llu: %s\n";
+const char *hex_fmt = "%4d: %*llx..%*llx: %*llx..%*llx: %6llx: %s\n";
+
+#define FILEFRAG_FIEMAP_FLAGS_COMPAT (FIEMAP_FLAG_SYNC | FIEMAP_FLAG_XATTR)
+
+#define FIBMAP _IO(0x00, 1) /* bmap access */
+#define FIGETBSZ _IO(0x00, 2) /* get the block size used for bmap */
+
+#define LUSTRE_SUPER_MAGIC 0x0BD00BD0
+
+#define EXT4_EXTENTS_FL 0x00080000 /* Inode uses extents */
+#define EXT3_IOC_GETFLAGS _IOR('f', 1, long)
+
+static int ulong_log2(unsigned long arg)
+{
+ int l = 0;
+
+ arg >>= 1;
+ while (arg) {
+ l++;
+ arg >>= 1;
+ }
+ return l;
+}
+
+static int ulong_log10(unsigned long long arg)
+{
+ int l = 0;
+
+ arg = arg / 10;
+ while (arg) {
+ l++;
+ arg = arg / 10;
+ }
+ return l;
+}
+
+static unsigned int div_ceil(unsigned int a, unsigned int b)
+{
+ if (!a)
+ return 0;
+ return ((a - 1) / b) + 1;
+}
+
+static int get_bmap(int fd, unsigned long block, unsigned long *phy_blk)
+{
+ int ret;
+ unsigned int b;
+
+ b = block;
+ ret = ioctl(fd, FIBMAP, &b); /* FIBMAP takes pointer to integer */
+ if (ret < 0)
+ return -errno;
+ *phy_blk = b;
+
+ return ret;
+}
+
+static void print_extent_header(void)
+{
+ printf(" ext: %*s %*s length: %*s flags:\n",
+ logical_width * 2 + 3,
+ "logical_offset:",
+ physical_width * 2 + 3, "physical_offset:",
+ physical_width + 1,
+ "expected:");
+}
+
+static void print_flag(__u32 *flags, __u32 mask, char *buf, const char *name)
+{
+ char hex[sizeof(mask) * 2 + 4]; /* 2 chars/byte + 0x, + NUL */
+
+ if ((*flags & mask) == 0)
+ return;
+
+ if (name == NULL) {
+ sprintf(hex, "%#04x,", mask);
+ name = hex;
+ }
+ strcat(buf, name);
+ *flags &= ~mask;
+}
+
+static void print_flags(__u32 fe_flags, char *flags, int len, int print_unknown)
+{
+ __u32 mask;
+
+ print_flag(&fe_flags, FIEMAP_EXTENT_LAST, flags, "last,");
+ print_flag(&fe_flags, FIEMAP_EXTENT_UNKNOWN, flags, "unknown_loc,");
+ print_flag(&fe_flags, FIEMAP_EXTENT_DELALLOC, flags, "delalloc,");
+ print_flag(&fe_flags, FIEMAP_EXTENT_ENCODED, flags, "encoded,");
+ print_flag(&fe_flags, FIEMAP_EXTENT_DATA_ENCRYPTED, flags,"encrypted,");
+ print_flag(&fe_flags, FIEMAP_EXTENT_NOT_ALIGNED, flags, "not_aligned,");
+ print_flag(&fe_flags, FIEMAP_EXTENT_DATA_INLINE, flags, "inline,");
+ print_flag(&fe_flags, FIEMAP_EXTENT_DATA_TAIL, flags, "tail_packed,");
+ print_flag(&fe_flags, FIEMAP_EXTENT_UNWRITTEN, flags, "unwritten,");
+ print_flag(&fe_flags, FIEMAP_EXTENT_MERGED, flags, "merged,");
+ print_flag(&fe_flags, FIEMAP_EXTENT_SHARED, flags, "shared,");
+ print_flag(&fe_flags, EXT4_FIEMAP_EXTENT_HOLE, flags, "hole,");
+
+ if (!print_unknown)
+ goto out;
+
+ /* print any unknown flags as hex values */
+ for (mask = 1; fe_flags != 0 && mask != 0; mask <<= 1)
+ print_flag(&fe_flags, mask, flags, NULL);
+out:
+ /* Remove trailing comma, if any */
+ if (flags[0])
+ flags[strnlen(flags, len) - 1] = '\0';
+
+}
+
+static void print_extent_info(struct fiemap_extent *fm_extent, int cur_ex,
+ unsigned long long expected, int blk_shift,
+ ext2fs_struct_stat *st)
+{
+ unsigned long long physical_blk;
+ unsigned long long logical_blk;
+ unsigned long long ext_len;
+ unsigned long long ext_blks;
+ unsigned long long ext_blks_phys;
+ char flags[256] = "";
+
+ /* For inline data all offsets should be in bytes, not blocks */
+ if (fm_extent->fe_flags & FIEMAP_EXTENT_DATA_INLINE)
+ blk_shift = 0;
+
+ ext_len = fm_extent->fe_length >> blk_shift;
+ ext_blks = (fm_extent->fe_length - 1) >> blk_shift;
+ logical_blk = fm_extent->fe_logical >> blk_shift;
+ if (fm_extent->fe_flags & FIEMAP_EXTENT_UNKNOWN) {
+ physical_blk = 0;
+ } else {
+ physical_blk = fm_extent->fe_physical >> blk_shift;
+ }
+
+ if (expected &&
+ !(fm_extent->fe_flags & FIEMAP_EXTENT_UNKNOWN) &&
+ !(fm_extent->fe_flags & EXT4_FIEMAP_EXTENT_HOLE))
+ sprintf(flags, ext_fmt == hex_fmt ? "%*llx: " : "%*llu: ",
+ physical_width, expected >> blk_shift);
+ else
+ sprintf(flags, "%.*s ", physical_width, " ");
+
+ print_flags(fm_extent->fe_flags, flags, sizeof(flags), 1);
+
+ if (fm_extent->fe_logical + fm_extent->fe_length >=
+ (unsigned long long)st->st_size)
+ strcat(flags, flags[0] ? ",eof" : "eof");
+
+ if ((fm_extent->fe_flags & FIEMAP_EXTENT_UNKNOWN) ||
+ (fm_extent->fe_flags & EXT4_FIEMAP_EXTENT_HOLE)) {
+ ext_len = 0;
+ ext_blks_phys = 0;
+ } else
+ ext_blks_phys = ext_blks;
+
+ printf(ext_fmt, cur_ex, logical_width, logical_blk,
+ logical_width, logical_blk + ext_blks,
+ physical_width, physical_blk,
+ physical_width, physical_blk + ext_blks_phys,
+ ext_len, flags);
+}
+
+static int filefrag_fiemap(int fd, int blk_shift, int *num_extents,
+ ext2fs_struct_stat *st)
+{
+ __u64 buf[2048]; /* __u64 for proper field alignment */
+ struct fiemap *fiemap = (struct fiemap *)buf;
+ struct fiemap_extent *fm_ext = &fiemap->fm_extents[0];
+ struct fiemap_extent fm_last;
+ int count = (sizeof(buf) - sizeof(*fiemap)) /
+ sizeof(struct fiemap_extent);
+ unsigned long long expected = 0;
+ unsigned long long expected_dense = 0;
+ unsigned long flags = 0;
+ unsigned int i;
+ unsigned long cmd = FS_IOC_FIEMAP;
+ int fiemap_header_printed = 0;
+ int tot_extents = 0, n = 0;
+ int last = 0;
+ int rc;
+
+ memset(fiemap, 0, sizeof(struct fiemap));
+ memset(&fm_last, 0, sizeof(fm_last));
+
+ if (sync_file)
+ flags |= FIEMAP_FLAG_SYNC;
+
+ if (precache_file)
+ flags |= FIEMAP_FLAG_CACHE;
+
+ if (xattr_map)
+ flags |= FIEMAP_FLAG_XATTR;
+
+ if (use_extent_cache)
+ cmd = EXT4_IOC_GET_ES_CACHE;
+
+ do {
+ fiemap->fm_length = ~0ULL;
+ fiemap->fm_flags = flags;
+ fiemap->fm_extent_count = count;
+ rc = ioctl(fd, cmd, (unsigned long) fiemap);
+ if (rc < 0) {
+ static int fiemap_incompat_printed;
+
+ rc = -errno;
+ if (rc == -EBADR && !fiemap_incompat_printed) {
+ fprintf(stderr, "FIEMAP failed with unknown "
+ "flags %x\n",
+ fiemap->fm_flags);
+ fiemap_incompat_printed = 1;
+ }
+ return rc;
+ }
+
+ /* If 0 extents are returned, then more ioctls are not needed */
+ if (fiemap->fm_mapped_extents == 0)
+ break;
+
+ if (verbose && !fiemap_header_printed) {
+ print_extent_header();
+ fiemap_header_printed = 1;
+ }
+
+ for (i = 0; i < fiemap->fm_mapped_extents; i++) {
+ expected_dense = fm_last.fe_physical +
+ fm_last.fe_length;
+ expected = fm_last.fe_physical +
+ fm_ext[i].fe_logical - fm_last.fe_logical;
+ if (fm_ext[i].fe_logical != 0 &&
+ fm_ext[i].fe_physical != expected &&
+ fm_ext[i].fe_physical != expected_dense) {
+ tot_extents++;
+ } else {
+ expected = 0;
+ if (!tot_extents)
+ tot_extents = 1;
+ }
+ if (verbose)
+ print_extent_info(&fm_ext[i], n, expected,
+ blk_shift, st);
+ if (fm_ext[i].fe_flags & FIEMAP_EXTENT_LAST)
+ last = 1;
+ fm_last = fm_ext[i];
+ n++;
+ }
+
+ fiemap->fm_start = (fm_ext[i - 1].fe_logical +
+ fm_ext[i - 1].fe_length);
+ } while (last == 0);
+
+ *num_extents = tot_extents;
+
+ return 0;
+}
+
+#define EXT2_DIRECT 12
+
+static int filefrag_fibmap(int fd, int blk_shift, int *num_extents,
+ ext2fs_struct_stat *st,
+ unsigned long numblocks, int is_ext2)
+{
+ struct fiemap_extent fm_ext, fm_last;
+ unsigned long i, last_block;
+ unsigned long long logical, expected = 0;
+ /* Blocks per indirect block */
+ const long bpib = st->st_blksize / 4;
+ int count;
+
+ memset(&fm_ext, 0, sizeof(fm_ext));
+ memset(&fm_last, 0, sizeof(fm_last));
+ if (force_extent) {
+ fm_ext.fe_flags = FIEMAP_EXTENT_MERGED;
+ }
+
+ if (sync_file && fsync(fd) != 0)
+ return -errno;
+
+ for (i = 0, logical = 0, *num_extents = 0, count = last_block = 0;
+ i < numblocks;
+ i++, logical += st->st_blksize) {
+ unsigned long block = 0;
+ int rc;
+
+ if (is_ext2 && last_block) {
+ if (((i - EXT2_DIRECT) % bpib) == 0)
+ last_block++;
+ if (((i - EXT2_DIRECT - bpib) % (bpib * bpib)) == 0)
+ last_block++;
+ if (((i - EXT2_DIRECT - bpib - bpib * bpib) %
+ (((unsigned long long)bpib) * bpib * bpib)) == 0)
+ last_block++;
+ }
+ rc = get_bmap(fd, i, &block);
+ if (rc < 0)
+ return rc;
+ if (block == 0)
+ continue;
+
+ if (*num_extents == 0 || block != last_block + 1 ||
+ fm_ext.fe_logical + fm_ext.fe_length != logical) {
+ /*
+ * This is the start of a new extent; figure out where
+ * we expected it to be and report the extent.
+ */
+ if (*num_extents != 0 && fm_last.fe_length) {
+ expected = fm_last.fe_physical +
+ (fm_ext.fe_logical - fm_last.fe_logical);
+ if (expected == fm_ext.fe_physical)
+ expected = 0;
+ }
+ if (force_extent && *num_extents == 0)
+ print_extent_header();
+ if (force_extent && *num_extents != 0) {
+ print_extent_info(&fm_ext, *num_extents - 1,
+ expected, blk_shift, st);
+ }
+ if (verbose && expected != 0) {
+ printf("Discontinuity: Block %llu is at %llu "
+ "(was %llu)\n",
+ (unsigned long long) (fm_ext.fe_logical / st->st_blksize),
+ (unsigned long long) (fm_ext.fe_physical / st->st_blksize),
+ (unsigned long long) (expected / st->st_blksize));
+ }
+ /* create the new extent */
+ fm_last = fm_ext;
+ (*num_extents)++;
+ fm_ext.fe_physical = block * st->st_blksize;
+ fm_ext.fe_logical = logical;
+ fm_ext.fe_length = 0;
+ }
+ fm_ext.fe_length += st->st_blksize;
+ last_block = block;
+ }
+ if (force_extent && *num_extents != 0) {
+ if (fm_last.fe_length) {
+ expected = fm_last.fe_physical +
+ (fm_ext.fe_logical - fm_last.fe_logical);
+ if (expected == fm_ext.fe_physical)
+ expected = 0;
+ }
+ print_extent_info(&fm_ext, *num_extents - 1, expected,
+ blk_shift, st);
+ }
+
+ return count;
+}
+
+static int frag_report(const char *filename)
+{
+ static struct statfs fsinfo;
+ static unsigned int blksize;
+ ext2fs_struct_stat st;
+ int blk_shift;
+ long fd;
+ unsigned long long numblocks;
+ int data_blocks_per_cyl = 1;
+ int num_extents = 1, expected = ~0;
+ int is_ext2 = 0;
+ static dev_t last_device;
+ int width;
+ int rc = 0;
+
+#if defined(HAVE_OPEN64) && !defined(__OSX_AVAILABLE_BUT_DEPRECATED)
+ fd = open64(filename, O_RDONLY);
+#else
+ fd = open(filename, O_RDONLY);
+#endif
+ if (fd < 0) {
+ rc = -errno;
+ perror("open");
+ return rc;
+ }
+
+#if defined(HAVE_FSTAT64) && !defined(__OSX_AVAILABLE_BUT_DEPRECATED)
+ if (fstat64(fd, &st) < 0) {
+#else
+ if (fstat(fd, &st) < 0) {
+#endif
+ rc = -errno;
+ perror("stat");
+ goto out_close;
+ }
+
+ if ((last_device != st.st_dev) || !st.st_dev) {
+ if (fstatfs(fd, &fsinfo) < 0) {
+ rc = -errno;
+ perror("fstatfs");
+ goto out_close;
+ }
+ if ((ioctl(fd, FIGETBSZ, &blksize) < 0) || !blksize)
+ blksize = fsinfo.f_bsize;
+ if (verbose)
+ printf("Filesystem type is: %lx\n",
+ (unsigned long)fsinfo.f_type);
+ }
+ st.st_blksize = blksize;
+ if (fsinfo.f_type == 0xef51 || fsinfo.f_type == 0xef52 ||
+ fsinfo.f_type == 0xef53) {
+ unsigned int flags;
+
+ if (ioctl(fd, EXT3_IOC_GETFLAGS, &flags) == 0 &&
+ !(flags & EXT4_EXTENTS_FL))
+ is_ext2 = 1;
+ }
+
+ if (is_ext2) {
+ long cylgroups = div_ceil(fsinfo.f_blocks, blksize * 8);
+
+ if (verbose && last_device != st.st_dev)
+ printf("Filesystem cylinder groups approximately %ld\n",
+ cylgroups);
+
+ data_blocks_per_cyl = blksize * 8 -
+ (fsinfo.f_files / 8 / cylgroups) - 3;
+ }
+ last_device = st.st_dev;
+
+ width = ulong_log10(fsinfo.f_blocks);
+ if (width > physical_width)
+ physical_width = width;
+
+ numblocks = (st.st_size + blksize - 1) / blksize;
+ if (blocksize != 0)
+ blk_shift = ulong_log2(blocksize);
+ else
+ blk_shift = ulong_log2(blksize);
+
+ if (use_extent_cache)
+ width = 10;
+ else
+ width = ulong_log10(numblocks);
+ if (width > logical_width)
+ logical_width = width;
+ if (verbose) {
+ __u32 state;
+
+ printf("File size of %s is %llu (%llu block%s of %d bytes)",
+ filename, (unsigned long long) st.st_size,
+ (unsigned long long) (numblocks * blksize >> blk_shift),
+ numblocks == 1 ? "" : "s", 1 << blk_shift);
+ if (use_extent_cache &&
+ ioctl(fd, EXT4_IOC_GETSTATE, &state) == 0 &&
+ (state & EXT4_STATE_FLAG_EXT_PRECACHED))
+ fputs(" -- pre-cached", stdout);
+ fputc('\n', stdout);
+ }
+
+ if (!force_bmap) {
+ rc = filefrag_fiemap(fd, blk_shift, &num_extents, &st);
+ expected = 0;
+ if (rc < 0 &&
+ (use_extent_cache || precache_file || xattr_map)) {
+ if (rc != -EBADR)
+ fprintf(stderr, "%s: %s: %s\n ",
+ filename,
+ use_extent_cache ?
+ "EXT4_IOC_GET_ES_CACHE" :
+ "FS_IOC_FIEMAP", strerror(-rc));
+ goto out_close;
+ }
+ }
+
+ if (force_bmap || rc < 0) { /* FIEMAP failed, try FIBMAP instead */
+ expected = filefrag_fibmap(fd, blk_shift, &num_extents,
+ &st, numblocks, is_ext2);
+ if (expected < 0) {
+ if (expected == -EINVAL || expected == -ENOTTY) {
+ fprintf(stderr, "%s: FIBMAP%s unsupported\n",
+ filename, force_bmap ? "" : "/FIEMAP");
+ } else if (expected == -EPERM) {
+ fprintf(stderr,
+ "%s: FIBMAP requires root privileges\n",
+ filename);
+ } else {
+ fprintf(stderr, "%s: FIBMAP error: %s",
+ filename, strerror(expected));
+ }
+ rc = expected;
+ goto out_close;
+ } else {
+ rc = 0;
+ }
+ expected = expected / data_blocks_per_cyl + 1;
+ }
+
+ if (num_extents == 1)
+ printf("%s: 1 extent found", filename);
+ else
+ printf("%s: %d extents found", filename, num_extents);
+ /* count, and thus expected, only set for indirect FIBMAP'd files */
+ if (is_ext2 && expected && expected < num_extents)
+ printf(", perfection would be %d extent%s\n", expected,
+ (expected > 1) ? "s" : "");
+ else
+ fputc('\n', stdout);
+out_close:
+ close(fd);
+
+ return rc;
+}
+
+static void usage(const char *progname)
+{
+ fprintf(stderr, "Usage: %s [-b{blocksize}[KMG]] [-BeEksvxX] file ...\n",
+ progname);
+ exit(1);
+}
+
+int main(int argc, char**argv)
+{
+ char **cpp;
+ int rc = 0, c;
+ int version = 0;
+
+ while ((c = getopt(argc, argv, "Bb::eEkPsvVxX")) != EOF) {
+ switch (c) {
+ case 'B':
+ force_bmap++;
+ break;
+ case 'b':
+ if (optarg) {
+ char *end;
+ unsigned long val;
+
+ val = strtoul(optarg, &end, 0);
+ if (end) {
+#if __GNUC_PREREQ (7, 0)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
+#endif
+ switch (end[0]) {
+ case 'g':
+ case 'G':
+ val *= 1024;
+ /* fall through */
+ case 'm':
+ case 'M':
+ val *= 1024;
+ /* fall through */
+ case 'k':
+ case 'K':
+ val *= 1024;
+ break;
+ default:
+ break;
+ }
+#if __GNUC_PREREQ (7, 0)
+#pragma GCC diagnostic pop
+#endif
+ }
+ /* Specifying too large a blocksize will just
+ * shift all extents down to zero length. Even
+ * 1GB is questionable, but caveat emptor. */
+ if (val > 1024 * 1024 * 1024) {
+ fprintf(stderr,
+ "%s: blocksize %lu over 1GB\n",
+ argv[0], val);
+ usage(argv[0]);
+ }
+ blocksize = val;
+ } else { /* Allow -b without argument for compat. Remove
+ * this eventually so "-b {blocksize}" works */
+ fprintf(stderr, "%s: -b needs a blocksize "
+ "option, assuming 1024-byte blocks.\n",
+ argv[0]);
+ blocksize = 1024;
+ }
+ break;
+ case 'E':
+ use_extent_cache++;
+ /* fallthrough */
+ case 'e':
+ force_extent++;
+ if (!verbose)
+ verbose++;
+ break;
+ case 'k':
+ blocksize = 1024;
+ break;
+ case 'P':
+ precache_file++;
+ break;
+ case 's':
+ sync_file++;
+ break;
+ case 'v':
+ verbose++;
+ break;
+ case 'V':
+ version++;
+ break;
+ case 'x':
+ xattr_map++;
+ break;
+ case 'X':
+ ext_fmt = hex_fmt;
+ break;
+ default:
+ usage(argv[0]);
+ break;
+ }
+ }
+ if (version) {
+ /* Print version number and exit */
+ printf("filefrag %s (%s)\n", E2FSPROGS_VERSION, E2FSPROGS_DATE);
+ if (version + verbose > 1) {
+ char flags[256] = "";
+
+ print_flags(0xffffffff, flags, sizeof(flags), 0);
+ printf("supported: %s\n", flags);
+ }
+ exit(0);
+ }
+
+ if (optind == argc)
+ usage(argv[0]);
+
+ for (cpp = argv + optind; *cpp != NULL; cpp++) {
+ int rc2 = frag_report(*cpp);
+
+ if (rc2 < 0 && rc == 0)
+ rc = rc2;
+ }
+
+ return -rc;
+}
+#endif
diff --git a/misc/findfs.8.in b/misc/findfs.8.in
new file mode 100644
index 0000000..00c33ab
--- /dev/null
+++ b/misc/findfs.8.in
@@ -0,0 +1,33 @@
+.\" -*- nroff -*-
+.\" Copyright 1993, 1994, 1995 by Theodore Ts'o. All Rights Reserved.
+.\" This file may be copied under the terms of the GNU Public License.
+.\"
+.TH FINDFS 8 "@E2FSPROGS_MONTH@ @E2FSPROGS_YEAR@" "E2fsprogs version @E2FSPROGS_VERSION@"
+.SH NAME
+findfs \- Find a file system by label or UUID
+.SH SYNOPSIS
+.B findfs
+.BI LABEL= label
+.sp
+.B findfs
+.BI UUID= uuid
+.SH DESCRIPTION
+.B findfs
+will search the disks in the system looking for a file system which has
+a label matching
+.I label
+or a UUID equal to
+.IR uuid .
+If the file system is found, the device name for the file system will
+be printed on stdout.
+.PP
+.SH AUTHOR
+.B findfs
+was written by Theodore Ts'o (tytso@mit.edu).
+.SH AVAILABILITY
+.B findfs
+is part of the e2fsprogs package and is available from
+http://e2fsprogs.sourceforge.net.
+.SH SEE ALSO
+.BR fsck (8)
+
diff --git a/misc/findsuper.c b/misc/findsuper.c
new file mode 100644
index 0000000..7e78c1f
--- /dev/null
+++ b/misc/findsuper.c
@@ -0,0 +1,269 @@
+/*
+ * findsuper --- quick hacked up program to find ext2 superblocks.
+ *
+ * This is a hack, and really shouldn't be installed anywhere. If you
+ * need a program which does this sort of functionality, please try
+ * using gpart program.
+ *
+ * Portions Copyright 1998-2000, Theodore Ts'o.
+ *
+ * Well, here's my linux version of findsuper.
+ * I'm sure you coulda done it faster. :)
+ * IMHO there isn't as much interesting data to print in the
+ * linux superblock as there is in the SunOS superblock--disk geometry is
+ * not there...and linux seems to update the dates in all the superblocks.
+ * SunOS doesn't ever touch the backup superblocks after the fs is created,
+ * as far as I can tell, so the date is more interesting IMHO and certainly
+ * marks which superblocks are backup ones.
+ *
+ * I wanted to add msdos support, but I couldn't make heads or tails
+ * of the kernel include files to find anything I could look for in msdos.
+ *
+ * Reading every block of a Sun partition is fairly quick. Doing the
+ * same under linux (slower hardware I suppose) just isn't the same.
+ * It might be more useful to default to reading the first (second?) block
+ * on each cyl; however, if the disk geometry is wrong, this is useless.
+ * But ya could still get the cyl size to print the numbers as cyls instead
+ * of blocks...
+ *
+ * run this as (for example)
+ * findsuper /dev/hda
+ * findsuper /dev/hda 437760 1024 (my disk has cyls of 855*512)
+ *
+ * I suppose the next step is to figure out a way to determine if
+ * the block found is the first superblock somehow, and if so, build
+ * a partition table from the superblocks found... but this is still
+ * useful as is.
+ *
+ * Steve
+ * ssd@nevets.oau.org
+ * ssd@mae.engr.ucf.edu
+ *
+ * Additional notes by Andreas Dilger <adilger@turbolinux.com>:
+ * - fixed to support > 2G devices by using lseek64
+ * - add reliability checking for the superblock to avoid random garbage
+ * - add adaptive progress meter
+ *
+ * It _should_ also handle signals and tell you the ending block, so
+ * that you can resume at a later time, but it doesn't yet...
+ *
+ * Note that gpart does not appear to find all superblocks that aren't aligned
+ * with the start of a possible partition, so it is not useful in systems
+ * with LVM or similar setups which don't use fat partition alignment.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ * %End-Header%
+ */
+
+/*
+ * Documentation addendum added by Andreas dwguest@win.tue.nl/aeb@cwi.nl
+ *
+ * The program findsuper is a utility that scans a disk and finds
+ * copies of ext2 superblocks (by checking for the ext2 signature).
+ *
+ * For each superblock found, it prints the offset in bytes, the
+ * offset in 1024-byte blocks, the size of the ext2 partition in fs
+ * blocks, the filesystem blocksize (in bytes), the block group number
+ * (always 0 for older ext2 systems), and a timestamp (s_mtime).
+ *
+ * This program can be used to retrieve partitions that have been
+ * lost. The superblock for block group 0 is found 1 block (2
+ * sectors) after the partition start.
+ *
+ * For new systems that have a block group number in the superblock it
+ * is immediately clear which superblock is the first of a partition.
+ * For old systems where no group numbers are given, the first
+ * superblock can be recognized by the timestamp: all superblock
+ * copies have the creation time in s_mtime, except the first, which
+ * has the last time e2fsck or tune2fs wrote to the filesystem.
+ *
+ */
+
+#define _FILE_OFFSET_BITS 64
+
+#include "config.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <time.h>
+
+#include "ext2fs/ext2_fs.h"
+#include "ext2fs/ext2fs.h"
+#include "support/nls-enable.h"
+
+#undef DEBUG
+
+#ifdef DEBUG
+#define WHY(fmt, arg...) { printf("\r%Ld: " fmt, sk, ##arg) ; continue; }
+#else
+#define WHY(fmt, arg...) { continue; }
+#endif
+
+static void usage(void)
+{
+ fprintf(stderr,
+ _("Usage: findsuper device [skipbytes [startkb]]\n"));
+ exit(1);
+}
+
+
+int main(int argc, char *argv[])
+{
+ int skiprate=512; /* one sector */
+ ext2_loff_t sk=0, skl=0;
+ int fd;
+ char *s;
+ time_t tm, last = time(0);
+ ext2_loff_t interval = 1024 * 1024;
+ int c, print_jnl_copies = 0;
+ const char * device_name;
+ struct ext2_super_block ext2;
+ /* interesting fields: EXT2_SUPER_MAGIC
+ * s_blocks_count s_log_block_size s_mtime s_magic s_lastcheck */
+
+#ifdef ENABLE_NLS
+ setlocale(LC_MESSAGES, "");
+ setlocale(LC_CTYPE, "");
+ bindtextdomain(NLS_CAT_NAME, LOCALEDIR);
+ textdomain(NLS_CAT_NAME);
+ set_com_err_gettext(gettext);
+#endif
+
+ while ((c = getopt (argc, argv, "j")) != EOF) {
+ switch (c) {
+ case 'j':
+ print_jnl_copies++;
+ break;
+ default:
+ usage();
+ }
+ }
+
+ if (optind == argc)
+ usage();
+
+ device_name = argv[optind++];
+
+ if (optind < argc) {
+ skiprate = strtol(argv[optind], &s, 0);
+ if (s == argv[optind]) {
+ fprintf(stderr,_("skipbytes should be a number, not %s\n"), s);
+ exit(1);
+ }
+ optind++;
+ }
+ if (skiprate & 0x1ff) {
+ fprintf(stderr,
+ _("skipbytes must be a multiple of the sector size\n"));
+ exit(2);
+ }
+ if (optind < argc) {
+ sk = skl = strtoll(argv[optind], &s, 0) << 10;
+ if (s == argv[optind]) {
+ fprintf(stderr,
+ _("startkb should be a number, not %s\n"), s);
+ exit(1);
+ }
+ optind++;
+ }
+ if (sk < 0) {
+ fprintf(stderr, _("startkb should be positive, not %llu\n"),sk);
+ exit(1);
+ }
+
+ fd = open(device_name, O_RDONLY);
+ if (fd < 0) {
+ perror(device_name);
+ exit(1);
+ }
+
+ /* Now, go looking for the superblock! */
+ printf(_("starting at %llu, with %u byte increments\n"), sk, skiprate);
+ if (print_jnl_copies)
+ printf(_("[*] probably superblock written in the ext3 "
+ "journal superblock,\n\tso start/end/grp wrong\n"));
+ printf(_("byte_offset byte_start byte_end fs_blocks blksz grp mkfs/mount_time sb_uuid label\n"));
+ for (; lseek64(fd, sk, SEEK_SET) != -1 &&
+ read(fd, &ext2, 512) == 512; sk += skiprate) {
+ static unsigned char last_uuid[16] = "blah";
+ unsigned long long bsize, grpsize;
+ int jnl_copy, sb_offset;
+
+ if (sk && !(sk & (interval - 1))) {
+ time_t now, diff;
+
+ now = time(0);
+ diff = now - last;
+
+ if (diff > 0) {
+ s = ctime(&now);
+ s[24] = 0;
+ printf("\r%11Lu: %8LukB/s @ %s", sk,
+ (((sk - skl)) / diff) >> 10, s);
+ fflush(stdout);
+ }
+ if (diff < 5)
+ interval <<= 1;
+ else if (diff > 20)
+ interval >>= 1;
+ last = now;
+ skl = sk;
+ }
+ if (ext2.s_magic != EXT2_SUPER_MAGIC)
+ continue;
+ if (ext2.s_log_block_size > 6)
+ WHY("log block size > 6 (%u)\n", ext2.s_log_block_size);
+ if (ext2fs_r_blocks_count(&ext2) > ext2fs_blocks_count(&ext2))
+ WHY("r_blocks_count > blocks_count (%u > %u)\n",
+ ext2fs_r_blocks_count(&ext2),
+ ext2fs_blocks_count(&ext2));
+ if (ext2fs_free_blocks_count(&ext2) > ext2fs_blocks_count(&ext2))
+ WHY("free_blocks_count > blocks_count\n (%u > %u)\n",
+ ext2fs_free_blocks_count(&ext2),
+ ext2fs_blocks_count(&ext2));
+ if (ext2.s_free_inodes_count > ext2.s_inodes_count)
+ WHY("free_inodes_count > inodes_count (%u > %u)\n",
+ ext2.s_free_inodes_count, ext2.s_inodes_count);
+
+ if (ext2.s_mkfs_time != 0)
+ tm = ext2.s_mkfs_time;
+ else
+ tm = ext2.s_mtime;
+ s = ctime(&tm);
+ s[24] = 0;
+ bsize = 1 << (ext2.s_log_block_size + 10);
+ grpsize = bsize * ext2.s_blocks_per_group;
+ if (memcmp(ext2.s_uuid, last_uuid, sizeof(last_uuid)) == 0 &&
+ ext2.s_rev_level > 0 && ext2.s_block_group_nr == 0) {
+ jnl_copy = 1;
+ } else {
+ jnl_copy = 0;
+ memcpy(last_uuid, ext2.s_uuid, sizeof(last_uuid));
+ }
+ if (ext2.s_block_group_nr == 0 || bsize == 1024)
+ sb_offset = 1024;
+ else
+ sb_offset = 0;
+ if (jnl_copy && !print_jnl_copies)
+ continue;
+ printf("\r%11Lu %11Lu%s %11Lu%s %9u %5Lu %4u%s %s %02x%02x%02x%02x %.*s\n",
+ sk, sk - ext2.s_block_group_nr * grpsize - sb_offset,
+ jnl_copy ? "*":" ",
+ sk + ext2fs_blocks_count(&ext2) * bsize -
+ ext2.s_block_group_nr * grpsize - sb_offset,
+ jnl_copy ? "*" : " ", ext2fs_blocks_count(&ext2), bsize,
+ ext2.s_block_group_nr, jnl_copy ? "*" : " ", s,
+ ext2.s_uuid[0], ext2.s_uuid[1],
+ ext2.s_uuid[2], ext2.s_uuid[3],
+ EXT2_LEN_STR(ext2.s_volume_name));
+ }
+ printf(_("\n%11Lu: finished with errno %d\n"), sk, errno);
+ close(fd);
+
+ return errno;
+}
diff --git a/misc/fsck.8.in b/misc/fsck.8.in
new file mode 100644
index 0000000..c1890df
--- /dev/null
+++ b/misc/fsck.8.in
@@ -0,0 +1,414 @@
+.\" -*- nroff -*-
+.\" Copyright 1993, 1994, 1995 by Theodore Ts'o. All Rights Reserved.
+.\" This file may be copied under the terms of the GNU Public License.
+.\"
+.TH FSCK 8 "@E2FSPROGS_MONTH@ @E2FSPROGS_YEAR@" "E2fsprogs version @E2FSPROGS_VERSION@"
+.SH NAME
+fsck \- check and repair a Linux file system
+.SH SYNOPSIS
+.B fsck
+[
+.B \-sAVRTMNP
+]
+[
+.B \-C
+[
+.I fd
+]
+]
+[
+.B \-t
+.I fstype
+]
+.I [filesys ... ]
+[\-\-] [
+.B fs-specific-options
+]
+.SH DESCRIPTION
+.B fsck
+is used to check and optionally repair one or more Linux file systems.
+.I filesys
+can be a device name (e.g.
+.IR /dev/hdc1 ", " /dev/sdb2 ),
+a mount point (e.g.
+.IR / ", " /usr ", " /home ),
+or an ext2 label or UUID specifier (e.g.
+UUID=8868abf6-88c5-4a83-98b8-bfc24057f7bd or LABEL=root).
+Normally, the
+.B fsck
+program will try to handle file systems on different physical disk drives
+in parallel to reduce the total amount of time needed to check all of the
+file systems.
+.PP
+If no file systems are specified on the command line, and the
+.B \-A
+option is not specified,
+.B fsck
+will default to checking file systems in
+.B /etc/fstab
+serially. This is equivalent to the
+.B \-As
+options.
+.PP
+The exit code returned by
+.B fsck
+is the sum of the following conditions:
+.br
+\ 0\ \-\ No errors
+.br
+\ 1\ \-\ File system errors corrected
+.br
+\ 2\ \-\ System should be rebooted
+.br
+\ 4\ \-\ File system errors left uncorrected
+.br
+\ 8\ \-\ Operational error
+.br
+\ 16\ \-\ Usage or syntax error
+.br
+\ 32\ \-\ Fsck canceled by user request
+.br
+\ 128\ \-\ Shared library error
+.br
+The exit code returned when multiple file systems are checked
+is the bit-wise OR of the exit codes for each
+file system that is checked.
+.PP
+In actuality,
+.B fsck
+is simply a front-end for the various file system checkers
+(\fBfsck\fR.\fIfstype\fR) available under Linux. The file
+system-specific checker is searched for in
+.I /sbin
+first, then in
+.I /etc/fs
+and
+.IR /etc ,
+and finally in the directories listed in the PATH environment
+variable. Please see the file system-specific checker manual pages for
+further details.
+.SH OPTIONS
+.TP
+.B \-s
+Serialize
+.B fsck
+operations. This is a good idea if you are checking multiple
+file systems and the checkers are in an interactive mode. (Note:
+.BR e2fsck (8)
+runs in an interactive mode by default. To make
+.BR e2fsck (8)
+run in a non-interactive mode, you must either specify the
+.B \-p
+or
+.B \-a
+option, if you wish for errors to be corrected automatically, or the
+.B \-n
+option if you do not.)
+.TP
+.BI \-t " fslist"
+Specifies the type(s) of file system to be checked. When the
+.B \-A
+flag is specified, only file systems that match
+.I fslist
+are checked. The
+.I fslist
+parameter is a comma-separated list of file systems and options
+specifiers. All of the file systems in this comma-separated list may be
+prefixed by a negation operator
+.RB ' no '
+or
+.RB ' ! ',
+which requests that only those file systems not listed in
+.I fslist
+will be checked. If all of the file systems in
+.I fslist
+are not prefixed by a negation operator, then only those file systems
+listed
+in
+.I fslist
+will be checked.
+.sp
+Options specifiers may be included in the comma-separated
+.IR fslist .
+They must have the format
+.BI opts= fs-option\fR.
+If an options specifier is present, then only file systems which contain
+.I fs-option
+in their mount options field of
+.B /etc/fstab
+will be checked. If the options specifier is prefixed by a negation
+operator, then only
+those file systems that do not have
+.I fs-option
+in their mount options field of
+.B /etc/fstab
+will be checked.
+.sp
+For example, if
+.B opts=ro
+appears in
+.IR fslist ,
+then only file systems listed in
+.B /etc/fstab
+with the
+.B ro
+option will be checked.
+.sp
+For compatibility with Mandrake distributions whose boot scripts
+depend upon an unauthorized UI change to the
+.B fsck
+program, if a file system type of
+.B loop
+is found in
+.IR fslist ,
+it is treated as if
+.B opts=loop
+were specified as an argument to the
+.B \-t
+option.
+.sp
+Normally, the file system type is deduced by searching for
+.I filesys
+in the
+.I /etc/fstab
+file and using the corresponding entry.
+If the type can not be deduced, and there is only a single file system
+given as an argument to the
+.B \-t
+option,
+.B fsck
+will use the specified file system type. If this type is not
+available, then the default file system type (currently ext2) is used.
+.TP
+.B \-A
+Walk through the
+.I /etc/fstab
+file and try to check all file systems in one run. This option is
+typically used from the
+.I /etc/rc
+system initialization file, instead of multiple commands for checking
+a single file system.
+.sp
+The root file system will be checked first unless the
+.B \-P
+option is specified (see below). After that,
+file systems will be checked in the order specified by the
+.I fs_passno
+(the sixth) field in the
+.I /etc/fstab
+file.
+File Systems with a
+.I fs_passno
+value of 0 are skipped and are not checked at all. File Systems with a
+.I fs_passno
+value of greater than zero will be checked in order,
+with file systems with the lowest
+.I fs_passno
+number being checked first.
+If there are multiple file systems with the same pass number,
+fsck will attempt to check them in parallel, although it will avoid running
+multiple file system checks on the same physical disk.
+.sp
+Hence, a very common configuration in
+.I /etc/fstab
+files is to set the root file system to have a
+.I fs_passno
+value of 1
+and to set all other file systems to have a
+.I fs_passno
+value of 2. This will allow
+.B fsck
+to automatically run file system 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 file system
+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.
+.TP
+.B \-C\fR [ \fI "fd" \fR ]
+Display completion/progress bars for those file system checkers (currently
+only for ext2 and ext3) which support them. Fsck will manage the
+file system checkers so that only one of them will display
+a progress bar at a time. GUI front-ends may specify a file descriptor
+.IR fd ,
+in which case the progress bar information will be sent to that file descriptor.
+.TP
+.B \-M
+Do not check mounted file systems and return an exit code of 0
+for mounted file systems.
+.TP
+.B \-N
+Don't execute, just show what would be done.
+.TP
+.B \-P
+When the
+.B \-A
+flag is set, check the root file system in parallel with the other file systems.
+This is not the safest thing in the world to do,
+since if the root file system is in doubt things like the
+.BR e2fsck (8)
+executable might be corrupted! This option is mainly provided
+for those sysadmins who don't want to repartition the root
+file system to be small and compact (which is really the right solution).
+.TP
+.B \-R
+When checking all file systems with the
+.B \-A
+flag, skip the root file system (in case it's already mounted read-write).
+.TP
+.B \-T
+Don't show the title on startup.
+.TP
+.B \-V
+Produce verbose output, including all file system-specific commands
+that are executed.
+.TP
+.B fs-specific-options
+Options which are not understood by
+.B fsck
+are passed to the file system-specific checker. These arguments
+.B must
+not take arguments, as there is no
+way for
+.B fsck
+to be able to properly guess which arguments take options and which
+don't.
+.IP
+Options and arguments which follow the
+.B \-\-
+are treated as file system-specific options to be passed to the
+file system-specific checker.
+.IP
+Please note that fsck is not
+designed to pass arbitrarily complicated options to file system-specific
+checkers. If you're doing something complicated, please just
+execute the file system-specific checker directly. If you pass
+.B fsck
+some horribly complicated option and arguments, and it doesn't do
+what you expect,
+.B don't bother reporting it as a bug.
+You're almost certainly doing something that you shouldn't be doing
+with
+.BR fsck.
+.PP
+Options to different file system-specific fsck's are not standardized.
+If in doubt, please consult the man pages of the file system-specific
+checker. Although not guaranteed, the following options are supported
+by most file system checkers:
+.TP
+.B \-a
+Automatically repair the file system without any questions (use
+this option with caution). Note that
+.BR e2fsck (8)
+supports
+.B \-a
+for backwards compatibility only. This option is mapped to
+.BR e2fsck 's
+.B \-p
+option which is safe to use, unlike the
+.B \-a
+option that some file system checkers support.
+.TP
+.B \-n
+For some file system-specific checkers, the
+.B \-n
+option will cause the fs-specific fsck to avoid attempting to repair any
+problems, but simply report such problems to stdout. This is however
+not true for all file system-specific checkers. In particular,
+.BR fsck.reiserfs (8)
+will not report any corruption if given this option.
+.BR fsck.minix (8)
+does not support the
+.B \-n
+option at all.
+.TP
+.B \-r
+Interactively repair the file system (ask for confirmations). Note: It
+is generally a bad idea to use this option if multiple fsck's are being
+run in parallel. Also note that this is
+.BR e2fsck 's
+default behavior; it supports this option for backwards compatibility
+reasons only.
+.TP
+.B \-y
+For some file system-specific checkers, the
+.B \-y
+option will cause the fs-specific fsck to always attempt to fix any
+detected file system corruption automatically. Sometimes an expert may
+be able to do better driving the fsck manually. Note that
+.B not
+all file system-specific checkers implement this option. In particular
+.BR fsck.minix (8)
+and
+.BR fsck.cramfs (8)
+does not support the
+.B -y
+option as of this writing.
+.SH AUTHOR
+Theodore Ts'o (tytso@mit.edu)
+.SH FILES
+.IR /etc/fstab .
+.SH ENVIRONMENT VARIABLES
+The
+.B fsck
+program's behavior is affected by the following environment variables:
+.TP
+.B FSCK_FORCE_ALL_PARALLEL
+If this environment variable is set,
+.B fsck
+will attempt to run all of the specified file systems in parallel,
+regardless of whether the file systems 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.)
+.TP
+.B FSCK_MAX_INST
+This environment variable will limit the maximum number of file system
+checkers that can be running at one time. This allows configurations
+which have a large number of disks to avoid
+.B fsck
+starting too many file system checkers at once, which might overload
+CPU and memory resources available on the system. If this value is
+zero, then an unlimited number of processes can be spawned. This is
+currently the default, but future versions of
+.B fsck
+may attempt to automatically determine how many file system checks can
+be run based on gathering accounting data from the operating system.
+.TP
+.B PATH
+The
+.B PATH
+environment variable is used to find file system checkers. A set of
+system directories are searched first:
+.BR /sbin ,
+.BR /sbin/fs.d ,
+.BR /sbin/fs ,
+.BR /etc/fs ,
+and
+.BR /etc .
+Then the set of directories found in the
+.B PATH
+environment are searched.
+.TP
+.B FSTAB_FILE
+This environment variable allows the system administrator
+to override the standard location of the
+.B /etc/fstab
+file. It is also useful for developers who are testing
+.BR fsck .
+.SH SEE ALSO
+.BR fstab (5),
+.BR mkfs (8),
+.BR fsck.ext2 (8)
+or
+.BR fsck.ext3 (8)
+or
+.BR e2fsck (8),
+.BR cramfsck (8),
+.BR fsck.minix (8),
+.BR fsck.msdos (8),
+.BR fsck.jfs (8),
+.BR fsck.nfs (8),
+.BR fsck.vfat (8),
+.BR fsck.xfs (8),
+.BR fsck.xiafs (8),
+.BR reiserfsck (8).
diff --git a/misc/fsck.c b/misc/fsck.c
new file mode 100644
index 0000000..1769a10
--- /dev/null
+++ b/misc/fsck.c
@@ -0,0 +1,1353 @@
+/*
+ * pfsck --- 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.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ * %End-Header%
+ */
+
+#define _XOPEN_SOURCE 600 /* for inclusion of sa_handler in Solaris */
+
+#include "config.h"
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <limits.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+#if HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#if HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#if HAVE_PATHS_H
+#include <paths.h>
+#endif
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#if HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#if HAVE_MALLOC_H
+#include <malloc.h>
+#endif
+#ifdef HAVE_SIGNAL_H
+#include <signal.h>
+#endif
+
+#include "../version.h"
+#include "support/devname.h"
+#include "support/nls-enable.h"
+#include "fsck.h"
+#include "blkid/blkid.h"
+
+#ifndef _PATH_MNTTAB
+#define _PATH_MNTTAB "/etc/fstab"
+#endif
+
+static const char *ignored_types[] = {
+ "ignore",
+ "iso9660",
+ "nfs",
+ "proc",
+ "sw",
+ "swap",
+ "tmpfs",
+ "devpts",
+ NULL
+};
+
+static const char *really_wanted[] = {
+ "minix",
+ "ext2",
+ "ext3",
+ "ext4",
+ "ext4dev",
+ "jfs",
+ "reiserfs",
+ "xiafs",
+ "xfs",
+ NULL
+};
+
+#define BASE_MD "/dev/md"
+
+/*
+ * Global variables for options
+ */
+static char *devices[MAX_DEVICES];
+static char *args[MAX_ARGS];
+static int num_devices, num_args;
+
+static int verbose = 0;
+static int doall = 0;
+static int noexecute = 0;
+static int serialize = 0;
+static int skip_root = 0;
+static int ignore_mounted = 0;
+static int notitle = 0;
+static int parallel_root = 0;
+static int progress = 0;
+static int progress_fd = 0;
+static int force_all_parallel = 0;
+static int num_running = 0;
+static int max_running = 0;
+static volatile int cancel_requested = 0;
+static int kill_sent = 0;
+static char *progname;
+static char *fstype = NULL;
+static struct fs_info *filesys_info = NULL, *filesys_last = NULL;
+static struct fsck_instance *instance_list;
+static const char *fsck_prefix_path = "/sbin:/sbin/fs.d:/sbin/fs:/etc/fs:/etc";
+static char *fsck_path = 0;
+static blkid_cache cache = NULL;
+
+static char *string_copy(const char *s)
+{
+ char *ret;
+
+ if (!s)
+ return 0;
+ ret = malloc(strlen(s)+1);
+ if (ret)
+ strcpy(ret, s);
+ return ret;
+}
+
+static int string_to_int(const char *s)
+{
+ long l;
+ char *p;
+
+ l = strtol(s, &p, 0);
+ if (*p || l == LONG_MIN || l == LONG_MAX || l < 0 || l > INT_MAX)
+ return -1;
+ else
+ return (int) l;
+}
+
+static int ignore(struct fs_info *);
+
+static char *skip_over_blank(char *cp)
+{
+ while (*cp && isspace(*cp))
+ cp++;
+ return cp;
+}
+
+static char *skip_over_word(char *cp)
+{
+ while (*cp && !isspace(*cp))
+ cp++;
+ return cp;
+}
+
+static void strip_line(char *line)
+{
+ char *p;
+
+ while (*line) {
+ p = line + strlen(line) - 1;
+ if ((*p == '\n') || (*p == '\r'))
+ *p = 0;
+ else
+ break;
+ }
+}
+
+static char *parse_word(char **buf)
+{
+ char *word, *next;
+
+ word = *buf;
+ if (*word == 0)
+ return 0;
+
+ word = skip_over_blank(word);
+ next = skip_over_word(word);
+ if (*next)
+ *next++ = 0;
+ *buf = next;
+ return word;
+}
+
+static void parse_escape(char *word)
+{
+ char *p, *q;
+ int ac, i;
+
+ if (!word)
+ return;
+
+ for (p = word, q = word; *p; p++, q++) {
+ *q = *p;
+ if (*p != '\\')
+ continue;
+ if (*++p == 0)
+ break;
+ if (*p == 't') {
+ *q = '\t';
+ continue;
+ }
+ if (*p == 'n') {
+ *q = '\n';
+ continue;
+ }
+ if (!isdigit(*p)) {
+ *q = *p;
+ continue;
+ }
+ ac = 0;
+ for (i = 0; i < 3; i++, p++) {
+ if (!isdigit(*p))
+ break;
+ ac = (ac * 8) + (*p - '0');
+ }
+ *q = ac;
+ p--;
+ }
+ *q = 0;
+}
+
+static void free_instance(struct fsck_instance *i)
+{
+ free(i->prog);
+ free(i->device);
+ free(i->base_device);
+ free(i);
+ return;
+}
+
+static struct fs_info *create_fs_device(const char *device, const char *mntpnt,
+ const char *type, const char *opts,
+ int freq, int passno)
+{
+ struct fs_info *fs;
+
+ if (!(fs = malloc(sizeof(struct fs_info))))
+ return NULL;
+
+ fs->device = string_copy(device);
+ fs->mountpt = string_copy(mntpnt);
+ fs->type = string_copy(type);
+ fs->opts = string_copy(opts ? opts : "");
+ fs->freq = freq;
+ fs->passno = passno;
+ fs->flags = 0;
+ fs->next = NULL;
+
+ if (!filesys_info)
+ filesys_info = fs;
+ else
+ filesys_last->next = fs;
+ filesys_last = fs;
+
+ return fs;
+}
+
+
+
+static int parse_fstab_line(char *line, struct fs_info **ret_fs)
+{
+ char *dev, *device, *mntpnt, *type, *opts, *freq, *passno, *cp;
+ struct fs_info *fs;
+
+ *ret_fs = 0;
+ strip_line(line);
+ cp = line;
+
+ device = parse_word(&cp);
+ if (!device || *device == '#')
+ return 0; /* Ignore blank lines and comments */
+ mntpnt = parse_word(&cp);
+ type = parse_word(&cp);
+ opts = parse_word(&cp);
+ freq = parse_word(&cp);
+ passno = parse_word(&cp);
+
+ if (!mntpnt || !type)
+ return -1;
+
+ parse_escape(device);
+ parse_escape(mntpnt);
+ parse_escape(type);
+ parse_escape(opts);
+ parse_escape(freq);
+ parse_escape(passno);
+
+ dev = get_devname(cache, device, NULL);
+ if (dev)
+ device = dev;
+
+ if (strchr(type, ','))
+ type = 0;
+
+ fs = create_fs_device(device, mntpnt, type ? type : "auto", opts,
+ freq ? atoi(freq) : -1,
+ passno ? atoi(passno) : -1);
+ free(dev);
+
+ if (!fs)
+ return -1;
+ *ret_fs = fs;
+ return 0;
+}
+
+static void interpret_type(struct fs_info *fs)
+{
+ char *t;
+
+ if (strcmp(fs->type, "auto") != 0)
+ return;
+ t = blkid_get_tag_value(cache, "TYPE", fs->device);
+ if (t) {
+ free(fs->type);
+ fs->type = t;
+ }
+}
+
+/*
+ * Load the filesystem database from /etc/fstab
+ */
+static void load_fs_info(const char *filename)
+{
+ FILE *f;
+ char buf[1024];
+ int lineno = 0;
+ int old_fstab = 1;
+ struct fs_info *fs;
+
+ if ((f = fopen(filename, "r")) == NULL) {
+ fprintf(stderr, _("WARNING: couldn't open %s: %s\n"),
+ filename, strerror(errno));
+ return;
+ }
+ while (!feof(f)) {
+ lineno++;
+ if (!fgets(buf, sizeof(buf), f))
+ break;
+ buf[sizeof(buf)-1] = 0;
+ if (parse_fstab_line(buf, &fs) < 0) {
+ fprintf(stderr, _("WARNING: bad format "
+ "on line %d of %s\n"), lineno, filename);
+ continue;
+ }
+ if (!fs)
+ continue;
+ if (fs->passno < 0)
+ fs->passno = 0;
+ else
+ old_fstab = 0;
+ }
+
+ fclose(f);
+
+ if (old_fstab && filesys_info) {
+ fputs("\007\007\007", stderr);
+ fputs(_(
+ "WARNING: Your /etc/fstab does not contain the fsck passno\n"
+ " field. I will kludge around things for you, but you\n"
+ " should fix your /etc/fstab file as soon as you can.\n\n"), stderr);
+
+ for (fs = filesys_info; fs; fs = fs->next) {
+ fs->passno = 1;
+ }
+ }
+}
+
+/* Lookup filesys in /etc/fstab and return the corresponding entry. */
+static struct fs_info *lookup(char *filesys)
+{
+ struct fs_info *fs;
+
+ /* No filesys name given. */
+ if (filesys == NULL)
+ return NULL;
+
+ for (fs = filesys_info; fs; fs = fs->next) {
+ if (!strcmp(filesys, fs->device) ||
+ (fs->mountpt && !strcmp(filesys, fs->mountpt)))
+ break;
+ }
+
+ return fs;
+}
+
+/* Find fsck program for a given fs type. */
+static char *find_fsck(char *type)
+{
+ char *s;
+ const char *tpl;
+ static char prog[256];
+ char *p = string_copy(fsck_path);
+ struct stat st;
+
+ /* 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, ":")) {
+ int len = snprintf(prog, sizeof(prog), tpl, s, type);
+
+ if ((len < 0) || (len >= (int) sizeof(prog)))
+ continue;
+ if (stat(prog, &st) == 0)
+ break;
+ }
+ free(p);
+ return(s ? prog : NULL);
+}
+
+static int progress_active(NOARGS)
+{
+ 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;
+}
+
+/*
+ * Execute a particular fsck program, and link it into the list of
+ * child processes we are waiting for.
+ */
+static int execute(const char *type, const char *device, const char *mntpt,
+ int interactive)
+{
+ char *s, *argv[80], prog[256];
+ int argc, i, len;
+ struct fsck_instance *inst, *p;
+ pid_t pid;
+
+ len = snprintf(prog, sizeof(prog), "fsck.%s", type);
+ if ((len < 0) || (len >= (int) sizeof(prog)))
+ return EINVAL;
+
+ inst = malloc(sizeof(struct fsck_instance));
+ if (!inst)
+ return ENOMEM;
+ memset(inst, 0, sizeof(struct fsck_instance));
+
+ argv[0] = string_copy(prog);
+ argc = 1;
+
+ for (i=0; i <num_args; i++)
+ argv[argc++] = string_copy(args[i]);
+
+ if (progress) {
+ if ((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++] = string_copy(tmp);
+ }
+ }
+
+ argv[argc++] = string_copy(device);
+ argv[argc] = 0;
+
+ s = find_fsck(prog);
+ if (s == NULL) {
+ fprintf(stderr, _("fsck: %s: not found\n"), prog);
+ free(inst);
+ return ENOENT;
+ }
+
+ if (verbose || noexecute) {
+ printf("[%s (%d) -- %s] ", s, num_running,
+ mntpt ? mntpt : device);
+ for (i=0; i < argc; i++)
+ printf("%s ", argv[i]);
+ printf("\n");
+ }
+
+ /* Fork and execute the correct program. */
+ if (noexecute)
+ pid = -1;
+ else if ((pid = fork()) < 0) {
+ perror("fork");
+ free(inst);
+ return errno;
+ } else if (pid == 0) {
+ if (!interactive)
+ close(0);
+ (void) execv(s, argv);
+ perror(argv[0]);
+ free(inst);
+ exit(EXIT_ERROR);
+ }
+
+ for (i=0; i < argc; i++)
+ free(argv[i]);
+
+ inst->pid = pid;
+ inst->prog = string_copy(prog);
+ inst->type = string_copy(type);
+ inst->device = string_copy(device);
+ inst->base_device = base_device(device);
+ inst->start_time = time(0);
+ 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;
+ int sig;
+ struct fsck_instance *inst, *inst2, *prev;
+ pid_t pid;
+
+ if (!instance_list)
+ return NULL;
+
+ if (noexecute) {
+ inst = instance_list;
+ prev = 0;
+#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 = waitpid(-1, &status, flags);
+ 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) {
+ fprintf(stderr,
+ _("%s: wait: No more child process?!?\n"),
+ progname);
+ return NULL;
+ }
+ perror("wait");
+ continue;
+ }
+ for (prev = 0, 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 = EXIT_UNCORRECTED;
+ } else {
+ printf(_("Warning... %s for device %s exited "
+ "with signal %d.\n"),
+ inst->prog, inst->device, sig);
+ status = EXIT_ERROR;
+ }
+ } else {
+ printf(_("%s %s: status is %x, should never happen.\n"),
+ inst->prog, inst->device, status);
+ status = EXIT_ERROR;
+ }
+ inst->exit_status = status;
+ inst->flags |= FLAG_DONE;
+ if (progress && (inst->flags & FLAG_PROGRESS) &&
+ !progress_active()) {
+ for (inst2 = instance_list; inst2; inst2 = inst2->next) {
+ if (inst2->flags & FLAG_DONE)
+ continue;
+ if (strcmp(inst2->type, "ext2") &&
+ strcmp(inst2->type, "ext3") &&
+ strcmp(inst2->type, "ext4") &&
+ strcmp(inst2->type, "ext4dev"))
+ continue;
+ /*
+ * If we've just started the fsck, wait a tiny
+ * bit before sending the kill, to give it
+ * time to set up the signal handler
+ */
+ if (inst2->start_time < time(0)+2) {
+ if (fork() == 0) {
+ sleep(1);
+ kill(inst2->pid, SIGUSR1);
+ exit(0);
+ }
+ } else
+ kill(inst2->pid, SIGUSR1);
+ inst2->flags |= FLAG_PROGRESS;
+ break;
+ }
+ }
+ret_inst:
+ if (prev)
+ prev->next = inst->next;
+ else
+ instance_list = inst->next;
+ if (verbose > 1)
+ printf(_("Finished with %s (exit status %d)\n"),
+ inst->device, 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 void fsck_device(struct fs_info *fs, int interactive)
+{
+ const char *type;
+ int retval;
+
+ interpret_type(fs);
+
+ if (strcmp(fs->type, "auto") != 0)
+ type = fs->type;
+ else if (fstype && strncmp(fstype, "no", 2) &&
+ strncmp(fstype, "opts=", 5) && strncmp(fstype, "loop", 4) &&
+ !strchr(fstype, ','))
+ type = fstype;
+ else
+ type = DEFAULT_FSTYPE;
+
+ num_running++;
+ retval = execute(type, fs->device, fs->mountpt, interactive);
+ if (retval) {
+ fprintf(stderr, _("%s: Error %d while executing fsck.%s "
+ "for %s\n"), progname, retval, type, fs->device);
+ num_running--;
+ }
+}
+
+
+/*
+ * 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 const char *fs_type_syntax_error =
+N_("Either all or none of the filesystem types passed to -t must be prefixed\n"
+ "with 'no' or '!'.\n");
+
+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 = malloc(num * sizeof(char *));
+ cmp->type = malloc(num * sizeof(int));
+ if (!cmp->list || !cmp->type) {
+ fputs(_("Couldn't allocate memory for filesystem types\n"),
+ stderr);
+ exit(EXIT_ERROR);
+ }
+ memset(cmp->list, 0, num * sizeof(char *));
+ memset(cmp->type, 0, num * sizeof(int));
+ cmp->negate = 0;
+
+ if (!fs_type)
+ return;
+
+ list = string_copy(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)) {
+ fputs(_(fs_type_syntax_error), stderr);
+ exit(EXIT_USAGE);
+ }
+ }
+#if 0
+ printf("Adding %s to list (type %d).\n", s, cmp->type[num]);
+#endif
+ cmp->list[num++] = string_copy(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, char *optlist)
+{
+ char *list, *s;
+
+ if (!optlist)
+ return 0;
+ list = string_copy(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 fs_info *fs, struct fs_type_compile *cmp)
+{
+ int n, ret = 0, checked_type = 0;
+ char *cp;
+
+ if (cmp->list == 0 || cmp->list[0] == 0)
+ return 1;
+
+ for (n=0; (cp = cmp->list[n]); n++) {
+ switch (cmp->type[n]) {
+ case FS_TYPE_NORMAL:
+ checked_type++;
+ if (strcmp(cp, fs->type) == 0) {
+ ret = 1;
+ }
+ break;
+ case FS_TYPE_NEGOPT:
+ if (opt_in_list(cp, fs->opts))
+ return 0;
+ break;
+ case FS_TYPE_OPT:
+ if (!opt_in_list(cp, fs->opts))
+ return 0;
+ break;
+ }
+ }
+ if (checked_type == 0)
+ return 1;
+ return (cmp->negate ? !ret : ret);
+}
+
+/* Check if we should ignore this filesystem. */
+static int ignore(struct fs_info *fs)
+{
+ const char **ip;
+ int wanted = 0;
+
+ /*
+ * If the pass number is 0, ignore it.
+ */
+ if (fs->passno == 0)
+ return 1;
+
+ /*
+ * If this is a bind mount, ignore it.
+ */
+ if (opt_in_list("bind", fs->opts)) {
+ fprintf(stderr,
+ _("%s: skipping bad line in /etc/fstab: bind mount with nonzero fsck pass number\n"),
+ fs->mountpt);
+ return 1;
+ }
+
+ 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;
+
+ /* Are we ignoring this type? */
+ for(ip = ignored_types; *ip; ip++)
+ if (strcmp(fs->type, *ip) == 0) return 1;
+
+ /* Do we really really want to check this fs? */
+ for(ip = really_wanted; *ip; ip++)
+ if (strcmp(fs->type, *ip) == 0) {
+ wanted = 1;
+ break;
+ }
+
+ /* See if the <fsck.fs> program is available. */
+ if (find_fsck(fs->type) == NULL) {
+ if (wanted)
+ fprintf(stderr, _("fsck: cannot check %s: fsck.%s not found\n"),
+ fs->device, fs->type);
+ return 1;
+ }
+
+ /* We can and want to check this file system type. */
+ return 0;
+}
+
+/*
+ * Returns TRUE if a partition on the same disk is already being
+ * checked.
+ */
+static int device_already_active(char *device)
+{
+ struct fsck_instance *inst;
+ char *base;
+
+ if (force_all_parallel)
+ return 0;
+
+#ifdef BASE_MD
+ /* Don't check a soft raid disk with any other disk */
+ if (instance_list &&
+ (!strncmp(instance_list->device, BASE_MD, sizeof(BASE_MD)-1) ||
+ !strncmp(device, BASE_MD, sizeof(BASE_MD)-1)))
+ return 1;
+#endif
+
+ base = base_device(device);
+ /*
+ * If we don't know the base device, assume that the device is
+ * already active if there are any fsck instances running.
+ */
+ if (!base)
+ return (instance_list != 0);
+ for (inst = instance_list; inst; inst = inst->next) {
+ if (!inst->base_device || !strcmp(base, inst->base_device)) {
+ free(base);
+ return 1;
+ }
+ }
+ free(base);
+ return 0;
+}
+
+/* Check all file systems, using the /etc/fstab table. */
+static int check_all(NOARGS)
+{
+ struct fs_info *fs = NULL;
+ int status = EXIT_OK;
+ int not_done_yet = 1;
+ int passno = 1;
+ int pass_done;
+
+ if (verbose)
+ fputs(_("Checking all file systems.\n"), stdout);
+
+ /*
+ * 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()).
+ */
+ for (fs = filesys_info; fs; fs = fs->next) {
+ if (ignore(fs))
+ fs->flags |= FLAG_DONE;
+ }
+
+ /*
+ * Find and check the root filesystem.
+ */
+ if (!parallel_root) {
+ for (fs = filesys_info; fs; fs = fs->next) {
+ if (!strcmp(fs->mountpt, "/"))
+ break;
+ }
+ if (fs) {
+ if (!skip_root && !ignore(fs) &&
+ !(ignore_mounted && is_mounted(fs->device))) {
+ fsck_device(fs, 1);
+ status |= wait_many(FLAG_WAIT_ALL);
+ if (status > EXIT_NONDESTRUCT)
+ return status;
+ }
+ fs->flags |= FLAG_DONE;
+ }
+ }
+ /*
+ * This is for the bone-headed user who enters the root
+ * filesystem twice. Skip root will skip all root entries.
+ */
+ if (skip_root)
+ for (fs = filesys_info; fs; fs = fs->next)
+ if (!strcmp(fs->mountpt, "/"))
+ fs->flags |= FLAG_DONE;
+
+ while (not_done_yet) {
+ not_done_yet = 0;
+ pass_done = 1;
+
+ for (fs = filesys_info; fs; fs = fs->next) {
+ if (cancel_requested)
+ break;
+ if (fs->flags & FLAG_DONE)
+ continue;
+ /*
+ * If the filesystem's pass number is higher
+ * than the current pass number, then we don't
+ * do it yet.
+ */
+ if (fs->passno > passno) {
+ not_done_yet++;
+ continue;
+ }
+ if (ignore_mounted && is_mounted(fs->device)) {
+ fs->flags |= FLAG_DONE;
+ continue;
+ }
+ /*
+ * If a filesystem on a particular device has
+ * already been spawned, then we need to defer
+ * this to another pass.
+ */
+ if (device_already_active(fs->device)) {
+ pass_done = 0;
+ continue;
+ }
+ /*
+ * Spawn off the fsck process
+ */
+ fsck_device(fs, serialize);
+ fs->flags |= FLAG_DONE;
+
+ /*
+ * 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);
+ return status;
+}
+
+static void usage(NOARGS)
+{
+ fputs(_("Usage: fsck [-AMNPRTV] [ -C [ fd ] ] [-t fstype] [fs-options] [filesys ...]\n"), stderr);
+ exit(EXIT_USAGE);
+}
+
+#ifdef HAVE_SIGNAL_H
+static void signal_cancel(int sig FSCK_ATTR((unused)))
+{
+ cancel_requested++;
+}
+#endif
+
+static void PRS(int argc, char *argv[])
+{
+ int i, j;
+ char *arg, *dev, *tmp = 0;
+ char options[128];
+ int opt = 0;
+ int opts_for_fsck = 0;
+#ifdef HAVE_SIGNAL_H
+ struct sigaction sa;
+
+ /*
+ * Set up signal action
+ */
+ memset(&sa, 0, sizeof(struct sigaction));
+ sa.sa_handler = signal_cancel;
+ sigaction(SIGINT, &sa, 0);
+ sigaction(SIGTERM, &sa, 0);
+#endif
+
+ num_devices = 0;
+ num_args = 0;
+ instance_list = 0;
+
+ progname = argv[0];
+
+ for (i=1; i < argc; i++) {
+ arg = argv[i];
+ if (!arg)
+ continue;
+ if ((arg[0] == '/' && !opts_for_fsck) || strchr(arg, '=')) {
+ if (num_devices >= MAX_DEVICES) {
+ fprintf(stderr, _("%s: too many devices\n"),
+ progname);
+ exit(EXIT_ERROR);
+ }
+ dev = get_devname(cache, arg, NULL);
+ if (!dev && strchr(arg, '=')) {
+ /*
+ * Check to see if we failed because
+ * /proc/partitions isn't found.
+ */
+ if (access("/proc/partitions", R_OK) < 0) {
+ fprintf(stderr, "Couldn't open /proc/partitions: %s\n",
+ strerror(errno));
+ fprintf(stderr, "Is /proc mounted?\n");
+ exit(EXIT_ERROR);
+ }
+ /*
+ * Check to see if this is because
+ * we're not running as root
+ */
+ if (geteuid())
+ fprintf(stderr,
+ "Must be root to scan for matching filesystems: %s\n", arg);
+ else
+ fprintf(stderr,
+ "Couldn't find matching filesystem: %s\n", arg);
+ exit(EXIT_ERROR);
+ }
+ devices[num_devices++] = dev ? dev : string_copy(arg);
+ continue;
+ }
+ if (arg[0] != '-' || opts_for_fsck) {
+ if (num_args >= MAX_ARGS) {
+ fprintf(stderr, _("%s: too many arguments\n"),
+ progname);
+ exit(EXIT_ERROR);
+ }
+ args[num_args++] = string_copy(arg);
+ continue;
+ }
+ for (j=1; arg[j]; j++) {
+ if (opts_for_fsck) {
+ options[++opt] = arg[j];
+ continue;
+ }
+ switch (arg[j]) {
+ case 'A':
+ doall++;
+ break;
+ case 'C':
+ progress++;
+ if (arg[j+1]) {
+ progress_fd = string_to_int(arg+j+1);
+ if (progress_fd < 0)
+ progress_fd = 0;
+ else
+ goto next_arg;
+ } else if (argc > i + 1 &&
+ argv[i + 1][0] != '-') {
+ progress_fd = string_to_int(argv[i]);
+ if (progress_fd < 0)
+ progress_fd = 0;
+ else {
+ ++i;
+ goto next_arg;
+ }
+ }
+ break;
+ case 'V':
+ verbose++;
+ break;
+ case 'N':
+ noexecute++;
+ break;
+ case 'R':
+ skip_root++;
+ break;
+ case 'T':
+ notitle++;
+ break;
+ case 'M':
+ ignore_mounted++;
+ break;
+ case 'P':
+ parallel_root++;
+ break;
+ case 's':
+ serialize++;
+ break;
+ case 't':
+ tmp = 0;
+ if (fstype)
+ usage();
+ if (arg[j+1])
+ tmp = arg+j+1;
+ else if ((i+1) < argc)
+ tmp = argv[++i];
+ else
+ usage();
+ fstype = string_copy(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) {
+ fprintf(stderr,
+ _("%s: too many arguments\n"),
+ progname);
+ exit(EXIT_ERROR);
+ }
+ args[num_args++] = string_copy(options);
+ opt = 0;
+ }
+ }
+ if (getenv("FSCK_FORCE_ALL_PARALLEL"))
+ force_all_parallel++;
+ if ((tmp = getenv("FSCK_MAX_INST")))
+ max_running = atoi(tmp);
+}
+
+int main(int argc, char *argv[])
+{
+ int i, status = 0;
+ int interactive = 0;
+ char *oldpath = getenv("PATH");
+ const char *fstab;
+ struct fs_info *fs;
+
+ setvbuf(stdout, NULL, _IONBF, BUFSIZ);
+ setvbuf(stderr, NULL, _IONBF, BUFSIZ);
+
+#ifdef ENABLE_NLS
+ setlocale(LC_MESSAGES, "");
+ setlocale(LC_CTYPE, "");
+ bindtextdomain(NLS_CAT_NAME, LOCALEDIR);
+ textdomain(NLS_CAT_NAME);
+#endif
+ blkid_get_cache(&cache, NULL);
+ PRS(argc, argv);
+
+ if (!notitle)
+ printf("fsck %s (%s)\n", E2FSPROGS_VERSION, E2FSPROGS_DATE);
+
+ fstab = getenv("FSTAB_FILE");
+ if (!fstab)
+ fstab = _PATH_MNTTAB;
+ load_fs_info(fstab);
+
+ /* Update our search path to include uncommon directories. */
+ if (oldpath) {
+ fsck_path = malloc (strlen (fsck_prefix_path) + 1 +
+ strlen (oldpath) + 1);
+ if (!fsck_path) {
+ fprintf(stderr, "%s: Unable to allocate memory for fsck_path\n", progname);
+ exit(EXIT_ERROR);
+ }
+ strcpy (fsck_path, fsck_prefix_path);
+ strcat (fsck_path, ":");
+ strcat (fsck_path, oldpath);
+ } else {
+ fsck_path = string_copy(fsck_prefix_path);
+ }
+
+ if ((num_devices == 1) || (serialize))
+ interactive = 1;
+
+ /* 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 = create_fs_device(devices[i], 0, "auto",
+ 0, -1, -1);
+ if (!fs)
+ continue;
+ }
+ if (ignore_mounted && is_mounted(fs->device))
+ continue;
+ 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);
+ blkid_put_cache(cache);
+ return status;
+}
diff --git a/misc/fsck.h b/misc/fsck.h
new file mode 100644
index 0000000..8a55fbd
--- /dev/null
+++ b/misc/fsck.h
@@ -0,0 +1,73 @@
+/*
+ * fsck.h
+ */
+
+#include <time.h>
+
+#ifdef __STDC__
+#define NOARGS void
+#else
+#define NOARGS
+#define const
+#endif
+
+#ifdef __GNUC__
+#define FSCK_ATTR(x) __attribute__(x)
+#else
+#define FSCK_ATTR(x)
+#endif
+
+
+#ifndef DEFAULT_FSTYPE
+#define DEFAULT_FSTYPE "ext2"
+#endif
+
+#define MAX_DEVICES 32
+#define MAX_ARGS 32
+
+#define EXIT_OK 0
+#define EXIT_NONDESTRUCT 1
+#define EXIT_DESTRUCT 2
+#define EXIT_UNCORRECTED 4
+#define EXIT_ERROR 8
+#define EXIT_USAGE 16
+#define EXIT_LIBRARY 128
+
+/*
+ * Internal structure for mount table entries.
+ */
+
+struct fs_info {
+ char *device;
+ char *mountpt;
+ char *type;
+ char *opts;
+ int freq;
+ int passno;
+ int flags;
+ struct fs_info *next;
+};
+
+#define FLAG_DONE 1
+#define FLAG_PROGRESS 2
+
+/*
+ * Structure to allow exit codes to be stored
+ */
+struct fsck_instance {
+ int pid;
+ int flags;
+ int exit_status;
+ time_t start_time;
+ char * prog;
+ char * type;
+ char * device;
+ char * base_device;
+ struct fsck_instance *next;
+};
+
+extern char *base_device(const char *device);
+extern const char *identify_fs(const char *fs_name, const char *fs_types);
+
+/* ismounted.h */
+extern int is_mounted(const char *file);
diff --git a/misc/fsmap.h b/misc/fsmap.h
new file mode 100644
index 0000000..e9590aa
--- /dev/null
+++ b/misc/fsmap.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2017 Oracle.
+ * All Rights Reserved.
+ *
+ * Author: Darrick J. Wong <darrick.wong@oracle.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.
+ *
+ * This program is distributed in the hope that it would 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 the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#ifndef FSMAP_H_
+#define FSMAP_H_
+
+/* FS_IOC_GETFSMAP ioctl definitions */
+#ifndef FS_IOC_GETFSMAP
+struct fsmap {
+ __u32 fmr_device; /* device id */
+ __u32 fmr_flags; /* mapping flags */
+ __u64 fmr_physical; /* device offset of segment */
+ __u64 fmr_owner; /* owner id */
+ __u64 fmr_offset; /* file offset of segment */
+ __u64 fmr_length; /* length of segment */
+ __u64 fmr_reserved[3]; /* must be zero */
+};
+
+struct fsmap_head {
+ __u32 fmh_iflags; /* control flags */
+ __u32 fmh_oflags; /* output flags */
+ __u32 fmh_count; /* # of entries in array incl. input */
+ __u32 fmh_entries; /* # of entries filled in (output). */
+ __u64 fmh_reserved[6]; /* must be zero */
+
+ struct fsmap fmh_keys[2]; /* low and high keys for the mapping search */
+ struct fsmap fmh_recs[]; /* returned records */
+};
+
+/* Size of an fsmap_head with room for nr records. */
+static inline size_t
+fsmap_sizeof(
+ unsigned int nr)
+{
+ return sizeof(struct fsmap_head) + nr * sizeof(struct fsmap);
+}
+
+/* Start the next fsmap query at the end of the current query results. */
+static inline void
+fsmap_advance(
+ struct fsmap_head *head)
+{
+ head->fmh_keys[0] = head->fmh_recs[head->fmh_entries - 1];
+}
+
+/* fmh_iflags values - set by FS_IOC_GETFSMAP caller in the header. */
+/* no flags defined yet */
+#define FMH_IF_VALID 0
+
+/* fmh_oflags values - returned in the header segment only. */
+#define FMH_OF_DEV_T 0x1 /* fmr_device values will be dev_t */
+
+/* fmr_flags values - returned for each non-header segment */
+#define FMR_OF_PREALLOC 0x1 /* segment = unwritten pre-allocation */
+#define FMR_OF_ATTR_FORK 0x2 /* segment = attribute fork */
+#define FMR_OF_EXTENT_MAP 0x4 /* segment = extent map */
+#define FMR_OF_SHARED 0x8 /* segment = shared with another file */
+#define FMR_OF_SPECIAL_OWNER 0x10 /* owner is a special value */
+#define FMR_OF_LAST 0x20 /* segment is the last in the FS */
+
+/* Each FS gets to define its own special owner codes. */
+#define FMR_OWNER(type, code) (((__u64)type << 32) | \
+ ((__u64)code & 0xFFFFFFFFULL))
+#define FMR_OWNER_TYPE(owner) ((__u32)((__u64)owner >> 32))
+#define FMR_OWNER_CODE(owner) ((__u32)(((__u64)owner & 0xFFFFFFFFULL)))
+#define FMR_OWN_FREE FMR_OWNER(0, 1) /* free space */
+#define FMR_OWN_UNKNOWN FMR_OWNER(0, 2) /* unknown owner */
+#define FMR_OWN_METADATA FMR_OWNER(0, 3) /* metadata */
+
+#define FS_IOC_GETFSMAP _IOWR('X', 59, struct fsmap_head)
+#endif /* FS_IOC_GETFSMAP */
+
+#endif
diff --git a/misc/fuse2fs.1.in b/misc/fuse2fs.1.in
new file mode 100644
index 0000000..1a0c9d5
--- /dev/null
+++ b/misc/fuse2fs.1.in
@@ -0,0 +1,79 @@
+.\" -*- nroff -*-
+.\" Copyright 1993, 1994, 1995 by Theodore Ts'o. All Rights Reserved.
+.\" This file may be copied under the terms of the GNU Public License.
+.\"
+.TH FUSE2FS 1 "@E2FSPROGS_MONTH@ @E2FSPROGS_YEAR@" "E2fsprogs version @E2FSPROGS_VERSION@"
+.SH NAME
+fuse2fs \- FUSE file system client for ext2/ext3/ext4 file systems
+.SH SYNOPSIS
+.B fuse2fs
+[
+.B device/image
+]
+[
+.B mountpoint
+]
+[
+.I options
+]
+.SH DESCRIPTION
+.B fuse2fs
+is a FUSE file system client that supports reading and writing from
+devices or image files containing ext2, ext3, and ext4 file systems.
+.SH OPTIONS
+.SS "general options:"
+.TP
+\fB\-o\fR opt,[opt...]
+mount options
+.TP
+\fB\-h\fR \fB\-\-help\fR
+print help
+.TP
+\fB\-V\fR \fB\-\-version\fR
+print version
+.SS "fuse2fs options:"
+.TP
+\fB-o\fR ro
+read-only mount
+.TP
+\fB-o\fR errors=panic
+dump core on error
+.TP
+\fB-o\fR minixdf
+minix-style df
+.TP
+\fB-o\fR fakeroot
+pretend to be root for permission checks
+.TP
+\fB-o\fR no_default_opts
+do not include default fuse options
+.TP
+\fB-o\fR norecovery
+do not replay the journal and mount the file system read-only
+.TP
+\fB-o\fR fuse2fs_debug
+enable fuse2fs debugging
+.SS "FUSE options:"
+.TP
+\fB-d -o\fR debug
+enable debug output (implies -f)
+.TP
+\fB-f\fR
+foreground operation
+.TP
+\fB-s\fR
+disable multi-threaded operation
+.P
+For other FUSE options please see
+.BR mount.fuse (8)
+or see the output of
+.I fuse2fs \-\-helpfull
+.SH AVAILABILITY
+.B fuse2fs
+is part of the e2fsprogs package and is available from
+http://e2fsprogs.sourceforge.net.
+.SH SEE ALSO
+.BR ext4 (5)
+.BR e2fsck (8),
+.BR mount.fuse (8)
+
diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
new file mode 100644
index 0000000..0dc77ea
--- /dev/null
+++ b/misc/fuse2fs.c
@@ -0,0 +1,4009 @@
+/*
+ * fuse2fs.c - FUSE server for e2fsprogs.
+ *
+ * Copyright (C) 2014 Oracle.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ * %End-Header%
+ */
+#define _FILE_OFFSET_BITS 64
+#define FUSE_USE_VERSION 29
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+#include "config.h"
+#include <pthread.h>
+#ifdef __linux__
+# include <linux/fs.h>
+# include <linux/falloc.h>
+# include <linux/xattr.h>
+# define FUSE_PLATFORM_OPTS ",big_writes"
+# ifdef HAVE_SYS_ACL_H
+# define TRANSLATE_LINUX_ACLS
+# endif
+#else
+# define FUSE_PLATFORM_OPTS ""
+#endif
+#ifdef TRANSLATE_LINUX_ACLS
+# include <sys/acl.h>
+#endif
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <fuse.h>
+#include <inttypes.h>
+#include "ext2fs/ext2fs.h"
+#include "ext2fs/ext2_fs.h"
+
+#include "../version.h"
+
+#ifdef ENABLE_NLS
+#include <libintl.h>
+#include <locale.h>
+#define _(a) (gettext(a))
+#ifdef gettext_noop
+#define N_(a) gettext_noop(a)
+#else
+#define N_(a) (a)
+#endif
+#define P_(singular, plural, n) (ngettext(singular, plural, n))
+#ifndef NLS_CAT_NAME
+#define NLS_CAT_NAME "e2fsprogs"
+#endif
+#ifndef LOCALEDIR
+#define LOCALEDIR "/usr/share/locale"
+#endif
+#else
+#define _(a) (a)
+#define N_(a) a
+#define P_(singular, plural, n) ((n) == 1 ? (singular) : (plural))
+#endif
+
+static ext2_filsys global_fs; /* Try not to use this directly */
+
+#undef DEBUG
+
+#ifdef DEBUG
+# define dbg_printf(f, a...) do {printf("FUSE2FS-" f, ## a); \
+ fflush(stdout); \
+} while (0)
+#else
+# define dbg_printf(f, a...)
+#endif
+
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(2, 8)
+# ifdef _IOR
+# ifdef _IOW
+# define SUPPORT_I_FLAGS
+# endif
+# endif
+#endif
+
+#ifdef FALLOC_FL_KEEP_SIZE
+# define FL_KEEP_SIZE_FLAG FALLOC_FL_KEEP_SIZE
+# define SUPPORT_FALLOCATE
+#else
+# define FL_KEEP_SIZE_FLAG (0)
+#endif
+
+#ifdef FALLOC_FL_PUNCH_HOLE
+# define FL_PUNCH_HOLE_FLAG FALLOC_FL_PUNCH_HOLE
+#else
+# define FL_PUNCH_HOLE_FLAG (0)
+#endif
+
+errcode_t ext2fs_run_ext3_journal(ext2_filsys *fs);
+
+#ifdef CONFIG_JBD_DEBUG /* Enabled by configure --enable-jbd-debug */
+int journal_enable_debug = -1;
+#endif
+
+/* ACL translation stuff */
+#ifdef TRANSLATE_LINUX_ACLS
+/*
+ * Copied from acl_ea.h in libacl source; ACLs have to be sent to and from fuse
+ * in this format... at least on Linux.
+ */
+#define ACL_EA_ACCESS "system.posix_acl_access"
+#define ACL_EA_DEFAULT "system.posix_acl_default"
+
+#define ACL_EA_VERSION 0x0002
+
+typedef struct {
+ u_int16_t e_tag;
+ u_int16_t e_perm;
+ u_int32_t e_id;
+} acl_ea_entry;
+
+typedef struct {
+ u_int32_t a_version;
+#if __GNUC_PREREQ (4, 8)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wpedantic"
+#endif
+ acl_ea_entry a_entries[0];
+#if __GNUC_PREREQ (4, 8)
+#pragma GCC diagnostic pop
+#endif
+} acl_ea_header;
+
+static inline size_t acl_ea_size(int count)
+{
+ return sizeof(acl_ea_header) + count * sizeof(acl_ea_entry);
+}
+
+static inline int acl_ea_count(size_t size)
+{
+ if (size < sizeof(acl_ea_header))
+ return -1;
+ size -= sizeof(acl_ea_header);
+ if (size % sizeof(acl_ea_entry))
+ return -1;
+ return size / sizeof(acl_ea_entry);
+}
+
+/*
+ * ext4 ACL structures, copied from fs/ext4/acl.h.
+ */
+#define EXT4_ACL_VERSION 0x0001
+
+typedef struct {
+ __u16 e_tag;
+ __u16 e_perm;
+ __u32 e_id;
+} ext4_acl_entry;
+
+typedef struct {
+ __u16 e_tag;
+ __u16 e_perm;
+} ext4_acl_entry_short;
+
+typedef struct {
+ __u32 a_version;
+} ext4_acl_header;
+
+static inline size_t ext4_acl_size(int count)
+{
+ if (count <= 4) {
+ return sizeof(ext4_acl_header) +
+ count * sizeof(ext4_acl_entry_short);
+ } else {
+ return sizeof(ext4_acl_header) +
+ 4 * sizeof(ext4_acl_entry_short) +
+ (count - 4) * sizeof(ext4_acl_entry);
+ }
+}
+
+static inline int ext4_acl_count(size_t size)
+{
+ ssize_t s;
+
+ size -= sizeof(ext4_acl_header);
+ s = size - 4 * sizeof(ext4_acl_entry_short);
+ if (s < 0) {
+ if (size % sizeof(ext4_acl_entry_short))
+ return -1;
+ return size / sizeof(ext4_acl_entry_short);
+ }
+ if (s % sizeof(ext4_acl_entry))
+ return -1;
+ return s / sizeof(ext4_acl_entry) + 4;
+}
+
+static errcode_t fuse_to_ext4_acl(acl_ea_header *facl, size_t facl_sz,
+ ext4_acl_header **eacl, size_t *eacl_sz)
+{
+ int i, facl_count;
+ ext4_acl_header *h;
+ size_t h_sz;
+ ext4_acl_entry *e;
+ acl_ea_entry *a;
+ unsigned char *hptr;
+ errcode_t err;
+
+ facl_count = acl_ea_count(facl_sz);
+ h_sz = ext4_acl_size(facl_count);
+ if (facl_count < 0 || facl->a_version != ACL_EA_VERSION)
+ return EXT2_ET_INVALID_ARGUMENT;
+
+ err = ext2fs_get_mem(h_sz, &h);
+ if (err)
+ return err;
+
+ h->a_version = ext2fs_cpu_to_le32(EXT4_ACL_VERSION);
+ hptr = (unsigned char *) (h + 1);
+ for (i = 0, a = facl->a_entries; i < facl_count; i++, a++) {
+ e = (ext4_acl_entry *) hptr;
+ e->e_tag = ext2fs_cpu_to_le16(a->e_tag);
+ e->e_perm = ext2fs_cpu_to_le16(a->e_perm);
+
+ switch (a->e_tag) {
+ case ACL_USER:
+ case ACL_GROUP:
+ e->e_id = ext2fs_cpu_to_le32(a->e_id);
+ hptr += sizeof(ext4_acl_entry);
+ break;
+ case ACL_USER_OBJ:
+ case ACL_GROUP_OBJ:
+ case ACL_MASK:
+ case ACL_OTHER:
+ hptr += sizeof(ext4_acl_entry_short);
+ break;
+ default:
+ err = EXT2_ET_INVALID_ARGUMENT;
+ goto out;
+ }
+ }
+
+ *eacl = h;
+ *eacl_sz = h_sz;
+ return err;
+out:
+ ext2fs_free_mem(&h);
+ return err;
+}
+
+static errcode_t ext4_to_fuse_acl(acl_ea_header **facl, size_t *facl_sz,
+ ext4_acl_header *eacl, size_t eacl_sz)
+{
+ int i, eacl_count;
+ acl_ea_header *f;
+ ext4_acl_entry *e;
+ acl_ea_entry *a;
+ size_t f_sz;
+ unsigned char *hptr;
+ errcode_t err;
+
+ eacl_count = ext4_acl_count(eacl_sz);
+ f_sz = acl_ea_size(eacl_count);
+ if (eacl_count < 0 ||
+ eacl->a_version != ext2fs_cpu_to_le32(EXT4_ACL_VERSION))
+ return EXT2_ET_INVALID_ARGUMENT;
+
+ err = ext2fs_get_mem(f_sz, &f);
+ if (err)
+ return err;
+
+ f->a_version = ACL_EA_VERSION;
+ hptr = (unsigned char *) (eacl + 1);
+ for (i = 0, a = f->a_entries; i < eacl_count; i++, a++) {
+ e = (ext4_acl_entry *) hptr;
+ a->e_tag = ext2fs_le16_to_cpu(e->e_tag);
+ a->e_perm = ext2fs_le16_to_cpu(e->e_perm);
+
+ switch (a->e_tag) {
+ case ACL_USER:
+ case ACL_GROUP:
+ a->e_id = ext2fs_le32_to_cpu(e->e_id);
+ hptr += sizeof(ext4_acl_entry);
+ break;
+ case ACL_USER_OBJ:
+ case ACL_GROUP_OBJ:
+ case ACL_MASK:
+ case ACL_OTHER:
+ hptr += sizeof(ext4_acl_entry_short);
+ break;
+ default:
+ err = EXT2_ET_INVALID_ARGUMENT;
+ goto out;
+ }
+ }
+
+ *facl = f;
+ *facl_sz = f_sz;
+ return err;
+out:
+ ext2fs_free_mem(&f);
+ return err;
+}
+#endif /* TRANSLATE_LINUX_ACLS */
+
+/*
+ * ext2_file_t contains a struct inode, so we can't leave files open.
+ * Use this as a proxy instead.
+ */
+#define FUSE2FS_FILE_MAGIC (0xEF53DEAFUL)
+struct fuse2fs_file_handle {
+ unsigned long magic;
+ ext2_ino_t ino;
+ int open_flags;
+};
+
+/* Main program context */
+#define FUSE2FS_MAGIC (0xEF53DEADUL)
+struct fuse2fs {
+ unsigned long magic;
+ ext2_filsys fs;
+ pthread_mutex_t bfl;
+ char *device;
+ int ro;
+ int debug;
+ int no_default_opts;
+ int panic_on_error;
+ int minixdf;
+ int fakeroot;
+ int alloc_all_blocks;
+ int norecovery;
+ unsigned long offset;
+ FILE *err_fp;
+ unsigned int next_generation;
+};
+
+#define FUSE2FS_CHECK_MAGIC(fs, ptr, num) do {if ((ptr)->magic != (num)) \
+ return translate_error((fs), 0, EXT2_ET_MAGIC_EXT2_FILE); \
+} while (0)
+
+#define FUSE2FS_CHECK_CONTEXT(ptr) do {if ((ptr)->magic != FUSE2FS_MAGIC) \
+ return translate_error(global_fs, 0, EXT2_ET_BAD_MAGIC); \
+} while (0)
+
+static int __translate_error(ext2_filsys fs, errcode_t err, ext2_ino_t ino,
+ const char *file, int line);
+#define translate_error(fs, ino, err) __translate_error((fs), (err), (ino), \
+ __FILE__, __LINE__)
+
+/* for macosx */
+#ifndef W_OK
+# define W_OK 2
+#endif
+
+#ifndef R_OK
+# define R_OK 4
+#endif
+
+#define EXT4_EPOCH_BITS 2
+#define EXT4_EPOCH_MASK ((1 << EXT4_EPOCH_BITS) - 1)
+#define EXT4_NSEC_MASK (~0UL << EXT4_EPOCH_BITS)
+
+/*
+ * Extended fields will fit into an inode if the filesystem was formatted
+ * with large inodes (-I 256 or larger) and there are not currently any EAs
+ * consuming all of the available space. For new inodes we always reserve
+ * enough space for the kernel's known extended fields, but for inodes
+ * created with an old kernel this might not have been the case. None of
+ * the extended inode fields is critical for correct filesystem operation.
+ * This macro checks if a certain field fits in the inode. Note that
+ * inode-size = GOOD_OLD_INODE_SIZE + i_extra_isize
+ */
+#define EXT4_FITS_IN_INODE(ext4_inode, field) \
+ ((offsetof(typeof(*ext4_inode), field) + \
+ sizeof((ext4_inode)->field)) \
+ <= ((size_t) EXT2_GOOD_OLD_INODE_SIZE + \
+ (ext4_inode)->i_extra_isize)) \
+
+static inline __u32 ext4_encode_extra_time(const struct timespec *time)
+{
+ __u32 extra = sizeof(time->tv_sec) > 4 ?
+ ((time->tv_sec - (__s32)time->tv_sec) >> 32) &
+ EXT4_EPOCH_MASK : 0;
+ return extra | (time->tv_nsec << EXT4_EPOCH_BITS);
+}
+
+static inline void ext4_decode_extra_time(struct timespec *time, __u32 extra)
+{
+ if (sizeof(time->tv_sec) > 4 && (extra & EXT4_EPOCH_MASK)) {
+ __u64 extra_bits = extra & EXT4_EPOCH_MASK;
+ /*
+ * Prior to kernel 3.14?, we had a broken decode function,
+ * wherein we effectively did this:
+ * if (extra_bits == 3)
+ * extra_bits = 0;
+ */
+ time->tv_sec += extra_bits << 32;
+ }
+ time->tv_nsec = ((extra) & EXT4_NSEC_MASK) >> EXT4_EPOCH_BITS;
+}
+
+#define EXT4_INODE_SET_XTIME(xtime, timespec, raw_inode) \
+do { \
+ (raw_inode)->xtime = (timespec)->tv_sec; \
+ if (EXT4_FITS_IN_INODE(raw_inode, xtime ## _extra)) \
+ (raw_inode)->xtime ## _extra = \
+ ext4_encode_extra_time(timespec); \
+} while (0)
+
+#define EXT4_EINODE_SET_XTIME(xtime, timespec, raw_inode) \
+do { \
+ if (EXT4_FITS_IN_INODE(raw_inode, xtime)) \
+ (raw_inode)->xtime = (timespec)->tv_sec; \
+ if (EXT4_FITS_IN_INODE(raw_inode, xtime ## _extra)) \
+ (raw_inode)->xtime ## _extra = \
+ ext4_encode_extra_time(timespec); \
+} while (0)
+
+#define EXT4_INODE_GET_XTIME(xtime, timespec, raw_inode) \
+do { \
+ (timespec)->tv_sec = (signed)((raw_inode)->xtime); \
+ if (EXT4_FITS_IN_INODE(raw_inode, xtime ## _extra)) \
+ ext4_decode_extra_time((timespec), \
+ (raw_inode)->xtime ## _extra); \
+ else \
+ (timespec)->tv_nsec = 0; \
+} while (0)
+
+#define EXT4_EINODE_GET_XTIME(xtime, timespec, raw_inode) \
+do { \
+ if (EXT4_FITS_IN_INODE(raw_inode, xtime)) \
+ (timespec)->tv_sec = \
+ (signed)((raw_inode)->xtime); \
+ if (EXT4_FITS_IN_INODE(raw_inode, xtime ## _extra)) \
+ ext4_decode_extra_time((timespec), \
+ raw_inode->xtime ## _extra); \
+ else \
+ (timespec)->tv_nsec = 0; \
+} while (0)
+
+static void get_now(struct timespec *now)
+{
+#ifdef CLOCK_REALTIME
+ if (!clock_gettime(CLOCK_REALTIME, now))
+ return;
+#endif
+
+ now->tv_sec = time(NULL);
+ now->tv_nsec = 0;
+}
+
+static void increment_version(struct ext2_inode_large *inode)
+{
+ __u64 ver;
+
+ ver = inode->osd1.linux1.l_i_version;
+ if (EXT4_FITS_IN_INODE(inode, i_version_hi))
+ ver |= (__u64)inode->i_version_hi << 32;
+ ver++;
+ inode->osd1.linux1.l_i_version = ver;
+ if (EXT4_FITS_IN_INODE(inode, i_version_hi))
+ inode->i_version_hi = ver >> 32;
+}
+
+static void init_times(struct ext2_inode_large *inode)
+{
+ struct timespec now;
+
+ get_now(&now);
+ EXT4_INODE_SET_XTIME(i_atime, &now, inode);
+ EXT4_INODE_SET_XTIME(i_ctime, &now, inode);
+ EXT4_INODE_SET_XTIME(i_mtime, &now, inode);
+ EXT4_EINODE_SET_XTIME(i_crtime, &now, inode);
+ increment_version(inode);
+}
+
+static int update_ctime(ext2_filsys fs, ext2_ino_t ino,
+ struct ext2_inode_large *pinode)
+{
+ errcode_t err;
+ struct timespec now;
+ struct ext2_inode_large inode;
+
+ get_now(&now);
+
+ /* If user already has a inode buffer, just update that */
+ if (pinode) {
+ increment_version(pinode);
+ EXT4_INODE_SET_XTIME(i_ctime, &now, pinode);
+ return 0;
+ }
+
+ /* Otherwise we have to read-modify-write the inode */
+ memset(&inode, 0, sizeof(inode));
+ err = ext2fs_read_inode_full(fs, ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err)
+ return translate_error(fs, ino, err);
+
+ increment_version(&inode);
+ EXT4_INODE_SET_XTIME(i_ctime, &now, &inode);
+
+ err = ext2fs_write_inode_full(fs, ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err)
+ return translate_error(fs, ino, err);
+
+ return 0;
+}
+
+static int update_atime(ext2_filsys fs, ext2_ino_t ino)
+{
+ errcode_t err;
+ struct ext2_inode_large inode, *pinode;
+ struct timespec atime, mtime, now;
+
+ if (!(fs->flags & EXT2_FLAG_RW))
+ return 0;
+ memset(&inode, 0, sizeof(inode));
+ err = ext2fs_read_inode_full(fs, ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err)
+ return translate_error(fs, ino, err);
+
+ pinode = &inode;
+ EXT4_INODE_GET_XTIME(i_atime, &atime, pinode);
+ EXT4_INODE_GET_XTIME(i_mtime, &mtime, pinode);
+ get_now(&now);
+ /*
+ * If atime is newer than mtime and atime hasn't been updated in thirty
+ * seconds, skip the atime update. Same idea as Linux "relatime".
+ */
+ if (atime.tv_sec >= mtime.tv_sec && atime.tv_sec >= now.tv_sec - 30)
+ return 0;
+ EXT4_INODE_SET_XTIME(i_atime, &now, &inode);
+
+ err = ext2fs_write_inode_full(fs, ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err)
+ return translate_error(fs, ino, err);
+
+ return 0;
+}
+
+static int update_mtime(ext2_filsys fs, ext2_ino_t ino,
+ struct ext2_inode_large *pinode)
+{
+ errcode_t err;
+ struct ext2_inode_large inode;
+ struct timespec now;
+
+ if (pinode) {
+ get_now(&now);
+ EXT4_INODE_SET_XTIME(i_mtime, &now, pinode);
+ EXT4_INODE_SET_XTIME(i_ctime, &now, pinode);
+ increment_version(pinode);
+ return 0;
+ }
+
+ memset(&inode, 0, sizeof(inode));
+ err = ext2fs_read_inode_full(fs, ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err)
+ return translate_error(fs, ino, err);
+
+ get_now(&now);
+ EXT4_INODE_SET_XTIME(i_mtime, &now, &inode);
+ EXT4_INODE_SET_XTIME(i_ctime, &now, &inode);
+ increment_version(&inode);
+
+ err = ext2fs_write_inode_full(fs, ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err)
+ return translate_error(fs, ino, err);
+
+ return 0;
+}
+
+static int ext2_file_type(unsigned int mode)
+{
+ if (LINUX_S_ISREG(mode))
+ return EXT2_FT_REG_FILE;
+
+ if (LINUX_S_ISDIR(mode))
+ return EXT2_FT_DIR;
+
+ if (LINUX_S_ISCHR(mode))
+ return EXT2_FT_CHRDEV;
+
+ if (LINUX_S_ISBLK(mode))
+ return EXT2_FT_BLKDEV;
+
+ if (LINUX_S_ISLNK(mode))
+ return EXT2_FT_SYMLINK;
+
+ if (LINUX_S_ISFIFO(mode))
+ return EXT2_FT_FIFO;
+
+ if (LINUX_S_ISSOCK(mode))
+ return EXT2_FT_SOCK;
+
+ return 0;
+}
+
+static int fs_can_allocate(struct fuse2fs *ff, blk64_t num)
+{
+ ext2_filsys fs = ff->fs;
+ blk64_t reserved;
+
+ dbg_printf("%s: Asking for %llu; alloc_all=%d total=%llu free=%llu "
+ "rsvd=%llu\n", __func__, num, ff->alloc_all_blocks,
+ ext2fs_blocks_count(fs->super),
+ ext2fs_free_blocks_count(fs->super),
+ ext2fs_r_blocks_count(fs->super));
+ if (num > ext2fs_blocks_count(fs->super))
+ return 0;
+
+ if (ff->alloc_all_blocks)
+ return 1;
+
+ /*
+ * Different meaning for r_blocks -- libext2fs has bugs where the FS
+ * can get corrupted if it totally runs out of blocks. Avoid this
+ * by refusing to allocate any of the reserve blocks to anybody.
+ */
+ reserved = ext2fs_r_blocks_count(fs->super);
+ if (reserved == 0)
+ reserved = ext2fs_blocks_count(fs->super) / 10;
+ return ext2fs_free_blocks_count(fs->super) > reserved + num;
+}
+
+static int fs_writeable(ext2_filsys fs)
+{
+ return (fs->flags & EXT2_FLAG_RW) && (fs->super->s_error_count == 0);
+}
+
+static int check_inum_access(ext2_filsys fs, ext2_ino_t ino, mode_t mask)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ struct ext2_inode inode;
+ mode_t perms;
+ errcode_t err;
+
+ /* no writing to read-only or broken fs */
+ if ((mask & W_OK) && !fs_writeable(fs))
+ return -EROFS;
+
+ err = ext2fs_read_inode(fs, ino, &inode);
+ if (err)
+ return translate_error(fs, ino, err);
+ perms = inode.i_mode & 0777;
+
+ dbg_printf("access ino=%d mask=e%s%s%s perms=0%o fuid=%d fgid=%d "
+ "uid=%d gid=%d\n", ino,
+ (mask & R_OK ? "r" : ""), (mask & W_OK ? "w" : ""),
+ (mask & X_OK ? "x" : ""), perms, inode_uid(inode),
+ inode_gid(inode), ctxt->uid, ctxt->gid);
+
+ /* existence check */
+ if (mask == 0)
+ return 0;
+
+ /* is immutable? */
+ if ((mask & W_OK) &&
+ (inode.i_flags & EXT2_IMMUTABLE_FL))
+ return -EACCES;
+
+ /* Figure out what root's allowed to do */
+ if (ff->fakeroot || ctxt->uid == 0) {
+ /* Non-file access always ok */
+ if (!LINUX_S_ISREG(inode.i_mode))
+ return 0;
+
+ /* R/W access to a file always ok */
+ if (!(mask & X_OK))
+ return 0;
+
+ /* X access to a file ok if a user/group/other can X */
+ if (perms & 0111)
+ return 0;
+
+ /* Trying to execute a file that's not executable. BZZT! */
+ return -EACCES;
+ }
+
+ /* allow owner, if perms match */
+ if (inode_uid(inode) == ctxt->uid) {
+ if ((mask & (perms >> 6)) == mask)
+ return 0;
+ return -EACCES;
+ }
+
+ /* allow group, if perms match */
+ if (inode_gid(inode) == ctxt->gid) {
+ if ((mask & (perms >> 3)) == mask)
+ return 0;
+ return -EACCES;
+ }
+
+ /* otherwise check other */
+ if ((mask & perms) == mask)
+ return 0;
+ return -EACCES;
+}
+
+static void op_destroy(void *p EXT2FS_ATTR((unused)))
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ ext2_filsys fs;
+ errcode_t err;
+
+ if (ff->magic != FUSE2FS_MAGIC) {
+ translate_error(global_fs, 0, EXT2_ET_BAD_MAGIC);
+ return;
+ }
+ fs = ff->fs;
+ dbg_printf("%s: dev=%s\n", __func__, fs->device_name);
+ if (fs->flags & EXT2_FLAG_RW) {
+ fs->super->s_state |= EXT2_VALID_FS;
+ if (fs->super->s_error_count)
+ fs->super->s_state |= EXT2_ERROR_FS;
+ ext2fs_mark_super_dirty(fs);
+ err = ext2fs_set_gdt_csum(fs);
+ if (err)
+ translate_error(fs, 0, err);
+
+ err = ext2fs_flush2(fs, 0);
+ if (err)
+ translate_error(fs, 0, err);
+ }
+}
+
+static void *op_init(struct fuse_conn_info *conn)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ ext2_filsys fs;
+ errcode_t err;
+
+ if (ff->magic != FUSE2FS_MAGIC) {
+ translate_error(global_fs, 0, EXT2_ET_BAD_MAGIC);
+ return NULL;
+ }
+ fs = ff->fs;
+ dbg_printf("%s: dev=%s\n", __func__, fs->device_name);
+#ifdef FUSE_CAP_IOCTL_DIR
+ conn->want |= FUSE_CAP_IOCTL_DIR;
+#endif
+ if (fs->flags & EXT2_FLAG_RW) {
+ fs->super->s_mnt_count++;
+ fs->super->s_mtime = time(NULL);
+ fs->super->s_state &= ~EXT2_VALID_FS;
+ ext2fs_mark_super_dirty(fs);
+ err = ext2fs_flush2(fs, 0);
+ if (err)
+ translate_error(fs, 0, err);
+ }
+ return ff;
+}
+
+static int stat_inode(ext2_filsys fs, ext2_ino_t ino, struct stat *statbuf)
+{
+ struct ext2_inode_large inode;
+ dev_t fakedev = 0;
+ errcode_t err;
+ int ret = 0;
+ struct timespec tv;
+
+ memset(&inode, 0, sizeof(inode));
+ err = ext2fs_read_inode_full(fs, ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err)
+ return translate_error(fs, ino, err);
+
+ memcpy(&fakedev, fs->super->s_uuid, sizeof(fakedev));
+ statbuf->st_dev = fakedev;
+ statbuf->st_ino = ino;
+ statbuf->st_mode = inode.i_mode;
+ statbuf->st_nlink = inode.i_links_count;
+ statbuf->st_uid = inode_uid(inode);
+ statbuf->st_gid = inode_gid(inode);
+ statbuf->st_size = EXT2_I_SIZE(&inode);
+ statbuf->st_blksize = fs->blocksize;
+ statbuf->st_blocks = ext2fs_get_stat_i_blocks(fs,
+ (struct ext2_inode *)&inode);
+ EXT4_INODE_GET_XTIME(i_atime, &tv, &inode);
+ statbuf->st_atime = tv.tv_sec;
+ EXT4_INODE_GET_XTIME(i_mtime, &tv, &inode);
+ statbuf->st_mtime = tv.tv_sec;
+ EXT4_INODE_GET_XTIME(i_ctime, &tv, &inode);
+ statbuf->st_ctime = tv.tv_sec;
+ if (LINUX_S_ISCHR(inode.i_mode) ||
+ LINUX_S_ISBLK(inode.i_mode)) {
+ if (inode.i_block[0])
+ statbuf->st_rdev = inode.i_block[0];
+ else
+ statbuf->st_rdev = inode.i_block[1];
+ }
+
+ return ret;
+}
+
+static int op_getattr(const char *path, struct stat *statbuf)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ ext2_filsys fs;
+ ext2_ino_t ino;
+ errcode_t err;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ dbg_printf("%s: path=%s\n", __func__, path);
+ pthread_mutex_lock(&ff->bfl);
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
+ if (err) {
+ ret = translate_error(fs, 0, err);
+ goto out;
+ }
+ ret = stat_inode(fs, ino, statbuf);
+out:
+ pthread_mutex_unlock(&ff->bfl);
+ return ret;
+}
+
+static int op_readlink(const char *path, char *buf, size_t len)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ ext2_filsys fs;
+ errcode_t err;
+ ext2_ino_t ino;
+ struct ext2_inode inode;
+ unsigned int got;
+ ext2_file_t file;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ dbg_printf("%s: path=%s\n", __func__, path);
+ pthread_mutex_lock(&ff->bfl);
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
+ if (err || ino == 0) {
+ ret = translate_error(fs, 0, err);
+ goto out;
+ }
+
+ err = ext2fs_read_inode(fs, ino, &inode);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+
+ if (!LINUX_S_ISLNK(inode.i_mode)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ len--;
+ if (inode.i_size < len)
+ len = inode.i_size;
+ if (ext2fs_is_fast_symlink(&inode))
+ memcpy(buf, (char *)inode.i_block, len);
+ else {
+ /* big/inline symlink */
+
+ err = ext2fs_file_open(fs, ino, 0, &file);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+
+ err = ext2fs_file_read(file, buf, len, &got);
+ if (err || got != len) {
+ ext2fs_file_close(file);
+ ret = translate_error(fs, ino, err);
+ goto out2;
+ }
+
+out2:
+ err = ext2fs_file_close(file);
+ if (ret)
+ goto out;
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+ }
+ buf[len] = 0;
+
+ if (fs_writeable(fs)) {
+ ret = update_atime(fs, ino);
+ if (ret)
+ goto out;
+ }
+
+out:
+ pthread_mutex_unlock(&ff->bfl);
+ return ret;
+}
+
+static int op_mknod(const char *path, mode_t mode, dev_t dev)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ ext2_filsys fs;
+ ext2_ino_t parent, child;
+ char *temp_path;
+ errcode_t err;
+ char *node_name, a;
+ int filetype;
+ struct ext2_inode_large inode;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ dbg_printf("%s: path=%s mode=0%o dev=0x%x\n", __func__, path, mode,
+ (unsigned int)dev);
+ temp_path = strdup(path);
+ if (!temp_path) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ node_name = strrchr(temp_path, '/');
+ if (!node_name) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ node_name++;
+ a = *node_name;
+ *node_name = 0;
+
+ pthread_mutex_lock(&ff->bfl);
+ if (!fs_can_allocate(ff, 2)) {
+ ret = -ENOSPC;
+ goto out2;
+ }
+
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_path,
+ &parent);
+ if (err) {
+ ret = translate_error(fs, 0, err);
+ goto out2;
+ }
+
+ ret = check_inum_access(fs, parent, W_OK);
+ if (ret)
+ goto out2;
+
+ *node_name = a;
+
+ if (LINUX_S_ISCHR(mode))
+ filetype = EXT2_FT_CHRDEV;
+ else if (LINUX_S_ISBLK(mode))
+ filetype = EXT2_FT_BLKDEV;
+ else if (LINUX_S_ISFIFO(mode))
+ filetype = EXT2_FT_FIFO;
+ else if (LINUX_S_ISSOCK(mode))
+ filetype = EXT2_FT_SOCK;
+ else {
+ ret = -EINVAL;
+ goto out2;
+ }
+
+ err = ext2fs_new_inode(fs, parent, mode, 0, &child);
+ if (err) {
+ ret = translate_error(fs, 0, err);
+ goto out2;
+ }
+
+ dbg_printf("%s: create ino=%d/name=%s in dir=%d\n", __func__, child,
+ node_name, parent);
+ err = ext2fs_link(fs, parent, node_name, child, filetype);
+ if (err == EXT2_ET_DIR_NO_SPACE) {
+ err = ext2fs_expand_dir(fs, parent);
+ if (err) {
+ ret = translate_error(fs, parent, err);
+ goto out2;
+ }
+
+ err = ext2fs_link(fs, parent, node_name, child,
+ filetype);
+ }
+ if (err) {
+ ret = translate_error(fs, parent, err);
+ goto out2;
+ }
+
+ ret = update_mtime(fs, parent, NULL);
+ if (ret)
+ goto out2;
+
+ memset(&inode, 0, sizeof(inode));
+ inode.i_mode = mode;
+
+ if (dev & ~0xFFFF)
+ inode.i_block[1] = dev;
+ else
+ inode.i_block[0] = dev;
+ inode.i_links_count = 1;
+ inode.i_extra_isize = sizeof(struct ext2_inode_large) -
+ EXT2_GOOD_OLD_INODE_SIZE;
+ inode.i_uid = ctxt->uid;
+ ext2fs_set_i_uid_high(inode, ctxt->uid >> 16);
+ inode.i_gid = ctxt->gid;
+ ext2fs_set_i_gid_high(inode, ctxt->gid >> 16);
+
+ err = ext2fs_write_new_inode(fs, child, (struct ext2_inode *)&inode);
+ if (err) {
+ ret = translate_error(fs, child, err);
+ goto out2;
+ }
+
+ inode.i_generation = ff->next_generation++;
+ init_times(&inode);
+ err = ext2fs_write_inode_full(fs, child, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err) {
+ ret = translate_error(fs, child, err);
+ goto out2;
+ }
+
+ ext2fs_inode_alloc_stats2(fs, child, 1, 0);
+
+out2:
+ pthread_mutex_unlock(&ff->bfl);
+out:
+ free(temp_path);
+ return ret;
+}
+
+static int op_mkdir(const char *path, mode_t mode)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ ext2_filsys fs;
+ ext2_ino_t parent, child;
+ char *temp_path;
+ errcode_t err;
+ char *node_name, a;
+ struct ext2_inode_large inode;
+ char *block;
+ blk64_t blk;
+ int ret = 0;
+ mode_t parent_sgid;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ dbg_printf("%s: path=%s mode=0%o\n", __func__, path, mode);
+ temp_path = strdup(path);
+ if (!temp_path) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ node_name = strrchr(temp_path, '/');
+ if (!node_name) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ node_name++;
+ a = *node_name;
+ *node_name = 0;
+
+ pthread_mutex_lock(&ff->bfl);
+ if (!fs_can_allocate(ff, 1)) {
+ ret = -ENOSPC;
+ goto out2;
+ }
+
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_path,
+ &parent);
+ if (err) {
+ ret = translate_error(fs, 0, err);
+ goto out2;
+ }
+
+ ret = check_inum_access(fs, parent, W_OK);
+ if (ret)
+ goto out2;
+
+ /* Is the parent dir sgid? */
+ err = ext2fs_read_inode_full(fs, parent, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err) {
+ ret = translate_error(fs, parent, err);
+ goto out2;
+ }
+ parent_sgid = inode.i_mode & S_ISGID;
+
+ *node_name = a;
+
+ err = ext2fs_mkdir(fs, parent, 0, node_name);
+ if (err == EXT2_ET_DIR_NO_SPACE) {
+ err = ext2fs_expand_dir(fs, parent);
+ if (err) {
+ ret = translate_error(fs, parent, err);
+ goto out2;
+ }
+
+ err = ext2fs_mkdir(fs, parent, 0, node_name);
+ }
+ if (err) {
+ ret = translate_error(fs, parent, err);
+ goto out2;
+ }
+
+ ret = update_mtime(fs, parent, NULL);
+ if (ret)
+ goto out2;
+
+ /* Still have to update the uid/gid of the dir */
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_path,
+ &child);
+ if (err) {
+ ret = translate_error(fs, 0, err);
+ goto out2;
+ }
+ dbg_printf("%s: created ino=%d/path=%s in dir=%d\n", __func__, child,
+ node_name, parent);
+
+ memset(&inode, 0, sizeof(inode));
+ err = ext2fs_read_inode_full(fs, child, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err) {
+ ret = translate_error(fs, child, err);
+ goto out2;
+ }
+
+ inode.i_uid = ctxt->uid;
+ ext2fs_set_i_uid_high(inode, ctxt->uid >> 16);
+ inode.i_gid = ctxt->gid;
+ ext2fs_set_i_gid_high(inode, ctxt->gid >> 16);
+ inode.i_mode = LINUX_S_IFDIR | (mode & ~(S_ISUID | fs->umask)) |
+ parent_sgid;
+ inode.i_generation = ff->next_generation++;
+
+ err = ext2fs_write_inode_full(fs, child, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err) {
+ ret = translate_error(fs, child, err);
+ goto out2;
+ }
+
+ /* Rewrite the directory block checksum, having set i_generation */
+ if ((inode.i_flags & EXT4_INLINE_DATA_FL) ||
+ !ext2fs_has_feature_metadata_csum(fs->super))
+ goto out2;
+ err = ext2fs_new_dir_block(fs, child, parent, &block);
+ if (err) {
+ ret = translate_error(fs, child, err);
+ goto out2;
+ }
+ err = ext2fs_bmap2(fs, child, (struct ext2_inode *)&inode, NULL, 0, 0,
+ NULL, &blk);
+ if (err) {
+ ret = translate_error(fs, child, err);
+ goto out3;
+ }
+ err = ext2fs_write_dir_block4(fs, blk, block, 0, child);
+ if (err) {
+ ret = translate_error(fs, child, err);
+ goto out3;
+ }
+
+out3:
+ ext2fs_free_mem(&block);
+out2:
+ pthread_mutex_unlock(&ff->bfl);
+out:
+ free(temp_path);
+ return ret;
+}
+
+static int unlink_file_by_name(ext2_filsys fs, const char *path)
+{
+ errcode_t err;
+ ext2_ino_t dir;
+ char *filename = strdup(path);
+ char *base_name;
+ int ret;
+
+ base_name = strrchr(filename, '/');
+ if (base_name) {
+ *base_name++ = '\0';
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, filename,
+ &dir);
+ if (err) {
+ free(filename);
+ return translate_error(fs, 0, err);
+ }
+ } else {
+ dir = EXT2_ROOT_INO;
+ base_name = filename;
+ }
+
+ ret = check_inum_access(fs, dir, W_OK);
+ if (ret) {
+ free(filename);
+ return ret;
+ }
+
+ dbg_printf("%s: unlinking name=%s from dir=%d\n", __func__,
+ base_name, dir);
+ err = ext2fs_unlink(fs, dir, base_name, 0, 0);
+ free(filename);
+ if (err)
+ return translate_error(fs, dir, err);
+
+ return update_mtime(fs, dir, NULL);
+}
+
+static int remove_inode(struct fuse2fs *ff, ext2_ino_t ino)
+{
+ ext2_filsys fs = ff->fs;
+ errcode_t err;
+ struct ext2_inode_large inode;
+ int ret = 0;
+
+ memset(&inode, 0, sizeof(inode));
+ err = ext2fs_read_inode_full(fs, ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+ dbg_printf("%s: put ino=%d links=%d\n", __func__, ino,
+ inode.i_links_count);
+
+ switch (inode.i_links_count) {
+ case 0:
+ return 0; /* XXX: already done? */
+ case 1:
+ inode.i_links_count--;
+ inode.i_dtime = fs->now ? fs->now : time(0);
+ break;
+ default:
+ inode.i_links_count--;
+ }
+
+ ret = update_ctime(fs, ino, &inode);
+ if (ret)
+ goto out;
+
+ if (inode.i_links_count)
+ goto write_out;
+
+ /* Nobody holds this file; free its blocks! */
+ err = ext2fs_free_ext_attr(fs, ino, &inode);
+ if (err)
+ goto write_out;
+
+ if (ext2fs_inode_has_valid_blocks2(fs, (struct ext2_inode *)&inode)) {
+ err = ext2fs_punch(fs, ino, (struct ext2_inode *)&inode, NULL,
+ 0, ~0ULL);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto write_out;
+ }
+ }
+
+ ext2fs_inode_alloc_stats2(fs, ino, -1,
+ LINUX_S_ISDIR(inode.i_mode));
+
+write_out:
+ err = ext2fs_write_inode_full(fs, ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+out:
+ return ret;
+}
+
+static int __op_unlink(struct fuse2fs *ff, const char *path)
+{
+ ext2_filsys fs = ff->fs;
+ ext2_ino_t ino;
+ errcode_t err;
+ int ret = 0;
+
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
+ if (err) {
+ ret = translate_error(fs, 0, err);
+ goto out;
+ }
+
+ ret = unlink_file_by_name(fs, path);
+ if (ret)
+ goto out;
+
+ ret = remove_inode(ff, ino);
+ if (ret)
+ goto out;
+out:
+ return ret;
+}
+
+static int op_unlink(const char *path)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ int ret;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ pthread_mutex_lock(&ff->bfl);
+ ret = __op_unlink(ff, path);
+ pthread_mutex_unlock(&ff->bfl);
+ return ret;
+}
+
+struct rd_struct {
+ ext2_ino_t parent;
+ int empty;
+};
+
+static int rmdir_proc(ext2_ino_t dir EXT2FS_ATTR((unused)),
+ int entry EXT2FS_ATTR((unused)),
+ struct ext2_dir_entry *dirent,
+ int offset EXT2FS_ATTR((unused)),
+ int blocksize EXT2FS_ATTR((unused)),
+ char *buf EXT2FS_ATTR((unused)),
+ void *private)
+{
+ struct rd_struct *rds = (struct rd_struct *) private;
+
+ if (dirent->inode == 0)
+ return 0;
+ if (((dirent->name_len & 0xFF) == 1) && (dirent->name[0] == '.'))
+ return 0;
+ if (((dirent->name_len & 0xFF) == 2) && (dirent->name[0] == '.') &&
+ (dirent->name[1] == '.')) {
+ rds->parent = dirent->inode;
+ return 0;
+ }
+ rds->empty = 0;
+ return 0;
+}
+
+static int __op_rmdir(struct fuse2fs *ff, const char *path)
+{
+ ext2_filsys fs = ff->fs;
+ ext2_ino_t child;
+ errcode_t err;
+ struct ext2_inode_large inode;
+ struct rd_struct rds;
+ int ret = 0;
+
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &child);
+ if (err) {
+ ret = translate_error(fs, 0, err);
+ goto out;
+ }
+ dbg_printf("%s: rmdir path=%s ino=%d\n", __func__, path, child);
+
+ rds.parent = 0;
+ rds.empty = 1;
+
+ err = ext2fs_dir_iterate2(fs, child, 0, 0, rmdir_proc, &rds);
+ if (err) {
+ ret = translate_error(fs, child, err);
+ goto out;
+ }
+
+ if (rds.empty == 0) {
+ ret = -ENOTEMPTY;
+ goto out;
+ }
+
+ ret = unlink_file_by_name(fs, path);
+ if (ret)
+ goto out;
+ /* Directories have to be "removed" twice. */
+ ret = remove_inode(ff, child);
+ if (ret)
+ goto out;
+ ret = remove_inode(ff, child);
+ if (ret)
+ goto out;
+
+ if (rds.parent) {
+ dbg_printf("%s: decr dir=%d link count\n", __func__,
+ rds.parent);
+ err = ext2fs_read_inode_full(fs, rds.parent,
+ (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err) {
+ ret = translate_error(fs, rds.parent, err);
+ goto out;
+ }
+ if (inode.i_links_count > 1)
+ inode.i_links_count--;
+ ret = update_mtime(fs, rds.parent, &inode);
+ if (ret)
+ goto out;
+ err = ext2fs_write_inode_full(fs, rds.parent,
+ (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err) {
+ ret = translate_error(fs, rds.parent, err);
+ goto out;
+ }
+ }
+
+out:
+ return ret;
+}
+
+static int op_rmdir(const char *path)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ int ret;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ pthread_mutex_lock(&ff->bfl);
+ ret = __op_rmdir(ff, path);
+ pthread_mutex_unlock(&ff->bfl);
+ return ret;
+}
+
+static int op_symlink(const char *src, const char *dest)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ ext2_filsys fs;
+ ext2_ino_t parent, child;
+ char *temp_path;
+ errcode_t err;
+ char *node_name, a;
+ struct ext2_inode_large inode;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ dbg_printf("%s: symlink %s to %s\n", __func__, src, dest);
+ temp_path = strdup(dest);
+ if (!temp_path) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ node_name = strrchr(temp_path, '/');
+ if (!node_name) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ node_name++;
+ a = *node_name;
+ *node_name = 0;
+
+ pthread_mutex_lock(&ff->bfl);
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_path,
+ &parent);
+ *node_name = a;
+ if (err) {
+ ret = translate_error(fs, 0, err);
+ goto out2;
+ }
+
+ ret = check_inum_access(fs, parent, W_OK);
+ if (ret)
+ goto out2;
+
+
+ /* Create symlink */
+ err = ext2fs_symlink(fs, parent, 0, node_name, src);
+ if (err == EXT2_ET_DIR_NO_SPACE) {
+ err = ext2fs_expand_dir(fs, parent);
+ if (err) {
+ ret = translate_error(fs, parent, err);
+ goto out2;
+ }
+
+ err = ext2fs_symlink(fs, parent, 0, node_name, src);
+ }
+ if (err) {
+ ret = translate_error(fs, parent, err);
+ goto out2;
+ }
+
+ /* Update parent dir's mtime */
+ ret = update_mtime(fs, parent, NULL);
+ if (ret)
+ goto out2;
+
+ /* Still have to update the uid/gid of the symlink */
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_path,
+ &child);
+ if (err) {
+ ret = translate_error(fs, 0, err);
+ goto out2;
+ }
+ dbg_printf("%s: symlinking ino=%d/name=%s to dir=%d\n", __func__,
+ child, node_name, parent);
+
+ memset(&inode, 0, sizeof(inode));
+ err = ext2fs_read_inode_full(fs, child, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err) {
+ ret = translate_error(fs, child, err);
+ goto out2;
+ }
+
+ inode.i_uid = ctxt->uid;
+ ext2fs_set_i_uid_high(inode, ctxt->uid >> 16);
+ inode.i_gid = ctxt->gid;
+ ext2fs_set_i_gid_high(inode, ctxt->gid >> 16);
+ inode.i_generation = ff->next_generation++;
+
+ err = ext2fs_write_inode_full(fs, child, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err) {
+ ret = translate_error(fs, child, err);
+ goto out2;
+ }
+out2:
+ pthread_mutex_unlock(&ff->bfl);
+out:
+ free(temp_path);
+ return ret;
+}
+
+struct update_dotdot {
+ ext2_ino_t new_dotdot;
+};
+
+static int update_dotdot_helper(ext2_ino_t dir EXT2FS_ATTR((unused)),
+ int entry EXT2FS_ATTR((unused)),
+ struct ext2_dir_entry *dirent,
+ int offset EXT2FS_ATTR((unused)),
+ int blocksize EXT2FS_ATTR((unused)),
+ char *buf EXT2FS_ATTR((unused)),
+ void *priv_data)
+{
+ struct update_dotdot *ud = priv_data;
+
+ if (ext2fs_dirent_name_len(dirent) == 2 &&
+ dirent->name[0] == '.' && dirent->name[1] == '.') {
+ dirent->inode = ud->new_dotdot;
+ return DIRENT_CHANGED | DIRENT_ABORT;
+ }
+
+ return 0;
+}
+
+static int op_rename(const char *from, const char *to)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ ext2_filsys fs;
+ errcode_t err;
+ ext2_ino_t from_ino, to_ino, to_dir_ino, from_dir_ino;
+ char *temp_to = NULL, *temp_from = NULL;
+ char *cp, a;
+ struct ext2_inode inode;
+ struct update_dotdot ud;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ dbg_printf("%s: renaming %s to %s\n", __func__, from, to);
+ pthread_mutex_lock(&ff->bfl);
+ if (!fs_can_allocate(ff, 5)) {
+ ret = -ENOSPC;
+ goto out;
+ }
+
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, from, &from_ino);
+ if (err || from_ino == 0) {
+ ret = translate_error(fs, 0, err);
+ goto out;
+ }
+
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, to, &to_ino);
+ if (err && err != EXT2_ET_FILE_NOT_FOUND) {
+ ret = translate_error(fs, 0, err);
+ goto out;
+ }
+
+ if (err == EXT2_ET_FILE_NOT_FOUND)
+ to_ino = 0;
+
+ /* Already the same file? */
+ if (to_ino != 0 && to_ino == from_ino) {
+ ret = 0;
+ goto out;
+ }
+
+ temp_to = strdup(to);
+ if (!temp_to) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ temp_from = strdup(from);
+ if (!temp_from) {
+ ret = -ENOMEM;
+ goto out2;
+ }
+
+ /* Find parent dir of the source and check write access */
+ cp = strrchr(temp_from, '/');
+ if (!cp) {
+ ret = -EINVAL;
+ goto out2;
+ }
+
+ a = *(cp + 1);
+ *(cp + 1) = 0;
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_from,
+ &from_dir_ino);
+ *(cp + 1) = a;
+ if (err) {
+ ret = translate_error(fs, 0, err);
+ goto out2;
+ }
+ if (from_dir_ino == 0) {
+ ret = -ENOENT;
+ goto out2;
+ }
+
+ ret = check_inum_access(fs, from_dir_ino, W_OK);
+ if (ret)
+ goto out2;
+
+ /* Find parent dir of the destination and check write access */
+ cp = strrchr(temp_to, '/');
+ if (!cp) {
+ ret = -EINVAL;
+ goto out2;
+ }
+
+ a = *(cp + 1);
+ *(cp + 1) = 0;
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_to,
+ &to_dir_ino);
+ *(cp + 1) = a;
+ if (err) {
+ ret = translate_error(fs, 0, err);
+ goto out2;
+ }
+ if (to_dir_ino == 0) {
+ ret = -ENOENT;
+ goto out2;
+ }
+
+ ret = check_inum_access(fs, to_dir_ino, W_OK);
+ if (ret)
+ goto out2;
+
+ /* If the target exists, unlink it first */
+ if (to_ino != 0) {
+ err = ext2fs_read_inode(fs, to_ino, &inode);
+ if (err) {
+ ret = translate_error(fs, to_ino, err);
+ goto out2;
+ }
+
+ dbg_printf("%s: unlinking %s ino=%d\n", __func__,
+ LINUX_S_ISDIR(inode.i_mode) ? "dir" : "file",
+ to_ino);
+ if (LINUX_S_ISDIR(inode.i_mode))
+ ret = __op_rmdir(ff, to);
+ else
+ ret = __op_unlink(ff, to);
+ if (ret)
+ goto out2;
+ }
+
+ /* Get ready to do the move */
+ err = ext2fs_read_inode(fs, from_ino, &inode);
+ if (err) {
+ ret = translate_error(fs, from_ino, err);
+ goto out2;
+ }
+
+ /* Link in the new file */
+ dbg_printf("%s: linking ino=%d/path=%s to dir=%d\n", __func__,
+ from_ino, cp + 1, to_dir_ino);
+ err = ext2fs_link(fs, to_dir_ino, cp + 1, from_ino,
+ ext2_file_type(inode.i_mode));
+ if (err == EXT2_ET_DIR_NO_SPACE) {
+ err = ext2fs_expand_dir(fs, to_dir_ino);
+ if (err) {
+ ret = translate_error(fs, to_dir_ino, err);
+ goto out2;
+ }
+
+ err = ext2fs_link(fs, to_dir_ino, cp + 1, from_ino,
+ ext2_file_type(inode.i_mode));
+ }
+ if (err) {
+ ret = translate_error(fs, to_dir_ino, err);
+ goto out2;
+ }
+
+ /* Update '..' pointer if dir */
+ err = ext2fs_read_inode(fs, from_ino, &inode);
+ if (err) {
+ ret = translate_error(fs, from_ino, err);
+ goto out2;
+ }
+
+ if (LINUX_S_ISDIR(inode.i_mode)) {
+ ud.new_dotdot = to_dir_ino;
+ dbg_printf("%s: updating .. entry for dir=%d\n", __func__,
+ to_dir_ino);
+ err = ext2fs_dir_iterate2(fs, from_ino, 0, NULL,
+ update_dotdot_helper, &ud);
+ if (err) {
+ ret = translate_error(fs, from_ino, err);
+ goto out2;
+ }
+
+ /* Decrease from_dir_ino's links_count */
+ dbg_printf("%s: moving linkcount from dir=%d to dir=%d\n",
+ __func__, from_dir_ino, to_dir_ino);
+ err = ext2fs_read_inode(fs, from_dir_ino, &inode);
+ if (err) {
+ ret = translate_error(fs, from_dir_ino, err);
+ goto out2;
+ }
+ inode.i_links_count--;
+ err = ext2fs_write_inode(fs, from_dir_ino, &inode);
+ if (err) {
+ ret = translate_error(fs, from_dir_ino, err);
+ goto out2;
+ }
+
+ /* Increase to_dir_ino's links_count */
+ err = ext2fs_read_inode(fs, to_dir_ino, &inode);
+ if (err) {
+ ret = translate_error(fs, to_dir_ino, err);
+ goto out2;
+ }
+ inode.i_links_count++;
+ err = ext2fs_write_inode(fs, to_dir_ino, &inode);
+ if (err) {
+ ret = translate_error(fs, to_dir_ino, err);
+ goto out2;
+ }
+ }
+
+ /* Update timestamps */
+ ret = update_ctime(fs, from_ino, NULL);
+ if (ret)
+ goto out2;
+
+ ret = update_mtime(fs, to_dir_ino, NULL);
+ if (ret)
+ goto out2;
+
+ /* Remove the old file */
+ ret = unlink_file_by_name(fs, from);
+ if (ret)
+ goto out2;
+
+ /* Flush the whole mess out */
+ err = ext2fs_flush2(fs, 0);
+ if (err)
+ ret = translate_error(fs, 0, err);
+
+out2:
+ free(temp_from);
+ free(temp_to);
+out:
+ pthread_mutex_unlock(&ff->bfl);
+ return ret;
+}
+
+static int op_link(const char *src, const char *dest)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ ext2_filsys fs;
+ char *temp_path;
+ errcode_t err;
+ char *node_name, a;
+ ext2_ino_t parent, ino;
+ struct ext2_inode_large inode;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ dbg_printf("%s: src=%s dest=%s\n", __func__, src, dest);
+ temp_path = strdup(dest);
+ if (!temp_path) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ node_name = strrchr(temp_path, '/');
+ if (!node_name) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ node_name++;
+ a = *node_name;
+ *node_name = 0;
+
+ pthread_mutex_lock(&ff->bfl);
+ if (!fs_can_allocate(ff, 2)) {
+ ret = -ENOSPC;
+ goto out2;
+ }
+
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_path,
+ &parent);
+ *node_name = a;
+ if (err) {
+ err = -ENOENT;
+ goto out2;
+ }
+
+ ret = check_inum_access(fs, parent, W_OK);
+ if (ret)
+ goto out2;
+
+
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, src, &ino);
+ if (err || ino == 0) {
+ ret = translate_error(fs, 0, err);
+ goto out2;
+ }
+
+ memset(&inode, 0, sizeof(inode));
+ err = ext2fs_read_inode_full(fs, ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out2;
+ }
+
+ inode.i_links_count++;
+ ret = update_ctime(fs, ino, &inode);
+ if (ret)
+ goto out2;
+
+ err = ext2fs_write_inode_full(fs, ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out2;
+ }
+
+ dbg_printf("%s: linking ino=%d/name=%s to dir=%d\n", __func__, ino,
+ node_name, parent);
+ err = ext2fs_link(fs, parent, node_name, ino,
+ ext2_file_type(inode.i_mode));
+ if (err == EXT2_ET_DIR_NO_SPACE) {
+ err = ext2fs_expand_dir(fs, parent);
+ if (err) {
+ ret = translate_error(fs, parent, err);
+ goto out2;
+ }
+
+ err = ext2fs_link(fs, parent, node_name, ino,
+ ext2_file_type(inode.i_mode));
+ }
+ if (err) {
+ ret = translate_error(fs, parent, err);
+ goto out2;
+ }
+
+ ret = update_mtime(fs, parent, NULL);
+ if (ret)
+ goto out2;
+
+out2:
+ pthread_mutex_unlock(&ff->bfl);
+out:
+ free(temp_path);
+ return ret;
+}
+
+static int op_chmod(const char *path, mode_t mode)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ ext2_filsys fs;
+ errcode_t err;
+ ext2_ino_t ino;
+ struct ext2_inode_large inode;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ pthread_mutex_lock(&ff->bfl);
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
+ if (err) {
+ ret = translate_error(fs, 0, err);
+ goto out;
+ }
+ dbg_printf("%s: path=%s mode=0%o ino=%d\n", __func__, path, mode, ino);
+
+ memset(&inode, 0, sizeof(inode));
+ err = ext2fs_read_inode_full(fs, ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+
+ if (!ff->fakeroot && ctxt->uid != 0 && ctxt->uid != inode_uid(inode)) {
+ ret = -EPERM;
+ goto out;
+ }
+
+ /*
+ * XXX: We should really check that the inode gid is not in /any/
+ * of the user's groups, but FUSE only tells us about the primary
+ * group.
+ */
+ if (!ff->fakeroot && ctxt->uid != 0 && ctxt->gid != inode_gid(inode))
+ mode &= ~S_ISGID;
+
+ inode.i_mode &= ~0xFFF;
+ inode.i_mode |= mode & 0xFFF;
+ ret = update_ctime(fs, ino, &inode);
+ if (ret)
+ goto out;
+
+ err = ext2fs_write_inode_full(fs, ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+
+out:
+ pthread_mutex_unlock(&ff->bfl);
+ return ret;
+}
+
+static int op_chown(const char *path, uid_t owner, gid_t group)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ ext2_filsys fs;
+ errcode_t err;
+ ext2_ino_t ino;
+ struct ext2_inode_large inode;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ pthread_mutex_lock(&ff->bfl);
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
+ if (err) {
+ ret = translate_error(fs, 0, err);
+ goto out;
+ }
+ dbg_printf("%s: path=%s owner=%d group=%d ino=%d\n", __func__,
+ path, owner, group, ino);
+
+ memset(&inode, 0, sizeof(inode));
+ err = ext2fs_read_inode_full(fs, ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+
+ /* FUSE seems to feed us ~0 to mean "don't change" */
+ if (owner != (uid_t) ~0) {
+ /* Only root gets to change UID. */
+ if (!ff->fakeroot && ctxt->uid != 0 &&
+ !(inode_uid(inode) == ctxt->uid && owner == ctxt->uid)) {
+ ret = -EPERM;
+ goto out;
+ }
+ inode.i_uid = owner;
+ ext2fs_set_i_uid_high(inode, owner >> 16);
+ }
+
+ if (group != (gid_t) ~0) {
+ /* Only root or the owner get to change GID. */
+ if (!ff->fakeroot && ctxt->uid != 0 &&
+ inode_uid(inode) != ctxt->uid) {
+ ret = -EPERM;
+ goto out;
+ }
+
+ /* XXX: We /should/ check group membership but FUSE */
+ inode.i_gid = group;
+ ext2fs_set_i_gid_high(inode, group >> 16);
+ }
+
+ ret = update_ctime(fs, ino, &inode);
+ if (ret)
+ goto out;
+
+ err = ext2fs_write_inode_full(fs, ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+
+out:
+ pthread_mutex_unlock(&ff->bfl);
+ return ret;
+}
+
+static int op_truncate(const char *path, off_t len)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ ext2_filsys fs;
+ errcode_t err;
+ ext2_ino_t ino;
+ ext2_file_t file;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ pthread_mutex_lock(&ff->bfl);
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
+ if (err || ino == 0) {
+ ret = translate_error(fs, 0, err);
+ goto out;
+ }
+ dbg_printf("%s: ino=%d len=%jd\n", __func__, ino, len);
+
+ ret = check_inum_access(fs, ino, W_OK);
+ if (ret)
+ goto out;
+
+ err = ext2fs_file_open(fs, ino, EXT2_FILE_WRITE, &file);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+
+ err = ext2fs_file_set_size2(file, len);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out2;
+ }
+
+out2:
+ err = ext2fs_file_close(file);
+ if (ret)
+ goto out;
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+
+ ret = update_mtime(fs, ino, NULL);
+
+out:
+ pthread_mutex_unlock(&ff->bfl);
+ return err;
+}
+
+#ifdef __linux__
+static void detect_linux_executable_open(int kernel_flags, int *access_check,
+ int *e2fs_open_flags)
+{
+ /*
+ * On Linux, execve will bleed __FMODE_EXEC into the file mode flags,
+ * and FUSE is more than happy to let that slip through.
+ */
+ if (kernel_flags & 0x20) {
+ *access_check = X_OK;
+ *e2fs_open_flags &= ~EXT2_FILE_WRITE;
+ }
+}
+#else
+static void detect_linux_executable_open(int kernel_flags, int *access_check,
+ int *e2fs_open_flags)
+{
+ /* empty */
+}
+#endif /* __linux__ */
+
+static int __op_open(struct fuse2fs *ff, const char *path,
+ struct fuse_file_info *fp)
+{
+ ext2_filsys fs = ff->fs;
+ errcode_t err;
+ struct fuse2fs_file_handle *file;
+ int check = 0, ret = 0;
+
+ dbg_printf("%s: path=%s\n", __func__, path);
+ err = ext2fs_get_mem(sizeof(*file), &file);
+ if (err)
+ return translate_error(fs, 0, err);
+ file->magic = FUSE2FS_FILE_MAGIC;
+
+ file->open_flags = 0;
+ switch (fp->flags & O_ACCMODE) {
+ case O_RDONLY:
+ check = R_OK;
+ break;
+ case O_WRONLY:
+ check = W_OK;
+ file->open_flags |= EXT2_FILE_WRITE;
+ break;
+ case O_RDWR:
+ check = R_OK | W_OK;
+ file->open_flags |= EXT2_FILE_WRITE;
+ break;
+ }
+
+ detect_linux_executable_open(fp->flags, &check, &file->open_flags);
+
+ if (fp->flags & O_CREAT)
+ file->open_flags |= EXT2_FILE_CREATE;
+
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &file->ino);
+ if (err || file->ino == 0) {
+ ret = translate_error(fs, 0, err);
+ goto out;
+ }
+ dbg_printf("%s: ino=%d\n", __func__, file->ino);
+
+ ret = check_inum_access(fs, file->ino, check);
+ if (ret) {
+ /*
+ * In a regular (Linux) fs driver, the kernel will open
+ * binaries for reading if the user has --x privileges (i.e.
+ * execute without read). Since the kernel doesn't have any
+ * way to tell us if it's opening a file via execve, we'll
+ * just assume that allowing access is ok if asking for ro mode
+ * fails but asking for x mode succeeds. Of course we can
+ * also employ undocumented hacks (see above).
+ */
+ if (check == R_OK) {
+ ret = check_inum_access(fs, file->ino, X_OK);
+ if (ret)
+ goto out;
+ } else
+ goto out;
+ }
+ fp->fh = (uintptr_t)file;
+
+out:
+ if (ret)
+ ext2fs_free_mem(&file);
+ return ret;
+}
+
+static int op_open(const char *path, struct fuse_file_info *fp)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ int ret;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ pthread_mutex_lock(&ff->bfl);
+ ret = __op_open(ff, path, fp);
+ pthread_mutex_unlock(&ff->bfl);
+ return ret;
+}
+
+static int op_read(const char *path EXT2FS_ATTR((unused)), char *buf,
+ size_t len, off_t offset,
+ struct fuse_file_info *fp)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ struct fuse2fs_file_handle *fh =
+ (struct fuse2fs_file_handle *)(uintptr_t)fp->fh;
+ ext2_filsys fs;
+ ext2_file_t efp;
+ errcode_t err;
+ unsigned int got = 0;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ FUSE2FS_CHECK_MAGIC(fs, fh, FUSE2FS_FILE_MAGIC);
+ dbg_printf("%s: ino=%d off=%jd len=%jd\n", __func__, fh->ino, offset,
+ len);
+ pthread_mutex_lock(&ff->bfl);
+ err = ext2fs_file_open(fs, fh->ino, fh->open_flags, &efp);
+ if (err) {
+ ret = translate_error(fs, fh->ino, err);
+ goto out;
+ }
+
+ err = ext2fs_file_llseek(efp, offset, SEEK_SET, NULL);
+ if (err) {
+ ret = translate_error(fs, fh->ino, err);
+ goto out2;
+ }
+
+ err = ext2fs_file_read(efp, buf, len, &got);
+ if (err) {
+ ret = translate_error(fs, fh->ino, err);
+ goto out2;
+ }
+
+out2:
+ err = ext2fs_file_close(efp);
+ if (ret)
+ goto out;
+ if (err) {
+ ret = translate_error(fs, fh->ino, err);
+ goto out;
+ }
+
+ if (fs_writeable(fs)) {
+ ret = update_atime(fs, fh->ino);
+ if (ret)
+ goto out;
+ }
+out:
+ pthread_mutex_unlock(&ff->bfl);
+ return got ? (int) got : ret;
+}
+
+static int op_write(const char *path EXT2FS_ATTR((unused)),
+ const char *buf, size_t len, off_t offset,
+ struct fuse_file_info *fp)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ struct fuse2fs_file_handle *fh =
+ (struct fuse2fs_file_handle *)(uintptr_t)fp->fh;
+ ext2_filsys fs;
+ ext2_file_t efp;
+ errcode_t err;
+ unsigned int got = 0;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ FUSE2FS_CHECK_MAGIC(fs, fh, FUSE2FS_FILE_MAGIC);
+ dbg_printf("%s: ino=%d off=%jd len=%jd\n", __func__, fh->ino, offset,
+ len);
+ pthread_mutex_lock(&ff->bfl);
+ if (!fs_writeable(fs)) {
+ ret = -EROFS;
+ goto out;
+ }
+
+ if (!fs_can_allocate(ff, len / fs->blocksize)) {
+ ret = -ENOSPC;
+ goto out;
+ }
+
+ err = ext2fs_file_open(fs, fh->ino, fh->open_flags, &efp);
+ if (err) {
+ ret = translate_error(fs, fh->ino, err);
+ goto out;
+ }
+
+ err = ext2fs_file_llseek(efp, offset, SEEK_SET, NULL);
+ if (err) {
+ ret = translate_error(fs, fh->ino, err);
+ goto out2;
+ }
+
+ err = ext2fs_file_write(efp, buf, len, &got);
+ if (err) {
+ ret = translate_error(fs, fh->ino, err);
+ goto out2;
+ }
+
+ err = ext2fs_file_flush(efp);
+ if (err) {
+ got = 0;
+ ret = translate_error(fs, fh->ino, err);
+ goto out2;
+ }
+
+out2:
+ err = ext2fs_file_close(efp);
+ if (ret)
+ goto out;
+ if (err) {
+ ret = translate_error(fs, fh->ino, err);
+ goto out;
+ }
+
+ ret = update_mtime(fs, fh->ino, NULL);
+ if (ret)
+ goto out;
+
+out:
+ pthread_mutex_unlock(&ff->bfl);
+ return got ? (int) got : ret;
+}
+
+static int op_release(const char *path EXT2FS_ATTR((unused)),
+ struct fuse_file_info *fp)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ struct fuse2fs_file_handle *fh =
+ (struct fuse2fs_file_handle *)(uintptr_t)fp->fh;
+ ext2_filsys fs;
+ errcode_t err;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ FUSE2FS_CHECK_MAGIC(fs, fh, FUSE2FS_FILE_MAGIC);
+ dbg_printf("%s: ino=%d\n", __func__, fh->ino);
+ pthread_mutex_lock(&ff->bfl);
+ if (fs_writeable(fs) && fh->open_flags & EXT2_FILE_WRITE) {
+ err = ext2fs_flush2(fs, EXT2_FLAG_FLUSH_NO_SYNC);
+ if (err)
+ ret = translate_error(fs, fh->ino, err);
+ }
+ fp->fh = 0;
+ pthread_mutex_unlock(&ff->bfl);
+
+ ext2fs_free_mem(&fh);
+
+ return ret;
+}
+
+static int op_fsync(const char *path EXT2FS_ATTR((unused)),
+ int datasync EXT2FS_ATTR((unused)),
+ struct fuse_file_info *fp)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ struct fuse2fs_file_handle *fh =
+ (struct fuse2fs_file_handle *)(uintptr_t)fp->fh;
+ ext2_filsys fs;
+ errcode_t err;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ FUSE2FS_CHECK_MAGIC(fs, fh, FUSE2FS_FILE_MAGIC);
+ dbg_printf("%s: ino=%d\n", __func__, fh->ino);
+ /* For now, flush everything, even if it's slow */
+ pthread_mutex_lock(&ff->bfl);
+ if (fs_writeable(fs) && fh->open_flags & EXT2_FILE_WRITE) {
+ err = ext2fs_flush2(fs, 0);
+ if (err)
+ ret = translate_error(fs, fh->ino, err);
+ }
+ pthread_mutex_unlock(&ff->bfl);
+
+ return ret;
+}
+
+static int op_statfs(const char *path EXT2FS_ATTR((unused)),
+ struct statvfs *buf)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ ext2_filsys fs;
+ uint64_t fsid, *f;
+ blk64_t overhead, reserved, free;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ dbg_printf("%s: path=%s\n", __func__, path);
+ buf->f_bsize = fs->blocksize;
+ buf->f_frsize = 0;
+
+ if (ff->minixdf)
+ overhead = 0;
+ else
+ overhead = fs->desc_blocks +
+ (blk64_t)fs->group_desc_count *
+ (fs->inode_blocks_per_group + 2);
+ reserved = ext2fs_r_blocks_count(fs->super);
+ if (!reserved)
+ reserved = ext2fs_blocks_count(fs->super) / 10;
+ free = ext2fs_free_blocks_count(fs->super);
+
+ buf->f_blocks = ext2fs_blocks_count(fs->super) - overhead;
+ buf->f_bfree = free;
+ if (free < reserved)
+ buf->f_bavail = 0;
+ else
+ buf->f_bavail = free - reserved;
+ buf->f_files = fs->super->s_inodes_count;
+ buf->f_ffree = fs->super->s_free_inodes_count;
+ buf->f_favail = fs->super->s_free_inodes_count;
+ f = (uint64_t *)fs->super->s_uuid;
+ fsid = *f;
+ f++;
+ fsid ^= *f;
+ buf->f_fsid = fsid;
+ buf->f_flag = 0;
+ if (fs->flags & EXT2_FLAG_RW)
+ buf->f_flag |= ST_RDONLY;
+ buf->f_namemax = EXT2_NAME_LEN;
+
+ return 0;
+}
+
+typedef errcode_t (*xattr_xlate_get)(void **cooked_buf, size_t *cooked_sz,
+ const void *raw_buf, size_t raw_sz);
+typedef errcode_t (*xattr_xlate_set)(const void *cooked_buf, size_t cooked_sz,
+ const void **raw_buf, size_t *raw_sz);
+struct xattr_translate {
+ const char *prefix;
+ xattr_xlate_get get;
+ xattr_xlate_set set;
+};
+
+#define XATTR_TRANSLATOR(p, g, s) \
+ {.prefix = (p), \
+ .get = (xattr_xlate_get)(g), \
+ .set = (xattr_xlate_set)(s)}
+
+static struct xattr_translate xattr_translators[] = {
+#ifdef TRANSLATE_LINUX_ACLS
+ XATTR_TRANSLATOR(ACL_EA_ACCESS, ext4_to_fuse_acl, fuse_to_ext4_acl),
+ XATTR_TRANSLATOR(ACL_EA_DEFAULT, ext4_to_fuse_acl, fuse_to_ext4_acl),
+#endif
+ XATTR_TRANSLATOR(NULL, NULL, NULL),
+};
+#undef XATTR_TRANSLATOR
+
+static int op_getxattr(const char *path, const char *key, char *value,
+ size_t len)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ ext2_filsys fs;
+ struct ext2_xattr_handle *h;
+ struct xattr_translate *xt;
+ void *ptr, *cptr;
+ size_t plen, clen;
+ ext2_ino_t ino;
+ errcode_t err;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ pthread_mutex_lock(&ff->bfl);
+ if (!ext2fs_has_feature_xattr(fs->super)) {
+ ret = -ENOTSUP;
+ goto out;
+ }
+
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
+ if (err || ino == 0) {
+ ret = translate_error(fs, 0, err);
+ goto out;
+ }
+ dbg_printf("%s: ino=%d\n", __func__, ino);
+
+ ret = check_inum_access(fs, ino, R_OK);
+ if (ret)
+ goto out;
+
+ err = ext2fs_xattrs_open(fs, ino, &h);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+
+ err = ext2fs_xattrs_read(h);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out2;
+ }
+
+ err = ext2fs_xattr_get(h, key, &ptr, &plen);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out2;
+ }
+
+ for (xt = xattr_translators; xt->prefix != NULL; xt++) {
+ if (strncmp(key, xt->prefix, strlen(xt->prefix)) == 0) {
+ err = xt->get(&cptr, &clen, ptr, plen);
+ if (err)
+ goto out3;
+ ext2fs_free_mem(&ptr);
+ ptr = cptr;
+ plen = clen;
+ }
+ }
+
+ if (!len) {
+ ret = plen;
+ } else if (len < plen) {
+ ret = -ERANGE;
+ } else {
+ memcpy(value, ptr, plen);
+ ret = plen;
+ }
+
+out3:
+ ext2fs_free_mem(&ptr);
+out2:
+ err = ext2fs_xattrs_close(&h);
+ if (err)
+ ret = translate_error(fs, ino, err);
+out:
+ pthread_mutex_unlock(&ff->bfl);
+
+ return ret;
+}
+
+static int count_buffer_space(char *name, char *value EXT2FS_ATTR((unused)),
+ size_t value_len EXT2FS_ATTR((unused)),
+ void *data)
+{
+ unsigned int *x = data;
+
+ *x = *x + strlen(name) + 1;
+ return 0;
+}
+
+static int copy_names(char *name, char *value EXT2FS_ATTR((unused)),
+ size_t value_len EXT2FS_ATTR((unused)), void *data)
+{
+ char **b = data;
+ size_t name_len = strlen(name);
+
+ memcpy(*b, name, name_len + 1);
+ *b = *b + name_len + 1;
+
+ return 0;
+}
+
+static int op_listxattr(const char *path, char *names, size_t len)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ ext2_filsys fs;
+ struct ext2_xattr_handle *h;
+ unsigned int bufsz;
+ ext2_ino_t ino;
+ errcode_t err;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ pthread_mutex_lock(&ff->bfl);
+ if (!ext2fs_has_feature_xattr(fs->super)) {
+ ret = -ENOTSUP;
+ goto out;
+ }
+
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
+ if (err || ino == 0) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+ dbg_printf("%s: ino=%d\n", __func__, ino);
+
+ ret = check_inum_access(fs, ino, R_OK);
+ if (ret)
+ goto out;
+
+ err = ext2fs_xattrs_open(fs, ino, &h);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+
+ err = ext2fs_xattrs_read(h);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out2;
+ }
+
+ /* Count buffer space needed for names */
+ bufsz = 0;
+ err = ext2fs_xattrs_iterate(h, count_buffer_space, &bufsz);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out2;
+ }
+
+ if (len == 0) {
+ ret = bufsz;
+ goto out2;
+ } else if (len < bufsz) {
+ ret = -ERANGE;
+ goto out2;
+ }
+
+ /* Copy names out */
+ memset(names, 0, len);
+ err = ext2fs_xattrs_iterate(h, copy_names, &names);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out2;
+ }
+ ret = bufsz;
+out2:
+ err = ext2fs_xattrs_close(&h);
+ if (err)
+ ret = translate_error(fs, ino, err);
+out:
+ pthread_mutex_unlock(&ff->bfl);
+
+ return ret;
+}
+
+static int op_setxattr(const char *path EXT2FS_ATTR((unused)),
+ const char *key, const char *value,
+ size_t len, int flags EXT2FS_ATTR((unused)))
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ ext2_filsys fs;
+ struct ext2_xattr_handle *h;
+ struct xattr_translate *xt;
+ const void *cvalue;
+ size_t clen;
+ ext2_ino_t ino;
+ errcode_t err;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ pthread_mutex_lock(&ff->bfl);
+ if (!ext2fs_has_feature_xattr(fs->super)) {
+ ret = -ENOTSUP;
+ goto out;
+ }
+
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
+ if (err || ino == 0) {
+ ret = translate_error(fs, 0, err);
+ goto out;
+ }
+ dbg_printf("%s: ino=%d\n", __func__, ino);
+
+ ret = check_inum_access(fs, ino, W_OK);
+ if (ret == -EACCES) {
+ ret = -EPERM;
+ goto out;
+ } else if (ret)
+ goto out;
+
+ err = ext2fs_xattrs_open(fs, ino, &h);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+
+ err = ext2fs_xattrs_read(h);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out2;
+ }
+
+ cvalue = value;
+ clen = len;
+ for (xt = xattr_translators; xt->prefix != NULL; xt++) {
+ if (strncmp(key, xt->prefix, strlen(xt->prefix)) == 0) {
+ err = xt->set(value, len, &cvalue, &clen);
+ if (err)
+ goto out3;
+ }
+ }
+
+ err = ext2fs_xattr_set(h, key, cvalue, clen);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out3;
+ }
+
+ ret = update_ctime(fs, ino, NULL);
+out3:
+ if (cvalue != value)
+ ext2fs_free_mem(&cvalue);
+out2:
+ err = ext2fs_xattrs_close(&h);
+ if (!ret && err)
+ ret = translate_error(fs, ino, err);
+out:
+ pthread_mutex_unlock(&ff->bfl);
+
+ return ret;
+}
+
+static int op_removexattr(const char *path, const char *key)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ ext2_filsys fs;
+ struct ext2_xattr_handle *h;
+ ext2_ino_t ino;
+ errcode_t err;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ pthread_mutex_lock(&ff->bfl);
+ if (!ext2fs_has_feature_xattr(fs->super)) {
+ ret = -ENOTSUP;
+ goto out;
+ }
+
+ if (!fs_can_allocate(ff, 1)) {
+ ret = -ENOSPC;
+ goto out;
+ }
+
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
+ if (err || ino == 0) {
+ ret = translate_error(fs, 0, err);
+ goto out;
+ }
+ dbg_printf("%s: ino=%d\n", __func__, ino);
+
+ ret = check_inum_access(fs, ino, W_OK);
+ if (ret)
+ goto out;
+
+ err = ext2fs_xattrs_open(fs, ino, &h);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+
+ err = ext2fs_xattrs_read(h);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out2;
+ }
+
+ err = ext2fs_xattr_remove(h, key);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out2;
+ }
+
+ ret = update_ctime(fs, ino, NULL);
+out2:
+ err = ext2fs_xattrs_close(&h);
+ if (err)
+ ret = translate_error(fs, ino, err);
+out:
+ pthread_mutex_unlock(&ff->bfl);
+
+ return ret;
+}
+
+struct readdir_iter {
+ void *buf;
+ fuse_fill_dir_t func;
+};
+
+static int op_readdir_iter(ext2_ino_t dir EXT2FS_ATTR((unused)),
+ int entry EXT2FS_ATTR((unused)),
+ struct ext2_dir_entry *dirent,
+ int offset EXT2FS_ATTR((unused)),
+ int blocksize EXT2FS_ATTR((unused)),
+ char *buf EXT2FS_ATTR((unused)), void *data)
+{
+ struct readdir_iter *i = data;
+ char namebuf[EXT2_NAME_LEN + 1];
+ int ret;
+
+ memcpy(namebuf, dirent->name, dirent->name_len & 0xFF);
+ namebuf[dirent->name_len & 0xFF] = 0;
+ ret = i->func(i->buf, namebuf, NULL, 0);
+ if (ret)
+ return DIRENT_ABORT;
+
+ return 0;
+}
+
+static int op_readdir(const char *path EXT2FS_ATTR((unused)),
+ void *buf, fuse_fill_dir_t fill_func,
+ off_t offset EXT2FS_ATTR((unused)),
+ struct fuse_file_info *fp)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ struct fuse2fs_file_handle *fh =
+ (struct fuse2fs_file_handle *)(uintptr_t)fp->fh;
+ ext2_filsys fs;
+ errcode_t err;
+ struct readdir_iter i;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ FUSE2FS_CHECK_MAGIC(fs, fh, FUSE2FS_FILE_MAGIC);
+ dbg_printf("%s: ino=%d\n", __func__, fh->ino);
+ pthread_mutex_lock(&ff->bfl);
+ i.buf = buf;
+ i.func = fill_func;
+ err = ext2fs_dir_iterate2(fs, fh->ino, 0, NULL, op_readdir_iter, &i);
+ if (err) {
+ ret = translate_error(fs, fh->ino, err);
+ goto out;
+ }
+
+ if (fs_writeable(fs)) {
+ ret = update_atime(fs, fh->ino);
+ if (ret)
+ goto out;
+ }
+out:
+ pthread_mutex_unlock(&ff->bfl);
+ return ret;
+}
+
+static int op_access(const char *path, int mask)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ ext2_filsys fs;
+ errcode_t err;
+ ext2_ino_t ino;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ dbg_printf("%s: path=%s mask=0x%x\n", __func__, path, mask);
+ pthread_mutex_lock(&ff->bfl);
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
+ if (err || ino == 0) {
+ ret = translate_error(fs, 0, err);
+ goto out;
+ }
+
+ ret = check_inum_access(fs, ino, mask);
+ if (ret)
+ goto out;
+
+out:
+ pthread_mutex_unlock(&ff->bfl);
+ return ret;
+}
+
+static int op_create(const char *path, mode_t mode, struct fuse_file_info *fp)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ ext2_filsys fs;
+ ext2_ino_t parent, child;
+ char *temp_path;
+ errcode_t err;
+ char *node_name, a;
+ int filetype;
+ struct ext2_inode_large inode;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ dbg_printf("%s: path=%s mode=0%o\n", __func__, path, mode);
+ temp_path = strdup(path);
+ if (!temp_path) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ node_name = strrchr(temp_path, '/');
+ if (!node_name) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ node_name++;
+ a = *node_name;
+ *node_name = 0;
+
+ pthread_mutex_lock(&ff->bfl);
+ if (!fs_can_allocate(ff, 1)) {
+ ret = -ENOSPC;
+ goto out2;
+ }
+
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_path,
+ &parent);
+ if (err) {
+ ret = translate_error(fs, 0, err);
+ goto out2;
+ }
+
+ ret = check_inum_access(fs, parent, W_OK);
+ if (ret)
+ goto out2;
+
+ *node_name = a;
+
+ filetype = ext2_file_type(mode);
+
+ err = ext2fs_new_inode(fs, parent, mode, 0, &child);
+ if (err) {
+ ret = translate_error(fs, parent, err);
+ goto out2;
+ }
+
+ dbg_printf("%s: creating ino=%d/name=%s in dir=%d\n", __func__, child,
+ node_name, parent);
+ err = ext2fs_link(fs, parent, node_name, child, filetype);
+ if (err == EXT2_ET_DIR_NO_SPACE) {
+ err = ext2fs_expand_dir(fs, parent);
+ if (err) {
+ ret = translate_error(fs, parent, err);
+ goto out2;
+ }
+
+ err = ext2fs_link(fs, parent, node_name, child,
+ filetype);
+ }
+ if (err) {
+ ret = translate_error(fs, parent, err);
+ goto out2;
+ }
+
+ ret = update_mtime(fs, parent, NULL);
+ if (ret)
+ goto out2;
+
+ memset(&inode, 0, sizeof(inode));
+ inode.i_mode = mode;
+ inode.i_links_count = 1;
+ inode.i_extra_isize = sizeof(struct ext2_inode_large) -
+ EXT2_GOOD_OLD_INODE_SIZE;
+ inode.i_uid = ctxt->uid;
+ ext2fs_set_i_uid_high(inode, ctxt->uid >> 16);
+ inode.i_gid = ctxt->gid;
+ ext2fs_set_i_gid_high(inode, ctxt->gid >> 16);
+ if (ext2fs_has_feature_extents(fs->super)) {
+ ext2_extent_handle_t handle;
+
+ inode.i_flags &= ~EXT4_EXTENTS_FL;
+ ret = ext2fs_extent_open2(fs, child,
+ (struct ext2_inode *)&inode, &handle);
+ if (ret)
+ return ret;
+ ext2fs_extent_free(handle);
+ }
+
+ err = ext2fs_write_new_inode(fs, child, (struct ext2_inode *)&inode);
+ if (err) {
+ ret = translate_error(fs, child, err);
+ goto out2;
+ }
+
+ inode.i_generation = ff->next_generation++;
+ init_times(&inode);
+ err = ext2fs_write_inode_full(fs, child, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err) {
+ ret = translate_error(fs, child, err);
+ goto out2;
+ }
+
+ ext2fs_inode_alloc_stats2(fs, child, 1, 0);
+
+ ret = __op_open(ff, path, fp);
+ if (ret)
+ goto out2;
+out2:
+ pthread_mutex_unlock(&ff->bfl);
+out:
+ free(temp_path);
+ return ret;
+}
+
+static int op_ftruncate(const char *path EXT2FS_ATTR((unused)),
+ off_t len, struct fuse_file_info *fp)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ struct fuse2fs_file_handle *fh =
+ (struct fuse2fs_file_handle *)(uintptr_t)fp->fh;
+ ext2_filsys fs;
+ ext2_file_t efp;
+ errcode_t err;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ FUSE2FS_CHECK_MAGIC(fs, fh, FUSE2FS_FILE_MAGIC);
+ dbg_printf("%s: ino=%d len=%jd\n", __func__, fh->ino, len);
+ pthread_mutex_lock(&ff->bfl);
+ if (!fs_writeable(fs)) {
+ ret = -EROFS;
+ goto out;
+ }
+
+ err = ext2fs_file_open(fs, fh->ino, fh->open_flags, &efp);
+ if (err) {
+ ret = translate_error(fs, fh->ino, err);
+ goto out;
+ }
+
+ err = ext2fs_file_set_size2(efp, len);
+ if (err) {
+ ret = translate_error(fs, fh->ino, err);
+ goto out2;
+ }
+
+out2:
+ err = ext2fs_file_close(efp);
+ if (ret)
+ goto out;
+ if (err) {
+ ret = translate_error(fs, fh->ino, err);
+ goto out;
+ }
+
+ ret = update_mtime(fs, fh->ino, NULL);
+ if (ret)
+ goto out;
+
+out:
+ pthread_mutex_unlock(&ff->bfl);
+ return 0;
+}
+
+static int op_fgetattr(const char *path EXT2FS_ATTR((unused)),
+ struct stat *statbuf,
+ struct fuse_file_info *fp)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ ext2_filsys fs;
+ struct fuse2fs_file_handle *fh =
+ (struct fuse2fs_file_handle *)(uintptr_t)fp->fh;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ FUSE2FS_CHECK_MAGIC(fs, fh, FUSE2FS_FILE_MAGIC);
+ dbg_printf("%s: ino=%d\n", __func__, fh->ino);
+ pthread_mutex_lock(&ff->bfl);
+ ret = stat_inode(fs, fh->ino, statbuf);
+ pthread_mutex_unlock(&ff->bfl);
+
+ return ret;
+}
+
+static int op_utimens(const char *path, const struct timespec ctv[2])
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ struct timespec tv[2];
+ ext2_filsys fs;
+ errcode_t err;
+ ext2_ino_t ino;
+ struct ext2_inode_large inode;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ pthread_mutex_lock(&ff->bfl);
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
+ if (err) {
+ ret = translate_error(fs, 0, err);
+ goto out;
+ }
+ dbg_printf("%s: ino=%d\n", __func__, ino);
+
+ ret = check_inum_access(fs, ino, W_OK);
+ if (ret)
+ goto out;
+
+ memset(&inode, 0, sizeof(inode));
+ err = ext2fs_read_inode_full(fs, ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+
+ tv[0] = ctv[0];
+ tv[1] = ctv[1];
+#ifdef UTIME_NOW
+ if (tv[0].tv_nsec == UTIME_NOW)
+ get_now(tv);
+ if (tv[1].tv_nsec == UTIME_NOW)
+ get_now(tv + 1);
+#endif /* UTIME_NOW */
+#ifdef UTIME_OMIT
+ if (tv[0].tv_nsec != UTIME_OMIT)
+ EXT4_INODE_SET_XTIME(i_atime, tv, &inode);
+ if (tv[1].tv_nsec != UTIME_OMIT)
+ EXT4_INODE_SET_XTIME(i_mtime, tv + 1, &inode);
+#endif /* UTIME_OMIT */
+ ret = update_ctime(fs, ino, &inode);
+ if (ret)
+ goto out;
+
+ err = ext2fs_write_inode_full(fs, ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+
+out:
+ pthread_mutex_unlock(&ff->bfl);
+ return ret;
+}
+
+#ifdef SUPPORT_I_FLAGS
+static int ioctl_getflags(ext2_filsys fs, struct fuse2fs_file_handle *fh,
+ void *data)
+{
+ errcode_t err;
+ struct ext2_inode_large inode;
+
+ FUSE2FS_CHECK_MAGIC(fs, fh, FUSE2FS_FILE_MAGIC);
+ dbg_printf("%s: ino=%d\n", __func__, fh->ino);
+ memset(&inode, 0, sizeof(inode));
+ err = ext2fs_read_inode_full(fs, fh->ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err)
+ return translate_error(fs, fh->ino, err);
+
+ *(__u32 *)data = inode.i_flags & EXT2_FL_USER_VISIBLE;
+ return 0;
+}
+
+#define FUSE2FS_MODIFIABLE_IFLAGS \
+ (EXT2_IMMUTABLE_FL | EXT2_APPEND_FL | EXT2_NODUMP_FL | \
+ EXT2_NOATIME_FL | EXT3_JOURNAL_DATA_FL | EXT2_DIRSYNC_FL | \
+ EXT2_TOPDIR_FL)
+
+static int ioctl_setflags(ext2_filsys fs, struct fuse2fs_file_handle *fh,
+ void *data)
+{
+ errcode_t err;
+ struct ext2_inode_large inode;
+ int ret;
+ __u32 flags = *(__u32 *)data;
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+
+ FUSE2FS_CHECK_MAGIC(fs, fh, FUSE2FS_FILE_MAGIC);
+ dbg_printf("%s: ino=%d\n", __func__, fh->ino);
+ memset(&inode, 0, sizeof(inode));
+ err = ext2fs_read_inode_full(fs, fh->ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err)
+ return translate_error(fs, fh->ino, err);
+
+ if (!ff->fakeroot && ctxt->uid != 0 && inode_uid(inode) != ctxt->uid)
+ return -EPERM;
+
+ if ((inode.i_flags ^ flags) & ~FUSE2FS_MODIFIABLE_IFLAGS)
+ return -EINVAL;
+
+ inode.i_flags = (inode.i_flags & ~FUSE2FS_MODIFIABLE_IFLAGS) |
+ (flags & FUSE2FS_MODIFIABLE_IFLAGS);
+
+ ret = update_ctime(fs, fh->ino, &inode);
+ if (ret)
+ return ret;
+
+ err = ext2fs_write_inode_full(fs, fh->ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err)
+ return translate_error(fs, fh->ino, err);
+
+ return 0;
+}
+
+static int ioctl_getversion(ext2_filsys fs, struct fuse2fs_file_handle *fh,
+ void *data)
+{
+ errcode_t err;
+ struct ext2_inode_large inode;
+
+ FUSE2FS_CHECK_MAGIC(fs, fh, FUSE2FS_FILE_MAGIC);
+ dbg_printf("%s: ino=%d\n", __func__, fh->ino);
+ memset(&inode, 0, sizeof(inode));
+ err = ext2fs_read_inode_full(fs, fh->ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err)
+ return translate_error(fs, fh->ino, err);
+
+ *(__u32 *)data = inode.i_generation;
+ return 0;
+}
+
+static int ioctl_setversion(ext2_filsys fs, struct fuse2fs_file_handle *fh,
+ void *data)
+{
+ errcode_t err;
+ struct ext2_inode_large inode;
+ int ret;
+ __u32 generation = *(__u32 *)data;
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+
+ FUSE2FS_CHECK_MAGIC(fs, fh, FUSE2FS_FILE_MAGIC);
+ dbg_printf("%s: ino=%d\n", __func__, fh->ino);
+ memset(&inode, 0, sizeof(inode));
+ err = ext2fs_read_inode_full(fs, fh->ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err)
+ return translate_error(fs, fh->ino, err);
+
+ if (!ff->fakeroot && ctxt->uid != 0 && inode_uid(inode) != ctxt->uid)
+ return -EPERM;
+
+ inode.i_generation = generation;
+
+ ret = update_ctime(fs, fh->ino, &inode);
+ if (ret)
+ return ret;
+
+ err = ext2fs_write_inode_full(fs, fh->ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err)
+ return translate_error(fs, fh->ino, err);
+
+ return 0;
+}
+#endif /* SUPPORT_I_FLAGS */
+
+#ifdef FITRIM
+static int ioctl_fitrim(ext2_filsys fs, struct fuse2fs_file_handle *fh,
+ void *data)
+{
+ struct fstrim_range *fr = data;
+ blk64_t start, end, max_blocks, b, cleared;
+ errcode_t err = 0;
+
+ start = fr->start / fs->blocksize;
+ end = (fr->start + fr->len - 1) / fs->blocksize;
+ dbg_printf("%s: start=%llu end=%llu\n", __func__, start, end);
+
+ if (start < fs->super->s_first_data_block)
+ start = fs->super->s_first_data_block;
+ if (start >= ext2fs_blocks_count(fs->super))
+ start = ext2fs_blocks_count(fs->super) - 1;
+
+ if (end < fs->super->s_first_data_block)
+ end = fs->super->s_first_data_block;
+ if (end >= ext2fs_blocks_count(fs->super))
+ end = ext2fs_blocks_count(fs->super) - 1;
+
+ cleared = 0;
+ max_blocks = 2048ULL * 1024 * 1024 / fs->blocksize;
+
+ fr->len = 0;
+ while (start <= end) {
+ err = ext2fs_find_first_zero_block_bitmap2(fs->block_map,
+ start, end, &start);
+ if (err == ENOENT)
+ return 0;
+ else if (err)
+ return translate_error(fs, fh->ino, err);
+
+ b = start + max_blocks < end ? start + max_blocks : end;
+ err = ext2fs_find_first_set_block_bitmap2(fs->block_map,
+ start, b, &b);
+ if (err && err != ENOENT)
+ return translate_error(fs, fh->ino, err);
+ if (b - start >= fr->minlen) {
+ err = io_channel_discard(fs->io, start, b - start);
+ if (err)
+ return translate_error(fs, fh->ino, err);
+ cleared += b - start;
+ fr->len = cleared * fs->blocksize;
+ }
+ start = b + 1;
+ }
+
+ return err;
+}
+#endif /* FITRIM */
+
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(2, 8)
+static int op_ioctl(const char *path EXT2FS_ATTR((unused)), int cmd,
+ void *arg EXT2FS_ATTR((unused)),
+ struct fuse_file_info *fp,
+ unsigned int flags EXT2FS_ATTR((unused)), void *data)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ struct fuse2fs_file_handle *fh =
+ (struct fuse2fs_file_handle *)(uintptr_t)fp->fh;
+ ext2_filsys fs;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ pthread_mutex_lock(&ff->bfl);
+ switch ((unsigned long) cmd) {
+#ifdef SUPPORT_I_FLAGS
+ case EXT2_IOC_GETFLAGS:
+ ret = ioctl_getflags(fs, fh, data);
+ break;
+ case EXT2_IOC_SETFLAGS:
+ ret = ioctl_setflags(fs, fh, data);
+ break;
+ case EXT2_IOC_GETVERSION:
+ ret = ioctl_getversion(fs, fh, data);
+ break;
+ case EXT2_IOC_SETVERSION:
+ ret = ioctl_setversion(fs, fh, data);
+ break;
+#endif
+#ifdef FITRIM
+ case FITRIM:
+ ret = ioctl_fitrim(fs, fh, data);
+ break;
+#endif
+ default:
+ dbg_printf("%s: Unknown ioctl %d\n", __func__, cmd);
+ ret = -ENOTTY;
+ }
+ pthread_mutex_unlock(&ff->bfl);
+
+ return ret;
+}
+#endif /* FUSE 28 */
+
+static int op_bmap(const char *path, size_t blocksize EXT2FS_ATTR((unused)),
+ uint64_t *idx)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ ext2_filsys fs;
+ ext2_ino_t ino;
+ errcode_t err;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ pthread_mutex_lock(&ff->bfl);
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
+ if (err) {
+ ret = translate_error(fs, 0, err);
+ goto out;
+ }
+ dbg_printf("%s: ino=%d blk=%"PRIu64"\n", __func__, ino, *idx);
+
+ err = ext2fs_bmap2(fs, ino, NULL, NULL, 0, *idx, 0, (blk64_t *)idx);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+
+out:
+ pthread_mutex_unlock(&ff->bfl);
+ return ret;
+}
+
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(2, 9)
+# ifdef SUPPORT_FALLOCATE
+static int fallocate_helper(struct fuse_file_info *fp, int mode, off_t offset,
+ off_t len)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ struct fuse2fs_file_handle *fh =
+ (struct fuse2fs_file_handle *)(uintptr_t)fp->fh;
+ ext2_filsys fs;
+ struct ext2_inode_large inode;
+ blk64_t start, end;
+ __u64 fsize;
+ errcode_t err;
+ int flags;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ FUSE2FS_CHECK_MAGIC(fs, fh, FUSE2FS_FILE_MAGIC);
+ start = offset / fs->blocksize;
+ end = (offset + len - 1) / fs->blocksize;
+ dbg_printf("%s: ino=%d mode=0x%x start=%jd end=%llu\n", __func__,
+ fh->ino, mode, offset / fs->blocksize, end);
+ if (!fs_can_allocate(ff, len / fs->blocksize))
+ return -ENOSPC;
+
+ memset(&inode, 0, sizeof(inode));
+ err = ext2fs_read_inode_full(fs, fh->ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err)
+ return err;
+ fsize = EXT2_I_SIZE(&inode);
+
+ /* Allocate a bunch of blocks */
+ flags = (mode & FL_KEEP_SIZE_FLAG ? 0 :
+ EXT2_FALLOCATE_INIT_BEYOND_EOF);
+ err = ext2fs_fallocate(fs, flags, fh->ino,
+ (struct ext2_inode *)&inode,
+ ~0ULL, start, end - start + 1);
+ if (err && err != EXT2_ET_BLOCK_ALLOC_FAIL)
+ return translate_error(fs, fh->ino, err);
+
+ /* Update i_size */
+ if (!(mode & FL_KEEP_SIZE_FLAG)) {
+ if ((__u64) offset + len > fsize) {
+ err = ext2fs_inode_size_set(fs,
+ (struct ext2_inode *)&inode,
+ offset + len);
+ if (err)
+ return translate_error(fs, fh->ino, err);
+ }
+ }
+
+ err = update_mtime(fs, fh->ino, &inode);
+ if (err)
+ return err;
+
+ err = ext2fs_write_inode_full(fs, fh->ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err)
+ return translate_error(fs, fh->ino, err);
+
+ return err;
+}
+
+static errcode_t clean_block_middle(ext2_filsys fs, ext2_ino_t ino,
+ struct ext2_inode_large *inode, off_t offset,
+ off_t len, char **buf)
+{
+ blk64_t blk;
+ off_t residue;
+ int retflags;
+ errcode_t err;
+
+ residue = offset % fs->blocksize;
+ if (residue == 0)
+ return 0;
+
+ if (!*buf) {
+ err = ext2fs_get_mem(fs->blocksize, buf);
+ if (err)
+ return err;
+ }
+
+ err = ext2fs_bmap2(fs, ino, (struct ext2_inode *)inode, *buf, 0,
+ offset / fs->blocksize, &retflags, &blk);
+ if (err)
+ return err;
+ if (!blk || (retflags & BMAP_RET_UNINIT))
+ return 0;
+
+ err = io_channel_read_blk(fs->io, blk, 1, *buf);
+ if (err)
+ return err;
+
+ memset(*buf + residue, 0, len);
+
+ return io_channel_write_blk(fs->io, blk, 1, *buf);
+}
+
+static errcode_t clean_block_edge(ext2_filsys fs, ext2_ino_t ino,
+ struct ext2_inode_large *inode, off_t offset,
+ int clean_before, char **buf)
+{
+ blk64_t blk;
+ int retflags;
+ off_t residue;
+ errcode_t err;
+
+ residue = offset % fs->blocksize;
+ if (residue == 0)
+ return 0;
+
+ if (!*buf) {
+ err = ext2fs_get_mem(fs->blocksize, buf);
+ if (err)
+ return err;
+ }
+
+ err = ext2fs_bmap2(fs, ino, (struct ext2_inode *)inode, *buf, 0,
+ offset / fs->blocksize, &retflags, &blk);
+ if (err)
+ return err;
+
+ err = io_channel_read_blk(fs->io, blk, 1, *buf);
+ if (err)
+ return err;
+ if (!blk || (retflags & BMAP_RET_UNINIT))
+ return 0;
+
+ if (clean_before)
+ memset(*buf, 0, residue);
+ else
+ memset(*buf + residue, 0, fs->blocksize - residue);
+
+ return io_channel_write_blk(fs->io, blk, 1, *buf);
+}
+
+static int punch_helper(struct fuse_file_info *fp, int mode, off_t offset,
+ off_t len)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ struct fuse2fs_file_handle *fh =
+ (struct fuse2fs_file_handle *)(uintptr_t)fp->fh;
+ ext2_filsys fs;
+ struct ext2_inode_large inode;
+ blk64_t start, end;
+ errcode_t err;
+ char *buf = NULL;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ FUSE2FS_CHECK_MAGIC(fs, fh, FUSE2FS_FILE_MAGIC);
+ dbg_printf("%s: offset=%jd len=%jd\n", __func__, offset, len);
+
+ /* kernel ext4 punch requires this flag to be set */
+ if (!(mode & FL_KEEP_SIZE_FLAG))
+ return -EINVAL;
+
+ /* Punch out a bunch of blocks */
+ start = (offset + fs->blocksize - 1) / fs->blocksize;
+ end = (offset + len - fs->blocksize) / fs->blocksize;
+ dbg_printf("%s: ino=%d mode=0x%x start=%llu end=%llu\n", __func__,
+ fh->ino, mode, start, end);
+
+ memset(&inode, 0, sizeof(inode));
+ err = ext2fs_read_inode_full(fs, fh->ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err)
+ return translate_error(fs, fh->ino, err);
+
+ /* Zero everything before the first block and after the last block */
+ if ((offset / fs->blocksize) == ((offset + len) / fs->blocksize))
+ err = clean_block_middle(fs, fh->ino, &inode, offset,
+ len, &buf);
+ else {
+ err = clean_block_edge(fs, fh->ino, &inode, offset, 0, &buf);
+ if (!err)
+ err = clean_block_edge(fs, fh->ino, &inode,
+ offset + len, 1, &buf);
+ }
+ if (buf)
+ ext2fs_free_mem(&buf);
+ if (err)
+ return translate_error(fs, fh->ino, err);
+
+ /* Unmap full blocks in the middle */
+ if (start <= end) {
+ err = ext2fs_punch(fs, fh->ino, (struct ext2_inode *)&inode,
+ NULL, start, end);
+ if (err)
+ return translate_error(fs, fh->ino, err);
+ }
+
+ err = update_mtime(fs, fh->ino, &inode);
+ if (err)
+ return err;
+
+ err = ext2fs_write_inode_full(fs, fh->ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err)
+ return translate_error(fs, fh->ino, err);
+
+ return 0;
+}
+
+static int op_fallocate(const char *path EXT2FS_ATTR((unused)), int mode,
+ off_t offset, off_t len,
+ struct fuse_file_info *fp)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ ext2_filsys fs = ff->fs;
+ int ret;
+
+ /* Catch unknown flags */
+ if (mode & ~(FL_PUNCH_HOLE_FLAG | FL_KEEP_SIZE_FLAG))
+ return -EINVAL;
+
+ pthread_mutex_lock(&ff->bfl);
+ if (!fs_writeable(fs)) {
+ ret = -EROFS;
+ goto out;
+ }
+ if (mode & FL_PUNCH_HOLE_FLAG)
+ ret = punch_helper(fp, mode, offset, len);
+ else
+ ret = fallocate_helper(fp, mode, offset, len);
+out:
+ pthread_mutex_unlock(&ff->bfl);
+
+ return ret;
+}
+# endif /* SUPPORT_FALLOCATE */
+#endif /* FUSE 29 */
+
+static struct fuse_operations fs_ops = {
+ .init = op_init,
+ .destroy = op_destroy,
+ .getattr = op_getattr,
+ .readlink = op_readlink,
+ .mknod = op_mknod,
+ .mkdir = op_mkdir,
+ .unlink = op_unlink,
+ .rmdir = op_rmdir,
+ .symlink = op_symlink,
+ .rename = op_rename,
+ .link = op_link,
+ .chmod = op_chmod,
+ .chown = op_chown,
+ .truncate = op_truncate,
+ .open = op_open,
+ .read = op_read,
+ .write = op_write,
+ .statfs = op_statfs,
+ .release = op_release,
+ .fsync = op_fsync,
+ .setxattr = op_setxattr,
+ .getxattr = op_getxattr,
+ .listxattr = op_listxattr,
+ .removexattr = op_removexattr,
+ .opendir = op_open,
+ .readdir = op_readdir,
+ .releasedir = op_release,
+ .fsyncdir = op_fsync,
+ .access = op_access,
+ .create = op_create,
+ .ftruncate = op_ftruncate,
+ .fgetattr = op_fgetattr,
+ .utimens = op_utimens,
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(2, 9)
+# if defined(UTIME_NOW) || defined(UTIME_OMIT)
+ .flag_utime_omit_ok = 1,
+# endif
+#endif
+ .bmap = op_bmap,
+#ifdef SUPERFLUOUS
+ .lock = op_lock,
+ .poll = op_poll,
+#endif
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(2, 8)
+ .ioctl = op_ioctl,
+ .flag_nullpath_ok = 1,
+#endif
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(2, 9)
+ .flag_nopath = 1,
+# ifdef SUPPORT_FALLOCATE
+ .fallocate = op_fallocate,
+# endif
+#endif
+};
+
+static int get_random_bytes(void *p, size_t sz)
+{
+ int fd;
+ ssize_t r;
+
+ fd = open("/dev/urandom", O_RDONLY);
+ if (fd < 0) {
+ perror("/dev/urandom");
+ return 0;
+ }
+
+ r = read(fd, p, sz);
+
+ close(fd);
+ return (size_t) r == sz;
+}
+
+enum {
+ FUSE2FS_VERSION,
+ FUSE2FS_HELP,
+ FUSE2FS_HELPFULL,
+};
+
+#define FUSE2FS_OPT(t, p, v) { t, offsetof(struct fuse2fs, p), v }
+
+static struct fuse_opt fuse2fs_opts[] = {
+ FUSE2FS_OPT("ro", ro, 1),
+ FUSE2FS_OPT("errors=panic", panic_on_error, 1),
+ FUSE2FS_OPT("minixdf", minixdf, 1),
+ FUSE2FS_OPT("fakeroot", fakeroot, 1),
+ FUSE2FS_OPT("fuse2fs_debug", debug, 1),
+ FUSE2FS_OPT("no_default_opts", no_default_opts, 1),
+ FUSE2FS_OPT("norecovery", norecovery, 1),
+ FUSE2FS_OPT("offset=%lu", offset, 0),
+
+ FUSE_OPT_KEY("-V", FUSE2FS_VERSION),
+ FUSE_OPT_KEY("--version", FUSE2FS_VERSION),
+ FUSE_OPT_KEY("-h", FUSE2FS_HELP),
+ FUSE_OPT_KEY("--help", FUSE2FS_HELP),
+ FUSE_OPT_KEY("--helpfull", FUSE2FS_HELPFULL),
+ FUSE_OPT_END
+};
+
+
+static int fuse2fs_opt_proc(void *data, const char *arg,
+ int key, struct fuse_args *outargs)
+{
+ struct fuse2fs *ff = data;
+
+ switch (key) {
+ case FUSE_OPT_KEY_NONOPT:
+ if (!ff->device) {
+ ff->device = strdup(arg);
+ return 0;
+ }
+ return 1;
+ case FUSE2FS_HELP:
+ case FUSE2FS_HELPFULL:
+ fprintf(stderr,
+ "usage: %s device/image mountpoint [options]\n"
+ "\n"
+ "general options:\n"
+ " -o opt,[opt...] mount options\n"
+ " -h --help print help\n"
+ " -V --version print version\n"
+ "\n"
+ "fuse2fs options:\n"
+ " -o ro read-only mount\n"
+ " -o errors=panic dump core on error\n"
+ " -o minixdf minix-style df\n"
+ " -o fakeroot pretend to be root for permission checks\n"
+ " -o no_default_opts do not include default fuse options\n"
+ " -o offset=<bytes> similar to mount -o offset=<bytes>, mount the partition starting at <bytes>\n"
+ " -o norecovery don't replay the journal (implies ro)\n"
+ " -o fuse2fs_debug enable fuse2fs debugging\n"
+ "\n",
+ outargs->argv[0]);
+ if (key == FUSE2FS_HELPFULL) {
+ fuse_opt_add_arg(outargs, "-ho");
+ fuse_main(outargs->argc, outargs->argv, &fs_ops, NULL);
+ } else {
+ fprintf(stderr, "Try --helpfull to get a list of "
+ "all flags, including the FUSE options.\n");
+ }
+ exit(1);
+
+ case FUSE2FS_VERSION:
+ fprintf(stderr, "fuse2fs %s (%s)\n", E2FSPROGS_VERSION,
+ E2FSPROGS_DATE);
+ fuse_opt_add_arg(outargs, "--version");
+ fuse_main(outargs->argc, outargs->argv, &fs_ops, NULL);
+ exit(0);
+ }
+ return 1;
+}
+
+int main(int argc, char *argv[])
+{
+ struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+ struct fuse2fs fctx;
+ errcode_t err;
+ char *logfile;
+ char extra_args[BUFSIZ];
+ int ret = 0;
+ int flags = EXT2_FLAG_64BITS | EXT2_FLAG_THREADS | EXT2_FLAG_EXCLUSIVE;
+
+ memset(&fctx, 0, sizeof(fctx));
+ fctx.magic = FUSE2FS_MAGIC;
+
+ fuse_opt_parse(&args, &fctx, fuse2fs_opts, fuse2fs_opt_proc);
+ if (fctx.device == NULL) {
+ fprintf(stderr, "Missing ext4 device/image\n");
+ fprintf(stderr, "See '%s -h' for usage\n", argv[0]);
+ exit(1);
+ }
+
+ if (fctx.norecovery)
+ fctx.ro = 1;
+ if (fctx.ro)
+ printf("%s", _("Mounting read-only.\n"));
+
+#ifdef ENABLE_NLS
+ setlocale(LC_MESSAGES, "");
+ setlocale(LC_CTYPE, "");
+ bindtextdomain(NLS_CAT_NAME, LOCALEDIR);
+ textdomain(NLS_CAT_NAME);
+ set_com_err_gettext(gettext);
+#endif
+ add_error_table(&et_ext2_error_table);
+
+ /* Set up error logging */
+ logfile = getenv("FUSE2FS_LOGFILE");
+ if (logfile) {
+ fctx.err_fp = fopen(logfile, "a");
+ if (!fctx.err_fp) {
+ perror(logfile);
+ goto out;
+ }
+ } else
+ fctx.err_fp = stderr;
+
+ /* Will we allow users to allocate every last block? */
+ if (getenv("FUSE2FS_ALLOC_ALL_BLOCKS")) {
+ printf(_("%s: Allowing users to allocate all blocks. "
+ "This is dangerous!\n"), fctx.device);
+ fctx.alloc_all_blocks = 1;
+ }
+
+ /* Start up the fs (while we still can use stdout) */
+ ret = 2;
+ if (!fctx.ro)
+ flags |= EXT2_FLAG_RW;
+ char options[50];
+ sprintf(options, "offset=%lu", fctx.offset);
+ err = ext2fs_open2(fctx.device, options, flags, 0, 0, unix_io_manager,
+ &global_fs);
+ if (err) {
+ printf(_("%s: %s.\n"), fctx.device, error_message(err));
+ printf(_("Please run e2fsck -fy %s.\n"), fctx.device);
+ goto out;
+ }
+ fctx.fs = global_fs;
+ global_fs->priv_data = &fctx;
+
+ ret = 3;
+
+ if (ext2fs_has_feature_journal_needs_recovery(global_fs->super)) {
+ if (fctx.norecovery) {
+ printf(_("%s: mounting read-only without "
+ "recovering journal\n"),
+ fctx.device);
+ } else if (!fctx.ro) {
+ printf(_("%s: recovering journal\n"), fctx.device);
+ err = ext2fs_run_ext3_journal(&global_fs);
+ if (err) {
+ printf(_("%s: %s.\n"), fctx.device,
+ error_message(err));
+ printf(_("Please run e2fsck -fy %s.\n"),
+ fctx.device);
+ goto out;
+ }
+ ext2fs_clear_feature_journal_needs_recovery(global_fs->super);
+ ext2fs_mark_super_dirty(global_fs);
+ } else {
+ printf("%s", _("Journal needs recovery; running "
+ "`e2fsck -E journal_only' is required.\n"));
+ goto out;
+ }
+ }
+
+ if (!fctx.ro) {
+ if (ext2fs_has_feature_journal(global_fs->super))
+ printf(_("%s: Writing to the journal is not supported.\n"),
+ fctx.device);
+ err = ext2fs_read_inode_bitmap(global_fs);
+ if (err) {
+ translate_error(global_fs, 0, err);
+ goto out;
+ }
+ err = ext2fs_read_block_bitmap(global_fs);
+ if (err) {
+ translate_error(global_fs, 0, err);
+ goto out;
+ }
+ }
+
+ if (!(global_fs->super->s_state & EXT2_VALID_FS))
+ printf("%s", _("Warning: Mounting unchecked fs, running e2fsck "
+ "is recommended.\n"));
+ if (global_fs->super->s_max_mnt_count > 0 &&
+ global_fs->super->s_mnt_count >= global_fs->super->s_max_mnt_count)
+ printf("%s", _("Warning: Maximal mount count reached, running "
+ "e2fsck is recommended.\n"));
+ if (global_fs->super->s_checkinterval > 0 &&
+ (time_t) (global_fs->super->s_lastcheck +
+ global_fs->super->s_checkinterval) <= time(0))
+ printf("%s", _("Warning: Check time reached; running e2fsck "
+ "is recommended.\n"));
+ if (global_fs->super->s_last_orphan)
+ printf("%s",
+ _("Orphans detected; running e2fsck is recommended.\n"));
+
+ if (global_fs->super->s_state & EXT2_ERROR_FS) {
+ printf("%s",
+ _("Errors detected; running e2fsck is required.\n"));
+ goto out;
+ }
+
+ /* Initialize generation counter */
+ get_random_bytes(&fctx.next_generation, sizeof(unsigned int));
+
+ /* Set up default fuse parameters */
+ snprintf(extra_args, BUFSIZ, "-okernel_cache,subtype=ext4,use_ino,"
+ "fsname=%s,attr_timeout=0" FUSE_PLATFORM_OPTS,
+ fctx.device);
+ if (fctx.no_default_opts == 0)
+ fuse_opt_add_arg(&args, extra_args);
+
+ if (fctx.fakeroot) {
+#ifdef HAVE_MOUNT_NODEV
+ fuse_opt_add_arg(&args,"-onodev");
+#endif
+#ifdef HAVE_MOUNT_NOSUID
+ fuse_opt_add_arg(&args,"-onosuid");
+#endif
+ }
+
+ if (fctx.debug) {
+ int i;
+
+ printf("fuse arguments:");
+ for (i = 0; i < args.argc; i++)
+ printf(" '%s'", args.argv[i]);
+ printf("\n");
+ }
+
+ pthread_mutex_init(&fctx.bfl, NULL);
+ fuse_main(args.argc, args.argv, &fs_ops, &fctx);
+ pthread_mutex_destroy(&fctx.bfl);
+
+ ret = 0;
+out:
+ if (global_fs) {
+ err = ext2fs_close(global_fs);
+ if (err)
+ com_err(argv[0], err, "while closing fs");
+ global_fs = NULL;
+ }
+ return ret;
+}
+
+static int __translate_error(ext2_filsys fs, errcode_t err, ext2_ino_t ino,
+ const char *file, int line)
+{
+ struct timespec now;
+ int ret = err;
+ struct fuse2fs *ff = fs->priv_data;
+ int is_err = 0;
+
+ /* Translate ext2 error to unix error code */
+ if (err < EXT2_ET_BASE)
+ goto no_translation;
+ switch (err) {
+ case EXT2_ET_NO_MEMORY:
+ case EXT2_ET_TDB_ERR_OOM:
+ ret = -ENOMEM;
+ break;
+ case EXT2_ET_INVALID_ARGUMENT:
+ case EXT2_ET_LLSEEK_FAILED:
+ ret = -EINVAL;
+ break;
+ case EXT2_ET_NO_DIRECTORY:
+ ret = -ENOTDIR;
+ break;
+ case EXT2_ET_FILE_NOT_FOUND:
+ ret = -ENOENT;
+ break;
+ case EXT2_ET_DIR_NO_SPACE:
+ is_err = 1;
+ /* fallthrough */
+ case EXT2_ET_TOOSMALL:
+ case EXT2_ET_BLOCK_ALLOC_FAIL:
+ case EXT2_ET_INODE_ALLOC_FAIL:
+ case EXT2_ET_EA_NO_SPACE:
+ ret = -ENOSPC;
+ break;
+ case EXT2_ET_SYMLINK_LOOP:
+ ret = -EMLINK;
+ break;
+ case EXT2_ET_FILE_TOO_BIG:
+ ret = -EFBIG;
+ break;
+ case EXT2_ET_TDB_ERR_EXISTS:
+ case EXT2_ET_FILE_EXISTS:
+ ret = -EEXIST;
+ break;
+ case EXT2_ET_MMP_FAILED:
+ case EXT2_ET_MMP_FSCK_ON:
+ ret = -EBUSY;
+ break;
+ case EXT2_ET_EA_KEY_NOT_FOUND:
+#ifdef ENODATA
+ ret = -ENODATA;
+#else
+ ret = -ENOENT;
+#endif
+ break;
+ /* Sometimes fuse returns a garbage file handle pointer to us... */
+ case EXT2_ET_MAGIC_EXT2_FILE:
+ ret = -EFAULT;
+ break;
+ case EXT2_ET_UNIMPLEMENTED:
+ ret = -EOPNOTSUPP;
+ break;
+ default:
+ is_err = 1;
+ ret = -EIO;
+ break;
+ }
+
+no_translation:
+ if (!is_err)
+ return ret;
+
+ if (ino)
+ fprintf(ff->err_fp, "FUSE2FS (%s): %s (inode #%d) at %s:%d.\n",
+ fs->device_name ? fs->device_name : "???",
+ error_message(err), ino, file, line);
+ else
+ fprintf(ff->err_fp, "FUSE2FS (%s): %s at %s:%d.\n",
+ fs->device_name ? fs->device_name : "???",
+ error_message(err), file, line);
+ fflush(ff->err_fp);
+
+ /* Make a note in the error log */
+ get_now(&now);
+ fs->super->s_last_error_time = now.tv_sec;
+ fs->super->s_last_error_ino = ino;
+ fs->super->s_last_error_line = line;
+ fs->super->s_last_error_block = err; /* Yeah... */
+ strncpy((char *)fs->super->s_last_error_func, file,
+ sizeof(fs->super->s_last_error_func));
+ if (fs->super->s_first_error_time == 0) {
+ fs->super->s_first_error_time = now.tv_sec;
+ fs->super->s_first_error_ino = ino;
+ fs->super->s_first_error_line = line;
+ fs->super->s_first_error_block = err;
+ strncpy((char *)fs->super->s_first_error_func, file,
+ sizeof(fs->super->s_first_error_func));
+ }
+
+ fs->super->s_error_count++;
+ ext2fs_mark_super_dirty(fs);
+ ext2fs_flush(fs);
+ if (ff->panic_on_error)
+ abort();
+
+ return ret;
+}
diff --git a/misc/ismounted.c b/misc/ismounted.c
new file mode 100644
index 0000000..6aa0e04
--- /dev/null
+++ b/misc/ismounted.c
@@ -0,0 +1,218 @@
+/*
+ * ismounted.c --- Check to see if the filesystem was mounted
+ *
+ * Copyright (C) 1995,1996,1997,1998,1999,2000,2008 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ * %End-Header%
+ */
+
+#include "config.h"
+#include <stdio.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#if HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#include <fcntl.h>
+#ifdef HAVE_LINUX_FD_H
+#include <linux/fd.h>
+#endif
+#ifdef HAVE_MNTENT_H
+#include <mntent.h>
+#endif
+#include <string.h>
+#include <sys/stat.h>
+#include <ctype.h>
+
+#include "fsck.h"
+
+/*
+ * ext2fs_check_if_mounted flags
+ */
+#define MF_MOUNTED 1
+
+#include "et/com_err.h"
+
+#ifdef HAVE_SETMNTENT
+static char *skip_over_blank(char *cp)
+{
+ while (*cp && isspace(*cp))
+ cp++;
+ return cp;
+}
+
+static char *skip_over_word(char *cp)
+{
+ while (*cp && !isspace(*cp))
+ cp++;
+ return cp;
+}
+
+static char *parse_word(char **buf)
+{
+ char *word, *next;
+
+ word = *buf;
+ if (*word == 0)
+ return 0;
+
+ word = skip_over_blank(word);
+ next = skip_over_word(word);
+ if (*next)
+ *next++ = 0;
+ *buf = next;
+ return word;
+}
+#endif
+
+/*
+ * Helper function which checks a file in /etc/mtab format to see if a
+ * filesystem is mounted. Returns an error if the file doesn't exist
+ * or can't be opened.
+ */
+static errcode_t check_mntent_file(const char *mtab_file, const char *file,
+ int *mount_flags)
+{
+#ifdef HAVE_SETMNTENT
+ struct stat st_buf;
+ errcode_t retval = 0;
+ dev_t file_dev=0, file_rdev=0;
+ ino_t file_ino=0;
+ FILE *f;
+ char buf[1024], *device = 0, *mnt_dir = 0, *cp;
+
+ *mount_flags = 0;
+ if ((f = setmntent (mtab_file, "r")) == NULL)
+ return errno;
+ if (stat(file, &st_buf) == 0) {
+ if (S_ISBLK(st_buf.st_mode)) {
+#ifndef __GNU__ /* The GNU hurd is broken with respect to stat devices */
+ file_rdev = st_buf.st_rdev;
+#endif /* __GNU__ */
+ } else {
+ file_dev = st_buf.st_dev;
+ file_ino = st_buf.st_ino;
+ }
+ }
+ while (1) {
+ if (!fgets(buf, sizeof(buf), f)) {
+ device = mnt_dir = 0;
+ break;
+ }
+ buf[sizeof(buf)-1] = 0;
+
+ cp = buf;
+ device = parse_word(&cp);
+ if (!device || *device == '#')
+ return 0; /* Ignore blank lines and comments */
+ mnt_dir = parse_word(&cp);
+
+ if (device[0] != '/')
+ continue;
+
+ if (strcmp(file, device) == 0)
+ break;
+ if (stat(device, &st_buf) == 0) {
+ if (S_ISBLK(st_buf.st_mode)) {
+#ifndef __GNU__
+ if (file_rdev && (file_rdev == st_buf.st_rdev))
+ break;
+#endif /* __GNU__ */
+ } else {
+ if (file_dev && ((file_dev == st_buf.st_dev) &&
+ (file_ino == st_buf.st_ino)))
+ break;
+ }
+ }
+ }
+
+ if (mnt_dir == 0) {
+#ifndef __GNU__ /* The GNU hurd is broken with respect to stat devices */
+ /*
+ * Do an extra check to see if this is the root device. We
+ * can't trust /etc/mtab, and /proc/mounts will only list
+ * /dev/root for the root filesystem. Argh. Instead we
+ * check if the given device has the same major/minor number
+ * as the device that the root directory is on.
+ */
+ if (file_rdev && (stat("/", &st_buf) == 0) &&
+ (st_buf.st_dev == file_rdev))
+ *mount_flags = MF_MOUNTED;
+#endif /* __GNU__ */
+ goto errout;
+ }
+#ifndef __GNU__ /* The GNU hurd is deficient; what else is new? */
+ /* Validate the entry in case /etc/mtab is out of date */
+ /*
+ * We need to be paranoid, because some broken distributions
+ * (read: Slackware) don't initialize /etc/mtab before checking
+ * all of the non-root filesystems on the disk.
+ */
+ if (stat(mnt_dir, &st_buf) < 0) {
+ retval = errno;
+ if (retval == ENOENT) {
+#ifdef DEBUG
+ printf("Bogus entry in %s! (%s does not exist)\n",
+ mtab_file, mnt_dir);
+#endif /* DEBUG */
+ retval = 0;
+ }
+ goto errout;
+ }
+ if (file_rdev && (st_buf.st_dev != file_rdev)) {
+#ifdef DEBUG
+ printf("Bogus entry in %s! (%s not mounted on %s)\n",
+ mtab_file, file, mnt_dir);
+#endif /* DEBUG */
+ goto errout;
+ }
+#endif /* __GNU__ */
+ *mount_flags = MF_MOUNTED;
+
+ retval = 0;
+errout:
+ endmntent (f);
+ return retval;
+#else /* !HAVE_SETMNTENT */
+ return 0;
+#endif /* HAVE_MNTENT_H */
+}
+
+int is_mounted(const char *file)
+{
+ errcode_t retval;
+ int mount_flags = 0;
+
+#ifdef __linux__
+ retval = check_mntent_file("/proc/mounts", file, &mount_flags);
+ if (retval)
+ return 0;
+ if (mount_flags)
+ return 1;
+#endif /* __linux__ */
+ retval = check_mntent_file("/etc/mtab", file, &mount_flags);
+ if (retval)
+ return 0;
+ return (mount_flags);
+}
+
+#ifdef DEBUG
+int main(int argc, char **argv)
+{
+ if (argc < 2) {
+ fprintf(stderr, "Usage: %s device\n", argv[0]);
+ exit(1);
+ }
+
+ if (is_mounted(argv[1]))
+ printf("\t%s is mounted.\n", argv[1]);
+ exit(0);
+}
+#endif /* DEBUG */
diff --git a/misc/logsave.8.in b/misc/logsave.8.in
new file mode 100644
index 0000000..cc3ffde
--- /dev/null
+++ b/misc/logsave.8.in
@@ -0,0 +1,61 @@
+.\" -*- nroff -*-
+.\" Copyright 2003 by Theodore Ts'o. All Rights Reserved.
+.\" This file may be copied under the terms of the GNU Public License.
+.\"
+.TH LOGSAVE 8 "@E2FSPROGS_MONTH@ @E2FSPROGS_YEAR@" "E2fsprogs version @E2FSPROGS_VERSION@"
+.SH NAME
+logsave \- save the output of a command in a logfile
+.SH SYNOPSIS
+.B logsave
+[
+.B \-asv
+]
+.I logfile cmd_prog [ ... ]
+.SH DESCRIPTION
+The
+.B logsave
+program will execute
+.I cmd_prog
+with the specified argument(s), and save a copy of its output to
+.IR logfile .
+If the containing directory for
+.I logfile
+does not exist,
+.B logsave
+will accumulate the output in memory until it can be written out.
+A copy of the output will also be written to standard output.
+.PP
+If
+.I cmd_prog
+is a single hyphen ('-'), then instead of executing a program,
+.B logsave
+will take its input from standard input and save it in
+.I logfile
+.PP
+.B logsave
+is useful for saving the output of initial boot scripts
+until the /var partition is mounted, so the output can be written to
+/var/log.
+.SH OPTIONS
+.TP
+.B \-a
+This option will cause the output to be appended to
+.IR logfile ,
+instead of replacing its current contents.
+.TP
+.B \-s
+This option will cause
+.B logsave
+to skip writing to the log file text which is bracketed with a control-A
+(ASCII 001 or Start of Header) and control-B (ASCII 002 or Start of
+Text). This allows progress bar information to be visible to the user
+on the console, while not being written to the log file.
+.TP
+.B \-v
+This option will make
+.B logsave
+to be more verbose in its output to the user.
+.SH AUTHOR
+Theodore Ts'o (tytso@mit.edu)
+.SH SEE ALSO
+.BR fsck (8)
diff --git a/misc/logsave.c b/misc/logsave.c
new file mode 100644
index 0000000..96b6d8e
--- /dev/null
+++ b/misc/logsave.c
@@ -0,0 +1,334 @@
+/*
+ * logsave.c --- A program which saves the output of a program until
+ * /var/log is mounted.
+ *
+ * Copyright (C) 2003 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ * %End-Header%
+ */
+
+#define _XOPEN_SOURCE 600 /* for inclusion of sa_handler in Solaris */
+
+#include "config.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <fcntl.h>
+#include <time.h>
+#include <errno.h>
+#ifdef HAVE_SIGNAL_H
+#include <signal.h>
+#endif
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#else
+extern char *optarg;
+extern int optind;
+#endif
+
+static int outfd = -1;
+static int outbufsize = 0;
+static void *outbuf = 0;
+static int verbose = 0;
+static int do_skip = 0;
+static int skip_mode = 0;
+static pid_t child_pid = -1;
+
+static void usage(char *progname)
+{
+ printf("Usage: %s [-asv] logfile program\n", progname);
+ exit(1);
+}
+
+#define SEND_LOG 0x01
+#define SEND_CONSOLE 0x02
+#define SEND_BOTH 0x03
+
+/*
+ * Helper function that does the right thing if write returns a
+ * partial write, or an EAGAIN/EINTR error.
+ */
+static int write_all(int fd, const char *buf, size_t count)
+{
+ ssize_t ret;
+ int c = 0;
+
+ while (count > 0) {
+ ret = write(fd, buf, count);
+ if (ret < 0) {
+ if ((errno == EAGAIN) || (errno == EINTR))
+ continue;
+ return -1;
+ }
+ count -= ret;
+ buf += ret;
+ c += ret;
+ }
+ return c;
+}
+
+static void send_output(const char *buffer, int c, int flag)
+{
+ const char *cp;
+ char *n;
+ int cnt, d, del;
+
+ if (c == 0)
+ c = strlen(buffer);
+
+ if (flag & SEND_CONSOLE) {
+ cnt = c;
+ cp = buffer;
+ while (cnt) {
+ del = 0;
+ for (d=0; d < cnt; d++) {
+ if (skip_mode &&
+ (cp[d] == '\001' || cp[d] == '\002')) {
+ del = 1;
+ break;
+ }
+ }
+ write_all(1, cp, d);
+ if (del)
+ d++;
+ cnt -= d;
+ cp += d;
+ }
+ }
+ if (!(flag & SEND_LOG))
+ return;
+ if (outfd > 0)
+ write_all(outfd, buffer, c);
+ else {
+ n = realloc(outbuf, outbufsize + c);
+ if (n) {
+ outbuf = n;
+ memcpy(((char *)outbuf)+outbufsize, buffer, c);
+ outbufsize += c;
+ }
+ }
+}
+
+static int do_read(int fd)
+{
+ int c;
+ char buffer[4096], *cp, *sep;
+
+ c = read(fd, buffer, sizeof(buffer)-1);
+ if (c <= 0)
+ return c;
+ if (do_skip) {
+ send_output(buffer, c, SEND_CONSOLE);
+ buffer[c] = 0;
+ cp = buffer;
+ while (*cp) {
+ if (skip_mode) {
+ cp = strchr(cp, '\002');
+ if (!cp)
+ return 0;
+ cp++;
+ skip_mode = 0;
+ continue;
+ }
+ sep = strchr(cp, '\001');
+ if (sep)
+ *sep = 0;
+ send_output(cp, 0, SEND_LOG);
+ if (sep) {
+ cp = sep + 1;
+ skip_mode = 1;
+ } else
+ break;
+ }
+ } else
+ send_output(buffer, c, SEND_BOTH);
+ return c;
+}
+
+static void signal_term(int sig)
+{
+ if (child_pid > 0)
+ kill(child_pid, sig);
+}
+
+static int run_program(char **argv)
+{
+ int fds[2];
+ int status, rc, pid;
+ char buffer[80];
+#ifdef HAVE_SIGNAL_H
+ struct sigaction sa;
+#endif
+
+ if (pipe(fds) < 0) {
+ perror("pipe");
+ exit(1);
+ }
+
+#ifdef HAVE_SIGNAL_H
+ memset(&sa, 0, sizeof(struct sigaction));
+ sa.sa_handler = signal_term;
+ sigaction(SIGINT, &sa, 0);
+ sigaction(SIGTERM, &sa, 0);
+#ifdef SA_RESTART
+ sa.sa_flags = SA_RESTART;
+#endif
+#endif
+
+ pid = fork();
+ if (pid < 0) {
+ perror("vfork");
+ exit(1);
+ }
+ if (pid == 0) {
+ dup2(fds[1],1); /* fds[1] replaces stdout */
+ dup2(fds[1],2); /* fds[1] replaces stderr */
+ close(fds[0]); /* don't need this here */
+ close(fds[1]);
+
+ execvp(argv[0], argv);
+ perror(argv[0]);
+ exit(1);
+ }
+ child_pid = pid;
+ close(fds[1]);
+
+ while (!(waitpid(pid, &status, WNOHANG ))) {
+ do_read(fds[0]);
+ }
+ child_pid = -1;
+ do_read(fds[0]);
+ close(fds[0]);
+
+ if ( WIFEXITED(status) ) {
+ rc = WEXITSTATUS(status);
+ if (rc) {
+ send_output(argv[0], 0, SEND_BOTH);
+ sprintf(buffer, " exited with status code %d\n", rc);
+ send_output(buffer, 0, SEND_BOTH);
+ }
+ } else {
+ if (WIFSIGNALED(status)) {
+ send_output(argv[0], 0, SEND_BOTH);
+ sprintf(buffer, "died with signal %d\n",
+ WTERMSIG(status));
+ send_output(buffer, 0, SEND_BOTH);
+ return 1;
+ }
+ rc = 0;
+ }
+ return rc;
+}
+
+static int copy_from_stdin(void)
+{
+ int c, bad_read = 0;
+
+ while (1) {
+ c = do_read(0);
+ if ((c == 0 ) ||
+ ((c < 0) && ((errno == EAGAIN) || (errno == EINTR)))) {
+ if (bad_read++ > 3)
+ break;
+ continue;
+ }
+ if (c < 0) {
+ perror("read");
+ exit(1);
+ }
+ bad_read = 0;
+ }
+ return 0;
+}
+
+
+
+int main(int argc, char **argv)
+{
+ int c, pid, rc;
+ char *outfn, **cpp;
+ int openflags = O_CREAT|O_WRONLY|O_TRUNC;
+ int send_flag = SEND_LOG;
+ int do_stdin;
+ time_t t;
+
+ while ((c = getopt(argc, argv, "+asv")) != EOF) {
+ switch (c) {
+ case 'a':
+ openflags &= ~O_TRUNC;
+ openflags |= O_APPEND;
+ break;
+ case 's':
+ do_skip = 1;
+ break;
+ case 'v':
+ verbose++;
+ send_flag |= SEND_CONSOLE;
+ break;
+ }
+ }
+ if (optind == argc || optind+1 == argc)
+ usage(argv[0]);
+ outfn = argv[optind];
+ optind++;
+ argv += optind;
+ argc -= optind;
+
+ outfd = open(outfn, openflags, 0644);
+ do_stdin = !strcmp(argv[0], "-");
+
+ send_output("Log of ", 0, send_flag);
+ if (do_stdin)
+ send_output("stdin", 0, send_flag);
+ else {
+ for (cpp = argv; *cpp; cpp++) {
+ send_output(*cpp, 0, send_flag);
+ send_output(" ", 0, send_flag);
+ }
+ }
+ send_output("\n", 0, send_flag);
+ t = time(0);
+ send_output(ctime(&t), 0, send_flag);
+ send_output("\n", 0, send_flag);
+
+ if (do_stdin)
+ rc = copy_from_stdin();
+ else
+ rc = run_program(argv);
+
+ send_output("\n", 0, send_flag);
+ t = time(0);
+ send_output(ctime(&t), 0, send_flag);
+ send_output("----------------\n", 0, send_flag);
+
+ if (outbuf) {
+ pid = fork();
+ if (pid < 0) {
+ perror("fork");
+ exit(1);
+ }
+ if (pid) {
+ if (verbose)
+ printf("Backgrounding to save %s later\n",
+ outfn);
+ exit(rc);
+ }
+ setsid(); /* To avoid getting killed by init */
+ while (outfd < 0) {
+ outfd = open(outfn, openflags, 0644);
+ sleep(1);
+ }
+ write_all(outfd, outbuf, outbufsize);
+ free(outbuf);
+ }
+ if (outfd >= 0)
+ close(outfd);
+
+ exit(rc);
+}
diff --git a/misc/lsattr.1.in b/misc/lsattr.1.in
new file mode 100644
index 0000000..4d02a95
--- /dev/null
+++ b/misc/lsattr.1.in
@@ -0,0 +1,52 @@
+.\" -*- nroff -*-
+.TH LSATTR 1 "@E2FSPROGS_MONTH@ @E2FSPROGS_YEAR@" "E2fsprogs version @E2FSPROGS_VERSION@"
+.SH NAME
+lsattr \- list file attributes on a Linux second extended file system
+.SH SYNOPSIS
+.B lsattr
+[
+.B \-RVadlpv
+]
+[
+.I files...
+]
+.SH DESCRIPTION
+.B lsattr
+lists the file attributes on a second extended file system. See
+.BR chattr (1)
+for a description of the attributes and what they mean.
+.SH OPTIONS
+.TP
+.B \-R
+Recursively list attributes of directories and their contents.
+.TP
+.B \-V
+Display the program version.
+.TP
+.B \-a
+List all files in directories, including files that start with `.'.
+.TP
+.B \-d
+List directories like other files, rather than listing their contents.
+.TP
+.B \-l
+Print the options using long names instead of single
+character abbreviations.
+.TP
+.B \-p
+List the file's project number.
+.TP
+.B \-v
+List the file's version/generation number.
+.SH AUTHOR
+.B lsattr
+was written by Remy Card <Remy.Card@linux.org>. It is currently being
+maintained by Theodore Ts'o <tytso@alum.mit.edu>.
+.SH BUGS
+There are none :-).
+.SH AVAILABILITY
+.B lsattr
+is part of the e2fsprogs package and is available from
+http://e2fsprogs.sourceforge.net.
+.SH SEE ALSO
+.BR chattr (1)
diff --git a/misc/lsattr.c b/misc/lsattr.c
new file mode 100644
index 0000000..72f4c68
--- /dev/null
+++ b/misc/lsattr.c
@@ -0,0 +1,234 @@
+/*
+ * lsattr.c - List file attributes on an ext2 file system
+ *
+ * Copyright (C) 1993, 1994 Remy Card <card@masi.ibp.fr>
+ * Laboratoire MASI, Institut Blaise Pascal
+ * Universite Pierre et Marie Curie (Paris VI)
+ *
+ * This file can be redistributed under the terms of the GNU General
+ * Public License
+ */
+
+/*
+ * History:
+ * 93/10/30 - Creation
+ * 93/11/13 - Replace stat() calls by lstat() to avoid loops
+ * 94/02/27 - Integrated in Ted's distribution
+ * 98/12/29 - Display version info only when -V specified (G M Sipe)
+ */
+
+#define _LARGEFILE64_SOURCE
+
+#include "config.h"
+#include <sys/types.h>
+#include <dirent.h>
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#include <fcntl.h>
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#else
+extern int optind;
+extern char *optarg;
+#endif
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include "ext2fs/ext2_fs.h"
+#include "et/com_err.h"
+#include "e2p/e2p.h"
+
+#include "support/nls-enable.h"
+#include "../version.h"
+
+#ifdef __GNUC__
+#define EXT2FS_ATTR(x) __attribute__(x)
+#else
+#define EXT2FS_ATTR(x)
+#endif
+
+static const char * program_name = "lsattr";
+
+static int all;
+static int dirs_opt;
+static unsigned pf_options;
+static int recursive;
+static int verbose;
+static int generation_opt;
+static int project_opt;
+
+#ifdef _LFS64_LARGEFILE
+#define LSTAT lstat64
+#define STRUCT_STAT struct stat64
+#else
+#define LSTAT lstat
+#define STRUCT_STAT struct stat
+#endif
+
+static void usage(void)
+{
+ fprintf(stderr, _("Usage: %s [-RVadlpv] [files...]\n"), program_name);
+ exit(1);
+}
+
+static int list_attributes (const char * name)
+{
+ unsigned long flags;
+ unsigned long generation;
+ unsigned long project;
+
+ if (fgetflags (name, &flags) == -1) {
+ com_err (program_name, errno, _("While reading flags on %s"),
+ name);
+ return -1;
+ }
+ if (project_opt) {
+ if (fgetproject(name, &project) == -1) {
+ com_err (program_name, errno,
+ _("While reading project on %s"),
+ name);
+ return -1;
+ }
+ printf ("%5lu ", project);
+ }
+ if (generation_opt) {
+ if (fgetversion (name, &generation) == -1) {
+ com_err (program_name, errno,
+ _("While reading version on %s"),
+ name);
+ return -1;
+ }
+ printf ("%-10lu ", generation);
+ }
+ if (pf_options & PFOPT_LONG) {
+ printf("%-28s ", name);
+ print_flags(stdout, flags, pf_options);
+ fputc('\n', stdout);
+ } else {
+ print_flags(stdout, flags, pf_options);
+ printf(" %s\n", name);
+ }
+ return 0;
+}
+
+static int lsattr_dir_proc (const char *, struct dirent *, void *);
+
+static int lsattr_args (const char * name)
+{
+ STRUCT_STAT st;
+ int retval = 0;
+
+ if (LSTAT (name, &st) == -1) {
+ com_err (program_name, errno, _("while trying to stat %s"),
+ name);
+ retval = -1;
+ } else {
+ if (S_ISDIR(st.st_mode) && !dirs_opt)
+ retval = iterate_on_dir (name, lsattr_dir_proc, NULL);
+ else
+ retval = list_attributes (name);
+ }
+ return retval;
+}
+
+static int lsattr_dir_proc (const char * dir_name, struct dirent * de,
+ void * private EXT2FS_ATTR((unused)))
+{
+ STRUCT_STAT st;
+ char *path;
+ int dir_len = strlen(dir_name);
+
+ path = malloc(dir_len + strlen (de->d_name) + 2);
+ if (!path) {
+ fputs(_("Couldn't allocate path variable in lsattr_dir_proc\n"),
+ stderr);
+ return -1;
+ }
+
+ if (dir_len && dir_name[dir_len-1] == '/')
+ sprintf (path, "%s%s", dir_name, de->d_name);
+ else
+ sprintf (path, "%s/%s", dir_name, de->d_name);
+ if (LSTAT (path, &st) == -1)
+ perror (path);
+ else {
+ if (de->d_name[0] != '.' || all) {
+ list_attributes (path);
+ if (S_ISDIR(st.st_mode) && recursive &&
+ strcmp(de->d_name, ".") &&
+ strcmp(de->d_name, "..")) {
+ printf ("\n%s:\n", path);
+ iterate_on_dir (path, lsattr_dir_proc, NULL);
+ printf ("\n");
+ }
+ }
+ }
+ free(path);
+ return 0;
+}
+
+int main (int argc, char ** argv)
+{
+ int c;
+ int i;
+ int err, retval = 0;
+
+#ifdef ENABLE_NLS
+ setlocale(LC_MESSAGES, "");
+ setlocale(LC_CTYPE, "");
+ bindtextdomain(NLS_CAT_NAME, LOCALEDIR);
+ textdomain(NLS_CAT_NAME);
+ set_com_err_gettext(gettext);
+#endif
+ if (argc && *argv)
+ program_name = *argv;
+ else
+ usage();
+ while ((c = getopt (argc, argv, "RVadlvp")) != EOF)
+ switch (c)
+ {
+ case 'R':
+ recursive = 1;
+ break;
+ case 'V':
+ verbose = 1;
+ break;
+ case 'a':
+ all = 1;
+ break;
+ case 'd':
+ dirs_opt = 1;
+ break;
+ case 'l':
+ pf_options = PFOPT_LONG;
+ break;
+ case 'v':
+ generation_opt = 1;
+ break;
+ case 'p':
+ project_opt = 1;
+ break;
+ default:
+ usage();
+ }
+
+ if (verbose)
+ fprintf (stderr, "lsattr %s (%s)\n",
+ E2FSPROGS_VERSION, E2FSPROGS_DATE);
+ if (optind > argc - 1) {
+ if (lsattr_args (".") == -1)
+ retval = 1;
+ } else {
+ for (i = optind; i < argc; i++) {
+ err = lsattr_args (argv[i]);
+ if (err)
+ retval = 1;
+ }
+ }
+ exit(retval);
+}
diff --git a/misc/mk_hugefiles.c b/misc/mk_hugefiles.c
new file mode 100644
index 0000000..3caaf1b
--- /dev/null
+++ b/misc/mk_hugefiles.c
@@ -0,0 +1,491 @@
+/*
+ * mk_hugefiles.c -- create huge files
+ */
+
+#include "config.h"
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <time.h>
+#ifdef __linux__
+#include <sys/utsname.h>
+#endif
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#else
+extern char *optarg;
+extern int optind;
+#endif
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+#include <sys/types.h>
+#include <sys/stat.h>
+#ifdef HAVE_SYS_SYSMACROS_H
+#include <sys/sysmacros.h>
+#endif
+#include <libgen.h>
+#include <limits.h>
+#include <blkid/blkid.h>
+
+#include "ext2fs/ext2_fs.h"
+#include "ext2fs/ext2fsP.h"
+#include "et/com_err.h"
+#include "uuid/uuid.h"
+#include "e2p/e2p.h"
+#include "ext2fs/ext2fs.h"
+#include "util.h"
+#include "support/profile.h"
+#include "support/prof_err.h"
+#include "support/nls-enable.h"
+#include "mke2fs.h"
+
+static int uid;
+static int gid;
+static blk64_t num_blocks;
+static blk64_t num_slack;
+static unsigned long num_files;
+static blk64_t goal;
+static char *fn_prefix;
+static int idx_digits;
+static char *fn_buf;
+static char *fn_numbuf;
+int zero_hugefile = 1;
+
+static blk64_t
+get_partition_start(const char *device_name EXT2FS_ATTR((unused)))
+{
+#ifdef __linux__
+ unsigned long long start;
+ char path[128];
+ struct stat st;
+ FILE *f;
+ int n;
+
+ if ((stat(device_name, &st) < 0) || !S_ISBLK(st.st_mode))
+ return 0;
+
+ sprintf(path, "/sys/dev/block/%d:%d/start",
+ major(st.st_rdev), minor(st.st_rdev));
+ f = fopen(path, "r");
+ if (!f)
+ return 0;
+ n = fscanf(f, "%llu", &start);
+ fclose(f);
+ return (n == 1) ? start : 0;
+#else
+ return 0;
+#endif
+}
+
+static errcode_t create_directory(ext2_filsys fs, char *dir,
+ ext2_ino_t *ret_ino)
+
+{
+ struct ext2_inode inode;
+ ext2_ino_t ino = EXT2_ROOT_INO;
+ ext2_ino_t newdir;
+ errcode_t retval = 0;
+ char *fn, *cp, *next;
+
+ fn = malloc(strlen(dir) + 1);
+ if (fn == NULL)
+ return ENOMEM;
+
+ strcpy(fn, dir);
+ cp = fn;
+ while(1) {
+ next = strchr(cp, '/');
+ if (next)
+ *next++ = 0;
+ if (*cp) {
+ retval = ext2fs_new_inode(fs, ino, LINUX_S_IFDIR,
+ NULL, &newdir);
+ if (retval)
+ goto errout;
+
+ retval = ext2fs_mkdir(fs, ino, newdir, cp);
+ if (retval)
+ goto errout;
+
+ ino = newdir;
+ retval = ext2fs_read_inode(fs, ino, &inode);
+ if (retval)
+ goto errout;
+
+ inode.i_uid = uid & 0xFFFF;
+ ext2fs_set_i_uid_high(inode, (uid >> 16) & 0xffff);
+ inode.i_gid = gid & 0xFFFF;
+ ext2fs_set_i_gid_high(inode, (gid >> 16) & 0xffff);
+ retval = ext2fs_write_inode(fs, ino, &inode);
+ if (retval)
+ goto errout;
+ }
+ if (next == NULL || *next == '\0')
+ break;
+ cp = next;
+ }
+errout:
+ free(fn);
+ if (retval == 0)
+ *ret_ino = ino;
+ return retval;
+}
+
+static errcode_t mk_hugefile(ext2_filsys fs, blk64_t num,
+ ext2_ino_t dir, unsigned long idx, ext2_ino_t *ino)
+
+{
+ errcode_t retval;
+ blk64_t lblk, bend = 0;
+ __u64 size;
+ blk64_t left;
+ blk64_t count = 0;
+ struct ext2_inode inode;
+ ext2_extent_handle_t handle;
+
+ retval = ext2fs_new_inode(fs, 0, LINUX_S_IFREG, NULL, ino);
+ if (retval)
+ return retval;
+
+ memset(&inode, 0, sizeof(struct ext2_inode));
+ inode.i_mode = LINUX_S_IFREG | (0666 & ~fs->umask);
+ inode.i_links_count = 1;
+ inode.i_uid = uid & 0xFFFF;
+ ext2fs_set_i_uid_high(inode, (uid >> 16) & 0xffff);
+ inode.i_gid = gid & 0xFFFF;
+ ext2fs_set_i_gid_high(inode, (gid >> 16) & 0xffff);
+
+ retval = ext2fs_write_new_inode(fs, *ino, &inode);
+ if (retval)
+ return retval;
+
+ ext2fs_inode_alloc_stats2(fs, *ino, +1, 0);
+
+ retval = ext2fs_extent_open2(fs, *ino, &inode, &handle);
+ if (retval)
+ return retval;
+
+ /*
+ * We don't use ext2fs_fallocate() here because hugefiles are
+ * designed to be physically contiguous (if the block group
+ * descriptors are configured to be in a single block at the
+ * beginning of the file system, by using the
+ * packed_meta_blocks layout), with the extent tree blocks
+ * allocated near the beginning of the file system.
+ */
+ lblk = 0;
+ left = num ? num : 1;
+ while (left) {
+ blk64_t pblk, end;
+ blk64_t n = left;
+
+ retval = ext2fs_find_first_zero_block_bitmap2(fs->block_map,
+ goal, ext2fs_blocks_count(fs->super) - 1, &end);
+ if (retval)
+ goto errout;
+ goal = end;
+
+ retval = ext2fs_find_first_set_block_bitmap2(fs->block_map, goal,
+ ext2fs_blocks_count(fs->super) - 1, &bend);
+ if (retval == ENOENT) {
+ bend = ext2fs_blocks_count(fs->super);
+ if (num == 0)
+ left = 0;
+ }
+ if (!num || bend - goal < left)
+ n = bend - goal;
+ pblk = goal;
+ if (num)
+ left -= n;
+ goal += n;
+ count += n;
+ ext2fs_block_alloc_stats_range(fs, pblk, n, +1);
+
+ if (zero_hugefile) {
+ blk64_t ret_blk;
+ retval = ext2fs_zero_blocks2(fs, pblk, n,
+ &ret_blk, NULL);
+
+ if (retval)
+ com_err(program_name, retval,
+ _("while zeroing block %llu "
+ "for hugefile"),
+ (unsigned long long) ret_blk);
+ }
+
+ while (n) {
+ blk64_t l = n;
+ struct ext2fs_extent newextent;
+
+ if (l > EXT_INIT_MAX_LEN)
+ l = EXT_INIT_MAX_LEN;
+
+ newextent.e_len = l;
+ newextent.e_pblk = pblk;
+ newextent.e_lblk = lblk;
+ newextent.e_flags = 0;
+
+ retval = ext2fs_extent_insert(handle,
+ EXT2_EXTENT_INSERT_AFTER, &newextent);
+ if (retval)
+ return retval;
+ pblk += l;
+ lblk += l;
+ n -= l;
+ }
+ }
+
+ retval = ext2fs_read_inode(fs, *ino, &inode);
+ if (retval)
+ goto errout;
+
+ retval = ext2fs_iblk_add_blocks(fs, &inode,
+ count / EXT2FS_CLUSTER_RATIO(fs));
+ if (retval)
+ goto errout;
+ size = (__u64) count * fs->blocksize;
+ retval = ext2fs_inode_size_set(fs, &inode, size);
+ if (retval)
+ goto errout;
+
+ retval = ext2fs_write_new_inode(fs, *ino, &inode);
+ if (retval)
+ goto errout;
+
+ if (idx_digits)
+ sprintf(fn_numbuf, "%0*lu", idx_digits, idx);
+ else if (num_files > 1)
+ sprintf(fn_numbuf, "%lu", idx);
+
+retry:
+ retval = ext2fs_link(fs, dir, fn_buf, *ino, EXT2_FT_REG_FILE);
+ if (retval == EXT2_ET_DIR_NO_SPACE) {
+ retval = ext2fs_expand_dir(fs, dir);
+ if (retval)
+ goto errout;
+ goto retry;
+ }
+
+ if (retval)
+ goto errout;
+
+errout:
+ if (handle)
+ ext2fs_extent_free(handle);
+
+ return retval;
+}
+
+static blk64_t calc_overhead(ext2_filsys fs, blk64_t num)
+{
+ blk64_t e_blocks, e_blocks2, e_blocks3, e_blocks4;
+ int extents_per_block;
+ int extents = (num + EXT_INIT_MAX_LEN - 1) / EXT_INIT_MAX_LEN;
+
+ if (extents <= 4)
+ return 0;
+
+ /*
+ * This calculation is due to the fact that we are inefficient
+ * in how handle extent splits when appending to the end of
+ * the extent tree. Sigh. We should fix this so that we can
+ * actually store 340 extents per 4k block, instead of only 170.
+ */
+ extents_per_block = ((fs->blocksize -
+ sizeof(struct ext3_extent_header)) /
+ sizeof(struct ext3_extent));
+ extents_per_block = (extents_per_block/ 2) - 1;
+
+ e_blocks = (extents + extents_per_block - 1) / extents_per_block;
+ e_blocks2 = (e_blocks + extents_per_block - 1) / extents_per_block;
+ e_blocks3 = (e_blocks2 + extents_per_block - 1) / extents_per_block;
+ e_blocks4 = (e_blocks3 + extents_per_block - 1) / extents_per_block;
+ return (e_blocks + e_blocks2 + e_blocks3 + e_blocks4) *
+ EXT2FS_CLUSTER_RATIO(fs);
+}
+
+/*
+ * Find the place where we should start allocating blocks for the huge
+ * files. Leave <slack> free blocks at the beginning of the file
+ * system for things like metadata blocks.
+ */
+static blk64_t get_start_block(ext2_filsys fs, blk64_t slack)
+{
+ errcode_t retval;
+ blk64_t blk = fs->super->s_first_data_block, next;
+ blk64_t last_blk = ext2fs_blocks_count(fs->super) - 1;
+
+ while (slack) {
+ retval = ext2fs_find_first_zero_block_bitmap2(fs->block_map,
+ blk, last_blk, &blk);
+ if (retval)
+ break;
+
+ retval = ext2fs_find_first_set_block_bitmap2(fs->block_map,
+ blk, last_blk, &next);
+ if (retval)
+ next = last_blk;
+
+ if (next - blk > slack) {
+ blk += slack;
+ break;
+ }
+
+ slack -= (next - blk);
+ blk = next;
+ }
+ return blk;
+}
+
+static blk64_t round_up_align(blk64_t b, unsigned long align,
+ blk64_t part_offset)
+{
+ unsigned long m;
+
+ if (align == 0)
+ return b;
+ part_offset = part_offset % align;
+ m = (b + part_offset) % align;
+ if (m)
+ b += align - m;
+ return b;
+}
+
+errcode_t mk_hugefiles(ext2_filsys fs, const char *device_name)
+{
+ unsigned long i;
+ ext2_ino_t dir;
+ errcode_t retval;
+ blk64_t fs_blocks, part_offset = 0;
+ unsigned long align;
+ int d, dsize;
+ char *t;
+
+ if (!get_bool_from_profile(fs_types, "make_hugefiles", 0))
+ return 0;
+
+ if (!ext2fs_has_feature_extents(fs->super))
+ return EXT2_ET_EXTENT_NOT_SUPPORTED;
+
+ uid = get_int_from_profile(fs_types, "hugefiles_uid", 0);
+ gid = get_int_from_profile(fs_types, "hugefiles_gid", 0);
+ fs->umask = get_int_from_profile(fs_types, "hugefiles_umask", 077);
+ num_files = get_int_from_profile(fs_types, "num_hugefiles", 0);
+ t = get_string_from_profile(fs_types, "hugefiles_slack", "1M");
+ num_slack = parse_num_blocks2(t, fs->super->s_log_block_size);
+ free(t);
+ t = get_string_from_profile(fs_types, "hugefiles_size", "0");
+ num_blocks = parse_num_blocks2(t, fs->super->s_log_block_size);
+ free(t);
+ t = get_string_from_profile(fs_types, "hugefiles_align", "0");
+ align = parse_num_blocks2(t, fs->super->s_log_block_size);
+ free(t);
+ if (get_bool_from_profile(fs_types, "hugefiles_align_disk", 0)) {
+ part_offset = get_partition_start(device_name) /
+ (fs->blocksize / 512);
+ if (part_offset % EXT2FS_CLUSTER_RATIO(fs)) {
+ fprintf(stderr,
+ _("Partition offset of %llu (%uk) blocks "
+ "not compatible with cluster size %u.\n"),
+ (unsigned long long) part_offset, fs->blocksize,
+ EXT2_CLUSTER_SIZE(fs->super));
+ exit(1);
+ }
+ }
+ num_blocks = round_up_align(num_blocks, align, 0);
+ zero_hugefile = get_bool_from_profile(fs_types, "zero_hugefiles",
+ zero_hugefile);
+
+ t = get_string_from_profile(fs_types, "hugefiles_dir", "/");
+ retval = create_directory(fs, t, &dir);
+ free(t);
+ if (retval)
+ return retval;
+
+ fn_prefix = get_string_from_profile(fs_types, "hugefiles_name",
+ "hugefile");
+ idx_digits = get_int_from_profile(fs_types, "hugefiles_digits", 5);
+ d = int_log10(num_files) + 1;
+ if (idx_digits > d)
+ d = idx_digits;
+ dsize = strlen(fn_prefix) + d + 16;
+ fn_buf = malloc(dsize);
+ if (!fn_buf) {
+ free(fn_prefix);
+ return ENOMEM;
+ }
+ strcpy(fn_buf, fn_prefix);
+ fn_numbuf = fn_buf + strlen(fn_prefix);
+ free(fn_prefix);
+
+ fs_blocks = ext2fs_free_blocks_count(fs->super);
+ if (fs_blocks < num_slack + align)
+ return ENOSPC;
+ fs_blocks -= num_slack + align;
+ if (num_blocks && num_blocks > fs_blocks)
+ return ENOSPC;
+ if (num_blocks == 0 && num_files == 0)
+ num_files = 1;
+
+ if (num_files == 0 && num_blocks) {
+ num_files = fs_blocks / num_blocks;
+ fs_blocks -= (num_files / 16) + 1;
+ fs_blocks -= calc_overhead(fs, num_blocks) * num_files;
+ num_files = fs_blocks / num_blocks;
+ }
+
+ if (num_blocks == 0 && num_files > 1) {
+ num_blocks = fs_blocks / num_files;
+ fs_blocks -= (num_files / 16) + 1;
+ fs_blocks -= calc_overhead(fs, num_blocks) * num_files;
+ num_blocks = fs_blocks / num_files;
+ }
+
+ num_slack += (calc_overhead(fs, num_blocks ? num_blocks : fs_blocks) *
+ num_files);
+ num_slack += (num_files / 16) + 1; /* space for dir entries */
+ goal = get_start_block(fs, num_slack);
+ goal = round_up_align(goal, align, part_offset);
+
+ if ((num_blocks ? num_blocks : fs_blocks) >
+ (0x80000000UL / fs->blocksize))
+ ext2fs_set_feature_large_file(fs->super);
+
+ if (!quiet) {
+ if (zero_hugefile && verbose)
+ printf("%s", _("Huge files will be zero'ed\n"));
+ printf(_("Creating %lu huge file(s) "), num_files);
+ if (num_blocks)
+ printf(_("with %llu blocks each"),
+ (unsigned long long) num_blocks);
+ fputs(": ", stdout);
+ }
+ for (i=0; i < num_files; i++) {
+ ext2_ino_t ino;
+
+ retval = mk_hugefile(fs, num_blocks, dir, i, &ino);
+ if (retval) {
+ com_err(program_name, retval,
+ _("while creating huge file %lu"), i);
+ goto errout;
+ }
+ }
+ if (!quiet)
+ fputs(_("done\n"), stdout);
+
+errout:
+ free(fn_buf);
+ return retval;
+}
diff --git a/misc/mke2fs-hurd.conf b/misc/mke2fs-hurd.conf
new file mode 100644
index 0000000..4f0527d
--- /dev/null
+++ b/misc/mke2fs-hurd.conf
@@ -0,0 +1,42 @@
+[defaults]
+ base_features = sparse_super,filetype,resize_inode,dir_index,ext_attr
+ default_mntopts = acl,user_xattr
+ enable_periodic_fsck = 0
+ blocksize = 4096
+ inode_size = 128
+ inode_ratio = 16384
+
+[fs_types]
+ ext3 = {
+ features = has_journal
+ }
+ ext4 = {
+ features = has_journal,extent,huge_file,flex_bg,uninit_bg,dir_nlink,extra_isize
+ auto_64-bit_support = 1
+ inode_size = 256
+ }
+ small = {
+ inode_ratio = 4096
+ }
+ floppy = {
+ inode_ratio = 8192
+ }
+ big = {
+ inode_ratio = 32768
+ }
+ huge = {
+ inode_ratio = 65536
+ }
+ news = {
+ inode_ratio = 4096
+ }
+ largefile = {
+ inode_ratio = 1048576
+ }
+ largefile4 = {
+ inode_ratio = 4194304
+ }
+ hurd = {
+ blocksize = 4096
+ inode_size = 128
+ }
diff --git a/misc/mke2fs.8.in b/misc/mke2fs.8.in
new file mode 100644
index 0000000..30f97bb
--- /dev/null
+++ b/misc/mke2fs.8.in
@@ -0,0 +1,891 @@
+.\" -*- nroff -*-
+.\" Copyright 1993, 1994, 1995 by Theodore Ts'o. All Rights Reserved.
+.\" This file may be copied under the terms of the GNU Public License.
+.\"
+.TH MKE2FS 8 "@E2FSPROGS_MONTH@ @E2FSPROGS_YEAR@" "E2fsprogs version @E2FSPROGS_VERSION@"
+.SH NAME
+mke2fs \- create an ext2/ext3/ext4 file system
+.SH SYNOPSIS
+.B mke2fs
+[
+.B \-c
+|
+.B \-l
+.I filename
+]
+[
+.B \-b
+.I block-size
+]
+[
+.B \-C
+.I cluster-size
+]
+[
+.B \-d
+.I root-directory
+]
+[
+.B \-D
+]
+[
+.B \-g
+.I blocks-per-group
+]
+[
+.B \-G
+.I number-of-groups
+]
+[
+.B \-i
+.I bytes-per-inode
+]
+[
+.B \-I
+.I inode-size
+]
+[
+.B \-j
+]
+[
+.B \-J
+.I journal-options
+]
+[
+.B \-N
+.I number-of-inodes
+]
+[
+.B \-n
+]
+[
+.B \-m
+.I reserved-blocks-percentage
+]
+[
+.B \-o
+.I creator-os
+]
+[
+.B \-O
+[^]\fIfeature\fR[,...]
+]
+[
+.B \-q
+]
+[
+.B \-r
+.I fs-revision-level
+]
+[
+.B \-E
+.I extended-options
+]
+[
+.B \-v
+]
+[
+.B \-F
+]
+[
+.B \-L
+.I volume-label
+]
+[
+.B \-M
+.I last-mounted-directory
+]
+[
+.B \-S
+]
+[
+.B \-t
+.I fs-type
+]
+[
+.B \-T
+.I usage-type
+]
+[
+.B \-U
+.I UUID
+]
+[
+.B \-V
+]
+[
+.B \-e
+.I errors-behavior
+]
+[
+.B \-z
+.I undo_file
+]
+.I device
+[
+.I fs-size
+]
+@JDEV@.sp
+@JDEV@.B "mke2fs \-O journal_dev"
+@JDEV@[
+@JDEV@.B \-b
+@JDEV@.I block-size
+@JDEV@]
+.\" No external-journal specific journal options yet (size is ignored)
+.\" @JDEV@[
+.\" @JDEV@.B \-J
+.\" @JDEV@.I journal-options
+.\" @JDEV@]
+@JDEV@[
+@JDEV@.B \-L
+@JDEV@.I volume-label
+@JDEV@]
+@JDEV@[
+@JDEV@.B \-n
+@JDEV@]
+@JDEV@[
+@JDEV@.B \-q
+@JDEV@]
+@JDEV@[
+@JDEV@.B \-v
+@JDEV@]
+@JDEV@.I external-journal
+@JDEV@[
+@JDEV@.I fs-size
+@JDEV@]
+.SH DESCRIPTION
+.B mke2fs
+is used to create an ext2, ext3, or ext4 file system, usually in a disk
+partition (or file) named by
+.IR device .
+.PP
+The file system size is specified by
+.IR fs-size .
+If
+.I fs-size
+does not have a suffix, it is interpreted as power-of-two kilobytes,
+unless the
+.B \-b
+.I blocksize
+option is specified, in which case
+.I fs-size
+is interpreted as the number of
+.I blocksize
+blocks. If the fs-size is suffixed by 'k', 'm', 'g', 't'
+(either upper-case or lower-case), then it is interpreted in
+power-of-two kilobytes, megabytes, gigabytes, terabytes, etc.
+If
+.I fs-size
+is omitted,
+.B mke2fs
+will create the file system based on the device size.
+.PP
+If
+.B mke2fs
+is run as
+.B mkfs.XXX
+(i.e.,
+.BR mkfs.ext2 ,
+.BR mkfs.ext3 ,
+or
+.BR mkfs.ext4 )
+the option
+.B \-t
+.I XXX
+is implied; so
+.B mkfs.ext3
+will create a file system for use with ext3,
+.B mkfs.ext4
+will create a file system for use with ext4, and so on.
+.PP
+The defaults of the parameters for the newly created file system, if not
+overridden by the options listed below, are controlled by the
+.B /etc/mke2fs.conf
+configuration file. See the
+.BR mke2fs.conf (5)
+manual page for more details.
+.SH OPTIONS
+.TP
+.BI \-b " block-size"
+Specify the size of blocks in bytes. Valid block-size values are powers of two
+from 1024 up to 65536 (however note that the kernel is able to mount only
+file systems with block-size smaller or equal to the system page size - 4k on
+x86 systems, up to 64k on ppc64 or aarch64 depending on kernel configuration).
+If omitted, block-size is heuristically determined by the file system size and
+the expected usage of the file system (see the
+.B \-T
+option). In most common cases, the default block size is 4k. If
+.I block-size
+is preceded by a negative sign ('-'), then
+.B mke2fs
+will use heuristics to determine the
+appropriate block size, with the constraint that the block size will be
+at least
+.I block-size
+bytes. This is useful for certain hardware devices which require that
+the blocksize be a multiple of 2k.
+.TP
+.B \-c
+Check the device for bad blocks before creating the file system. If
+this option is specified twice, then a slower read-write
+test is used instead of a fast read-only test.
+.TP
+.B \-C " cluster-size"
+Specify the size of cluster in bytes for file systems using the bigalloc
+feature. Valid cluster-size values are from 2048 to 256M bytes per
+cluster. This can only be specified if the bigalloc feature is
+enabled. (See the
+.B ext4 (5)
+man page for more details about bigalloc.) The default cluster size if
+bigalloc is enabled is 16 times the block size.
+.TP
+.BI \-d " root-directory"
+Copy the contents of the given directory into the root directory of the
+file system.
+.TP
+.B \-D
+Use direct I/O when writing to the disk. This avoids mke2fs dirtying a
+lot of buffer cache memory, which may impact other applications running
+on a busy server. This option will cause mke2fs to run much more
+slowly, however, so there is a tradeoff to using direct I/O.
+.TP
+.BI \-e " error-behavior"
+Change the behavior of the kernel code when errors are detected.
+In all cases, a file system error will cause
+.BR e2fsck (8)
+to check the file system on the next boot.
+.I error-behavior
+can be one of the following:
+.RS 1.2i
+.TP 1.2i
+.B continue
+Continue normal execution.
+.TP
+.B remount-ro
+Remount file system read-only.
+.TP
+.B panic
+Cause a kernel panic.
+.RE
+.TP
+.BI \-E " extended-options"
+Set extended options for the file system. Extended options are comma
+separated, and may take an argument using the equals ('=') sign. The
+.B \-E
+option used to be
+.B \-R
+in earlier versions of
+.BR mke2fs .
+The
+.B \-R
+option is still accepted for backwards compatibility, but is deprecated.
+The following extended options are supported:
+.RS 1.2i
+.TP
+.BI encoding= encoding-name
+Enable the
+.I casefold
+feature in the super block and set
+.I encoding-name
+as the encoding to be used. If
+.I encoding-name
+is not specified, the encoding defined in
+.BR mke2fs.conf (5)
+is used.
+.TP
+.BI encoding_flags= encoding-flags
+Define parameters for file name character encoding operations. If a
+flag is not changed using this parameter, its default value is used.
+.I encoding-flags
+should be a comma-separated lists of flags to be enabled. To disable a
+flag, add it to the list with the prefix "no".
+
+The only flag that can be set right now is
+.I strict
+which means that invalid strings should be rejected by the file system.
+In the default configuration, the
+.I strict
+flag is disabled.
+.TP
+.BI mmp_update_interval= interval
+Adjust the initial MMP update interval to
+.I interval
+seconds. Specifying an
+.I interval
+of 0 means to use the default interval. The specified interval must
+be less than 300 seconds. Requires that the
+.B mmp
+feature be enabled.
+.TP
+.BI stride= stride-size
+Configure the file system for a RAID array with
+.I stride-size
+file system blocks. This is the number of blocks read or written to disk
+before moving to the next disk, which is sometimes referred to as the
+.I chunk size.
+This mostly affects placement of file system metadata like bitmaps at
+.B mke2fs
+time to avoid placing them on a single disk, which can hurt performance.
+It may also be used by the block allocator.
+.TP
+.BI stripe_width= stripe-width
+Configure the file system for a RAID array with
+.I stripe-width
+file system blocks per stripe. This is typically stride-size * N, where
+N is the number of data-bearing disks in the RAID (e.g. for RAID 5 there is one
+parity disk, so N will be the number of disks in the array minus 1).
+This allows the block allocator to prevent read-modify-write of the
+parity in a RAID stripe if possible when the data is written.
+.TP
+.BI offset= offset
+Create the file system at an offset from the beginning of the device or
+file. This can be useful when creating disk images for virtual machines.
+.TP
+.BI resize= max-online-resize
+Reserve enough space so that the block group descriptor table can grow
+to support a file system that has
+.I max-online-resize
+blocks.
+.TP
+.B lazy_itable_init\fR[\fB= \fI<0 to disable, 1 to enable>\fR]
+If enabled and the uninit_bg feature is enabled, the inode table will
+not be fully initialized by
+.BR mke2fs .
+This speeds up file system
+initialization noticeably, but it requires the kernel to finish
+initializing the file system in the background when the file system is
+first mounted. If the option value is omitted, it defaults to 1 to
+enable lazy inode table zeroing.
+.TP
+.B lazy_journal_init\fR[\fB= \fI<0 to disable, 1 to enable>\fR]
+If enabled, the journal inode will not be fully zeroed out by
+.BR mke2fs .
+This speeds up file system initialization noticeably, but carries some
+small risk if the system crashes before the journal has been overwritten
+entirely one time. If the option value is omitted, it defaults to 1 to
+enable lazy journal inode zeroing.
+.TP
+.B assume_storage_prezeroed\fR[\fB= \fI<0 to disable, 1 to enable>\fR]
+If enabled,
+.BR mke2fs
+assumes that the storage device has been prezeroed, skips zeroing the journal
+and inode tables, and annotates the block group flags to signal that the inode
+table has been zeroed.
+.TP
+.B no_copy_xattrs
+Normally
+.B mke2fs
+will copy the extended attributes of the files in the directory
+hierarchy specified via the (optional)
+.B \-d
+option. This will disable the copy and leaves the files in the newly
+created file system without any extended attributes.
+.TP
+.BI num_backup_sb= <0|1|2>
+If the
+.B sparse_super2
+file system feature is enabled this option controls whether there will
+be 0, 1, or 2 backup superblocks created in the file system.
+.TP
+.B packed_meta_blocks\fR[\fB= \fI<0 to disable, 1 to enable>\fR]
+Place the allocation bitmaps and the inode table at the beginning of the
+disk. This option requires that the flex_bg file system feature to be
+enabled in order for it to have effect, and will also create the journal
+at the beginning of the file system. This option is useful for flash
+devices that use SLC flash at the beginning of the disk.
+It also maximizes the range of contiguous data blocks, which
+can be useful for certain specialized use cases, such as supported
+Shingled Drives.
+.TP
+.BI root_owner [=uid:gid]
+Specify the numeric user and group ID of the root directory. If no UID:GID
+is specified, use the user and group ID of the user running \fBmke2fs\fR.
+In \fBmke2fs\fR 1.42 and earlier the UID and GID of the root directory were
+set by default to the UID and GID of the user running the mke2fs command.
+The \fBroot_owner=\fR option allows explicitly specifying these values,
+and avoid side-effects for users that do not expect the contents of the
+file system to change based on the user running \fBmke2fs\fR.
+.TP
+.B test_fs
+Set a flag in the file system superblock indicating that it may be
+mounted using experimental kernel code, such as the ext4dev file system.
+.TP
+.BI orphan_file_size= size
+Set size of the file for tracking unlinked but still open inodes and inodes
+with truncate in progress. Larger file allows for better scalability, reserving
+a few blocks per cpu is ideal.
+.TP
+.B discard
+Attempt to discard blocks at mkfs time (discarding blocks initially is useful
+on solid state devices and sparse / thin-provisioned storage). When the device
+advertises that discard also zeroes data (any subsequent read after the discard
+and before write returns zero), then mark all not-yet-zeroed inode tables as
+zeroed. This significantly speeds up file system initialization. This is set
+as default.
+.TP
+.B nodiscard
+Do not attempt to discard blocks at mkfs time.
+.TP
+.B quotatype
+Specify the which quota types (usrquota, grpquota, prjquota) which
+should be enabled in the created file system. The argument of this
+extended option should be a colon separated list. This option has
+effect only if the
+.B quota
+feature is set. The default quota types to be initialized if this
+option is not specified is both user and group quotas. If the project
+feature is enabled that project quotas will be initialized as well.
+.RE
+.TP
+.B \-F
+Force
+.B mke2fs
+to create a file system, even if the specified device is not a partition
+on a block special device, or if other parameters do not make sense.
+In order to force
+.B mke2fs
+to create a file system even if the file system appears to be in use
+or is mounted (a truly dangerous thing to do), this option must be
+specified twice.
+.TP
+.BI \-g " blocks-per-group"
+Specify the number of blocks in a block group. There is generally no
+reason for the user to ever set this parameter, as the default is optimal
+for the file system. (For administrators who are creating
+file systems on RAID arrays, it is preferable to use the
+.I stride
+RAID parameter as part of the
+.B \-E
+option rather than manipulating the number of blocks per group.)
+This option is generally used by developers who
+are developing test cases.
+.IP
+If the bigalloc feature is enabled, the
+.B \-g
+option will specify the number of clusters in a block group.
+.TP
+.BI \-G " number-of-groups"
+Specify the number of block groups that will be packed together to
+create a larger virtual block group (or "flex_bg group") in an
+ext4 file system. This improves meta-data locality and performance
+on meta-data heavy workloads. The number of groups must be a power
+of 2 and may only be specified if the
+.B flex_bg
+file system feature is enabled.
+.TP
+.BI \-i " bytes-per-inode"
+Specify the bytes/inode ratio.
+.B mke2fs
+creates an inode for every
+.I bytes-per-inode
+bytes of space on the disk. The larger the
+.I bytes-per-inode
+ratio, the fewer inodes will be created. This value generally shouldn't
+be smaller than the blocksize of the file system, since in that case more
+inodes would be made than can ever be used. Be warned that it is not
+possible to change this ratio on a file system after it is created, so be
+careful deciding the correct value for this parameter. Note that resizing
+a file system changes the number of inodes to maintain this ratio.
+.TP
+.BI \-I " inode-size"
+Specify the size of each inode in bytes.
+The
+.I inode-size
+value must be a power of 2 larger or equal to 128. The larger the
+.I inode-size
+the more space the inode table will consume, and this reduces the usable
+space in the file system and can also negatively impact performance.
+It is not
+possible to change this value after the file system is created.
+.IP
+File systems with an inode size of 128 bytes do not support timestamps
+beyond January 19, 2038. Inodes which are 256 bytes or larger will
+support extended timestamps, project id's, and the ability to store some
+extended attributes in the inode table for improved performance.
+.IP
+The default inode size is controlled by the
+.BR mke2fs.conf (5)
+file. In the
+.B mke2fs.conf
+file shipped with e2fsprogs, the default inode size is 256 bytes for
+most file systems, except for small file systems where the inode size
+will be 128 bytes.
+.TP
+.B \-j
+Create the file system with an ext3 journal. If the
+.B \-J
+option is not specified, the default journal parameters will be used to
+create an appropriately sized journal (given the size of the file system)
+stored within the file system. Note that you must be using a kernel
+which has ext3 support in order to actually make use of the journal.
+.TP
+.BI \-J " journal-options"
+Create the ext3 journal using options specified on the command-line.
+Journal options are comma
+separated, and may take an argument using the equals ('=') sign.
+The following journal options are supported:
+.RS 1.2i
+.TP
+.BI size= journal-size
+Create an internal journal (i.e., stored inside the file system) of size
+.I journal-size
+megabytes.
+The size of the journal must be at least 1024 file system blocks
+(i.e., 1MB if using 1k blocks, 4MB if using 4k blocks, etc.)
+and may be no more than 10,240,000 file system blocks or half the total
+file system size (whichever is smaller)
+.TP
+.BI fast_commit_size= fast-commit-size
+Create an additional fast commit journal area of size
+.I fast-commit-size
+kilobytes.
+This option is only valid if
+.B fast_commit
+feature is enabled
+on the file system. If this option is not specified and if
+.B fast_commit
+feature is turned on, fast commit area size defaults to
+.I journal-size
+/ 64 megabytes. The total size of the journal with
+.B fast_commit
+feature set is
+.I journal-size
++ (
+.I fast-commit-size
+* 1024) megabytes. The total journal size may be no more than
+10,240,000 file system blocks or half the total file system size
+(whichever is smaller).
+.TP
+.BI location =journal-location
+Specify the location of the journal. The argument
+.I journal-location
+can either be specified as a block number, or if the number has a units
+suffix (e.g., 'M', 'G', etc.) interpret it as the offset from the
+beginning of the file system.
+@JDEV@.TP
+@JDEV@.BI device= external-journal
+@JDEV@Attach the file system to the journal block device located on
+@JDEV@.IR external-journal .
+@JDEV@The external
+@JDEV@journal must already have been created using the command
+@JDEV@.IP
+@JDEV@.B mke2fs -O journal_dev
+@JDEV@.I external-journal
+@JDEV@.IP
+@JDEV@Note that
+@JDEV@.I external-journal
+@JDEV@must have been created with the
+@JDEV@same block size as the new file system.
+@JDEV@In addition, while there is support for attaching
+@JDEV@multiple file systems to a single external journal,
+@JDEV@the Linux kernel and
+@JDEV@.BR e2fsck (8)
+@JDEV@do not currently support shared external journals yet.
+@JDEV@.IP
+@JDEV@Instead of specifying a device name directly,
+@JDEV@.I external-journal
+@JDEV@can also be specified by either
+@JDEV@.BI LABEL= label
+@JDEV@or
+@JDEV@.BI UUID= UUID
+@JDEV@to locate the external journal by either the volume label or UUID
+@JDEV@stored in the ext2 superblock at the start of the journal. Use
+@JDEV@.BR dumpe2fs (8)
+@JDEV@to display a journal device's volume label and UUID. See also the
+@JDEV@.B -L
+@JDEV@option of
+@JDEV@.BR tune2fs (8).
+.RE
+@JDEV@.IP
+@JDEV@Only one of the
+@JDEV@.BR size " or " device
+@JDEV@options can be given for a file system.
+.TP
+.BI \-l " filename"
+Read the bad blocks list from
+.IR filename .
+Note that the block numbers in the bad block list must be generated
+using the same block size as used by
+.BR mke2fs .
+As a result, the
+.B \-c
+option to
+.B mke2fs
+is a much simpler and less error-prone method of checking a disk for bad
+blocks before formatting it, as
+.B mke2fs
+will automatically pass the correct parameters to the
+.B badblocks
+program.
+.TP
+.BI \-L " new-volume-label"
+Set the volume label for the file system to
+.IR new-volume-label .
+The maximum length of the
+volume label is 16 bytes.
+.TP
+.BI \-m " reserved-blocks-percentage"
+Specify the percentage of the file system blocks reserved for
+the super-user. This avoids fragmentation, and allows root-owned
+daemons, such as
+.BR syslogd (8),
+to continue to function correctly after non-privileged processes are
+prevented from writing to the file system. The default percentage
+is 5%.
+.TP
+.BI \-M " last-mounted-directory"
+Set the last mounted directory for the file system. This might be useful
+for the sake of utilities that key off of the last mounted directory to
+determine where the file system should be mounted.
+.TP
+.B \-n
+Causes
+.B mke2fs
+to not actually create a file system, but display what it
+would do if it were to create a file system. This can be used to
+determine the location of the backup superblocks for a particular
+file system, so long as the
+.B mke2fs
+parameters that were passed when the
+file system was originally created are used again. (With the
+.B \-n
+option added, of course!)
+.TP
+.BI \-N " number-of-inodes"
+Overrides the default calculation of the number of inodes that should be
+reserved for the file system (which is based on the number of blocks and
+the
+.I bytes-per-inode
+ratio). This allows the user to specify the number
+of desired inodes directly.
+.TP
+.BI \-o " creator-os"
+Overrides the default value of the "creator operating system" field of the
+file system. The creator field is set by default to the name of the OS the
+.B mke2fs
+executable was compiled for.
+.TP
+.B "\-O \fR[^]\fIfeature\fR[,...]"
+Create a file system with the given features (file system options),
+overriding the default file system options. The features that are
+enabled by default are specified by the
+.I base_features
+relation, either in the
+.I [defaults]
+section in the
+.B /etc/mke2fs.conf
+configuration file,
+or in the
+.I [fs_types]
+subsections for the usage types as specified by the
+.B \-T
+option, further modified by the
+.I features
+relation found in the
+.I [fs_types]
+subsections for the file system and usage types. See the
+.BR mke2fs.conf (5)
+manual page for more details.
+The file system type-specific configuration setting found in the
+.I [fs_types]
+section will override the global default found in
+.IR [defaults] .
+.sp
+The file system feature set will be further edited
+using either the feature set specified by this option,
+or if this option is not given, by the
+.I default_features
+relation for the file system type being created, or in the
+.I [defaults]
+section of the configuration file.
+.sp
+The file system feature set is comprised of a list of features, separated
+by commas, that are to be enabled. To disable a feature, simply
+prefix the feature name with a caret ('^') character.
+Features with dependencies will not be removed successfully.
+The pseudo-file system feature "none" will clear all file system features.
+.TP
+For more information about the features which can be set, please see
+the manual page
+.BR ext4 (5).
+.TP
+.B \-q
+Quiet execution. Useful if
+.B mke2fs
+is run in a script.
+.TP
+.BI \-r " revision"
+Set the file system revision for the new file system. Note that 1.2
+kernels only support revision 0 file systems. The default is to
+create revision 1 file systems.
+.TP
+.B \-S
+Write superblock and group descriptors only. This is an extreme
+measure to be taken only in the very unlikely case that all of
+the superblock and backup superblocks are corrupted, and a last-ditch
+recovery method is desired by experienced users. It causes
+.B mke2fs
+to reinitialize the superblock and group descriptors, while not
+touching the inode table and the block and inode bitmaps. The
+.B e2fsck
+program should be run immediately after this option is used, and there
+is no guarantee that any data will be salvageable. Due to the wide
+variety of possible options to
+.B mke2fs
+that affect the on-disk layout, it is critical to specify exactly
+the same format options, such as blocksize, fs-type, feature flags, and
+other tunables when using this option, or the file system will be further
+corrupted. In some cases, such as file systems that have been resized,
+or have had features enabled after format time, it is impossible to
+overwrite all of the superblocks correctly, and at least some file system
+corruption will occur. It is best to run this on a full copy of the
+file system so other options can be tried if this doesn't work.
+.\" .TP
+.\" .BI \-t " test"
+.\" Check the device for bad blocks before creating the file system
+.\" using the specified test.
+.TP
+.BI \-t " fs-type"
+Specify the file system type (i.e., ext2, ext3, ext4, etc.) that is
+to be created.
+If this option is not specified,
+.B mke2fs
+will pick a default either via how
+the command was run (for example, using a name of the form mkfs.ext2,
+mkfs.ext3, etc.) or via a default as defined by the
+.B /etc/mke2fs.conf
+file. This option controls which file system options are used by
+default, based on the
+.B fstypes
+configuration stanza in
+.BR /etc/mke2fs.conf .
+.sp
+If the
+.B \-O
+option is used to explicitly add or remove file system options that
+should be set in the newly created file system, the
+resulting file system may not be supported by the requested
+.IR fs-type .
+(e.g., "\fBmke2fs \-t ext3 \-O extent /dev/sdXX\fR" will create a
+file system that is not supported by the ext3 implementation as found in
+the Linux kernel; and "\fBmke2fs \-t ext3 \-O ^has_journal /dev/hdXX\fR"
+will create a file system that does not have a journal and hence will not
+be supported by the ext3 file system code in the Linux kernel.)
+.TP
+.BI \-T " usage-type[,...]"
+Specify how the file system is going to be used, so that
+.B mke2fs
+can choose optimal file system parameters for that use. The usage
+types that are supported are defined in the configuration file
+.BR /etc/mke2fs.conf .
+The user may specify one or more usage types
+using a comma separated list.
+.sp
+If this option is is not specified,
+.B mke2fs
+will pick a single default usage type based on the size of the file system to
+be created. If the file system size is less than 3 megabytes,
+.B mke2fs
+will use the file system type
+.IR floppy .
+If the file system size is greater than or equal to 3 but less than
+512 megabytes,
+.BR mke2fs (8)
+will use the file system type
+.IR small .
+If the file system size is greater than or equal to 4 terabytes but less than
+16 terabytes,
+.BR mke2fs (8)
+will use the file system type
+.IR big .
+If the file system size is greater than or equal to 16 terabytes,
+.BR mke2fs (8)
+will use the file system type
+.IR huge .
+Otherwise,
+.BR mke2fs (8)
+will use the default file system type
+.IR default .
+.TP
+.BI \-U " UUID"
+Set the universally unique identifier (UUID) of the file system to
+.IR UUID .
+The format of the UUID is a series of hex digits separated by hyphens,
+like this:
+"c1b9d5a2-f162-11cf-9ece-0020afc76f16".
+The
+.I UUID
+parameter may also be one of the following:
+.RS 1.2i
+.TP
+.I clear
+clear the file system UUID
+.TP
+.I random
+generate a new randomly-generated UUID
+.TP
+.I time
+generate a new time-based UUID
+.RE
+.TP
+.B \-v
+Verbose execution.
+.TP
+.B \-V
+Print the version number of
+.B mke2fs
+and exit.
+.TP
+.BI \-z " undo_file"
+Before overwriting a file system block, write the old contents of the block to
+an undo file. This undo file can be used with e2undo(8) to restore the old
+contents of the file system should something go wrong. If the empty string is
+passed as the undo_file argument, the undo file will be written to a file named
+mke2fs-\fIdevice\fR.e2undo in the directory specified via the
+\fIE2FSPROGS_UNDO_DIR\fR environment variable or the \fIundo_dir\fR directive
+in the configuration file.
+
+WARNING: The undo file cannot be used to recover from a power or system crash.
+.SH ENVIRONMENT
+.TP
+.B MKE2FS_SYNC
+If set to non-zero integer value, its value is used to determine how often
+.BR sync (2)
+is called during inode table initialization.
+.TP
+.B MKE2FS_CONFIG
+Determines the location of the configuration file (see
+.BR mke2fs.conf (5)).
+.TP
+.B MKE2FS_FIRST_META_BG
+If set to non-zero integer value, its value is used to determine first meta
+block group. This is mostly for debugging purposes.
+.TP
+.B MKE2FS_DEVICE_SECTSIZE
+If set to non-zero integer value, its value is used to determine logical
+sector size of the
+.IR device .
+.TP
+.B MKE2FS_DEVICE_PHYS_SECTSIZE
+If set to non-zero integer value, its value is used to determine physical
+sector size of the
+.IR device .
+.TP
+.B MKE2FS_SKIP_CHECK_MSG
+If set, do not show the message of file system automatic check caused by
+mount count or check interval.
+.SH AUTHOR
+This version of
+.B mke2fs
+has been written by Theodore Ts'o <tytso@mit.edu>.
+.SH AVAILABILITY
+.B mke2fs
+is part of the e2fsprogs package and is available from
+http://e2fsprogs.sourceforge.net.
+.SH SEE ALSO
+.BR mke2fs.conf (5),
+.BR badblocks (8),
+.BR dumpe2fs (8),
+.BR e2fsck (8),
+.BR tune2fs (8),
+.BR ext4 (5)
diff --git a/misc/mke2fs.c b/misc/mke2fs.c
new file mode 100644
index 0000000..4a9c1b0
--- /dev/null
+++ b/misc/mke2fs.c
@@ -0,0 +1,3592 @@
+/*
+ * mke2fs.c - Make a ext2fs filesystem.
+ *
+ * Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+ * 2003, 2004, 2005 by Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ * %End-Header%
+ */
+
+/* Usage: mke2fs [options] device
+ *
+ * The device may be a block device or a image of one, but this isn't
+ * enforced (but it's not much fun on a character device :-).
+ */
+
+#define _XOPEN_SOURCE 600
+
+#include "config.h"
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <ctype.h>
+#include <time.h>
+#ifdef __linux__
+#include <sys/utsname.h>
+#define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
+#endif
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#else
+extern char *optarg;
+extern int optind;
+#endif
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+#include <libgen.h>
+#include <limits.h>
+#include <blkid/blkid.h>
+
+#include "ext2fs/ext2_fs.h"
+#include "ext2fs/ext2fsP.h"
+#include "uuid/uuid.h"
+#include "util.h"
+#include "support/nls-enable.h"
+#include "support/plausible.h"
+#include "support/profile.h"
+#include "support/prof_err.h"
+#include "../version.h"
+#include "support/quotaio.h"
+#include "mke2fs.h"
+#include "create_inode.h"
+
+#define STRIDE_LENGTH 8
+
+#define MAX_32_NUM ((((unsigned long long) 1) << 32) - 1)
+
+#ifndef __sparc__
+#define ZAP_BOOTBLOCK
+#endif
+
+#define DISCARD_STEP_MB (2048)
+
+extern int isatty(int);
+extern FILE *fpopen(const char *cmd, const char *mode);
+
+const char * program_name = "mke2fs";
+static const char * device_name /* = NULL */;
+
+/* Command line options */
+static int cflag;
+int verbose;
+int quiet;
+static int super_only;
+static int discard = 1; /* attempt to discard device before fs creation */
+static int direct_io;
+static int force;
+static int noaction;
+static int num_backups = 2; /* number of backup bg's for sparse_super2 */
+static uid_t root_uid;
+static gid_t root_gid;
+int journal_size;
+int journal_flags;
+int journal_fc_size;
+static e2_blkcnt_t orphan_file_blocks;
+static int lazy_itable_init;
+static int assume_storage_prezeroed;
+static int packed_meta_blocks;
+int no_copy_xattrs;
+static char *bad_blocks_filename = NULL;
+static __u32 fs_stride;
+/* Initialize usr/grp quotas by default */
+static unsigned int quotatype_bits = (QUOTA_USR_BIT | QUOTA_GRP_BIT);
+static __u64 offset;
+static blk64_t journal_location = ~0LL;
+static int proceed_delay = -1;
+static blk64_t dev_size;
+
+static struct ext2_super_block fs_param;
+static __u32 zero_buf[4];
+static char *fs_uuid = NULL;
+static char *creator_os;
+static char *volume_label;
+static char *mount_dir;
+char *journal_device;
+static int sync_kludge; /* Set using the MKE2FS_SYNC env. option */
+char **fs_types;
+const char *src_root_dir; /* Copy files from the specified directory */
+static char *undo_file;
+
+static int android_sparse_file; /* -E android_sparse */
+
+static profile_t profile;
+
+static int sys_page_size = 4096;
+
+static int errors_behavior = 0;
+
+static void usage(void)
+{
+ fprintf(stderr, _("Usage: %s [-c|-l filename] [-b block-size] "
+ "[-C cluster-size]\n\t[-i bytes-per-inode] [-I inode-size] "
+ "[-J journal-options]\n"
+ "\t[-G flex-group-size] [-N number-of-inodes] "
+ "[-d root-directory]\n"
+ "\t[-m reserved-blocks-percentage] [-o creator-os]\n"
+ "\t[-g blocks-per-group] [-L volume-label] "
+ "[-M last-mounted-directory]\n\t[-O feature[,...]] "
+ "[-r fs-revision] [-E extended-option[,...]]\n"
+ "\t[-t fs-type] [-T usage-type ] [-U UUID] [-e errors_behavior]"
+ "[-z undo_file]\n"
+ "\t[-jnqvDFSV] device [blocks-count]\n"),
+ program_name);
+ exit(1);
+}
+
+static int int_log2(unsigned long long arg)
+{
+ int l = 0;
+
+ arg >>= 1;
+ while (arg) {
+ l++;
+ arg >>= 1;
+ }
+ return l;
+}
+
+int int_log10(unsigned long long arg)
+{
+ int l;
+
+ for (l=0; arg ; l++)
+ arg = arg / 10;
+ return l;
+}
+
+#ifdef __linux__
+static int parse_version_number(const char *s)
+{
+ int major, minor, rev;
+ char *endptr;
+ const char *cp = s;
+
+ if (!s)
+ return 0;
+ major = strtol(cp, &endptr, 10);
+ if (cp == endptr || *endptr != '.')
+ return 0;
+ cp = endptr + 1;
+ minor = strtol(cp, &endptr, 10);
+ if (cp == endptr || *endptr != '.')
+ return 0;
+ cp = endptr + 1;
+ rev = strtol(cp, &endptr, 10);
+ if (cp == endptr)
+ return 0;
+ return KERNEL_VERSION(major, minor, rev);
+}
+
+static int is_before_linux_ver(unsigned int major, unsigned int minor,
+ unsigned int rev)
+{
+ struct utsname ut;
+ static int linux_version_code = -1;
+
+ if (uname(&ut)) {
+ perror("uname");
+ exit(1);
+ }
+ if (linux_version_code < 0)
+ linux_version_code = parse_version_number(ut.release);
+ if (linux_version_code == 0)
+ return 0;
+
+ return linux_version_code < (int) KERNEL_VERSION(major, minor, rev);
+}
+#else
+static int is_before_linux_ver(unsigned int major, unsigned int minor,
+ unsigned int rev)
+{
+ return 0;
+}
+#endif
+
+/*
+ * Helper function for read_bb_file and test_disk
+ */
+static void invalid_block(ext2_filsys fs EXT2FS_ATTR((unused)), blk_t blk)
+{
+ fprintf(stderr, _("Bad block %u out of range; ignored.\n"), blk);
+ return;
+}
+
+/*
+ * Reads the bad blocks list from a file
+ */
+static void read_bb_file(ext2_filsys fs, badblocks_list *bb_list,
+ const char *bad_blocks_file)
+{
+ FILE *f;
+ errcode_t retval;
+
+ f = fopen(bad_blocks_file, "r");
+ if (!f) {
+ com_err("read_bad_blocks_file", errno,
+ _("while trying to open %s"), bad_blocks_file);
+ exit(1);
+ }
+ retval = ext2fs_read_bb_FILE(fs, f, bb_list, invalid_block);
+ fclose (f);
+ if (retval) {
+ com_err("ext2fs_read_bb_FILE", retval, "%s",
+ _("while reading in list of bad blocks from file"));
+ exit(1);
+ }
+}
+
+/*
+ * Runs the badblocks program to test the disk
+ */
+static void test_disk(ext2_filsys fs, badblocks_list *bb_list)
+{
+ FILE *f;
+ errcode_t retval;
+ char buf[1024];
+
+ sprintf(buf, "badblocks -b %d -X %s%s%s %llu", fs->blocksize,
+ quiet ? "" : "-s ", (cflag > 1) ? "-w " : "",
+ fs->device_name,
+ (unsigned long long) ext2fs_blocks_count(fs->super)-1);
+ if (verbose)
+ printf(_("Running command: %s\n"), buf);
+ f = popen(buf, "r");
+ if (!f) {
+ com_err("popen", errno,
+ _("while trying to run '%s'"), buf);
+ exit(1);
+ }
+ retval = ext2fs_read_bb_FILE(fs, f, bb_list, invalid_block);
+ pclose(f);
+ if (retval) {
+ com_err("ext2fs_read_bb_FILE", retval, "%s",
+ _("while processing list of bad blocks from program"));
+ exit(1);
+ }
+}
+
+static void handle_bad_blocks(ext2_filsys fs, badblocks_list bb_list)
+{
+ dgrp_t i;
+ blk_t j;
+ unsigned must_be_good;
+ blk_t blk;
+ badblocks_iterate bb_iter;
+ errcode_t retval;
+ blk_t group_block;
+ int group;
+ int group_bad;
+
+ if (!bb_list)
+ return;
+
+ /*
+ * The primary superblock and group descriptors *must* be
+ * good; if not, abort.
+ */
+ must_be_good = fs->super->s_first_data_block + 1 + fs->desc_blocks;
+ for (i = fs->super->s_first_data_block; i <= must_be_good; i++) {
+ if (ext2fs_badblocks_list_test(bb_list, i)) {
+ fprintf(stderr, _("Block %d in primary "
+ "superblock/group descriptor area bad.\n"), i);
+ fprintf(stderr, _("Blocks %u through %u must be good "
+ "in order to build a filesystem.\n"),
+ fs->super->s_first_data_block, must_be_good);
+ fputs(_("Aborting....\n"), stderr);
+ exit(1);
+ }
+ }
+
+ /*
+ * See if any of the bad blocks are showing up in the backup
+ * superblocks and/or group descriptors. If so, issue a
+ * warning and adjust the block counts appropriately.
+ */
+ group_block = fs->super->s_first_data_block +
+ fs->super->s_blocks_per_group;
+
+ for (i = 1; i < fs->group_desc_count; i++) {
+ group_bad = 0;
+ for (j=0; j < fs->desc_blocks+1; j++) {
+ if (ext2fs_badblocks_list_test(bb_list,
+ group_block + j)) {
+ if (!group_bad)
+ fprintf(stderr,
+_("Warning: the backup superblock/group descriptors at block %u contain\n"
+" bad blocks.\n\n"),
+ group_block);
+ group_bad++;
+ group = ext2fs_group_of_blk2(fs, group_block+j);
+ ext2fs_bg_free_blocks_count_set(fs, group, ext2fs_bg_free_blocks_count(fs, group) + 1);
+ ext2fs_group_desc_csum_set(fs, group);
+ ext2fs_free_blocks_count_add(fs->super, 1);
+ }
+ }
+ group_block += fs->super->s_blocks_per_group;
+ }
+
+ /*
+ * Mark all the bad blocks as used...
+ */
+ retval = ext2fs_badblocks_list_iterate_begin(bb_list, &bb_iter);
+ if (retval) {
+ com_err("ext2fs_badblocks_list_iterate_begin", retval, "%s",
+ _("while marking bad blocks as used"));
+ exit(1);
+ }
+ while (ext2fs_badblocks_list_iterate(bb_iter, &blk))
+ ext2fs_mark_block_bitmap2(fs->block_map, blk);
+ ext2fs_badblocks_list_iterate_end(bb_iter);
+}
+
+static void write_reserved_inodes(ext2_filsys fs)
+{
+ errcode_t retval;
+ ext2_ino_t ino;
+ struct ext2_inode *inode;
+
+ retval = ext2fs_get_memzero(EXT2_INODE_SIZE(fs->super), &inode);
+ if (retval) {
+ com_err("inode_init", retval, _("while allocating memory"));
+ exit(1);
+ }
+
+ for (ino = 1; ino < EXT2_FIRST_INO(fs->super); ino++) {
+ retval = ext2fs_write_inode_full(fs, ino, inode,
+ EXT2_INODE_SIZE(fs->super));
+ if (retval) {
+ com_err("ext2fs_write_inode_full", retval,
+ _("while writing reserved inodes"));
+ exit(1);
+ }
+ }
+
+ ext2fs_free_mem(&inode);
+}
+
+static errcode_t packed_allocate_tables(ext2_filsys fs)
+{
+ errcode_t retval;
+ dgrp_t i;
+ blk64_t goal = 0;
+
+ for (i = 0; i < fs->group_desc_count; i++) {
+ retval = ext2fs_new_block2(fs, goal, NULL, &goal);
+ if (retval)
+ return retval;
+ ext2fs_block_alloc_stats2(fs, goal, +1);
+ ext2fs_block_bitmap_loc_set(fs, i, goal);
+ }
+ for (i = 0; i < fs->group_desc_count; i++) {
+ retval = ext2fs_new_block2(fs, goal, NULL, &goal);
+ if (retval)
+ return retval;
+ ext2fs_block_alloc_stats2(fs, goal, +1);
+ ext2fs_inode_bitmap_loc_set(fs, i, goal);
+ }
+ for (i = 0; i < fs->group_desc_count; i++) {
+ blk64_t end = ext2fs_blocks_count(fs->super) - 1;
+ retval = ext2fs_get_free_blocks2(fs, goal, end,
+ fs->inode_blocks_per_group,
+ fs->block_map, &goal);
+ if (retval)
+ return retval;
+ ext2fs_block_alloc_stats_range(fs, goal,
+ fs->inode_blocks_per_group, +1);
+ ext2fs_inode_table_loc_set(fs, i, goal);
+ ext2fs_group_desc_csum_set(fs, i);
+ }
+ return 0;
+}
+
+static void write_inode_tables(ext2_filsys fs, int lazy_flag, int itable_zeroed)
+{
+ errcode_t retval;
+ blk64_t blk;
+ dgrp_t i;
+ int num;
+ struct ext2fs_numeric_progress_struct progress;
+
+ ext2fs_numeric_progress_init(fs, &progress,
+ _("Writing inode tables: "),
+ fs->group_desc_count);
+
+ for (i = 0; i < fs->group_desc_count; i++) {
+ ext2fs_numeric_progress_update(fs, &progress, i);
+
+ blk = ext2fs_inode_table_loc(fs, i);
+ num = fs->inode_blocks_per_group;
+
+ if (lazy_flag)
+ num = ext2fs_div_ceil((fs->super->s_inodes_per_group -
+ ext2fs_bg_itable_unused(fs, i)) *
+ EXT2_INODE_SIZE(fs->super),
+ EXT2_BLOCK_SIZE(fs->super));
+ if (!lazy_flag || itable_zeroed) {
+ /* The kernel doesn't need to zero the itable blocks */
+ ext2fs_bg_flags_set(fs, i, EXT2_BG_INODE_ZEROED);
+ ext2fs_group_desc_csum_set(fs, i);
+ }
+ if (!itable_zeroed) {
+ retval = ext2fs_zero_blocks2(fs, blk, num, &blk, &num);
+ if (retval) {
+ fprintf(stderr, _("\nCould not write %d "
+ "blocks in inode table starting at %llu: %s\n"),
+ num, (unsigned long long) blk,
+ error_message(retval));
+ exit(1);
+ }
+ }
+ if (sync_kludge) {
+ if (sync_kludge == 1)
+ io_channel_flush(fs->io);
+ else if ((i % sync_kludge) == 0)
+ io_channel_flush(fs->io);
+ }
+ }
+ ext2fs_numeric_progress_close(fs, &progress,
+ _("done \n"));
+
+ /* Reserved inodes must always have correct checksums */
+ if (ext2fs_has_feature_metadata_csum(fs->super))
+ write_reserved_inodes(fs);
+}
+
+static void create_root_dir(ext2_filsys fs)
+{
+ errcode_t retval;
+ struct ext2_inode inode;
+
+ retval = ext2fs_mkdir(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, 0);
+ if (retval) {
+ com_err("ext2fs_mkdir", retval, "%s",
+ _("while creating root dir"));
+ exit(1);
+ }
+ if (root_uid != 0 || root_gid != 0) {
+ retval = ext2fs_read_inode(fs, EXT2_ROOT_INO, &inode);
+ if (retval) {
+ com_err("ext2fs_read_inode", retval, "%s",
+ _("while reading root inode"));
+ exit(1);
+ }
+
+ inode.i_uid = root_uid;
+ ext2fs_set_i_uid_high(inode, root_uid >> 16);
+ inode.i_gid = root_gid;
+ ext2fs_set_i_gid_high(inode, root_gid >> 16);
+
+ retval = ext2fs_write_new_inode(fs, EXT2_ROOT_INO, &inode);
+ if (retval) {
+ com_err("ext2fs_write_inode", retval, "%s",
+ _("while setting root inode ownership"));
+ exit(1);
+ }
+ }
+}
+
+static void create_lost_and_found(ext2_filsys fs)
+{
+ unsigned int lpf_size = 0;
+ errcode_t retval;
+ ext2_ino_t ino;
+ const char *name = "lost+found";
+ int i;
+
+ fs->umask = 077;
+ retval = ext2fs_mkdir(fs, EXT2_ROOT_INO, 0, name);
+ if (retval) {
+ com_err("ext2fs_mkdir", retval, "%s",
+ _("while creating /lost+found"));
+ exit(1);
+ }
+
+ retval = ext2fs_lookup(fs, EXT2_ROOT_INO, name, strlen(name), 0, &ino);
+ if (retval) {
+ com_err("ext2_lookup", retval, "%s",
+ _("while looking up /lost+found"));
+ exit(1);
+ }
+
+ for (i=1; i < EXT2_NDIR_BLOCKS; i++) {
+ /* Ensure that lost+found is at least 2 blocks, so we always
+ * test large empty blocks for big-block filesystems. */
+ if ((lpf_size += fs->blocksize) >= 16*1024 &&
+ lpf_size >= 2 * fs->blocksize)
+ break;
+ retval = ext2fs_expand_dir(fs, ino);
+ if (retval) {
+ com_err("ext2fs_expand_dir", retval, "%s",
+ _("while expanding /lost+found"));
+ exit(1);
+ }
+ }
+}
+
+static void create_bad_block_inode(ext2_filsys fs, badblocks_list bb_list)
+{
+ errcode_t retval;
+
+ ext2fs_mark_inode_bitmap2(fs->inode_map, EXT2_BAD_INO);
+ ext2fs_inode_alloc_stats2(fs, EXT2_BAD_INO, +1, 0);
+ retval = ext2fs_update_bb_inode(fs, bb_list);
+ if (retval) {
+ com_err("ext2fs_update_bb_inode", retval, "%s",
+ _("while setting bad block inode"));
+ exit(1);
+ }
+
+}
+
+static void reserve_inodes(ext2_filsys fs)
+{
+ ext2_ino_t i;
+
+ for (i = EXT2_ROOT_INO + 1; i < EXT2_FIRST_INODE(fs->super); i++)
+ ext2fs_inode_alloc_stats2(fs, i, +1, 0);
+ ext2fs_mark_ib_dirty(fs);
+}
+
+#define BSD_DISKMAGIC (0x82564557UL) /* The disk magic number */
+#define BSD_MAGICDISK (0x57455682UL) /* The disk magic number reversed */
+#define BSD_LABEL_OFFSET 64
+
+static void zap_sector(ext2_filsys fs, int sect, int nsect)
+{
+ char *buf;
+ int retval;
+ unsigned int *magic;
+
+ buf = calloc(512, nsect);
+ if (!buf) {
+ printf(_("Out of memory erasing sectors %d-%d\n"),
+ sect, sect + nsect - 1);
+ exit(1);
+ }
+
+ if (sect == 0) {
+ /* Check for a BSD disklabel, and don't erase it if so */
+ retval = io_channel_read_blk64(fs->io, 0, -512, buf);
+ if (retval)
+ fprintf(stderr,
+ _("Warning: could not read block 0: %s\n"),
+ error_message(retval));
+ else {
+ magic = (unsigned int *) (buf + BSD_LABEL_OFFSET);
+ if ((*magic == BSD_DISKMAGIC) ||
+ (*magic == BSD_MAGICDISK)) {
+ free(buf);
+ return;
+ }
+ }
+ }
+
+ memset(buf, 0, 512*nsect);
+ io_channel_set_blksize(fs->io, 512);
+ retval = io_channel_write_blk64(fs->io, sect, -512*nsect, buf);
+ io_channel_set_blksize(fs->io, fs->blocksize);
+ free(buf);
+ if (retval)
+ fprintf(stderr, _("Warning: could not erase sector %d: %s\n"),
+ sect, error_message(retval));
+}
+
+static void create_journal_dev(ext2_filsys fs)
+{
+ struct ext2fs_numeric_progress_struct progress;
+ errcode_t retval;
+ char *buf;
+ blk64_t blk, err_blk;
+ int c, count, err_count;
+ struct ext2fs_journal_params jparams;
+
+ retval = ext2fs_get_journal_params(&jparams, fs);
+ if (retval) {
+ com_err("create_journal_dev", retval, "%s",
+ _("while splitting the journal size"));
+ exit(1);
+ }
+
+ retval = ext2fs_create_journal_superblock2(fs, &jparams, 0, &buf);
+ if (retval) {
+ com_err("create_journal_dev", retval, "%s",
+ _("while initializing journal superblock"));
+ exit(1);
+ }
+
+ if (journal_flags & EXT2_MKJOURNAL_LAZYINIT)
+ goto write_superblock;
+
+ ext2fs_numeric_progress_init(fs, &progress,
+ _("Zeroing journal device: "),
+ ext2fs_blocks_count(fs->super));
+ blk = 0;
+ count = ext2fs_blocks_count(fs->super);
+ while (count > 0) {
+ if (count > 1024)
+ c = 1024;
+ else
+ c = count;
+ retval = ext2fs_zero_blocks2(fs, blk, c, &err_blk, &err_count);
+ if (retval) {
+ com_err("create_journal_dev", retval,
+ _("while zeroing journal device "
+ "(block %llu, count %d)"),
+ (unsigned long long) err_blk, err_count);
+ exit(1);
+ }
+ blk += c;
+ count -= c;
+ ext2fs_numeric_progress_update(fs, &progress, blk);
+ }
+
+ ext2fs_numeric_progress_close(fs, &progress, NULL);
+write_superblock:
+ retval = io_channel_write_blk64(fs->io,
+ fs->super->s_first_data_block+1,
+ 1, buf);
+ (void) ext2fs_free_mem(&buf);
+ if (retval) {
+ com_err("create_journal_dev", retval, "%s",
+ _("while writing journal superblock"));
+ exit(1);
+ }
+}
+
+static void show_stats(ext2_filsys fs)
+{
+ struct ext2_super_block *s = fs->super;
+ char *os;
+ blk64_t group_block;
+ dgrp_t i;
+ int need, col_left;
+
+ if (!verbose) {
+ printf(_("Creating filesystem with %llu %dk blocks and "
+ "%u inodes\n"),
+ (unsigned long long) ext2fs_blocks_count(s),
+ fs->blocksize >> 10, s->s_inodes_count);
+ goto skip_details;
+ }
+
+ if (ext2fs_blocks_count(&fs_param) != ext2fs_blocks_count(s))
+ fprintf(stderr, _("warning: %llu blocks unused.\n\n"),
+ (unsigned long long) (ext2fs_blocks_count(&fs_param) -
+ ext2fs_blocks_count(s)));
+
+ printf(_("Filesystem label=%.*s\n"), EXT2_LEN_STR(s->s_volume_name));
+
+ os = e2p_os2string(fs->super->s_creator_os);
+ if (os)
+ printf(_("OS type: %s\n"), os);
+ free(os);
+ printf(_("Block size=%u (log=%u)\n"), fs->blocksize,
+ s->s_log_block_size);
+ if (ext2fs_has_feature_bigalloc(fs->super))
+ printf(_("Cluster size=%u (log=%u)\n"),
+ fs->blocksize << fs->cluster_ratio_bits,
+ s->s_log_cluster_size);
+ else
+ printf(_("Fragment size=%u (log=%u)\n"), EXT2_CLUSTER_SIZE(s),
+ s->s_log_cluster_size);
+ printf(_("Stride=%u blocks, Stripe width=%u blocks\n"),
+ s->s_raid_stride, s->s_raid_stripe_width);
+ printf(_("%u inodes, %llu blocks\n"), s->s_inodes_count,
+ (unsigned long long) ext2fs_blocks_count(s));
+ printf(_("%llu blocks (%2.2f%%) reserved for the super user\n"),
+ (unsigned long long) ext2fs_r_blocks_count(s),
+ 100.0 * ext2fs_r_blocks_count(s) / ext2fs_blocks_count(s));
+ printf(_("First data block=%u\n"), s->s_first_data_block);
+ if (root_uid != 0 || root_gid != 0)
+ printf(_("Root directory owner=%u:%u\n"), root_uid, root_gid);
+ if (s->s_reserved_gdt_blocks)
+ printf(_("Maximum filesystem blocks=%lu\n"),
+ (s->s_reserved_gdt_blocks + fs->desc_blocks) *
+ EXT2_DESC_PER_BLOCK(s) * s->s_blocks_per_group);
+ if (fs->group_desc_count > 1)
+ printf(_("%u block groups\n"), fs->group_desc_count);
+ else
+ printf(_("%u block group\n"), fs->group_desc_count);
+ if (ext2fs_has_feature_bigalloc(fs->super))
+ printf(_("%u blocks per group, %u clusters per group\n"),
+ s->s_blocks_per_group, s->s_clusters_per_group);
+ else
+ printf(_("%u blocks per group, %u fragments per group\n"),
+ s->s_blocks_per_group, s->s_clusters_per_group);
+ printf(_("%u inodes per group\n"), s->s_inodes_per_group);
+
+skip_details:
+ if (fs->group_desc_count == 1) {
+ printf("\n");
+ return;
+ }
+
+ if (!e2p_is_null_uuid(s->s_uuid))
+ printf(_("Filesystem UUID: %s\n"), e2p_uuid2str(s->s_uuid));
+ printf("%s", _("Superblock backups stored on blocks: "));
+ group_block = s->s_first_data_block;
+ col_left = 0;
+ for (i = 1; i < fs->group_desc_count; i++) {
+ group_block += s->s_blocks_per_group;
+ if (!ext2fs_bg_has_super(fs, i))
+ continue;
+ if (i != 1)
+ printf(", ");
+ need = int_log10(group_block) + 2;
+ if (need > col_left) {
+ printf("\n\t");
+ col_left = 72;
+ }
+ col_left -= need;
+ printf("%llu", (unsigned long long) group_block);
+ }
+ printf("\n\n");
+}
+
+/*
+ * Returns true if making a file system for the Hurd, else 0
+ */
+static int for_hurd(const char *os)
+{
+ if (!os) {
+#ifdef __GNU__
+ return 1;
+#else
+ return 0;
+#endif
+ }
+ if (isdigit(*os))
+ return (atoi(os) == EXT2_OS_HURD);
+ return (strcasecmp(os, "GNU") == 0 || strcasecmp(os, "hurd") == 0);
+}
+
+/*
+ * Set the S_CREATOR_OS field. Return true if OS is known,
+ * otherwise, 0.
+ */
+static int set_os(struct ext2_super_block *sb, char *os)
+{
+ if (isdigit (*os))
+ sb->s_creator_os = atoi (os);
+ else if (strcasecmp(os, "linux") == 0)
+ sb->s_creator_os = EXT2_OS_LINUX;
+ else if (strcasecmp(os, "GNU") == 0 || strcasecmp(os, "hurd") == 0)
+ sb->s_creator_os = EXT2_OS_HURD;
+ else if (strcasecmp(os, "freebsd") == 0)
+ sb->s_creator_os = EXT2_OS_FREEBSD;
+ else if (strcasecmp(os, "lites") == 0)
+ sb->s_creator_os = EXT2_OS_LITES;
+ else
+ return 0;
+ return 1;
+}
+
+#define PATH_SET "PATH=/sbin"
+
+static void parse_extended_opts(struct ext2_super_block *param,
+ const char *opts)
+{
+ char *buf, *token, *next, *p, *arg, *badopt = 0;
+ int len;
+ int r_usage = 0;
+ int ret;
+ int encoding = -1;
+ char *encoding_flags = NULL;
+
+ len = strlen(opts);
+ buf = malloc(len+1);
+ if (!buf) {
+ fprintf(stderr, "%s",
+ _("Couldn't allocate memory to parse options!\n"));
+ exit(1);
+ }
+ strcpy(buf, opts);
+ for (token = buf; token && *token; token = next) {
+ p = strchr(token, ',');
+ next = 0;
+ if (p) {
+ *p = 0;
+ next = p+1;
+ }
+ arg = strchr(token, '=');
+ if (arg) {
+ *arg = 0;
+ arg++;
+ }
+ if (strcmp(token, "desc-size") == 0 ||
+ strcmp(token, "desc_size") == 0) {
+ int desc_size;
+
+ if (!ext2fs_has_feature_64bit(&fs_param)) {
+ fprintf(stderr,
+ _("%s requires '-O 64bit'\n"), token);
+ r_usage++;
+ continue;
+ }
+ if (param->s_reserved_gdt_blocks != 0) {
+ fprintf(stderr,
+ _("'%s' must be before 'resize=%u'\n"),
+ token, param->s_reserved_gdt_blocks);
+ r_usage++;
+ continue;
+ }
+ if (!arg) {
+ r_usage++;
+ badopt = token;
+ continue;
+ }
+ desc_size = strtoul(arg, &p, 0);
+ if (*p || (desc_size & (desc_size - 1))) {
+ fprintf(stderr,
+ _("Invalid desc_size: '%s'\n"), arg);
+ r_usage++;
+ continue;
+ }
+ param->s_desc_size = desc_size;
+ } else if (strcmp(token, "hash_seed") == 0) {
+ if (!arg) {
+ r_usage++;
+ badopt = token;
+ continue;
+ }
+ if (uuid_parse(arg,
+ (unsigned char *)param->s_hash_seed) != 0) {
+ fprintf(stderr,
+ _("Invalid hash seed: %s\n"), arg);
+ r_usage++;
+ continue;
+ }
+ } else if (strcmp(token, "offset") == 0) {
+ if (!arg) {
+ r_usage++;
+ badopt = token;
+ continue;
+ }
+ offset = strtoull(arg, &p, 0);
+ if (*p) {
+ fprintf(stderr, _("Invalid offset: %s\n"),
+ arg);
+ r_usage++;
+ continue;
+ }
+ } else if (strcmp(token, "mmp_update_interval") == 0) {
+ if (!arg) {
+ r_usage++;
+ badopt = token;
+ continue;
+ }
+ param->s_mmp_update_interval = strtoul(arg, &p, 0);
+ if (*p) {
+ fprintf(stderr,
+ _("Invalid mmp_update_interval: %s\n"),
+ arg);
+ r_usage++;
+ continue;
+ }
+ } else if (strcmp(token, "no_copy_xattrs") == 0) {
+ no_copy_xattrs = 1;
+ continue;
+ } else if (strcmp(token, "num_backup_sb") == 0) {
+ if (!arg) {
+ r_usage++;
+ badopt = token;
+ continue;
+ }
+ num_backups = strtoul(arg, &p, 0);
+ if (*p || num_backups > 2) {
+ fprintf(stderr,
+ _("Invalid # of backup "
+ "superblocks: %s\n"),
+ arg);
+ r_usage++;
+ continue;
+ }
+ } else if (strcmp(token, "packed_meta_blocks") == 0) {
+ if (arg)
+ packed_meta_blocks = strtoul(arg, &p, 0);
+ else
+ packed_meta_blocks = 1;
+ if (packed_meta_blocks)
+ journal_location = 0;
+ } else if (strcmp(token, "stride") == 0) {
+ if (!arg) {
+ r_usage++;
+ badopt = token;
+ continue;
+ }
+ param->s_raid_stride = strtoul(arg, &p, 0);
+ if (*p) {
+ fprintf(stderr,
+ _("Invalid stride parameter: %s\n"),
+ arg);
+ r_usage++;
+ continue;
+ }
+ } else if (strcmp(token, "stripe-width") == 0 ||
+ strcmp(token, "stripe_width") == 0) {
+ if (!arg) {
+ r_usage++;
+ badopt = token;
+ continue;
+ }
+ param->s_raid_stripe_width = strtoul(arg, &p, 0);
+ if (*p) {
+ fprintf(stderr,
+ _("Invalid stripe-width parameter: %s\n"),
+ arg);
+ r_usage++;
+ continue;
+ }
+ } else if (!strcmp(token, "resize")) {
+ blk64_t resize;
+ unsigned long bpg, rsv_groups;
+ unsigned long group_desc_count, desc_blocks;
+ unsigned int gdpb, blocksize;
+ int rsv_gdb;
+
+ if (!arg) {
+ r_usage++;
+ badopt = token;
+ continue;
+ }
+
+ resize = parse_num_blocks2(arg,
+ param->s_log_block_size);
+
+ if (resize == 0) {
+ fprintf(stderr,
+ _("Invalid resize parameter: %s\n"),
+ arg);
+ r_usage++;
+ continue;
+ }
+ if (resize <= ext2fs_blocks_count(param)) {
+ fprintf(stderr, "%s",
+ _("The resize maximum must be greater "
+ "than the filesystem size.\n"));
+ r_usage++;
+ continue;
+ }
+
+ blocksize = EXT2_BLOCK_SIZE(param);
+ bpg = param->s_blocks_per_group;
+ if (!bpg)
+ bpg = blocksize * 8;
+ gdpb = EXT2_DESC_PER_BLOCK(param);
+ group_desc_count = (__u32) ext2fs_div64_ceil(
+ ext2fs_blocks_count(param), bpg);
+ desc_blocks = (group_desc_count +
+ gdpb - 1) / gdpb;
+ rsv_groups = ext2fs_div64_ceil(resize, bpg);
+ rsv_gdb = ext2fs_div_ceil(rsv_groups, gdpb) -
+ desc_blocks;
+ if (rsv_gdb > (int) EXT2_ADDR_PER_BLOCK(param))
+ rsv_gdb = EXT2_ADDR_PER_BLOCK(param);
+
+ if (rsv_gdb > 0) {
+ if (param->s_rev_level == EXT2_GOOD_OLD_REV) {
+ fprintf(stderr, "%s",
+ _("On-line resizing not supported with revision 0 filesystems\n"));
+ free(buf);
+ exit(1);
+ }
+ ext2fs_set_feature_resize_inode(param);
+
+ param->s_reserved_gdt_blocks = rsv_gdb;
+ }
+ } else if (!strcmp(token, "test_fs")) {
+ param->s_flags |= EXT2_FLAGS_TEST_FILESYS;
+ } else if (!strcmp(token, "lazy_itable_init")) {
+ if (arg)
+ lazy_itable_init = strtoul(arg, &p, 0);
+ else
+ lazy_itable_init = 1;
+ } else if (!strcmp(token, "assume_storage_prezeroed")) {
+ if (arg)
+ assume_storage_prezeroed = strtoul(arg, &p, 0);
+ else
+ assume_storage_prezeroed = 1;
+ } else if (!strcmp(token, "lazy_journal_init")) {
+ if (arg)
+ journal_flags |= strtoul(arg, &p, 0) ?
+ EXT2_MKJOURNAL_LAZYINIT : 0;
+ else
+ journal_flags |= EXT2_MKJOURNAL_LAZYINIT;
+ } else if (!strcmp(token, "root_owner")) {
+ if (arg) {
+ root_uid = strtoul(arg, &p, 0);
+ if (*p != ':') {
+ fprintf(stderr,
+ _("Invalid root_owner: '%s'\n"),
+ arg);
+ r_usage++;
+ continue;
+ }
+ p++;
+ root_gid = strtoul(p, &p, 0);
+ if (*p) {
+ fprintf(stderr,
+ _("Invalid root_owner: '%s'\n"),
+ arg);
+ r_usage++;
+ continue;
+ }
+ } else {
+ root_uid = getuid();
+ root_gid = getgid();
+ }
+ } else if (!strcmp(token, "discard")) {
+ discard = 1;
+ } else if (!strcmp(token, "nodiscard")) {
+ discard = 0;
+ } else if (!strcmp(token, "quotatype")) {
+ char *errtok = NULL;
+
+ if (!arg) {
+ r_usage++;
+ badopt = token;
+ continue;
+ }
+ quotatype_bits = 0;
+ ret = parse_quota_types(arg, &quotatype_bits, &errtok);
+ if (ret) {
+ if (errtok) {
+ fprintf(stderr,
+ "Failed to parse quota type at %s", errtok);
+ free(errtok);
+ } else
+ com_err(program_name, ret,
+ "while parsing quota type");
+ r_usage++;
+ badopt = token;
+ continue;
+ }
+ } else if (!strcmp(token, "android_sparse")) {
+ android_sparse_file = 1;
+ } else if (!strcmp(token, "encoding")) {
+ if (!arg) {
+ r_usage++;
+ continue;
+ }
+
+ encoding = e2p_str2encoding(arg);
+ if (encoding < 0) {
+ fprintf(stderr, _("Invalid encoding: %s"), arg);
+ r_usage++;
+ continue;
+ }
+ param->s_encoding = encoding;
+ ext2fs_set_feature_casefold(param);
+ } else if (!strcmp(token, "encoding_flags")) {
+ if (!arg) {
+ r_usage++;
+ continue;
+ }
+ encoding_flags = arg;
+ } else if (!strcmp(token, "orphan_file_size")) {
+ if (!arg) {
+ r_usage++;
+ badopt = token;
+ continue;
+ }
+ orphan_file_blocks = parse_num_blocks2(arg,
+ fs_param.s_log_block_size);
+ if (orphan_file_blocks == 0) {
+ fprintf(stderr,
+ _("Invalid size of orphan file %s\n"),
+ arg);
+ r_usage++;
+ continue;
+ }
+ } else {
+ r_usage++;
+ badopt = token;
+ }
+ }
+ if (r_usage) {
+ fprintf(stderr, _("\nBad option(s) specified: %s\n\n"
+ "Extended options are separated by commas, "
+ "and may take an argument which\n"
+ "\tis set off by an equals ('=') sign.\n\n"
+ "Valid extended options are:\n"
+ "\tmmp_update_interval=<interval>\n"
+ "\tnum_backup_sb=<0|1|2>\n"
+ "\tstride=<RAID per-disk data chunk in blocks>\n"
+ "\tstripe-width=<RAID stride * data disks in blocks>\n"
+ "\toffset=<offset to create the file system>\n"
+ "\tresize=<resize maximum size in blocks>\n"
+ "\tpacked_meta_blocks=<0 to disable, 1 to enable>\n"
+ "\tlazy_itable_init=<0 to disable, 1 to enable>\n"
+ "\tlazy_journal_init=<0 to disable, 1 to enable>\n"
+ "\troot_owner=<uid of root dir>:<gid of root dir>\n"
+ "\ttest_fs\n"
+ "\tdiscard\n"
+ "\tnodiscard\n"
+ "\tencoding=<encoding>\n"
+ "\tencoding_flags=<flags>\n"
+ "\tquotatype=<quota type(s) to be enabled>\n"
+ "\tassume_storage_prezeroed=<0 to disable, 1 to enable>\n\n"),
+ badopt ? badopt : "");
+ free(buf);
+ exit(1);
+ }
+ if (param->s_raid_stride &&
+ (param->s_raid_stripe_width % param->s_raid_stride) != 0)
+ fprintf(stderr, _("\nWarning: RAID stripe-width %u not an even "
+ "multiple of stride %u.\n\n"),
+ param->s_raid_stripe_width, param->s_raid_stride);
+
+ if (ext2fs_has_feature_casefold(param)) {
+ param->s_encoding_flags =
+ e2p_get_encoding_flags(param->s_encoding);
+
+ if (encoding_flags &&
+ e2p_str2encoding_flags(param->s_encoding, encoding_flags,
+ &param->s_encoding_flags)) {
+ fprintf(stderr, _("error: Invalid encoding flag: %s\n"),
+ encoding_flags);
+ free(buf);
+ exit(1);
+ }
+ } else if (encoding_flags) {
+ fprintf(stderr, _("error: An encoding must be explicitly "
+ "specified when passing encoding-flags\n"));
+ free(buf);
+ exit(1);
+ }
+
+ free(buf);
+}
+
+static __u32 ok_features[3] = {
+ /* Compat */
+ EXT3_FEATURE_COMPAT_HAS_JOURNAL |
+ EXT2_FEATURE_COMPAT_RESIZE_INODE |
+ EXT2_FEATURE_COMPAT_DIR_INDEX |
+ EXT2_FEATURE_COMPAT_EXT_ATTR |
+ EXT4_FEATURE_COMPAT_SPARSE_SUPER2 |
+ EXT4_FEATURE_COMPAT_FAST_COMMIT |
+ EXT4_FEATURE_COMPAT_STABLE_INODES |
+ EXT4_FEATURE_COMPAT_ORPHAN_FILE,
+ /* Incompat */
+ EXT2_FEATURE_INCOMPAT_FILETYPE|
+ EXT3_FEATURE_INCOMPAT_EXTENTS|
+ EXT3_FEATURE_INCOMPAT_JOURNAL_DEV|
+ EXT2_FEATURE_INCOMPAT_META_BG|
+ EXT4_FEATURE_INCOMPAT_FLEX_BG|
+ EXT4_FEATURE_INCOMPAT_EA_INODE|
+ EXT4_FEATURE_INCOMPAT_MMP |
+ EXT4_FEATURE_INCOMPAT_64BIT|
+ EXT4_FEATURE_INCOMPAT_INLINE_DATA|
+ EXT4_FEATURE_INCOMPAT_ENCRYPT |
+ EXT4_FEATURE_INCOMPAT_CASEFOLD |
+ EXT4_FEATURE_INCOMPAT_CSUM_SEED |
+ EXT4_FEATURE_INCOMPAT_LARGEDIR,
+ /* R/O compat */
+ EXT2_FEATURE_RO_COMPAT_LARGE_FILE|
+ EXT4_FEATURE_RO_COMPAT_HUGE_FILE|
+ EXT4_FEATURE_RO_COMPAT_DIR_NLINK|
+ EXT4_FEATURE_RO_COMPAT_EXTRA_ISIZE|
+ EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER|
+ EXT4_FEATURE_RO_COMPAT_GDT_CSUM|
+ EXT4_FEATURE_RO_COMPAT_BIGALLOC|
+ EXT4_FEATURE_RO_COMPAT_QUOTA|
+ EXT4_FEATURE_RO_COMPAT_METADATA_CSUM|
+ EXT4_FEATURE_RO_COMPAT_PROJECT|
+ EXT4_FEATURE_RO_COMPAT_VERITY
+};
+
+
+static void syntax_err_report(const char *filename, long err, int line_num)
+{
+ fprintf(stderr,
+ _("Syntax error in mke2fs config file (%s, line #%d)\n\t%s\n"),
+ filename, line_num, error_message(err));
+ exit(1);
+}
+
+static const char *config_fn[] = { ROOT_SYSCONFDIR "/mke2fs.conf", 0 };
+
+static void edit_feature(const char *str, __u32 *compat_array)
+{
+ if (!str)
+ return;
+
+ if (e2p_edit_feature(str, compat_array, ok_features)) {
+ fprintf(stderr, _("Invalid filesystem option set: %s\n"),
+ str);
+ exit(1);
+ }
+}
+
+static void edit_mntopts(const char *str, __u32 *mntopts)
+{
+ if (!str)
+ return;
+
+ if (e2p_edit_mntopts(str, mntopts, ~0)) {
+ fprintf(stderr, _("Invalid mount option set: %s\n"),
+ str);
+ exit(1);
+ }
+}
+
+struct str_list {
+ char **list;
+ int num;
+ int max;
+};
+
+static errcode_t init_list(struct str_list *sl)
+{
+ sl->num = 0;
+ sl->max = 1;
+ sl->list = malloc((sl->max+1) * sizeof(char *));
+ if (!sl->list)
+ return ENOMEM;
+ sl->list[0] = 0;
+ return 0;
+}
+
+static errcode_t push_string(struct str_list *sl, const char *str)
+{
+ char **new_list;
+
+ if (sl->num >= sl->max) {
+ sl->max += 2;
+ new_list = realloc(sl->list, (sl->max+1) * sizeof(char *));
+ if (!new_list)
+ return ENOMEM;
+ sl->list = new_list;
+ }
+ sl->list[sl->num] = malloc(strlen(str)+1);
+ if (sl->list[sl->num] == 0)
+ return ENOMEM;
+ strcpy(sl->list[sl->num], str);
+ sl->num++;
+ sl->list[sl->num] = 0;
+ return 0;
+}
+
+static void print_str_list(char **list)
+{
+ char **cpp;
+
+ for (cpp = list; *cpp; cpp++) {
+ printf("'%s'", *cpp);
+ if (cpp[1])
+ fputs(", ", stdout);
+ }
+ fputc('\n', stdout);
+}
+
+/*
+ * Return TRUE if the profile has the given subsection
+ */
+static int profile_has_subsection(profile_t prof, const char *section,
+ const char *subsection)
+{
+ void *state;
+ const char *names[4];
+ char *name;
+ int ret = 0;
+
+ names[0] = section;
+ names[1] = subsection;
+ names[2] = 0;
+
+ if (profile_iterator_create(prof, names,
+ PROFILE_ITER_LIST_SECTION |
+ PROFILE_ITER_RELATIONS_ONLY, &state))
+ return 0;
+
+ if ((profile_iterator(&state, &name, 0) == 0) && name) {
+ free(name);
+ ret = 1;
+ }
+
+ profile_iterator_free(&state);
+ return ret;
+}
+
+static char **parse_fs_type(const char *fs_type,
+ const char *usage_types,
+ struct ext2_super_block *sb,
+ blk64_t fs_blocks_count,
+ char *progname)
+{
+ const char *ext_type = 0;
+ char *parse_str;
+ char *profile_type = 0;
+ char *cp, *t;
+ const char *size_type;
+ struct str_list list;
+ unsigned long long meg;
+ int is_hurd = for_hurd(creator_os);
+
+ if (init_list(&list))
+ return 0;
+
+ if (fs_type)
+ ext_type = fs_type;
+ else if (is_hurd)
+ ext_type = "ext2";
+ else if (!strcmp(program_name, "mke3fs"))
+ ext_type = "ext3";
+ else if (!strcmp(program_name, "mke4fs"))
+ ext_type = "ext4";
+ else if (progname) {
+ ext_type = strrchr(progname, '/');
+ if (ext_type)
+ ext_type++;
+ else
+ ext_type = progname;
+
+ if (!strncmp(ext_type, "mkfs.", 5)) {
+ ext_type += 5;
+ if (ext_type[0] == 0)
+ ext_type = 0;
+ } else
+ ext_type = 0;
+ }
+
+ if (!ext_type) {
+ profile_get_string(profile, "defaults", "fs_type", 0,
+ "ext2", &profile_type);
+ ext_type = profile_type;
+ if (!strcmp(ext_type, "ext2") && (journal_size != 0))
+ ext_type = "ext3";
+ }
+
+
+ if (!profile_has_subsection(profile, "fs_types", ext_type) &&
+ strcmp(ext_type, "ext2")) {
+ printf(_("\nYour mke2fs.conf file does not define the "
+ "%s filesystem type.\n"), ext_type);
+ if (!strcmp(ext_type, "ext3") || !strcmp(ext_type, "ext4") ||
+ !strcmp(ext_type, "ext4dev")) {
+ printf("%s", _("You probably need to install an "
+ "updated mke2fs.conf file.\n\n"));
+ }
+ if (!force) {
+ printf("%s", _("Aborting...\n"));
+ exit(1);
+ }
+ }
+
+ meg = (1024 * 1024) / EXT2_BLOCK_SIZE(sb);
+ if (fs_blocks_count < 3 * meg)
+ size_type = "floppy";
+ else if (fs_blocks_count < 512 * meg)
+ size_type = "small";
+ else if (fs_blocks_count < 4 * 1024 * 1024 * meg)
+ size_type = "default";
+ else if (fs_blocks_count < 16 * 1024 * 1024 * meg)
+ size_type = "big";
+ else
+ size_type = "huge";
+
+ if (!usage_types)
+ usage_types = size_type;
+
+ parse_str = malloc(strlen(usage_types)+1);
+ if (!parse_str) {
+ free(profile_type);
+ free(list.list);
+ return 0;
+ }
+ strcpy(parse_str, usage_types);
+
+ if (ext_type)
+ push_string(&list, ext_type);
+ cp = parse_str;
+ while (1) {
+ t = strchr(cp, ',');
+ if (t)
+ *t = '\0';
+
+ if (*cp) {
+ if (profile_has_subsection(profile, "fs_types", cp))
+ push_string(&list, cp);
+ else if (strcmp(cp, "default") != 0)
+ fprintf(stderr,
+ _("\nWarning: the fs_type %s is not "
+ "defined in mke2fs.conf\n\n"),
+ cp);
+ }
+ if (t)
+ cp = t+1;
+ else
+ break;
+ }
+ free(parse_str);
+ free(profile_type);
+ if (is_hurd)
+ push_string(&list, "hurd");
+ return (list.list);
+}
+
+char *get_string_from_profile(char **types, const char *opt,
+ const char *def_val)
+{
+ char *ret = 0;
+ int i;
+
+ for (i=0; types[i]; i++);
+ for (i-=1; i >=0 ; i--) {
+ profile_get_string(profile, "fs_types", types[i],
+ opt, 0, &ret);
+ if (ret)
+ return ret;
+ }
+ profile_get_string(profile, "defaults", opt, 0, def_val, &ret);
+ return (ret);
+}
+
+int get_int_from_profile(char **types, const char *opt, int def_val)
+{
+ int ret;
+ char **cpp;
+
+ profile_get_integer(profile, "defaults", opt, 0, def_val, &ret);
+ for (cpp = types; *cpp; cpp++)
+ profile_get_integer(profile, "fs_types", *cpp, opt, ret, &ret);
+ return ret;
+}
+
+static unsigned int get_uint_from_profile(char **types, const char *opt,
+ unsigned int def_val)
+{
+ unsigned int ret;
+ char **cpp;
+
+ profile_get_uint(profile, "defaults", opt, 0, def_val, &ret);
+ for (cpp = types; *cpp; cpp++)
+ profile_get_uint(profile, "fs_types", *cpp, opt, ret, &ret);
+ return ret;
+}
+
+static double get_double_from_profile(char **types, const char *opt,
+ double def_val)
+{
+ double ret;
+ char **cpp;
+
+ profile_get_double(profile, "defaults", opt, 0, def_val, &ret);
+ for (cpp = types; *cpp; cpp++)
+ profile_get_double(profile, "fs_types", *cpp, opt, ret, &ret);
+ return ret;
+}
+
+int get_bool_from_profile(char **types, const char *opt, int def_val)
+{
+ int ret;
+ char **cpp;
+
+ profile_get_boolean(profile, "defaults", opt, 0, def_val, &ret);
+ for (cpp = types; *cpp; cpp++)
+ profile_get_boolean(profile, "fs_types", *cpp, opt, ret, &ret);
+ return ret;
+}
+
+extern const char *mke2fs_default_profile;
+static const char *default_files[] = { "<default>", 0 };
+
+struct device_param {
+ unsigned long min_io; /* preferred minimum IO size */
+ unsigned long opt_io; /* optimal IO size */
+ unsigned long alignment_offset; /* alignment offset wrt physical block size */
+ unsigned int dax:1; /* supports dax? */
+};
+
+#ifdef HAVE_BLKID_PROBE_GET_TOPOLOGY
+/*
+ * Sets the geometry of a device (stripe/stride), and returns the
+ * device's alignment offset, if any, or a negative error.
+ */
+static int get_device_geometry(const char *file,
+ unsigned int blocksize,
+ unsigned int psector_size,
+ struct device_param *dev_param)
+{
+ int rc = -1;
+ blkid_probe pr;
+ blkid_topology tp;
+ struct stat statbuf;
+
+ memset(dev_param, 0, sizeof(*dev_param));
+
+ /* Nothing to do for a regular file */
+ if (!stat(file, &statbuf) && S_ISREG(statbuf.st_mode))
+ return 0;
+
+ pr = blkid_new_probe_from_filename(file);
+ if (!pr)
+ goto out;
+
+ tp = blkid_probe_get_topology(pr);
+ if (!tp)
+ goto out;
+
+ dev_param->min_io = blkid_topology_get_minimum_io_size(tp);
+ dev_param->opt_io = blkid_topology_get_optimal_io_size(tp);
+ if ((dev_param->min_io == 0) && (psector_size > blocksize))
+ dev_param->min_io = psector_size;
+ if ((dev_param->opt_io == 0) && dev_param->min_io > 0)
+ dev_param->opt_io = dev_param->min_io;
+ if ((dev_param->opt_io == 0) && (psector_size > blocksize))
+ dev_param->opt_io = psector_size;
+
+ dev_param->alignment_offset = blkid_topology_get_alignment_offset(tp);
+#ifdef HAVE_BLKID_TOPOLOGY_GET_DAX
+ dev_param->dax = blkid_topology_get_dax(tp);
+#endif
+ rc = 0;
+out:
+ blkid_free_probe(pr);
+ return rc;
+}
+#endif
+
+static void PRS(int argc, char *argv[])
+{
+ int b, c, flags;
+ int cluster_size = 0;
+ char *tmp, **cpp;
+ int explicit_fssize = 0;
+ int blocksize = 0;
+ int inode_ratio = 0;
+ int inode_size = 0;
+ unsigned long flex_bg_size = 0;
+ double reserved_ratio = -1.0;
+ int lsector_size = 0, psector_size = 0;
+ int show_version_only = 0, is_device = 0;
+ unsigned long long num_inodes = 0; /* unsigned long long to catch too-large input */
+ int default_orphan_file = 0;
+ int default_csum_seed = 0;
+ errcode_t retval;
+ char * oldpath = getenv("PATH");
+ char * extended_opts = 0;
+ char * fs_type = 0;
+ char * usage_types = 0;
+ /*
+ * NOTE: A few words about fs_blocks_count and blocksize:
+ *
+ * Initially, blocksize is set to zero, which implies 1024.
+ * If -b is specified, blocksize is updated to the user's value.
+ *
+ * Next, the device size or the user's "blocks" command line argument
+ * is used to set fs_blocks_count; the units are blocksize.
+ *
+ * Later, if blocksize hasn't been set and the profile specifies a
+ * blocksize, then blocksize is updated and fs_blocks_count is scaled
+ * appropriately. Note the change in units!
+ *
+ * Finally, we complain about fs_blocks_count > 2^32 on a non-64bit fs.
+ */
+ blk64_t fs_blocks_count = 0;
+ int s_opt = -1, r_opt = -1;
+ char *fs_features = 0;
+ int fs_features_size = 0;
+ int use_bsize;
+ char *newpath;
+ int pathlen = sizeof(PATH_SET) + 1;
+#ifdef HAVE_BLKID_PROBE_GET_TOPOLOGY
+ struct device_param dev_param;
+#endif
+
+ if (oldpath)
+ pathlen += strlen(oldpath);
+ newpath = malloc(pathlen);
+ if (!newpath) {
+ fprintf(stderr, "%s",
+ _("Couldn't allocate memory for new PATH.\n"));
+ exit(1);
+ }
+ strcpy(newpath, PATH_SET);
+
+ /* Update our PATH to include /sbin */
+ if (oldpath) {
+ strcat (newpath, ":");
+ strcat (newpath, oldpath);
+ }
+ putenv (newpath);
+
+ /* Determine the system page size if possible */
+#ifdef HAVE_SYSCONF
+#if (!defined(_SC_PAGESIZE) && defined(_SC_PAGE_SIZE))
+#define _SC_PAGESIZE _SC_PAGE_SIZE
+#endif
+#ifdef _SC_PAGESIZE
+ {
+ long sysval = sysconf(_SC_PAGESIZE);
+
+ if (sysval > 0)
+ sys_page_size = sysval;
+ }
+#endif /* _SC_PAGESIZE */
+#endif /* HAVE_SYSCONF */
+
+ if ((tmp = getenv("MKE2FS_CONFIG")) != NULL)
+ config_fn[0] = tmp;
+ profile_set_syntax_err_cb(syntax_err_report);
+ retval = profile_init(config_fn, &profile);
+ if (retval == ENOENT) {
+ retval = profile_init(default_files, &profile);
+ if (retval)
+ goto profile_error;
+ retval = profile_set_default(profile, mke2fs_default_profile);
+ if (retval)
+ goto profile_error;
+ } else if (retval) {
+profile_error:
+ fprintf(stderr, _("Couldn't init profile successfully"
+ " (error: %ld).\n"), retval);
+ exit(1);
+ }
+
+ setbuf(stdout, NULL);
+ setbuf(stderr, NULL);
+ add_error_table(&et_ext2_error_table);
+ add_error_table(&et_prof_error_table);
+ memset(&fs_param, 0, sizeof(struct ext2_super_block));
+ fs_param.s_rev_level = 1; /* Create revision 1 filesystems now */
+
+ if (is_before_linux_ver(2, 2, 0))
+ fs_param.s_rev_level = 0;
+
+ if (argc && *argv) {
+ program_name = get_progname(*argv);
+
+ /* If called as mkfs.ext3, create a journal inode */
+ if (!strcmp(program_name, "mkfs.ext3") ||
+ !strcmp(program_name, "mke3fs"))
+ journal_size = -1;
+ }
+
+ while ((c = getopt (argc, argv,
+ "b:cd:e:g:i:jl:m:no:qr:s:t:vC:DE:FG:I:J:KL:M:N:O:R:ST:U:Vz:")) != EOF) {
+ switch (c) {
+ case 'b':
+ blocksize = parse_num_blocks2(optarg, -1);
+ b = (blocksize > 0) ? blocksize : -blocksize;
+ if (b < EXT2_MIN_BLOCK_SIZE ||
+ b > EXT2_MAX_BLOCK_SIZE) {
+ com_err(program_name, 0,
+ _("invalid block size - %s"), optarg);
+ exit(1);
+ }
+ if (blocksize > 4096)
+ fprintf(stderr, _("Warning: blocksize %d not "
+ "usable on most systems.\n"),
+ blocksize);
+ if (blocksize > 0)
+ fs_param.s_log_block_size =
+ int_log2(blocksize >>
+ EXT2_MIN_BLOCK_LOG_SIZE);
+ break;
+ case 'c': /* Check for bad blocks */
+ cflag++;
+ break;
+ case 'C':
+ cluster_size = parse_num_blocks2(optarg, -1);
+ if (cluster_size <= EXT2_MIN_CLUSTER_SIZE ||
+ cluster_size > EXT2_MAX_CLUSTER_SIZE) {
+ com_err(program_name, 0,
+ _("invalid cluster size - %s"),
+ optarg);
+ exit(1);
+ }
+ break;
+ case 'd':
+ src_root_dir = optarg;
+ break;
+ case 'D':
+ direct_io = 1;
+ break;
+ case 'R':
+ com_err(program_name, 0, "%s",
+ _("'-R' is deprecated, use '-E' instead"));
+ /* fallthrough */
+ case 'E':
+ extended_opts = optarg;
+ break;
+ case 'e':
+ if (strcmp(optarg, "continue") == 0)
+ errors_behavior = EXT2_ERRORS_CONTINUE;
+ else if (strcmp(optarg, "remount-ro") == 0)
+ errors_behavior = EXT2_ERRORS_RO;
+ else if (strcmp(optarg, "panic") == 0)
+ errors_behavior = EXT2_ERRORS_PANIC;
+ else {
+ com_err(program_name, 0,
+ _("bad error behavior - %s"),
+ optarg);
+ usage();
+ }
+ break;
+ case 'F':
+ force++;
+ break;
+ case 'g':
+ fs_param.s_blocks_per_group = strtoul(optarg, &tmp, 0);
+ if (*tmp) {
+ com_err(program_name, 0, "%s",
+ _("Illegal number for blocks per group"));
+ exit(1);
+ }
+ if ((fs_param.s_blocks_per_group % 8) != 0) {
+ com_err(program_name, 0, "%s",
+ _("blocks per group must be multiple of 8"));
+ exit(1);
+ }
+ break;
+ case 'G':
+ flex_bg_size = strtoul(optarg, &tmp, 0);
+ if (*tmp) {
+ com_err(program_name, 0, "%s",
+ _("Illegal number for flex_bg size"));
+ exit(1);
+ }
+ if (flex_bg_size < 1 ||
+ (flex_bg_size & (flex_bg_size-1)) != 0) {
+ com_err(program_name, 0, "%s",
+ _("flex_bg size must be a power of 2"));
+ exit(1);
+ }
+ if (flex_bg_size > MAX_32_NUM) {
+ com_err(program_name, 0,
+ _("flex_bg size (%lu) must be less than"
+ " or equal to 2^31"), flex_bg_size);
+ exit(1);
+ }
+ break;
+ case 'i':
+ inode_ratio = parse_num_blocks(optarg, -1);
+ if (inode_ratio < EXT2_MIN_BLOCK_SIZE ||
+ inode_ratio > EXT2_MAX_BLOCK_SIZE * 1024) {
+ com_err(program_name, 0,
+ _("invalid inode ratio %s (min %d/max %d)"),
+ optarg, EXT2_MIN_BLOCK_SIZE,
+ EXT2_MAX_BLOCK_SIZE * 1024);
+ exit(1);
+ }
+ break;
+ case 'I':
+ inode_size = strtoul(optarg, &tmp, 0);
+ if (*tmp) {
+ com_err(program_name, 0,
+ _("invalid inode size - %s"), optarg);
+ exit(1);
+ }
+ break;
+ case 'j':
+ if (!journal_size)
+ journal_size = -1;
+ if (!journal_fc_size)
+ journal_fc_size = -1;
+ break;
+ case 'J':
+ parse_journal_opts(optarg);
+ break;
+ case 'K':
+ fprintf(stderr, "%s",
+ _("Warning: -K option is deprecated and "
+ "should not be used anymore. Use "
+ "\'-E nodiscard\' extended option "
+ "instead!\n"));
+ discard = 0;
+ break;
+ case 'l':
+ bad_blocks_filename = realloc(bad_blocks_filename,
+ strlen(optarg) + 1);
+ if (!bad_blocks_filename) {
+ com_err(program_name, ENOMEM, "%s",
+ _("in malloc for bad_blocks_filename"));
+ exit(1);
+ }
+ strcpy(bad_blocks_filename, optarg);
+ break;
+ case 'L':
+ volume_label = optarg;
+ if (strlen(volume_label) > EXT2_LABEL_LEN) {
+ volume_label[EXT2_LABEL_LEN] = '\0';
+ fprintf(stderr, _("Warning: label too long; will be truncated to '%s'\n\n"),
+ volume_label);
+ }
+ break;
+ case 'm':
+ reserved_ratio = strtod(optarg, &tmp);
+ if ( *tmp || reserved_ratio > 50 ||
+ reserved_ratio < 0) {
+ com_err(program_name, 0,
+ _("invalid reserved blocks percent - %s"),
+ optarg);
+ exit(1);
+ }
+ break;
+ case 'M':
+ mount_dir = optarg;
+ break;
+ case 'n':
+ noaction++;
+ break;
+ case 'N':
+ num_inodes = strtoul(optarg, &tmp, 0);
+ if (*tmp) {
+ com_err(program_name, 0,
+ _("bad num inodes - %s"), optarg);
+ exit(1);
+ }
+ break;
+ case 'o':
+ creator_os = optarg;
+ break;
+ case 'O':
+ retval = ext2fs_resize_mem(fs_features_size,
+ fs_features_size + 1 + strlen(optarg),
+ &fs_features);
+ if (retval) {
+ com_err(program_name, retval,
+ _("while allocating fs_feature string"));
+ exit(1);
+ }
+ if (fs_features_size)
+ strcat(fs_features, ",");
+ else
+ fs_features[0] = 0;
+ strcat(fs_features, optarg);
+ fs_features_size += 1 + strlen(optarg);
+ break;
+ case 'q':
+ quiet = 1;
+ break;
+ case 'r':
+ r_opt = strtoul(optarg, &tmp, 0);
+ if (*tmp) {
+ com_err(program_name, 0,
+ _("bad revision level - %s"), optarg);
+ exit(1);
+ }
+ if (r_opt > EXT2_MAX_SUPP_REV) {
+ com_err(program_name, EXT2_ET_REV_TOO_HIGH,
+ _("while trying to create revision %d"), r_opt);
+ exit(1);
+ }
+ fs_param.s_rev_level = r_opt;
+ break;
+ case 's': /* deprecated */
+ s_opt = atoi(optarg);
+ break;
+ case 'S':
+ super_only = 1;
+ break;
+ case 't':
+ if (fs_type) {
+ com_err(program_name, 0, "%s",
+ _("The -t option may only be used once"));
+ exit(1);
+ }
+ fs_type = strdup(optarg);
+ break;
+ case 'T':
+ if (usage_types) {
+ com_err(program_name, 0, "%s",
+ _("The -T option may only be used once"));
+ exit(1);
+ }
+ usage_types = strdup(optarg);
+ break;
+ case 'U':
+ fs_uuid = optarg;
+ break;
+ case 'v':
+ verbose = 1;
+ break;
+ case 'V':
+ /* Print version number and exit */
+ show_version_only++;
+ break;
+ case 'z':
+ undo_file = optarg;
+ break;
+ default:
+ usage();
+ }
+ }
+ if ((optind == argc) && !show_version_only)
+ usage();
+ device_name = argv[optind++];
+
+ if (!quiet || show_version_only)
+ fprintf (stderr, "mke2fs %s (%s)\n", E2FSPROGS_VERSION,
+ E2FSPROGS_DATE);
+
+ if (show_version_only) {
+ fprintf(stderr, _("\tUsing %s\n"),
+ error_message(EXT2_ET_BASE));
+ exit(0);
+ }
+
+ /*
+ * If there's no blocksize specified and there is a journal
+ * device, use it to figure out the blocksize
+ */
+ if (blocksize <= 0 && journal_device) {
+ ext2_filsys jfs;
+ io_manager io_ptr;
+
+#ifdef CONFIG_TESTIO_DEBUG
+ if (getenv("TEST_IO_FLAGS") || getenv("TEST_IO_BLOCK")) {
+ io_ptr = test_io_manager;
+ test_io_backing_manager = default_io_manager;
+ } else
+#endif
+ io_ptr = default_io_manager;
+ retval = ext2fs_open(journal_device,
+ EXT2_FLAG_JOURNAL_DEV_OK, 0,
+ 0, io_ptr, &jfs);
+ if (retval) {
+ com_err(program_name, retval,
+ _("while trying to open journal device %s\n"),
+ journal_device);
+ exit(1);
+ }
+ if ((blocksize < 0) && (jfs->blocksize < (unsigned) (-blocksize))) {
+ com_err(program_name, 0,
+ _("Journal dev blocksize (%d) smaller than "
+ "minimum blocksize %d\n"), jfs->blocksize,
+ -blocksize);
+ exit(1);
+ }
+ blocksize = jfs->blocksize;
+ printf(_("Using journal device's blocksize: %d\n"), blocksize);
+ fs_param.s_log_block_size =
+ int_log2(blocksize >> EXT2_MIN_BLOCK_LOG_SIZE);
+ ext2fs_close_free(&jfs);
+ }
+
+ if (optind < argc) {
+ fs_blocks_count = parse_num_blocks2(argv[optind++],
+ fs_param.s_log_block_size);
+ if (!fs_blocks_count) {
+ com_err(program_name, 0,
+ _("invalid blocks '%s' on device '%s'"),
+ argv[optind - 1], device_name);
+ exit(1);
+ }
+ }
+ if (optind < argc)
+ usage();
+
+ profile_get_integer(profile, "options", "sync_kludge", 0, 0,
+ &sync_kludge);
+ tmp = getenv("MKE2FS_SYNC");
+ if (tmp)
+ sync_kludge = atoi(tmp);
+
+ profile_get_integer(profile, "options", "proceed_delay", 0, 0,
+ &proceed_delay);
+
+ if (fs_blocks_count)
+ explicit_fssize = 1;
+
+ check_mount(device_name, force, _("filesystem"));
+
+ /* Determine the size of the device (if possible) */
+ if (noaction && fs_blocks_count) {
+ dev_size = fs_blocks_count;
+ retval = 0;
+ } else
+ retval = ext2fs_get_device_size2(device_name,
+ EXT2_BLOCK_SIZE(&fs_param),
+ &dev_size);
+ if (retval == ENOENT) {
+ int fd;
+
+ if (!explicit_fssize) {
+ fprintf(stderr,
+ _("The file %s does not exist and no "
+ "size was specified.\n"), device_name);
+ exit(1);
+ }
+ fd = ext2fs_open_file(device_name,
+ O_CREAT | O_WRONLY, 0666);
+ if (fd < 0) {
+ retval = errno;
+ } else {
+ dev_size = 0;
+ retval = 0;
+ close(fd);
+ printf(_("Creating regular file %s\n"), device_name);
+ }
+ }
+ if (retval && (retval != EXT2_ET_UNIMPLEMENTED)) {
+ com_err(program_name, retval, "%s",
+ _("while trying to determine filesystem size"));
+ exit(1);
+ }
+ if (!fs_blocks_count) {
+ if (retval == EXT2_ET_UNIMPLEMENTED) {
+ com_err(program_name, 0, "%s",
+ _("Couldn't determine device size; you "
+ "must specify\nthe size of the "
+ "filesystem\n"));
+ exit(1);
+ } else {
+ if (dev_size == 0) {
+ com_err(program_name, 0, "%s",
+ _("Device size reported to be zero. "
+ "Invalid partition specified, or\n\t"
+ "partition table wasn't reread "
+ "after running fdisk, due to\n\t"
+ "a modified partition being busy "
+ "and in use. You may need to reboot\n\t"
+ "to re-read your partition table.\n"
+ ));
+ exit(1);
+ }
+ fs_blocks_count = dev_size;
+ if (sys_page_size > EXT2_BLOCK_SIZE(&fs_param))
+ fs_blocks_count &= ~((blk64_t) ((sys_page_size /
+ EXT2_BLOCK_SIZE(&fs_param))-1));
+ }
+ } else if (!force && is_device && (fs_blocks_count > dev_size)) {
+ com_err(program_name, 0, "%s",
+ _("Filesystem larger than apparent device size."));
+ proceed_question(proceed_delay);
+ }
+
+ if (!fs_type)
+ profile_get_string(profile, "devices", device_name,
+ "fs_type", 0, &fs_type);
+ if (!usage_types)
+ profile_get_string(profile, "devices", device_name,
+ "usage_types", 0, &usage_types);
+ if (!creator_os)
+ profile_get_string(profile, "defaults", "creator_os", 0,
+ 0, &creator_os);
+
+ /*
+ * We have the file system (or device) size, so we can now
+ * determine the appropriate file system types so the fs can
+ * be appropriately configured.
+ */
+ fs_types = parse_fs_type(fs_type, usage_types, &fs_param,
+ fs_blocks_count ? fs_blocks_count : dev_size,
+ argv[0]);
+ if (!fs_types) {
+ fprintf(stderr, "%s", _("Failed to parse fs types list\n"));
+ exit(1);
+ }
+
+ /* Figure out what features should be enabled */
+
+ tmp = NULL;
+ if (fs_param.s_rev_level != EXT2_GOOD_OLD_REV) {
+ tmp = get_string_from_profile(fs_types, "base_features",
+ "sparse_super,large_file,filetype,resize_inode,dir_index");
+ edit_feature(tmp, &fs_param.s_feature_compat);
+ free(tmp);
+
+ /* And which mount options as well */
+ tmp = get_string_from_profile(fs_types, "default_mntopts",
+ "acl,user_xattr");
+ edit_mntopts(tmp, &fs_param.s_default_mount_opts);
+ if (tmp)
+ free(tmp);
+
+ for (cpp = fs_types; *cpp; cpp++) {
+ tmp = NULL;
+ profile_get_string(profile, "fs_types", *cpp,
+ "features", "", &tmp);
+ if (tmp && *tmp)
+ edit_feature(tmp, &fs_param.s_feature_compat);
+ if (tmp)
+ free(tmp);
+ }
+ tmp = get_string_from_profile(fs_types, "default_features",
+ "");
+ }
+ /* Mask off features which aren't supported by the Hurd */
+ if (for_hurd(creator_os)) {
+ ext2fs_clear_feature_filetype(&fs_param);
+ ext2fs_clear_feature_huge_file(&fs_param);
+ ext2fs_clear_feature_metadata_csum(&fs_param);
+ ext2fs_clear_feature_ea_inode(&fs_param);
+ ext2fs_clear_feature_casefold(&fs_param);
+ }
+ if (!fs_features && tmp)
+ edit_feature(tmp, &fs_param.s_feature_compat);
+ /*
+ * Now all the defaults are incorporated in fs_param. Check the state
+ * of orphan_file feature so that we know whether we should silently
+ * disabled in case journal gets disabled.
+ */
+ if (ext2fs_has_feature_orphan_file(&fs_param))
+ default_orphan_file = 1;
+ if (ext2fs_has_feature_csum_seed(&fs_param))
+ default_csum_seed = 1;
+ if (fs_features)
+ edit_feature(fs_features, &fs_param.s_feature_compat);
+ /* Silently disable orphan_file if user chose fs without journal */
+ if (default_orphan_file && !ext2fs_has_feature_journal(&fs_param))
+ ext2fs_clear_feature_orphan_file(&fs_param);
+ if (default_csum_seed && !ext2fs_has_feature_metadata_csum(&fs_param))
+ ext2fs_clear_feature_csum_seed(&fs_param);
+ if (tmp)
+ free(tmp);
+ (void) ext2fs_free_mem(&fs_features);
+ /*
+ * If the user specified features incompatible with the Hurd, complain
+ */
+ if (for_hurd(creator_os)) {
+ if (ext2fs_has_feature_filetype(&fs_param)) {
+ fprintf(stderr, "%s", _("The HURD does not support the "
+ "filetype feature.\n"));
+ exit(1);
+ }
+ if (ext2fs_has_feature_huge_file(&fs_param)) {
+ fprintf(stderr, "%s", _("The HURD does not support the "
+ "huge_file feature.\n"));
+ exit(1);
+ }
+ if (ext2fs_has_feature_metadata_csum(&fs_param)) {
+ fprintf(stderr, "%s", _("The HURD does not support the "
+ "metadata_csum feature.\n"));
+ exit(1);
+ }
+ if (ext2fs_has_feature_ea_inode(&fs_param)) {
+ fprintf(stderr, "%s", _("The HURD does not support the "
+ "ea_inode feature.\n"));
+ exit(1);
+ }
+ }
+
+ /* Get the hardware sector sizes, if available */
+ retval = ext2fs_get_device_sectsize(device_name, &lsector_size);
+ if (retval) {
+ com_err(program_name, retval, "%s",
+ _("while trying to determine hardware sector size"));
+ exit(1);
+ }
+ retval = ext2fs_get_device_phys_sectsize(device_name, &psector_size);
+ if (retval) {
+ com_err(program_name, retval, "%s",
+ _("while trying to determine physical sector size"));
+ exit(1);
+ }
+
+ tmp = getenv("MKE2FS_DEVICE_SECTSIZE");
+ if (tmp != NULL)
+ lsector_size = atoi(tmp);
+ tmp = getenv("MKE2FS_DEVICE_PHYS_SECTSIZE");
+ if (tmp != NULL)
+ psector_size = atoi(tmp);
+
+ /* Older kernels may not have physical/logical distinction */
+ if (!psector_size)
+ psector_size = lsector_size;
+
+ if (blocksize <= 0) {
+ use_bsize = get_int_from_profile(fs_types, "blocksize", 4096);
+
+ if (use_bsize == -1) {
+ use_bsize = sys_page_size;
+ if (is_before_linux_ver(2, 6, 0) && use_bsize > 4096)
+ use_bsize = 4096;
+ }
+ if (lsector_size && use_bsize < lsector_size)
+ use_bsize = lsector_size;
+ if ((blocksize < 0) && (use_bsize < (-blocksize)))
+ use_bsize = -blocksize;
+ blocksize = use_bsize;
+ fs_blocks_count /= (blocksize / 1024);
+ } else {
+ if (blocksize < lsector_size) { /* Impossible */
+ com_err(program_name, EINVAL, "%s",
+ _("while setting blocksize; too small "
+ "for device\n"));
+ exit(1);
+ } else if ((blocksize < psector_size) &&
+ (psector_size <= sys_page_size)) { /* Suboptimal */
+ fprintf(stderr, _("Warning: specified blocksize %d is "
+ "less than device physical sectorsize %d\n"),
+ blocksize, psector_size);
+ }
+ }
+
+ fs_param.s_log_block_size =
+ int_log2(blocksize >> EXT2_MIN_BLOCK_LOG_SIZE);
+
+ /*
+ * We now need to do a sanity check of fs_blocks_count for
+ * 32-bit vs 64-bit block number support.
+ */
+ if ((fs_blocks_count > MAX_32_NUM) &&
+ ext2fs_has_feature_64bit(&fs_param))
+ ext2fs_clear_feature_resize_inode(&fs_param);
+ if ((fs_blocks_count > MAX_32_NUM) &&
+ !ext2fs_has_feature_64bit(&fs_param) &&
+ get_bool_from_profile(fs_types, "auto_64-bit_support", 0)) {
+ ext2fs_set_feature_64bit(&fs_param);
+ ext2fs_clear_feature_resize_inode(&fs_param);
+ }
+ if ((fs_blocks_count > MAX_32_NUM) &&
+ !ext2fs_has_feature_64bit(&fs_param)) {
+ fprintf(stderr, _("%s: Size of device (0x%llx blocks) %s "
+ "too big to be expressed\n\t"
+ "in 32 bits using a blocksize of %d.\n"),
+ program_name, (unsigned long long) fs_blocks_count,
+ device_name, EXT2_BLOCK_SIZE(&fs_param));
+ exit(1);
+ }
+ /*
+ * Guard against group descriptor count overflowing... Mostly to avoid
+ * strange results for absurdly large devices. This is in log2:
+ * (blocksize) * (bits per byte) * (maximum number of block groups)
+ */
+ if (fs_blocks_count >
+ (1ULL << (EXT2_BLOCK_SIZE_BITS(&fs_param) + 3 + 32)) - 1) {
+ fprintf(stderr, _("%s: Size of device (0x%llx blocks) %s "
+ "too big to create\n\t"
+ "a filesystem using a blocksize of %d.\n"),
+ program_name, (unsigned long long) fs_blocks_count,
+ device_name, EXT2_BLOCK_SIZE(&fs_param));
+ exit(1);
+ }
+
+ ext2fs_blocks_count_set(&fs_param, fs_blocks_count);
+
+ if (ext2fs_has_feature_journal_dev(&fs_param)) {
+ int i;
+
+ for (i=0; fs_types[i]; i++) {
+ free(fs_types[i]);
+ fs_types[i] = 0;
+ }
+ fs_types[0] = strdup("journal");
+ fs_types[1] = 0;
+ }
+
+ if (verbose) {
+ fputs(_("fs_types for mke2fs.conf resolution: "), stdout);
+ print_str_list(fs_types);
+ }
+
+ if (r_opt == EXT2_GOOD_OLD_REV &&
+ (fs_param.s_feature_compat || fs_param.s_feature_incompat ||
+ fs_param.s_feature_ro_compat)) {
+ fprintf(stderr, "%s", _("Filesystem features not supported "
+ "with revision 0 filesystems\n"));
+ exit(1);
+ }
+
+ if (s_opt > 0) {
+ if (r_opt == EXT2_GOOD_OLD_REV) {
+ fprintf(stderr, "%s",
+ _("Sparse superblocks not supported "
+ "with revision 0 filesystems\n"));
+ exit(1);
+ }
+ ext2fs_set_feature_sparse_super(&fs_param);
+ } else if (s_opt == 0)
+ ext2fs_clear_feature_sparse_super(&fs_param);
+
+ if (journal_size != 0) {
+ if (r_opt == EXT2_GOOD_OLD_REV) {
+ fprintf(stderr, "%s", _("Journals not supported with "
+ "revision 0 filesystems\n"));
+ exit(1);
+ }
+ ext2fs_set_feature_journal(&fs_param);
+ }
+
+ /* Get reserved_ratio from profile if not specified on cmd line. */
+ if (reserved_ratio < 0.0) {
+ reserved_ratio = get_double_from_profile(
+ fs_types, "reserved_ratio", 5.0);
+ if (reserved_ratio > 50 || reserved_ratio < 0) {
+ com_err(program_name, 0,
+ _("invalid reserved blocks percent - %lf"),
+ reserved_ratio);
+ exit(1);
+ }
+ }
+
+ if (ext2fs_has_feature_journal_dev(&fs_param)) {
+ reserved_ratio = 0;
+ fs_param.s_feature_incompat = EXT3_FEATURE_INCOMPAT_JOURNAL_DEV;
+ fs_param.s_feature_compat = 0;
+ fs_param.s_feature_ro_compat &=
+ EXT4_FEATURE_RO_COMPAT_METADATA_CSUM;
+ }
+
+ /* Check the user's mkfs options for 64bit */
+ if (ext2fs_has_feature_64bit(&fs_param) &&
+ !ext2fs_has_feature_extents(&fs_param)) {
+ printf("%s", _("Extents MUST be enabled for a 64-bit "
+ "filesystem. Pass -O extents to rectify.\n"));
+ exit(1);
+ }
+
+ /* Set first meta blockgroup via an environment variable */
+ /* (this is mostly for debugging purposes) */
+ if (ext2fs_has_feature_meta_bg(&fs_param) &&
+ (tmp = getenv("MKE2FS_FIRST_META_BG")))
+ fs_param.s_first_meta_bg = atoi(tmp);
+ if (ext2fs_has_feature_bigalloc(&fs_param)) {
+ if (!cluster_size)
+ cluster_size = get_int_from_profile(fs_types,
+ "cluster_size",
+ blocksize*16);
+ fs_param.s_log_cluster_size =
+ int_log2(cluster_size >> EXT2_MIN_CLUSTER_LOG_SIZE);
+ if (fs_param.s_log_cluster_size &&
+ fs_param.s_log_cluster_size < fs_param.s_log_block_size) {
+ com_err(program_name, 0, "%s",
+ _("The cluster size may not be "
+ "smaller than the block size.\n"));
+ exit(1);
+ }
+ } else if (cluster_size) {
+ com_err(program_name, 0, "%s",
+ _("specifying a cluster size requires the "
+ "bigalloc feature"));
+ exit(1);
+ } else
+ fs_param.s_log_cluster_size = fs_param.s_log_block_size;
+
+ if (inode_ratio == 0) {
+ inode_ratio = get_int_from_profile(fs_types, "inode_ratio",
+ 8192);
+ if (inode_ratio < blocksize)
+ inode_ratio = blocksize;
+ if (inode_ratio < EXT2_CLUSTER_SIZE(&fs_param))
+ inode_ratio = EXT2_CLUSTER_SIZE(&fs_param);
+ }
+
+#ifdef HAVE_BLKID_PROBE_GET_TOPOLOGY
+ retval = get_device_geometry(device_name, blocksize,
+ psector_size, &dev_param);
+ if (retval < 0) {
+ fprintf(stderr,
+ _("warning: Unable to get device geometry for %s\n"),
+ device_name);
+ } else {
+ /* setting stripe/stride to blocksize is pointless */
+ if (dev_param.min_io > (unsigned) blocksize)
+ fs_param.s_raid_stride = dev_param.min_io / blocksize;
+ if (dev_param.opt_io > (unsigned) blocksize) {
+ fs_param.s_raid_stripe_width =
+ dev_param.opt_io / blocksize;
+ }
+
+ if (dev_param.alignment_offset) {
+ printf(_("%s alignment is offset by %lu bytes.\n"),
+ device_name, dev_param.alignment_offset);
+ printf(_("This may result in very poor performance, "
+ "(re)-partitioning suggested.\n"));
+ }
+
+ if (dev_param.dax && blocksize != sys_page_size) {
+ fprintf(stderr,
+ _("%s is capable of DAX but current block size "
+ "%u is different from system page size %u so "
+ "filesystem will not support DAX.\n"),
+ device_name, blocksize, sys_page_size);
+ }
+ }
+#endif
+
+ num_backups = get_int_from_profile(fs_types, "num_backup_sb", 2);
+
+ blocksize = EXT2_BLOCK_SIZE(&fs_param);
+
+ /*
+ * Initialize s_desc_size so that the parse_extended_opts()
+ * can correctly handle "-E resize=NNN" if the 64-bit option
+ * is set.
+ */
+ if (ext2fs_has_feature_64bit(&fs_param))
+ fs_param.s_desc_size = EXT2_MIN_DESC_SIZE_64BIT;
+
+ /* This check should happen beyond the last assignment to blocksize */
+ if (blocksize > sys_page_size) {
+ if (!force) {
+ com_err(program_name, 0,
+ _("%d-byte blocks too big for system (max %d)"),
+ blocksize, sys_page_size);
+ proceed_question(proceed_delay);
+ }
+ fprintf(stderr, _("Warning: %d-byte blocks too big for system "
+ "(max %d), forced to continue\n"),
+ blocksize, sys_page_size);
+ }
+
+ /* Metadata checksumming wasn't totally stable before 3.18. */
+ if (is_before_linux_ver(3, 18, 0) &&
+ ext2fs_has_feature_metadata_csum(&fs_param))
+ fprintf(stderr, _("Suggestion: Use Linux kernel >= 3.18 for "
+ "improved stability of the metadata and journal "
+ "checksum features.\n"));
+
+ /*
+ * On newer kernels we do have lazy_itable_init support. So pick the
+ * right default in case ext4 module is not loaded.
+ */
+ if (is_before_linux_ver(2, 6, 37))
+ lazy_itable_init = 0;
+ else
+ lazy_itable_init = 1;
+
+ if (access("/sys/fs/ext4/features/lazy_itable_init", R_OK) == 0)
+ lazy_itable_init = 1;
+
+ lazy_itable_init = get_bool_from_profile(fs_types,
+ "lazy_itable_init",
+ lazy_itable_init);
+ discard = get_bool_from_profile(fs_types, "discard" , discard);
+ journal_flags |= get_bool_from_profile(fs_types,
+ "lazy_journal_init", 0) ?
+ EXT2_MKJOURNAL_LAZYINIT : 0;
+ journal_flags |= EXT2_MKJOURNAL_NO_MNT_CHECK;
+
+ if (!journal_location_string)
+ journal_location_string = get_string_from_profile(fs_types,
+ "journal_location", "");
+ if ((journal_location == ~0ULL) && journal_location_string &&
+ *journal_location_string)
+ journal_location = parse_num_blocks2(journal_location_string,
+ fs_param.s_log_block_size);
+ free(journal_location_string);
+
+ packed_meta_blocks = get_bool_from_profile(fs_types,
+ "packed_meta_blocks", 0);
+ if (packed_meta_blocks)
+ journal_location = 0;
+
+ if (ext2fs_has_feature_casefold(&fs_param)) {
+ char *ef, *en = get_string_from_profile(fs_types,
+ "encoding", "utf8");
+ int encoding = e2p_str2encoding(en);
+
+ if (encoding < 0) {
+ com_err(program_name, 0,
+ _("Unknown filename encoding from profile: %s"),
+ en);
+ exit(1);
+ }
+ free(en);
+ fs_param.s_encoding = encoding;
+ ef = get_string_from_profile(fs_types, "encoding_flags", NULL);
+ if (ef) {
+ if (e2p_str2encoding_flags(encoding, ef,
+ &fs_param.s_encoding_flags) < 0) {
+ com_err(program_name, 0,
+ _("Unknown encoding flags from profile: %s"), ef);
+ exit(1);
+ }
+ free(ef);
+ } else
+ fs_param.s_encoding_flags =
+ e2p_get_encoding_flags(encoding);
+ }
+
+ /* Get options from profile */
+ for (cpp = fs_types; *cpp; cpp++) {
+ tmp = NULL;
+ profile_get_string(profile, "fs_types", *cpp, "options", "", &tmp);
+ if (tmp && *tmp)
+ parse_extended_opts(&fs_param, tmp);
+ free(tmp);
+ }
+
+ if (extended_opts)
+ parse_extended_opts(&fs_param, extended_opts);
+
+ if (explicit_fssize == 0 && offset > 0) {
+ fs_blocks_count -= offset / EXT2_BLOCK_SIZE(&fs_param);
+ ext2fs_blocks_count_set(&fs_param, fs_blocks_count);
+ fprintf(stderr,
+ _("\nWarning: offset specified without an "
+ "explicit file system size.\n"
+ "Creating a file system with %llu blocks "
+ "but this might\n"
+ "not be what you want.\n\n"),
+ (unsigned long long) fs_blocks_count);
+ }
+
+ if (quotatype_bits & QUOTA_PRJ_BIT)
+ ext2fs_set_feature_project(&fs_param);
+
+ if (ext2fs_has_feature_project(&fs_param)) {
+ quotatype_bits |= QUOTA_PRJ_BIT;
+ if (inode_size == EXT2_GOOD_OLD_INODE_SIZE) {
+ com_err(program_name, 0,
+ _("%d byte inodes are too small for "
+ "project quota"),
+ inode_size);
+ exit(1);
+ }
+ if (inode_size == 0) {
+ inode_size = get_int_from_profile(fs_types,
+ "inode_size", 0);
+ if (inode_size <= EXT2_GOOD_OLD_INODE_SIZE*2)
+ inode_size = EXT2_GOOD_OLD_INODE_SIZE*2;
+ }
+ }
+
+ /* Don't allow user to set both metadata_csum and uninit_bg bits. */
+ if (ext2fs_has_feature_metadata_csum(&fs_param) &&
+ ext2fs_has_feature_gdt_csum(&fs_param))
+ ext2fs_clear_feature_gdt_csum(&fs_param);
+
+ /* Can't support bigalloc feature without extents feature */
+ if (ext2fs_has_feature_bigalloc(&fs_param) &&
+ !ext2fs_has_feature_extents(&fs_param)) {
+ com_err(program_name, 0, "%s",
+ _("Can't support bigalloc feature without "
+ "extents feature"));
+ exit(1);
+ }
+
+ if (ext2fs_has_feature_meta_bg(&fs_param) &&
+ ext2fs_has_feature_resize_inode(&fs_param)) {
+ fprintf(stderr, "%s", _("The resize_inode and meta_bg "
+ "features are not compatible.\n"
+ "They can not be both enabled "
+ "simultaneously.\n"));
+ exit(1);
+ }
+
+ if (!quiet && ext2fs_has_feature_bigalloc(&fs_param) &&
+ EXT2_CLUSTER_SIZE(&fs_param) > 16 * EXT2_BLOCK_SIZE(&fs_param))
+ fprintf(stderr, "%s", _("\nWarning: bigalloc file systems "
+ "with a cluster size greater than\n"
+ "16 times the block size is considered "
+ "experimental\n"));
+
+ /*
+ * Since sparse_super is the default, we would only have a problem
+ * here if it was explicitly disabled.
+ */
+ if (ext2fs_has_feature_resize_inode(&fs_param) &&
+ !ext2fs_has_feature_sparse_super(&fs_param)) {
+ com_err(program_name, 0, "%s",
+ _("reserved online resize blocks not supported "
+ "on non-sparse filesystem"));
+ exit(1);
+ }
+
+ if (fs_param.s_blocks_per_group) {
+ if (fs_param.s_blocks_per_group < 256 ||
+ fs_param.s_blocks_per_group > 8 * (unsigned) blocksize) {
+ com_err(program_name, 0, "%s",
+ _("blocks per group count out of range"));
+ exit(1);
+ }
+ }
+
+ /*
+ * If the bigalloc feature is enabled, then the -g option will
+ * specify the number of clusters per group.
+ */
+ if (ext2fs_has_feature_bigalloc(&fs_param)) {
+ fs_param.s_clusters_per_group = fs_param.s_blocks_per_group;
+ fs_param.s_blocks_per_group = 0;
+ }
+
+ if (inode_size == 0)
+ inode_size = get_int_from_profile(fs_types, "inode_size", 0);
+ if (!flex_bg_size && ext2fs_has_feature_flex_bg(&fs_param))
+ flex_bg_size = get_uint_from_profile(fs_types,
+ "flex_bg_size", 16);
+ if (flex_bg_size) {
+ if (!ext2fs_has_feature_flex_bg(&fs_param)) {
+ com_err(program_name, 0, "%s",
+ _("Flex_bg feature not enabled, so "
+ "flex_bg size may not be specified"));
+ exit(1);
+ }
+ fs_param.s_log_groups_per_flex = int_log2(flex_bg_size);
+ }
+
+ if (inode_size && fs_param.s_rev_level >= EXT2_DYNAMIC_REV) {
+ if (inode_size < EXT2_GOOD_OLD_INODE_SIZE ||
+ inode_size > EXT2_BLOCK_SIZE(&fs_param) ||
+ inode_size & (inode_size - 1)) {
+ com_err(program_name, 0,
+ _("invalid inode size %d (min %d/max %d)"),
+ inode_size, EXT2_GOOD_OLD_INODE_SIZE,
+ blocksize);
+ exit(1);
+ }
+ fs_param.s_inode_size = inode_size;
+ }
+
+ /*
+ * If inode size is 128 and inline data is enabled, we need
+ * to notify users that inline data will never be useful.
+ */
+ if (ext2fs_has_feature_inline_data(&fs_param) &&
+ fs_param.s_inode_size == EXT2_GOOD_OLD_INODE_SIZE) {
+ com_err(program_name, 0,
+ _("%d byte inodes are too small for inline data; "
+ "specify larger size"),
+ fs_param.s_inode_size);
+ exit(1);
+ }
+
+ /*
+ * Warn the user that filesystems with 128-byte inodes will
+ * not work properly beyond 2038. This can be suppressed via
+ * a boolean in the mke2fs.conf file, and we will disable this
+ * warning for file systems created for the GNU Hurd.
+ */
+ if (inode_size == EXT2_GOOD_OLD_INODE_SIZE &&
+ get_bool_from_profile(fs_types, "warn_y2038_dates", 1))
+ printf(
+_("128-byte inodes cannot handle dates beyond 2038 and are deprecated\n"));
+
+ /* Make sure number of inodes specified will fit in 32 bits */
+ if (num_inodes == 0) {
+ unsigned long long n;
+ n = ext2fs_blocks_count(&fs_param) * blocksize / inode_ratio;
+ if (n > MAX_32_NUM) {
+ if (ext2fs_has_feature_64bit(&fs_param))
+ num_inodes = MAX_32_NUM;
+ else {
+ com_err(program_name, 0,
+ _("too many inodes (%llu), raise "
+ "inode ratio?"),
+ (unsigned long long) n);
+ exit(1);
+ }
+ }
+ } else if (num_inodes > MAX_32_NUM) {
+ com_err(program_name, 0,
+ _("too many inodes (%llu), specify < 2^32 inodes"),
+ (unsigned long long) num_inodes);
+ exit(1);
+ }
+ /*
+ * Calculate number of inodes based on the inode ratio
+ */
+ fs_param.s_inodes_count = num_inodes ? num_inodes :
+ (ext2fs_blocks_count(&fs_param) * blocksize) / inode_ratio;
+
+ if ((((unsigned long long)fs_param.s_inodes_count) *
+ (inode_size ? inode_size : EXT2_GOOD_OLD_INODE_SIZE)) >=
+ ((ext2fs_blocks_count(&fs_param)) *
+ EXT2_BLOCK_SIZE(&fs_param))) {
+ com_err(program_name, 0, _("inode_size (%u) * inodes_count "
+ "(%u) too big for a\n\t"
+ "filesystem with %llu blocks, "
+ "specify higher inode_ratio (-i)\n\t"
+ "or lower inode count (-N).\n"),
+ inode_size ? inode_size : EXT2_GOOD_OLD_INODE_SIZE,
+ fs_param.s_inodes_count,
+ (unsigned long long) ext2fs_blocks_count(&fs_param));
+ exit(1);
+ }
+
+ /*
+ * Calculate number of blocks to reserve
+ */
+ ext2fs_r_blocks_count_set(&fs_param, reserved_ratio *
+ ext2fs_blocks_count(&fs_param) / 100.0);
+
+ if (ext2fs_has_feature_sparse_super2(&fs_param)) {
+ if (num_backups >= 1)
+ fs_param.s_backup_bgs[0] = 1;
+ if (num_backups >= 2)
+ fs_param.s_backup_bgs[1] = ~0;
+ }
+
+ free(fs_type);
+ free(usage_types);
+
+ /* The isatty() test is so we don't break existing scripts */
+ flags = CREATE_FILE;
+ if (isatty(0) && isatty(1) && !offset)
+ flags |= CHECK_FS_EXIST;
+ if (!quiet)
+ flags |= VERBOSE_CREATE;
+ if (!explicit_fssize)
+ flags |= NO_SIZE;
+ if (!check_plausibility(device_name, flags, &is_device) && !force)
+ proceed_question(proceed_delay);
+}
+
+static int should_do_undo(const char *name)
+{
+ errcode_t retval;
+ io_channel channel;
+ __u16 s_magic;
+ struct ext2_super_block super;
+ io_manager manager = default_io_manager;
+ int csum_flag, force_undo;
+
+ csum_flag = ext2fs_has_feature_metadata_csum(&fs_param) ||
+ ext2fs_has_feature_gdt_csum(&fs_param);
+ force_undo = get_int_from_profile(fs_types, "force_undo", 0);
+ if (!force_undo && (!csum_flag || !lazy_itable_init))
+ return 0;
+
+ retval = manager->open(name, IO_FLAG_EXCLUSIVE, &channel);
+ if (retval) {
+ /*
+ * We don't handle error cases instead we
+ * declare that the file system doesn't exist
+ * and let the rest of mke2fs take care of
+ * error
+ */
+ retval = 0;
+ goto open_err_out;
+ }
+
+ io_channel_set_blksize(channel, SUPERBLOCK_OFFSET);
+ retval = io_channel_read_blk64(channel, 1, -SUPERBLOCK_SIZE, &super);
+ if (retval) {
+ retval = 0;
+ goto err_out;
+ }
+
+#if defined(WORDS_BIGENDIAN)
+ s_magic = ext2fs_swab16(super.s_magic);
+#else
+ s_magic = super.s_magic;
+#endif
+
+ if (s_magic == EXT2_SUPER_MAGIC)
+ retval = 1;
+
+err_out:
+ io_channel_close(channel);
+
+open_err_out:
+
+ return retval;
+}
+
+static int mke2fs_setup_tdb(const char *name, io_manager *io_ptr)
+{
+ errcode_t retval = ENOMEM;
+ char *tdb_dir = NULL, *tdb_file = NULL;
+ char *dev_name, *tmp_name;
+ int free_tdb_dir = 0;
+
+ /* (re)open a specific undo file */
+ if (undo_file && undo_file[0] != 0) {
+ retval = set_undo_io_backing_manager(*io_ptr);
+ if (retval)
+ goto err;
+ *io_ptr = undo_io_manager;
+ retval = set_undo_io_backup_file(undo_file);
+ if (retval)
+ goto err;
+ printf(_("Overwriting existing filesystem; this can be undone "
+ "using the command:\n"
+ " e2undo %s %s\n\n"), undo_file, name);
+ return retval;
+ }
+
+ /*
+ * Configuration via a conf file would be
+ * nice
+ */
+ tdb_dir = getenv("E2FSPROGS_UNDO_DIR");
+ if (!tdb_dir) {
+ profile_get_string(profile, "defaults",
+ "undo_dir", 0, "/var/lib/e2fsprogs",
+ &tdb_dir);
+ free_tdb_dir = 1;
+ }
+
+ if (!strcmp(tdb_dir, "none") || (tdb_dir[0] == 0) ||
+ access(tdb_dir, W_OK)) {
+ if (free_tdb_dir)
+ free(tdb_dir);
+ return 0;
+ }
+
+ tmp_name = strdup(name);
+ if (!tmp_name)
+ goto errout;
+ dev_name = basename(tmp_name);
+ tdb_file = malloc(strlen(tdb_dir) + 8 + strlen(dev_name) + 7 + 1);
+ if (!tdb_file) {
+ free(tmp_name);
+ goto errout;
+ }
+ sprintf(tdb_file, "%s/mke2fs-%s.e2undo", tdb_dir, dev_name);
+ free(tmp_name);
+
+ if ((unlink(tdb_file) < 0) && (errno != ENOENT)) {
+ retval = errno;
+ com_err(program_name, retval,
+ _("while trying to delete %s"), tdb_file);
+ goto errout;
+ }
+
+ retval = set_undo_io_backing_manager(*io_ptr);
+ if (retval)
+ goto errout;
+ *io_ptr = undo_io_manager;
+ retval = set_undo_io_backup_file(tdb_file);
+ if (retval)
+ goto errout;
+ printf(_("Overwriting existing filesystem; this can be undone "
+ "using the command:\n"
+ " e2undo %s %s\n\n"), tdb_file, name);
+
+ if (free_tdb_dir)
+ free(tdb_dir);
+ free(tdb_file);
+ return 0;
+
+errout:
+ if (free_tdb_dir)
+ free(tdb_dir);
+ free(tdb_file);
+err:
+ com_err(program_name, retval, "%s",
+ _("while trying to setup undo file\n"));
+ return retval;
+}
+
+static int mke2fs_discard_device(ext2_filsys fs)
+{
+ struct ext2fs_numeric_progress_struct progress;
+ blk64_t blocks = ext2fs_blocks_count(fs->super);
+ blk64_t count = DISCARD_STEP_MB;
+ blk64_t cur = 0;
+ int retval = 0;
+
+ /*
+ * Let's try if discard really works on the device, so
+ * we do not print numeric progress resulting in failure
+ * afterwards.
+ */
+ retval = io_channel_discard(fs->io, 0, 1);
+ if (retval)
+ return retval;
+
+ count *= (1024 * 1024);
+ count /= fs->blocksize;
+
+ ext2fs_numeric_progress_init(fs, &progress,
+ _("Discarding device blocks: "),
+ blocks);
+ while (cur < blocks) {
+ ext2fs_numeric_progress_update(fs, &progress, cur);
+
+ if (cur + count > blocks)
+ count = blocks - cur;
+
+ retval = io_channel_discard(fs->io, cur, count);
+ if (retval)
+ break;
+ cur += count;
+ }
+
+ if (retval) {
+ ext2fs_numeric_progress_close(fs, &progress,
+ _("failed - "));
+ if (!quiet)
+ printf("%s\n",error_message(retval));
+ } else
+ ext2fs_numeric_progress_close(fs, &progress,
+ _("done \n"));
+
+ return retval;
+}
+
+static void fix_cluster_bg_counts(ext2_filsys fs)
+{
+ blk64_t block, num_blocks, last_block, next;
+ blk64_t tot_free = 0;
+ errcode_t retval;
+ dgrp_t group = 0;
+ int grp_free = 0;
+
+ num_blocks = ext2fs_blocks_count(fs->super);
+ last_block = ext2fs_group_last_block2(fs, group);
+ block = fs->super->s_first_data_block;
+ while (block < num_blocks) {
+ retval = ext2fs_find_first_zero_block_bitmap2(fs->block_map,
+ block, last_block, &next);
+ if (retval == 0)
+ block = next;
+ else {
+ block = last_block + 1;
+ goto next_bg;
+ }
+
+ retval = ext2fs_find_first_set_block_bitmap2(fs->block_map,
+ block, last_block, &next);
+ if (retval)
+ next = last_block + 1;
+ grp_free += EXT2FS_NUM_B2C(fs, next - block);
+ tot_free += next - block;
+ block = next;
+
+ if (block > last_block) {
+ next_bg:
+ ext2fs_bg_free_blocks_count_set(fs, group, grp_free);
+ ext2fs_group_desc_csum_set(fs, group);
+ grp_free = 0;
+ group++;
+ last_block = ext2fs_group_last_block2(fs, group);
+ }
+ }
+ ext2fs_free_blocks_count_set(fs->super, tot_free);
+}
+
+static int create_quota_inodes(ext2_filsys fs)
+{
+ quota_ctx_t qctx;
+ errcode_t retval;
+
+ retval = quota_init_context(&qctx, fs, quotatype_bits);
+ if (retval) {
+ com_err(program_name, retval,
+ _("while initializing quota context"));
+ exit(1);
+ }
+ quota_compute_usage(qctx);
+ retval = quota_write_inode(qctx, quotatype_bits);
+ if (retval) {
+ com_err(program_name, retval,
+ _("while writing quota inodes"));
+ exit(1);
+ }
+ quota_release_context(&qctx);
+
+ return 0;
+}
+
+static errcode_t set_error_behavior(ext2_filsys fs)
+{
+ char *arg = NULL;
+ short errors = fs->super->s_errors;
+
+ arg = get_string_from_profile(fs_types, "errors", NULL);
+ if (arg == NULL)
+ goto try_user;
+
+ if (strcmp(arg, "continue") == 0)
+ errors = EXT2_ERRORS_CONTINUE;
+ else if (strcmp(arg, "remount-ro") == 0)
+ errors = EXT2_ERRORS_RO;
+ else if (strcmp(arg, "panic") == 0)
+ errors = EXT2_ERRORS_PANIC;
+ else {
+ com_err(program_name, 0,
+ _("bad error behavior in profile - %s"),
+ arg);
+ free(arg);
+ return EXT2_ET_INVALID_ARGUMENT;
+ }
+ free(arg);
+
+try_user:
+ if (errors_behavior)
+ errors = errors_behavior;
+
+ fs->super->s_errors = errors;
+ return 0;
+}
+
+int main (int argc, char *argv[])
+{
+ errcode_t retval = 0;
+ ext2_filsys fs;
+ badblocks_list bb_list = 0;
+ badblocks_iterate bb_iter;
+ blk_t blk;
+ struct ext2fs_journal_params jparams = {0};
+ unsigned int i, checkinterval;
+ int max_mnt_count;
+ int val, hash_alg;
+ int flags;
+ int old_bitmaps;
+ io_manager io_ptr;
+ char opt_string[40];
+ char *hash_alg_str;
+ int itable_zeroed = 0;
+ blk64_t overhead;
+
+#ifdef ENABLE_NLS
+ setlocale(LC_MESSAGES, "");
+ setlocale(LC_CTYPE, "");
+ bindtextdomain(NLS_CAT_NAME, LOCALEDIR);
+ textdomain(NLS_CAT_NAME);
+ set_com_err_gettext(gettext);
+#endif
+ PRS(argc, argv);
+
+#ifdef CONFIG_TESTIO_DEBUG
+ if (getenv("TEST_IO_FLAGS") || getenv("TEST_IO_BLOCK")) {
+ io_ptr = test_io_manager;
+ test_io_backing_manager = default_io_manager;
+ } else
+#endif
+ io_ptr = default_io_manager;
+
+ if (undo_file != NULL || should_do_undo(device_name)) {
+ retval = mke2fs_setup_tdb(device_name, &io_ptr);
+ if (retval)
+ exit(1);
+ }
+
+ /*
+ * Initialize the superblock....
+ */
+ flags = EXT2_FLAG_EXCLUSIVE;
+ if (direct_io)
+ flags |= EXT2_FLAG_DIRECT_IO;
+ profile_get_boolean(profile, "options", "old_bitmaps", 0, 0,
+ &old_bitmaps);
+ if (!old_bitmaps)
+ flags |= EXT2_FLAG_64BITS;
+ /*
+ * By default, we print how many inode tables or block groups
+ * or whatever we've written so far. The quiet flag disables
+ * this, along with a lot of other output.
+ */
+ if (!quiet)
+ flags |= EXT2_FLAG_PRINT_PROGRESS;
+ if (android_sparse_file) {
+ char *android_sparse_params = malloc(strlen(device_name) + 48);
+
+ if (!android_sparse_params) {
+ com_err(program_name, ENOMEM, "%s",
+ _("in malloc for android_sparse_params"));
+ exit(1);
+ }
+ sprintf(android_sparse_params, "(%s):%u:%u",
+ device_name, fs_param.s_blocks_count,
+ 1024 << fs_param.s_log_block_size);
+ retval = ext2fs_initialize(android_sparse_params, flags,
+ &fs_param, sparse_io_manager, &fs);
+ free(android_sparse_params);
+ } else
+ retval = ext2fs_initialize(device_name, flags, &fs_param,
+ io_ptr, &fs);
+ if (retval) {
+ com_err(device_name, retval, "%s",
+ _("while setting up superblock"));
+ exit(1);
+ }
+ fs->progress_ops = &ext2fs_numeric_progress_ops;
+
+ /* Set the error behavior */
+ retval = set_error_behavior(fs);
+ if (retval)
+ usage();
+
+ /* Check the user's mkfs options for metadata checksumming */
+ if (!quiet &&
+ !ext2fs_has_feature_journal_dev(fs->super) &&
+ ext2fs_has_feature_metadata_csum(fs->super)) {
+ if (!ext2fs_has_feature_extents(fs->super))
+ printf("%s",
+ _("Extents are not enabled. The file extent "
+ "tree can be checksummed, whereas block maps "
+ "cannot. Not enabling extents reduces the "
+ "coverage of metadata checksumming. "
+ "Pass -O extents to rectify.\n"));
+ if (!ext2fs_has_feature_64bit(fs->super))
+ printf("%s",
+ _("64-bit filesystem support is not enabled. "
+ "The larger fields afforded by this feature "
+ "enable full-strength checksumming. "
+ "Pass -O 64bit to rectify.\n"));
+ }
+
+ if (ext2fs_has_feature_csum_seed(fs->super) &&
+ !ext2fs_has_feature_metadata_csum(fs->super)) {
+ printf("%s", _("The metadata_csum_seed feature "
+ "requires the metadata_csum feature.\n"));
+ exit(1);
+ }
+
+ /* Calculate journal blocks */
+ if (!journal_device && ((journal_size) ||
+ ext2fs_has_feature_journal(&fs_param)))
+ figure_journal_size(&jparams, journal_size, journal_fc_size, fs);
+
+ sprintf(opt_string, "tdb_data_size=%d", fs->blocksize <= 4096 ?
+ 32768 : fs->blocksize * 8);
+ io_channel_set_options(fs->io, opt_string);
+ if (offset) {
+ sprintf(opt_string, "offset=%llu", (unsigned long long) offset);
+ io_channel_set_options(fs->io, opt_string);
+ }
+
+ if (assume_storage_prezeroed) {
+ if (verbose)
+ printf("%s",
+ _("Assuming the storage device is prezeroed "
+ "- skipping inode table and journal wipe\n"));
+
+ lazy_itable_init = 1;
+ itable_zeroed = 1;
+ zero_hugefile = 0;
+ journal_flags |= EXT2_MKJOURNAL_LAZYINIT;
+ }
+
+ /* Can't undo discard ... */
+ if (!noaction && discard && dev_size && (io_ptr != undo_io_manager)) {
+ retval = mke2fs_discard_device(fs);
+ if (!retval && io_channel_discard_zeroes_data(fs->io)) {
+ if (verbose)
+ printf("%s",
+ _("Discard succeeded and will return "
+ "0s - skipping inode table wipe\n"));
+ lazy_itable_init = 1;
+ itable_zeroed = 1;
+ zero_hugefile = 0;
+ }
+ }
+
+ if (fs_param.s_flags & EXT2_FLAGS_TEST_FILESYS)
+ fs->super->s_flags |= EXT2_FLAGS_TEST_FILESYS;
+
+ if (ext2fs_has_feature_flex_bg(&fs_param) ||
+ ext2fs_has_feature_huge_file(&fs_param) ||
+ ext2fs_has_feature_gdt_csum(&fs_param) ||
+ ext2fs_has_feature_dir_nlink(&fs_param) ||
+ ext2fs_has_feature_metadata_csum(&fs_param) ||
+ ext2fs_has_feature_extra_isize(&fs_param))
+ fs->super->s_kbytes_written = 1;
+
+ /*
+ * Wipe out the old on-disk superblock
+ */
+ if (!noaction)
+ zap_sector(fs, 2, 6);
+
+ /*
+ * Parse or generate a UUID for the filesystem
+ */
+ if (fs_uuid) {
+ if ((strcasecmp(fs_uuid, "null") == 0) ||
+ (strcasecmp(fs_uuid, "clear") == 0)) {
+ uuid_clear(fs->super->s_uuid);
+ } else if (strcasecmp(fs_uuid, "time") == 0) {
+ uuid_generate_time(fs->super->s_uuid);
+ } else if (strcasecmp(fs_uuid, "random") == 0) {
+ uuid_generate(fs->super->s_uuid);
+ } else if (uuid_parse(fs_uuid, fs->super->s_uuid) != 0) {
+ com_err(device_name, 0, "could not parse UUID: %s\n",
+ fs_uuid);
+ exit(1);
+ }
+ } else
+ uuid_generate(fs->super->s_uuid);
+
+ if (ext2fs_has_feature_csum_seed(fs->super))
+ fs->super->s_checksum_seed = ext2fs_crc32c_le(~0,
+ fs->super->s_uuid, sizeof(fs->super->s_uuid));
+
+ ext2fs_init_csum_seed(fs);
+
+ /*
+ * Initialize the directory index variables
+ */
+ hash_alg_str = get_string_from_profile(fs_types, "hash_alg",
+ "half_md4");
+ hash_alg = e2p_string2hash(hash_alg_str);
+ free(hash_alg_str);
+ fs->super->s_def_hash_version = (hash_alg >= 0) ? hash_alg :
+ EXT2_HASH_HALF_MD4;
+
+ if (memcmp(fs_param.s_hash_seed, zero_buf,
+ sizeof(fs_param.s_hash_seed)) != 0) {
+ memcpy(fs->super->s_hash_seed, fs_param.s_hash_seed,
+ sizeof(fs->super->s_hash_seed));
+ } else
+ uuid_generate((unsigned char *) fs->super->s_hash_seed);
+
+ /*
+ * Periodic checks can be enabled/disabled via config file.
+ * Note we override the kernel include file's idea of what the default
+ * check interval (never) should be. It's a good idea to check at
+ * least *occasionally*, specially since servers will never rarely get
+ * to reboot, since Linux is so robust these days. :-)
+ *
+ * 180 days (six months) seems like a good value.
+ */
+#ifdef EXT2_DFL_CHECKINTERVAL
+#undef EXT2_DFL_CHECKINTERVAL
+#endif
+#define EXT2_DFL_CHECKINTERVAL (86400L * 180L)
+
+ if (get_bool_from_profile(fs_types, "enable_periodic_fsck", 0)) {
+ fs->super->s_checkinterval = EXT2_DFL_CHECKINTERVAL;
+ fs->super->s_max_mnt_count = EXT2_DFL_MAX_MNT_COUNT;
+ /*
+ * Add "jitter" to the superblock's check interval so that we
+ * don't check all the filesystems at the same time. We use a
+ * kludgy hack of using the UUID to derive a random jitter value
+ */
+ for (i = 0, val = 0 ; i < sizeof(fs->super->s_uuid); i++)
+ val += fs->super->s_uuid[i];
+ fs->super->s_max_mnt_count += val % EXT2_DFL_MAX_MNT_COUNT;
+ } else
+ fs->super->s_max_mnt_count = -1;
+
+ /*
+ * Override the creator OS, if applicable
+ */
+ if (creator_os && !set_os(fs->super, creator_os)) {
+ com_err (program_name, 0, _("unknown os - %s"), creator_os);
+ exit(1);
+ }
+
+ /*
+ * For the Hurd, we will turn off filetype since it doesn't
+ * support it.
+ */
+ if (fs->super->s_creator_os == EXT2_OS_HURD)
+ ext2fs_clear_feature_filetype(fs->super);
+
+ /*
+ * Set the volume label...
+ */
+ if (volume_label) {
+ memset(fs->super->s_volume_name, 0,
+ sizeof(fs->super->s_volume_name));
+ strncpy((char *) fs->super->s_volume_name, volume_label,
+ sizeof(fs->super->s_volume_name));
+ }
+
+ /*
+ * Set the last mount directory
+ */
+ if (mount_dir) {
+ memset(fs->super->s_last_mounted, 0,
+ sizeof(fs->super->s_last_mounted));
+ strncpy((char *) fs->super->s_last_mounted, mount_dir,
+ sizeof(fs->super->s_last_mounted));
+ }
+
+ /* Set current default encryption algorithms for data and
+ * filename encryption */
+ if (ext2fs_has_feature_encrypt(fs->super)) {
+ fs->super->s_encrypt_algos[0] =
+ EXT4_ENCRYPTION_MODE_AES_256_XTS;
+ fs->super->s_encrypt_algos[1] =
+ EXT4_ENCRYPTION_MODE_AES_256_CTS;
+ }
+
+ if (ext2fs_has_feature_metadata_csum(fs->super))
+ fs->super->s_checksum_type = EXT2_CRC32C_CHKSUM;
+
+ if (!quiet || noaction)
+ show_stats(fs);
+
+ if (noaction)
+ exit(0);
+
+ if (ext2fs_has_feature_journal_dev(fs->super)) {
+ create_journal_dev(fs);
+ printf("\n");
+ exit(ext2fs_close_free(&fs) ? 1 : 0);
+ }
+
+ if (bad_blocks_filename)
+ read_bb_file(fs, &bb_list, bad_blocks_filename);
+ if (cflag)
+ test_disk(fs, &bb_list);
+ handle_bad_blocks(fs, bb_list);
+
+ fs->stride = fs_stride = fs->super->s_raid_stride;
+ if (!quiet)
+ printf("%s", _("Allocating group tables: "));
+ if (ext2fs_has_feature_flex_bg(fs->super) &&
+ packed_meta_blocks)
+ retval = packed_allocate_tables(fs);
+ else
+ retval = ext2fs_allocate_tables(fs);
+ if (retval) {
+ com_err(program_name, retval, "%s",
+ _("while trying to allocate filesystem tables"));
+ exit(1);
+ }
+ if (!quiet)
+ printf("%s", _("done \n"));
+
+ /*
+ * Unmark bad blocks to calculate overhead, because metadata
+ * blocks and bad blocks can land on the same allocation cluster.
+ */
+ if (bb_list) {
+ retval = ext2fs_badblocks_list_iterate_begin(bb_list,
+ &bb_iter);
+ if (retval) {
+ com_err("ext2fs_badblocks_list_iterate_begin", retval,
+ "%s", _("while unmarking bad blocks"));
+ exit(1);
+ }
+ while (ext2fs_badblocks_list_iterate(bb_iter, &blk))
+ ext2fs_unmark_block_bitmap2(fs->block_map, blk);
+ ext2fs_badblocks_list_iterate_end(bb_iter);
+ }
+
+ retval = ext2fs_convert_subcluster_bitmap(fs, &fs->block_map);
+ if (retval) {
+ com_err(program_name, retval, "%s",
+ _("\n\twhile converting subcluster bitmap"));
+ exit(1);
+ }
+
+ retval = ext2fs_count_used_clusters(fs, fs->super->s_first_data_block,
+ ext2fs_blocks_count(fs->super) - 1,
+ &overhead);
+ if (retval) {
+ com_err(program_name, retval, "%s",
+ _("while calculating overhead"));
+ exit(1);
+ }
+
+ if (bb_list) {
+ retval = ext2fs_badblocks_list_iterate_begin(bb_list,
+ &bb_iter);
+ if (retval) {
+ com_err("ext2fs_badblocks_list_iterate_begin", retval,
+ "%s", _("while marking bad blocks as used"));
+ exit(1);
+ }
+ while (ext2fs_badblocks_list_iterate(bb_iter, &blk))
+ ext2fs_mark_block_bitmap2(fs->block_map, blk);
+ ext2fs_badblocks_list_iterate_end(bb_iter);
+ }
+
+ if (super_only) {
+ check_plausibility(device_name, CHECK_FS_EXIST, NULL);
+ printf(_("%s may be further corrupted by superblock rewrite\n"),
+ device_name);
+ if (!force)
+ proceed_question(proceed_delay);
+ fs->super->s_state |= EXT2_ERROR_FS;
+ fs->flags &= ~(EXT2_FLAG_IB_DIRTY|EXT2_FLAG_BB_DIRTY);
+ /*
+ * The command "mke2fs -S" is used to recover
+ * corrupted file systems, so do not mark any of the
+ * inodes as unused; we want e2fsck to consider all
+ * inodes as potentially containing recoverable data.
+ */
+ if (ext2fs_has_group_desc_csum(fs)) {
+ for (i = 0; i < fs->group_desc_count; i++)
+ ext2fs_bg_itable_unused_set(fs, i, 0);
+ }
+ } else {
+ /* rsv must be a power of two (64kB is MD RAID sb alignment) */
+ blk64_t rsv = 65536 / fs->blocksize;
+ blk64_t blocks = ext2fs_blocks_count(fs->super);
+ blk64_t start;
+ blk64_t ret_blk;
+
+#ifdef ZAP_BOOTBLOCK
+ zap_sector(fs, 0, 2);
+#endif
+
+ /*
+ * Wipe out any old MD RAID (or other) metadata at the end
+ * of the device. This will also verify that the device is
+ * as large as we think. Be careful with very small devices.
+ */
+ start = (blocks & ~(rsv - 1));
+ if (start > rsv)
+ start -= rsv;
+ if (start > 0)
+ retval = ext2fs_zero_blocks2(fs, start, blocks - start,
+ &ret_blk, NULL);
+
+ if (retval) {
+ com_err(program_name, retval,
+ _("while zeroing block %llu at end of filesystem"),
+ (unsigned long long) ret_blk);
+ }
+ write_inode_tables(fs, lazy_itable_init, itable_zeroed);
+ create_root_dir(fs);
+ create_lost_and_found(fs);
+ reserve_inodes(fs);
+ create_bad_block_inode(fs, bb_list);
+ if (ext2fs_has_feature_resize_inode(fs->super)) {
+ retval = ext2fs_create_resize_inode(fs);
+ if (retval) {
+ com_err("ext2fs_create_resize_inode", retval,
+ "%s",
+ _("while reserving blocks for online resize"));
+ exit(1);
+ }
+ }
+ }
+
+ if (journal_device) {
+ ext2_filsys jfs;
+
+ if (!check_plausibility(journal_device, CHECK_BLOCK_DEV,
+ NULL) && !force)
+ proceed_question(proceed_delay);
+ check_mount(journal_device, force, _("journal"));
+
+ retval = ext2fs_open(journal_device, EXT2_FLAG_RW|
+ EXT2_FLAG_JOURNAL_DEV_OK, 0,
+ fs->blocksize, default_io_manager, &jfs);
+ if (retval) {
+ com_err(program_name, retval,
+ _("while trying to open journal device %s\n"),
+ journal_device);
+ exit(1);
+ }
+ if (!quiet) {
+ printf(_("Adding journal to device %s: "),
+ journal_device);
+ fflush(stdout);
+ }
+ retval = ext2fs_add_journal_device(fs, jfs);
+ if(retval) {
+ com_err (program_name, retval,
+ _("\n\twhile trying to add journal to device %s"),
+ journal_device);
+ exit(1);
+ }
+ if (!quiet)
+ printf("%s", _("done\n"));
+ ext2fs_close_free(&jfs);
+ free(journal_device);
+ } else if ((journal_size) ||
+ ext2fs_has_feature_journal(&fs_param)) {
+ overhead += EXT2FS_NUM_B2C(fs, jparams.num_journal_blocks + jparams.num_fc_blocks);
+ if (super_only) {
+ printf("%s", _("Skipping journal creation in super-only mode\n"));
+ fs->super->s_journal_inum = EXT2_JOURNAL_INO;
+ goto no_journal;
+ }
+
+ if (!jparams.num_journal_blocks) {
+ ext2fs_clear_feature_journal(fs->super);
+ ext2fs_clear_feature_orphan_file(fs->super);
+ ext2fs_clear_feature_journal(&fs_param);
+ ext2fs_clear_feature_orphan_file(&fs_param);
+ goto no_journal;
+ }
+ if (!quiet) {
+ printf(_("Creating journal (%u blocks): "),
+ jparams.num_journal_blocks + jparams.num_fc_blocks);
+ fflush(stdout);
+ }
+ retval = ext2fs_add_journal_inode3(fs, &jparams,
+ journal_location,
+ journal_flags);
+ if (retval) {
+ com_err(program_name, retval, "%s",
+ _("\n\twhile trying to create journal"));
+ exit(1);
+ }
+ if (!quiet)
+ printf("%s", _("done\n"));
+ }
+no_journal:
+ if (!super_only &&
+ ext2fs_has_feature_mmp(fs->super)) {
+ retval = ext2fs_mmp_init(fs);
+ if (retval) {
+ fprintf(stderr, "%s",
+ _("\nError while enabling multiple "
+ "mount protection feature."));
+ exit(1);
+ }
+ if (!quiet)
+ printf(_("Multiple mount protection is enabled "
+ "with update interval %d seconds.\n"),
+ fs->super->s_mmp_update_interval);
+ }
+
+ overhead += fs->super->s_first_data_block;
+ if (!super_only)
+ fs->super->s_overhead_clusters = overhead;
+
+ if (ext2fs_has_feature_bigalloc(&fs_param))
+ fix_cluster_bg_counts(fs);
+ if (ext2fs_has_feature_quota(&fs_param))
+ create_quota_inodes(fs);
+ if (ext2fs_has_feature_orphan_file(&fs_param)) {
+ if (!ext2fs_has_feature_journal(&fs_param)) {
+ com_err(program_name, 0, _("cannot set orphan_file "
+ "feature without a journal."));
+ exit(1);
+ }
+ if (!orphan_file_blocks) {
+ orphan_file_blocks =
+ ext2fs_default_orphan_file_blocks(fs);
+ }
+ retval = ext2fs_create_orphan_file(fs, orphan_file_blocks);
+ if (retval) {
+ com_err(program_name, retval,
+ _("while creating orphan file"));
+ exit(1);
+ }
+ }
+
+ retval = mk_hugefiles(fs, device_name);
+ if (retval)
+ com_err(program_name, retval, "while creating huge files");
+ /* Copy files from the specified directory */
+ if (src_root_dir) {
+ if (!quiet)
+ printf("%s", _("Copying files into the device: "));
+
+ retval = populate_fs(fs, EXT2_ROOT_INO, src_root_dir,
+ EXT2_ROOT_INO);
+ if (retval) {
+ com_err(program_name, retval, "%s",
+ _("while populating file system"));
+ exit(1);
+ } else if (!quiet)
+ printf("%s", _("done\n"));
+ }
+
+ if (!quiet)
+ printf("%s", _("Writing superblocks and "
+ "filesystem accounting information: "));
+ checkinterval = fs->super->s_checkinterval;
+ max_mnt_count = fs->super->s_max_mnt_count;
+ retval = ext2fs_close_free(&fs);
+ if (retval) {
+ com_err(program_name, retval, "%s",
+ _("while writing out and closing file system"));
+ retval = 1;
+ } else if (!quiet) {
+ printf("%s", _("done\n\n"));
+ if (!getenv("MKE2FS_SKIP_CHECK_MSG"))
+ print_check_message(max_mnt_count, checkinterval);
+ }
+
+ remove_error_table(&et_ext2_error_table);
+ remove_error_table(&et_prof_error_table);
+ profile_release(profile);
+ for (i=0; fs_types[i]; i++)
+ free(fs_types[i]);
+ free(fs_types);
+ return retval;
+}
diff --git a/misc/mke2fs.conf.5.in b/misc/mke2fs.conf.5.in
new file mode 100644
index 0000000..96dbfcb
--- /dev/null
+++ b/misc/mke2fs.conf.5.in
@@ -0,0 +1,566 @@
+.\" -*- nroff -*-
+.\" Copyright 2006 by Theodore Ts'o. All Rights Reserved.
+.\" This file may be copied under the terms of the GNU Public License.
+.\"
+.TH mke2fs.conf 5 "@E2FSPROGS_MONTH@ @E2FSPROGS_YEAR@" "E2fsprogs version @E2FSPROGS_VERSION@"
+.SH NAME
+mke2fs.conf \- Configuration file for mke2fs
+.SH DESCRIPTION
+.I mke2fs.conf
+is the configuration file for
+.BR mke2fs (8).
+It controls the default parameters used by
+.BR mke2fs (8)
+when it is creating ext2, ext3, or ext4 file systems.
+.PP
+The
+.I mke2fs.conf
+file uses an INI-style format. Stanzas, or top-level sections, are
+delimited by square braces: [ ]. Within each section, each line
+defines a relation, which assigns tags to values, or to a subsection,
+which contains further relations or subsections.
+.\" Tags can be assigned multiple values
+An example of the INI-style format used by this configuration file
+follows below:
+.P
+ [section1]
+.br
+ tag1 = value_a
+.br
+ tag1 = value_b
+.br
+ tag2 = value_c
+.P
+ [section 2]
+.br
+ tag3 = {
+.br
+ subtag1 = subtag_value_a
+.br
+ subtag1 = subtag_value_b
+.br
+ subtag2 = subtag_value_c
+.br
+ }
+.br
+ tag1 = value_d
+.br
+ tag2 = value_e
+.br
+ }
+.P
+Comments are delimited by a semicolon (';') or a hash ('#') character
+at the beginning of the comment, and are terminated by the end of
+line character.
+.P
+Tags and values must be quoted using double quotes if they contain
+spaces. Within a quoted string, the standard backslash interpretations
+apply: "\en" (for the newline character),
+"\et" (for the tab character), "\eb" (for the backspace character),
+and "\e\e" (for the backslash character).
+.P
+Some relations expect a boolean value. The parser is quite liberal on
+recognizing ``yes'', '`y'', ``true'', ``t'', ``1'', ``on'', etc. as a
+boolean true value, and ``no'', ``n'', ``false'', ``nil'', ``0'',
+``off'' as a boolean false value.
+.P
+The following stanzas are used in the
+.I mke2fs.conf
+file. They will be described in more detail in future sections of this
+document.
+.TP
+.I [options]
+Contains relations which influence how mke2fs behaves.
+.TP
+.I [defaults]
+Contains relations which define the default parameters
+used by
+.BR mke2fs (8).
+In general, these defaults may be overridden by a definition in the
+.B fs_types
+stanza, or by a command-line option provided by the user.
+.TP
+.I [fs_types]
+Contains relations which define defaults that should be used for specific
+file system and usage types. The file system type and usage type can be
+specified explicitly using
+the
+.BR \-t and \-T
+options to
+.BR mke2fs (8),
+respectively.
+.TP
+.I [devices]
+Contains relations which define defaults for specific devices.
+.SH THE [options] STANZA
+The following relations are defined in the
+.I [options]
+stanza.
+.TP
+.I proceed_delay
+If this relation is set to a positive integer, then mke2fs will
+wait
+.I proceed_delay
+seconds after asking the user for permission to proceed and
+then continue, even if the
+user has not answered the question. Defaults to 0, which means to wait
+until the user answers the question one way or another.
+.TP
+.I sync_kludge
+If this relation is set to a positive integer, then while writing the
+inode table, mke2fs will request the operating system flush out pending
+writes to initialize the inode table every
+.I sync_kludge
+block groups. This is needed to work around buggy kernels that don't
+handle writeback throttling correctly.
+.SH THE [defaults] STANZA
+The following relations are defined in the
+.I [defaults]
+stanza.
+.TP
+.I creator_os
+This relation specifies the "creator operating system" for the
+file system unless it is overridden on the command line.
+The default value is the OS for which the
+.B mke2fs
+executable was compiled.
+.TP
+.I fs_type
+This relation specifies the default file system type if the user does not
+specify it via the
+.B \-t
+option, or if
+.B mke2fs
+is not started using a program name of the form
+.BI mkfs. fs-type\fR.
+If both the user and the
+.B mke2fs.conf
+file do not specify a default file system type, mke2fs will use a
+default file system type of
+.I ext3
+if a journal was requested via a command-line option, or
+.I ext2
+if not.
+.TP
+.I undo_dir
+This relation specifies the directory where the undo file should be
+stored. It can be overridden via the
+.B E2FSPROGS_UNDO_DIR
+environment variable. If the directory location is set to the value
+.IR none ,
+.B mke2fs
+will not create an undo file.
+.PP
+In addition, any tags that can be specified in a per-file system tags
+subsection as defined below (e.g.,
+.IR blocksize ,
+.IR hash_alg ,
+.IR inode_ratio ,
+.IR inode_size ,
+.IR reserved_ratio ,
+etc.) can also be specified in the
+.I defaults
+stanza to specify the default value to be used if the user does not
+specify one on the command line, and the file system-type
+specific section of the configuration file does not specify a default value.
+.SH THE [fs_types] STANZA
+Each tag in the
+.I [fs_types]
+stanza names a file system type or usage type which can be specified via the
+.B \-t
+or
+.B \-T
+options to
+.BR mke2fs (8),
+respectively.
+.P
+The
+.B mke2fs
+program constructs a list of fs_types by concatenating the file system
+type (i.e., ext2, ext3, etc.) with the usage type list. For most
+configuration options,
+.B mke2fs
+will look for a subsection in the
+.I [fs_types]
+stanza corresponding with each entry in the constructed list, with later
+entries overriding earlier file system or usage types.
+For
+example, consider the following
+.B mke2fs.conf
+fragment:
+.P
+[defaults]
+.br
+ base_features = sparse_super,filetype,resize_inode,dir_index
+.br
+ blocksize = 4096
+.br
+ inode_size = 256
+.br
+ inode_ratio = 16384
+.br
+
+.br
+[fs_types]
+.br
+ ext3 = {
+.br
+ features = has_journal
+.br
+ }
+.br
+ ext4 = {
+.br
+ features = extents,flex_bg
+.br
+ inode_size = 256
+.br
+ }
+.br
+ small = {
+.br
+ blocksize = 1024
+.br
+ inode_ratio = 4096
+.br
+ }
+.br
+ floppy = {
+.br
+ features = ^resize_inode
+.br
+ blocksize = 1024
+.br
+ inode_size = 128
+.br
+ }
+.P
+If mke2fs started with a program name of
+.BR mke2fs.ext4 ,
+then the file system type of ext4 will be used. If the file system is
+smaller than 3 megabytes, and no usage type is specified, then
+.B mke2fs
+will use a default
+usage type of
+.IR floppy .
+This results in an fs_types list of "ext4, floppy". Both the ext4
+subsection and the floppy subsection define an
+.I inode_size
+relation, but since the later entries in the fs_types list supersede
+earlier ones, the configuration parameter for fs_types.floppy.inode_size
+will be used, so the file system will have an inode size of 128.
+.P
+The exception to this resolution is the
+.I features
+tag, which specifies a set of changes to the features used by the
+file system, and which is cumulative. So in the above example, first
+the configuration relation defaults.base_features would enable an
+initial feature set with the sparse_super, filetype, resize_inode, and
+dir_index features enabled. Then configuration relation
+fs_types.ext4.features would enable the extents and flex_bg
+features, and finally the configuration relation
+fs_types.floppy.features would remove
+the resize_inode feature, resulting in a file system feature set
+consisting of the sparse_super, filetype, dir_index,
+extents_and flex_bg features.
+.P
+For each file system type, the following tags may be used in that
+fs_type's subsection. These tags may also be used in the
+.I default
+section:
+.TP
+.I base_features
+This relation specifies the features which are initially enabled for this
+file system type. Only one
+.I base_features
+will be used, so if there are multiple entries in the fs_types list
+whose subsections define the
+.I base_features
+relation, only the last will be used by
+.BR mke2fs (8).
+.TP
+.I enable_periodic_fsck
+This boolean relation specifies whether periodic file system checks should be
+enforced at boot time. If set to true, checks will be forced every
+180 days, or after a random number of mounts. These values may
+be changed later via the
+.B -i
+and
+.B -c
+command-line options to
+.BR tune2fs (8).
+.TP
+.I errors
+Change the behavior of the kernel code when errors are detected.
+In all cases, a file system error will cause
+.BR e2fsck (8)
+to check the file system on the next boot.
+.I errors
+can be one of the following:
+.RS 1.2i
+.TP 1.2i
+.B continue
+Continue normal execution.
+.TP
+.B remount-ro
+Remount file system read-only.
+.TP
+.B panic
+Cause a kernel panic.
+.RE
+.TP
+.I features
+This relation specifies a comma-separated list of features edit
+requests which modify the feature set
+used by the newly constructed file system. The syntax is the same as the
+.B -O
+command-line option to
+.BR mke2fs (8);
+that is, a feature can be prefixed by a caret ('^') symbol to disable
+a named feature. Each
+.I feature
+relation specified in the fs_types list will be applied in the order
+found in the fs_types list.
+.TP
+.I force_undo
+This boolean relation, if set to a value of true, forces
+.B mke2fs
+to always try to create an undo file, even if the undo file might be
+huge and it might extend the time to create the file system image
+because the inode table isn't being initialized lazily.
+.TP
+.I default_features
+This relation specifies set of features which should be enabled or
+disabled after applying the features listed in the
+.I base_features
+and
+.I features
+relations. It may be overridden by the
+.B -O
+command-line option to
+.BR mke2fs (8).
+.TP
+.I auto_64-bit_support
+This relation is a boolean which specifies whether
+.BR mke2fs (8)
+should automatically add the 64bit feature if the number of blocks for
+the file system requires this feature to be enabled. The resize_inode
+feature is also automatically disabled since it doesn't support 64-bit
+block numbers.
+.TP
+.I default_mntopts
+This relation specifies the set of mount options which should be enabled
+by default. These may be changed at a later time with the
+.B -o
+command-line option to
+.BR tune2fs (8).
+.TP
+.I blocksize
+This relation specifies the default blocksize if the user does not
+specify a blocksize on the command line.
+.TP
+.I lazy_itable_init
+This boolean relation specifies whether the inode table should
+be lazily initialized. It only has meaning if the uninit_bg feature is
+enabled. If lazy_itable_init is true and the uninit_bg feature is
+enabled, the inode table will
+not be fully initialized by
+.BR mke2fs (8).
+This speeds up file system
+initialization noticeably, but it requires the kernel to finish
+initializing the file system in the background when the file system is
+first mounted.
+.TP
+.I lazy_journal_init
+This boolean relation specifies whether the journal inode should be
+lazily initialized. It only has meaning if the has_journal feature is
+enabled. If lazy_journal_init is true, the journal inode will not be
+fully zeroed out by
+.BR mke2fs .
+This speeds up file system initialization noticeably, but carries some
+small risk if the system crashes before the journal has been overwritten
+entirely one time.
+.TP
+.I journal_location
+This relation specifies the location of the journal.
+.TP
+.I num_backup_sb
+This relation indicates whether file systems with the
+.B sparse_super2
+feature enabled should be created with 0, 1, or 2 backup superblocks.
+.TP
+.I packed_meta_blocks
+This boolean relation specifies whether the allocation bitmaps, inode
+table, and journal should be located at the beginning of the file system.
+.TP
+.I inode_ratio
+This relation specifies the default inode ratio if the user does not
+specify one on the command line.
+.TP
+.I inode_size
+This relation specifies the default inode size if the user does not
+specify one on the command line.
+.TP
+.I reserved_ratio
+This relation specifies the default percentage of file system blocks
+reserved for the super-user, if the user does not specify one on the command
+line.
+.TP
+.I hash_alg
+This relation specifies the default hash algorithm used for the
+new file systems with hashed b-tree directories. Valid algorithms
+accepted are:
+.IR legacy ,
+.IR half_md4 ,
+and
+.IR tea .
+.TP
+.I flex_bg_size
+This relation specifies the number of block groups that will be packed
+together to create one large virtual block group on an ext4 file system.
+This improves meta-data locality and performance on meta-data heavy
+workloads. The number of groups must be a power of 2 and may only be
+specified if the flex_bg file system feature is enabled.
+.TP
+.I options
+This relation specifies additional extended options which should be
+treated by
+.BR mke2fs (8)
+as if they were prepended to the argument of the
+.B -E
+option. This can be used to configure the default extended options used
+by
+.BR mke2fs (8)
+on a per-file system type basis.
+.TP
+.I discard
+This boolean relation specifies whether the
+.BR mke2fs (8)
+should attempt to discard device prior to file system creation.
+.TP
+.I cluster_size
+This relation specifies the default cluster size if the bigalloc file
+system feature is enabled. It can be overridden via the
+.B \-C
+command line option to
+.BR mke2fs (8)
+.TP
+.I make_hugefiles
+This boolean relation enables the creation of pre-allocated files as
+part of formatting the file system. The extent tree blocks for these
+pre-allocated files will be placed near the beginning of the file
+system, so that if all of the other metadata blocks are also configured
+to be placed near the beginning of the file system (by disabling the
+backup superblocks, using the packed_meta_blocks option, etc.), the data
+blocks of the pre-allocated files will be contiguous.
+.TP
+.I hugefiles_dir
+This relation specifies the directory where huge files are created,
+relative to the file system root.
+.TP
+.I hugefiles_uid
+This relation controls the user ownership for all of the files and
+directories created by the
+.I make_hugefiles
+feature.
+.TP
+.I hugefiles_gid
+This relation controls the group ownership for all of the files and
+directories created by the
+.I make_hugefiles
+feature.
+.TP
+.I hugefiles_umask
+This relation specifies the umask used when creating the files and
+directories by the
+.I make_hugefiles
+feature.
+.TP
+.I num_hugefiles
+This relation specifies the number of huge files to be created. If this
+relation is not specified, or is set to zero, and the
+.I hugefiles_size
+relation is non-zero, then
+.I make_hugefiles
+will create as many huge files as can fit to fill the entire file system.
+.TP
+.I hugefiles_slack
+This relation specifies how much space should be reserved for other
+files.
+.TP
+.I hugefiles_size
+This relation specifies the size of the huge files. If this relation is
+not specified, the default is to fill the entire file system.
+.TP
+.I hugefiles_align
+This relation specifies the alignment for the start block of the huge
+files. It also forces the size of huge files to be a multiple of the
+requested alignment. If this relation is not specified, no alignment
+requirement will be imposed on the huge files.
+.TP
+.I hugefiles_align_disk
+This relations specifies whether the alignment should be relative to the
+beginning of the hard drive (assuming that the starting offset of the
+partition is available to mke2fs). The default value is false, which
+will cause hugefile alignment to be relative to the beginning of the
+file system.
+.TP
+.I hugefiles_name
+This relation specifies the base file name for the huge files.
+.TP
+.I hugefiles_digits
+This relation specifies the (zero-padded) width of the field for the
+huge file number.
+.TP
+.I warn_y2038_dates
+This boolean relation specifies whether mke2fs will issue a warning
+when creating a file system with 128 byte inodes (and so therefore will
+not support dates after January 19th, 2038). The default value is true,
+except for file systems created for the GNU Hurd since it only supports
+128-byte inodes.
+.TP
+.I zero_hugefiles
+This boolean relation specifies whether or not zero blocks will be
+written to the hugefiles while
+.BR mke2fs (8)
+is creating them. By default, zero blocks will be written to the huge
+files to avoid stale data from being made available to potentially
+untrusted user programs, unless the device supports a discard/trim
+operation which will take care of zeroing the device blocks. By setting
+.I zero_hugefiles
+to false, this step will always be skipped, which can be useful if it is
+known that the disk has been previously erased, or if the user programs
+that will have access to the huge files are trusted to not reveal stale
+data.
+.TP
+.I encoding
+This relation defines the file name encoding to be used if the casefold
+feature is enabled. Currently the only valid encoding is utf8-12.1 or
+utf8, which requests the most recent Unicode version; since 12.1 is the only
+available Unicode version, utf8 and utf8-12.1 have the same result.
+.I encoding_flags
+This relation defines encoding-specific flags. For utf8 encodings, the
+only available flag is strict, which will cause attempts to create file
+names containing invalid Unicode characters to be rejected by the
+kernel. Strict mode is not enabled by default.
+.SH THE [devices] STANZA
+Each tag in the
+.I [devices]
+stanza names device name so that per-device defaults can be specified.
+.TP
+.I fs_type
+This relation specifies the default parameter for the
+.B \-t
+option, if this option isn't specified on the command line.
+.TP
+.I usage_types
+This relation specifies the default parameter for the
+.B \-T
+option, if this option isn't specified on the command line.
+.SH FILES
+.TP
+.I /etc/mke2fs.conf
+The configuration file for
+.BR mke2fs (8).
+.SH SEE ALSO
+.BR mke2fs (8)
diff --git a/misc/mke2fs.conf.in b/misc/mke2fs.conf.in
new file mode 100644
index 0000000..b7fc95d
--- /dev/null
+++ b/misc/mke2fs.conf.in
@@ -0,0 +1,45 @@
+[defaults]
+ base_features = sparse_super,large_file,filetype,resize_inode,dir_index,ext_attr
+ default_mntopts = acl,user_xattr
+ enable_periodic_fsck = 0
+ blocksize = 4096
+ inode_size = 256
+ inode_ratio = 16384
+
+[fs_types]
+ ext3 = {
+ features = has_journal
+ }
+ ext4 = {
+ features = has_journal,extent,huge_file,flex_bg,metadata_csum,metadata_csum_seed,64bit,dir_nlink,extra_isize,orphan_file
+ }
+ small = {
+ blocksize = 1024
+ inode_ratio = 4096
+ }
+ floppy = {
+ blocksize = 1024
+ inode_ratio = 8192
+ }
+ big = {
+ inode_ratio = 32768
+ }
+ huge = {
+ inode_ratio = 65536
+ }
+ news = {
+ inode_ratio = 4096
+ }
+ largefile = {
+ inode_ratio = 1048576
+ blocksize = -1
+ }
+ largefile4 = {
+ inode_ratio = 4194304
+ blocksize = -1
+ }
+ hurd = {
+ blocksize = 4096
+ inode_size = 128
+ warn_y2038_dates = 0
+ }
diff --git a/misc/mke2fs.h b/misc/mke2fs.h
new file mode 100644
index 0000000..ce72cb3
--- /dev/null
+++ b/misc/mke2fs.h
@@ -0,0 +1,30 @@
+/*
+ * mke2fs.h
+ *
+ * Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+ * 2003, 2004, 2005 by Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ * %End-Header%
+ */
+
+/* mke2fs.c */
+extern const char * program_name;
+extern int quiet;
+extern int verbose;
+extern int zero_hugefile;
+extern char **fs_types;
+
+extern char *get_string_from_profile(char **types, const char *opt,
+ const char *def_val);
+extern int get_int_from_profile(char **types, const char *opt, int def_val);
+extern int get_bool_from_profile(char **types, const char *opt, int def_val);
+extern int int_log10(unsigned long long arg);
+
+/* mk_hugefiles.c */
+extern errcode_t mk_hugefiles(ext2_filsys fs, const char *device_name);
+
+
+
diff --git a/misc/mklost+found.8.in b/misc/mklost+found.8.in
new file mode 100644
index 0000000..d338239
--- /dev/null
+++ b/misc/mklost+found.8.in
@@ -0,0 +1,43 @@
+.\" -*- nroff -*-
+.\" Copyright 1993, 1994, 1995 by Theodore Ts'o. All Rights Reserved.
+.\" This file may be copied under the terms of the GNU Public License.
+.\"
+.TH MKLOST+FOUND 8 "@E2FSPROGS_MONTH@ @E2FSPROGS_YEAR@" "E2fsprogs version @E2FSPROGS_VERSION@"
+.SH NAME
+mklost+found \- create a lost+found directory on a mounted Linux
+second extended file system
+.SH SYNOPSIS
+.B mklost+found
+.SH DESCRIPTION
+.B mklost+found
+is used to create a
+.I lost+found
+directory in the current working directory on a Linux second extended
+file system. There is normally a
+.I lost+found
+directory in the root directory of each file system.
+.PP
+.B mklost+found
+pre-allocates disk blocks to the
+.I lost+found
+directory so that when
+.BR e2fsck (8)
+is being run to recover a file system, it does not need to allocate blocks in
+the file system to store a large number of unlinked files. This ensures that
+.B e2fsck
+will not have to allocate data blocks in the file system during recovery.
+.SH OPTIONS
+There are none.
+.SH AUTHOR
+.B mklost+found
+has been written by Remy Card <Remy.Card@linux.org>. It is currently being
+maintained by Theodore Ts'o <tytso@alum.mit.edu>.
+.SH BUGS
+There are none :-)
+.SH AVAILABILITY
+.B mklost+found
+is part of the e2fsprogs package and is available from
+http://e2fsprogs.sourceforge.net.
+.SH SEE ALSO
+.BR e2fsck (8),
+.BR mke2fs (8)
diff --git a/misc/mklost+found.c b/misc/mklost+found.c
new file mode 100644
index 0000000..1431187
--- /dev/null
+++ b/misc/mklost+found.c
@@ -0,0 +1,87 @@
+/*
+ * mklost+found.c - Creates a directory lost+found on a mounted second
+ * extended file system
+ *
+ * Copyright (C) 1992, 1993 Remy Card <card@masi.ibp.fr>
+ *
+ * This file can be redistributed under the terms of the GNU General
+ * Public License
+ */
+
+/*
+ * History:
+ * 93/04/22 - Creation
+ */
+
+#include "config.h"
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include "ext2fs/ext2_fs.h"
+#include "../version.h"
+#include "support/nls-enable.h"
+
+#define LPF "lost+found"
+
+int main (int argc, char ** argv)
+{
+ char name[EXT2_NAME_LEN + 2];
+ char path[sizeof (LPF) + 1 + 256];
+ struct stat st;
+ int i, j;
+ int d;
+
+#ifdef ENABLE_NLS
+ setlocale(LC_MESSAGES, "");
+ setlocale(LC_CTYPE, "");
+ bindtextdomain(NLS_CAT_NAME, LOCALEDIR);
+ textdomain(NLS_CAT_NAME);
+#endif
+ fprintf (stderr, "mklost+found %s (%s)\n", E2FSPROGS_VERSION,
+ E2FSPROGS_DATE);
+ if (argc != 1) {
+ (void)argv; /* avoid unused argument warning */
+ fprintf (stderr, "%s", _("Usage: mklost+found\n"));
+ exit(1);
+ }
+ if (mkdir (LPF, 0700) == -1) {
+ perror ("mkdir");
+ exit(1);
+ }
+
+ i = 0;
+ memset (name, 'x', 246);
+ do {
+ sprintf (name + 246, "%08d", i);
+ strcpy (path, LPF);
+ strcat (path, "/");
+ strcat (path, name);
+ if ((d = creat (path, 0644)) == -1) {
+ perror ("creat");
+ exit (1);
+ }
+ i++;
+ close (d);
+ if (stat (LPF, &st) == -1) {
+ perror ("stat");
+ exit (1);
+ }
+ } while (st.st_size <= (EXT2_NDIR_BLOCKS - 1) * st.st_blksize);
+ for (j = 0; j < i; j++) {
+ sprintf (name + 246, "%08d", j);
+ strcpy (path, LPF);
+ strcat (path, "/");
+ strcat (path, name);
+ if (unlink (path) == -1) {
+ perror ("unlink");
+ exit (1);
+ }
+ }
+ exit (0);
+}
diff --git a/misc/partinfo.c b/misc/partinfo.c
new file mode 100644
index 0000000..b79d183
--- /dev/null
+++ b/misc/partinfo.c
@@ -0,0 +1,80 @@
+/*
+ * partinfo.c
+ *
+ * Originally written by Alain Knaff, <alknaff@innet.lu>.
+ *
+ * Cleaned up by Theodore Ts'o, <tytso@mit.edu>.
+ *
+ */
+
+#include "config.h"
+#include <sys/types.h>
+#include <fcntl.h>
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+#include <stdio.h>
+#include <linux/hdreg.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include "support/nls-enable.h"
+#include "et/com_err.h"
+
+#if defined(__linux__) && defined(_IO) && !defined(BLKGETSIZE)
+#define BLKGETSIZE _IO(0x12,96) /* return device size */
+#endif
+
+int main(int argc, char **argv)
+{
+ struct hd_geometry loc;
+ int fd, i;
+ unsigned long size;
+
+#ifdef ENABLE_NLS
+ setlocale(LC_MESSAGES, "");
+ setlocale(LC_CTYPE, "");
+ bindtextdomain(NLS_CAT_NAME, LOCALEDIR);
+ textdomain(NLS_CAT_NAME);
+ set_com_err_gettext(gettext);
+#endif
+ if (argc == 1) {
+ fprintf(stderr, _("Usage: %s device...\n\nPrints out the "
+ "partition information for each given device.\n"
+ "For example: %s /dev/hda\n\n"), argv[0], argv[0]);
+ exit(1);
+ }
+
+ for (i=1; i < argc; i++) {
+ fd = open(argv[i], O_RDONLY);
+
+ if (fd < 0) {
+ fprintf(stderr, _("Cannot open %s: %s"),
+ argv[i], strerror(errno));
+ continue;
+ }
+
+ if (ioctl(fd, HDIO_GETGEO, &loc) < 0) {
+ fprintf(stderr, _("Cannot get geometry of %s: %s"),
+ argv[i], strerror(errno));
+ close(fd);
+ continue;
+ }
+
+
+ if (ioctl(fd, BLKGETSIZE, &size) < 0) {
+ fprintf(stderr, _("Cannot get size of %s: %s"),
+ argv[i], strerror(errno));
+ close(fd);
+ continue;
+ }
+
+ printf(_("%s: h=%3d s=%3d c=%4d start=%8d size=%8lu end=%8d\n"),
+ argv[i],
+ loc.heads, (int)loc.sectors, loc.cylinders,
+ (int)loc.start, size, (int) loc.start + size -1);
+ close(fd);
+ }
+ exit(0);
+}
diff --git a/misc/profile-to-c.awk b/misc/profile-to-c.awk
new file mode 100644
index 0000000..814f723
--- /dev/null
+++ b/misc/profile-to-c.awk
@@ -0,0 +1,13 @@
+#!/bin/awk
+BEGIN {
+ printf("const char *mke2fs_default_profile = \n");
+}
+
+{
+ gsub("\"","\\\"",$0);
+ printf(" \"%s\\n\"\n", $0);
+}
+
+END {
+ printf(";\n", str)
+}
diff --git a/misc/tune2fs.8.in b/misc/tune2fs.8.in
new file mode 100644
index 0000000..dcf108c
--- /dev/null
+++ b/misc/tune2fs.8.in
@@ -0,0 +1,849 @@
+.\" Revision 1.0 93/06/3 23:00 chk
+.\" Initial revision
+.\"
+.\"
+.TH TUNE2FS 8 "@E2FSPROGS_MONTH@ @E2FSPROGS_YEAR@" "E2fsprogs version @E2FSPROGS_VERSION@"
+.SH NAME
+tune2fs \- adjust tunable file system parameters on ext2/ext3/ext4 file systems
+.SH SYNOPSIS
+.B tune2fs
+[
+.B \-l
+]
+[
+.B \-c
+.I max-mount-counts
+]
+[
+.B \-e
+.I errors-behavior
+]
+[
+.B \-f
+]
+[
+.B \-i
+.I interval-between-checks
+]
+[
+.B \-I
+.I new_inode_size
+]
+[
+.B \-j
+]
+[
+.B \-J
+.I journal-options
+]
+[
+.B \-m
+.I reserved-blocks-percentage
+]
+[
+.B \-o
+.RI [^]mount-options [,...]
+]
+[
+.B \-r
+.I reserved-blocks-count
+]
+[
+.B \-u
+.I user
+]
+[
+.B \-g
+.I group
+]
+[
+.B \-C
+.I mount-count
+]
+[
+.B \-E
+.I extended-options
+]
+[
+.B \-L
+.I volume-label
+]
+[
+.B \-M
+.I last-mounted-directory
+]
+[
+.B \-O
+.RI [^] feature [,...]
+]
+[
+.B \-Q
+.I quota-options
+]
+[
+.B \-T
+.I time-last-checked
+]
+[
+.B \-U
+.I UUID
+]
+[
+.B \-z
+.I undo_file
+]
+device
+.SH DESCRIPTION
+.B tune2fs
+allows the system administrator to adjust various tunable file system
+parameters on Linux ext2, ext3, or ext4 file systems. The current values
+of these options can be displayed by using the
+.B -l
+option to
+.BR tune2fs (8)
+program, or by using the
+.BR dumpe2fs (8)
+program.
+.PP
+The
+.I device
+specifier can either be a filename (i.e., /dev/sda1), or a LABEL or UUID
+specifier: "\fBLABEL=\fIvolume-label\fR" or "\fBUUID=\fIuuid\fR". (i.e.,
+LABEL=home or UUID=e40486c6-84d5-4f2f-b99c-032281799c9d).
+.SH OPTIONS
+.TP
+.BI \-c " max-mount-counts"
+Adjust the number of mounts after which the file system will be checked by
+.BR e2fsck (8).
+If
+.I max-mount-counts
+is the string "random", tune2fs will use a random value between 20 and 40.
+If
+.I max-mount-counts
+is 0 or \-1, the number of times the file system is mounted will be disregarded
+by
+.BR e2fsck (8)
+and the kernel.
+.sp
+Staggering the mount-counts at which file systems are forcibly
+checked will avoid all file systems being checked at one time
+when using journaled file systems.
+.sp
+Mount-count-dependent checking is disabled by default to avoid
+unanticipated long reboots while e2fsck does its work. If you
+are concerned about file system corruptions caused by potential hardware
+problems of kernel bugs, a better solution than mount-count-dependent
+checking is to use the
+.BR e2scrub (8)
+program. This does require placing the file system on an LVM volume,
+however.
+.TP
+.BI \-C " mount-count"
+Set the number of times the file system has been mounted.
+If set to a greater value than the max-mount-counts parameter
+set by the
+.B \-c
+option,
+.BR e2fsck (8)
+will check the file system at the next reboot.
+.TP
+.BI \-e " error-behavior"
+Change the behavior of the kernel code when errors are detected.
+In all cases, a file system error will cause
+.BR e2fsck (8)
+to check the file system on the next boot.
+.I error-behavior
+can be one of the following:
+.RS 1.2i
+.TP 1.2i
+.B continue
+Continue normal execution.
+.TP
+.B remount-ro
+Remount file system read-only.
+.TP
+.B panic
+Cause a kernel panic.
+.RE
+.TP
+.BI \-E " extended-options"
+Set extended options for the file system. Extended options are comma
+separated, and may take an argument using the equals ('=') sign.
+The following extended options are supported:
+.RS 1.2i
+.TP
+.B clear_mmp
+Reset the MMP block (if any) back to the clean state. Use only if
+absolutely certain the device is not currently mounted or being
+fscked, or major file system corruption can result. Needs '-f'.
+.TP
+.BI mmp_update_interval= interval
+Adjust the initial MMP update interval to
+.I interval
+seconds. Specifying an
+.I interval
+of 0 means to use the default interval. The specified interval must
+be less than 300 seconds. Requires that the
+.B mmp
+feature be enabled.
+.TP
+.BI stride= stride-size
+Configure the file system for a RAID array with
+.I stride-size
+file system blocks. This is the number of blocks read or written to disk
+before moving to next disk. This mostly affects placement of file system
+metadata like bitmaps at
+.BR mke2fs (2)
+time to avoid placing them on a single disk, which can hurt the performance.
+It may also be used by block allocator.
+.TP
+.BI stripe_width= stripe-width
+Configure the file system for a RAID array with
+.I stripe-width
+file system blocks per stripe. This is typically be stride-size * N, where
+N is the number of data disks in the RAID (e.g. RAID 5 N+1, RAID 6 N+2).
+This allows the block allocator to prevent read-modify-write of the
+parity in a RAID stripe if possible when the data is written.
+.TP
+.BI hash_alg= hash-alg
+Set the default hash algorithm used for file systems with hashed b-tree
+directories. Valid algorithms accepted are:
+.IR legacy ,
+.IR half_md4 ,
+and
+.IR tea .
+.TP
+.BI encoding= encoding-name
+Enable the
+.I casefold
+feature in the super block and set
+.I encoding-name
+as the encoding to be used. If
+.I encoding-name
+is not specified, utf8 is used. The encoding cannot be altered if casefold
+was previously enabled.
+.TP
+.BI encoding_flags= encoding-flags
+Define parameters for file name character encoding operations. If a
+flag is not changed using this parameter, its default value is used.
+.I encoding-flags
+should be a comma-separated lists of flags to be enabled. The flags cannot be
+altered if casefold was previously enabled.
+
+The only flag that can be set right now is
+.I strict
+which means that invalid strings should be rejected by the file system.
+In the default configuration, the
+.I strict
+flag is disabled.
+.TP
+.BI mount_opts= mount_option_string
+Set a set of default mount options which will be used when the file
+system is mounted. Unlike the bitmask-based default mount options which
+can be specified with the
+.B -o
+option,
+.I mount_option_string
+is an arbitrary string with a maximum length of 63 bytes, which is
+stored in the superblock.
+.IP
+The ext4 file system driver will first apply
+the bitmask-based default options, and then parse the
+.IR mount_option_string ,
+before parsing the mount options passed from the
+.BR mount (8)
+program.
+.IP
+This superblock setting is only honored in 2.6.35+ kernels;
+and not at all by the ext2 and ext3 file system drivers.
+.TP
+.BI orphan_file_size= size
+Set size of the file for tracking unlinked but still open inodes and inodes
+with truncate in progress. Larger file allows for better scalability, reserving
+a few blocks per cpu is ideal.
+.TP
+.B force_fsck
+Set a flag in the file system superblock indicating that errors have been found.
+This will force fsck to run at the next mount.
+.TP
+.B test_fs
+Set a flag in the file system superblock indicating that it may be
+mounted using experimental kernel code, such as the ext4dev file system.
+.TP
+.B ^test_fs
+Clear the test_fs flag, indicating the file system should only be mounted
+using production-level file system code.
+.RE
+.TP
+.B \-f
+Force the tune2fs operation to complete even in the face of errors. This
+option is useful when removing the
+.B has_journal
+file system feature from a file system which has
+an external journal (or is corrupted
+such that it appears to have an external journal), but that
+external journal is not available. If the file system appears to require
+journal replay, the
+.B \-f
+flag must be specified twice to proceed.
+.sp
+.B WARNING:
+Removing an external journal from a file system which was not cleanly unmounted
+without first replaying the external journal can result in
+severe data loss and file system corruption.
+.TP
+.BI \-g " group"
+Set the group which can use the reserved file system blocks.
+The
+.I group
+parameter can be a numerical gid or a group name. If a group name is given,
+it is converted to a numerical gid before it is stored in the superblock.
+.TP
+.B \-i " \fIinterval-between-checks\fR[\fBd\fR|\fBm\fR|\fBw\fR]"
+Adjust the maximal time between two file system checks.
+No suffix or
+.B d
+will interpret the number
+.I interval-between-checks
+as days,
+.B m
+as months, and
+.B w
+as weeks. A value of zero will disable the time-dependent checking.
+.sp
+There are pros and cons to disabling these periodic checks; see the
+discussion under the
+.B \-c
+(mount-count-dependent check) option for details.
+.TP
+.B \-I
+Change the inode size used by the file system. This requires rewriting
+the inode table, so it requires that the file system is checked for
+consistency first using
+.BR e2fsck (8).
+This operation can also take a while and the file system can be
+corrupted and data lost if it is interrupted while in the middle of
+converting the file system. Backing up the file system before changing
+inode size is recommended.
+.IP
+File systems with an inode size of 128 bytes do not support timestamps
+beyond January 19, 2038. Inodes which are 256 bytes or larger will
+support extended timestamps, project id's, and the ability to store some
+extended attributes in the inode table for improved performance.
+.TP
+.B \-j
+Add an ext3 journal to the file system. If the
+.B \-J
+option is not specified, the default journal parameters will be used to create
+an appropriately sized journal (given the size of the file system)
+stored within the file system. Note that you must be using a kernel
+which has ext3 support in order to actually make use of the journal.
+.IP
+If this option is used to create a journal on a mounted file system, an
+immutable file,
+.BR .journal ,
+will be created in the top-level directory of the file system, as it is
+the only safe way to create the journal inode while the file system is
+mounted. While the ext3 journal is visible, it is not safe to
+delete it, or modify it while the file system is mounted; for this
+reason the file is marked immutable.
+While checking unmounted file systems,
+.BR e2fsck (8)
+will automatically move
+.B .journal
+files to the invisible, reserved journal inode. For all file systems
+except for the root file system, this should happen automatically and
+naturally during the next reboot cycle. Since the root file system is
+mounted read-only,
+.BR e2fsck (8)
+must be run from a rescue floppy in order to effect this transition.
+.IP
+On some distributions, such as Debian, if an initial ramdisk is used,
+the initrd scripts will automatically convert an ext2 root file system
+to ext3 if the
+.B /etc/fstab
+file specifies the ext3 file system for the root file system in order to
+avoid requiring the use of a rescue floppy to add an ext3 journal to
+the root file system.
+.TP
+.BR \-J " journal-options"
+Override the default ext3 journal parameters. Journal options are comma
+separated, and may take an argument using the equals ('=') sign.
+The following journal options are supported:
+.RS 1.2i
+.TP
+.BI size= journal-size
+Create a journal stored in the file system of size
+.I journal-size
+megabytes. The size of the journal must be at least 1024 file system blocks
+(i.e., 1MB if using 1k blocks, 4MB if using 4k blocks, etc.)
+and may be no more than 10,240,000 file system blocks.
+There must be enough free space in the file system to create a journal of
+that size.
+.TP
+.BI fast_commit_size= fast-commit-size
+Create an additional fast commit journal area of size
+.I fast-commit-size
+kilobytes.
+This option is only valid if
+.B fast_commit
+feature is enabled
+on the file system. If this option is not specified and if
+.B fast_commit
+feature is turned on, fast commit area size defaults to
+.I journal-size
+/ 64 megabytes. The total size of the journal with
+.B fast_commit
+feature set is
+.I journal-size
++ (
+.I fast-commit-size
+* 1024) megabytes. The total journal size may be no more than
+10,240,000 file system blocks or half the total file system size
+(whichever is smaller).
+.TP
+.BI location =journal-location
+Specify the location of the journal. The argument
+.I journal-location
+can either be specified as a block number, or if the number has a units
+suffix (e.g., 'M', 'G', etc.) interpret it as the offset from the
+beginning of the file system.
+@JDEV@.TP
+@JDEV@.BI device= external-journal
+@JDEV@Attach the file system to the journal block device located on
+@JDEV@.IR external-journal .
+@JDEV@The external
+@JDEV@journal must have been already created using the command
+@JDEV@.IP
+@JDEV@.B mke2fs -O journal_dev
+@JDEV@.I external-journal
+@JDEV@.IP
+@JDEV@Note that
+@JDEV@.I external-journal
+@JDEV@must be formatted with the same block
+@JDEV@size as file systems which will be using it.
+@JDEV@In addition, while there is support for attaching
+@JDEV@multiple file systems to a single external journal,
+@JDEV@the Linux kernel and
+@JDEV@.BR e2fsck (8)
+@JDEV@do not currently support shared external journals yet.
+@JDEV@.IP
+@JDEV@Instead of specifying a device name directly,
+@JDEV@.I external-journal
+@JDEV@can also be specified by either
+@JDEV@.BI LABEL= label
+@JDEV@or
+@JDEV@.BI UUID= UUID
+@JDEV@to locate the external journal by either the volume label or UUID
+@JDEV@stored in the ext2 superblock at the start of the journal. Use
+@JDEV@.BR dumpe2fs (8)
+@JDEV@to display a journal device's volume label and UUID. See also the
+@JDEV@.B -L
+@JDEV@option of
+@JDEV@.BR tune2fs (8).
+.RE
+@JDEV@.IP
+@JDEV@Only one of the
+@JDEV@.BR size " or " device
+@JDEV@options can be given for a file system.
+.TP
+.B \-l
+List the contents of the file system superblock, including the current
+values of the parameters that can be set via this program.
+.TP
+.BI \-L " volume-label"
+Set the volume label of the file system.
+Ext2 file system labels can be at most 16 characters long; if
+.I volume-label
+is longer than 16 characters,
+.B tune2fs
+will truncate it and print a warning. For other file systems that
+support online label manipulation and are mounted
+.B tune2fs
+will work as well, but it will not attempt to truncate the
+.I volume-label
+at all. The volume label can be used by
+.BR mount (8),
+.BR fsck (8),
+and
+.BR /etc/fstab (5)
+(and possibly others) by specifying
+.BI LABEL= volume-label
+instead of a block special device name like
+.BR /dev/hda5 .
+.TP
+.BI \-m " reserved-blocks-percentage"
+Set the percentage of the file system which may only be allocated
+by privileged processes. Reserving some number of file system blocks
+for use by privileged processes is done
+to avoid file system fragmentation, and to allow system
+daemons, such as
+.BR syslogd (8),
+to continue to function correctly after non-privileged processes are
+prevented from writing to the file system. Normally, the default percentage
+of reserved blocks is 5%.
+.TP
+.BI \-M " last-mounted-directory"
+Set the last-mounted directory for the file system.
+.TP
+.BR \-o " [^]\fImount-option\fR[,...]"
+Set or clear the indicated default mount options in the file system.
+Default mount options can be overridden by mount options specified
+either in
+.BR /etc/fstab (5)
+or on the command line arguments to
+.BR mount (8).
+Older kernels may not support this feature; in particular,
+kernels which predate 2.4.20 will almost certainly ignore the
+default mount options field in the superblock.
+.IP
+More than one mount option can be cleared or set by separating
+features with commas. Mount options prefixed with a
+caret character ('^') will be cleared in the file system's superblock;
+mount options without a prefix character or prefixed with a plus
+character ('+') will be added to the file system.
+.IP
+The following mount options can be set or cleared using
+.BR tune2fs :
+.RS 1.2i
+.TP
+.B debug
+Enable debugging code for this file system.
+.TP
+.B bsdgroups
+Emulate BSD behavior when creating new files: they will take the group-id
+of the directory in which they were created. The standard System V behavior
+is the default, where newly created files take on the fsgid of the current
+process, unless the directory has the setgid bit set, in which case it takes
+the gid from the parent directory, and also gets the setgid bit set if it is
+a directory itself.
+.TP
+.B user_xattr
+Enable user-specified extended attributes.
+.TP
+.B acl
+Enable Posix Access Control Lists.
+.TP
+.B uid16
+Disables 32-bit UIDs and GIDs. This is for interoperability with
+older kernels which only store and expect 16-bit values.
+.TP
+.B journal_data
+When the file system is mounted with journaling enabled, all data
+(not just metadata) is committed into the journal prior to being written
+into the main file system.
+.TP
+.B journal_data_ordered
+When the file system is mounted with journaling enabled, all data is forced
+directly out to the main file system prior to its metadata being committed
+to the journal.
+.TP
+.B journal_data_writeback
+When the file system is mounted with journaling enabled, data may be
+written into the main file system after its metadata has been committed
+to the journal. This may increase throughput, however, it may allow old
+data to appear in files after a crash and journal recovery.
+.TP
+.B nobarrier
+The file system will be mounted with barrier operations in the journal
+disabled. (This option is currently only supported by the ext4 file
+system driver in 2.6.35+ kernels.)
+.TP
+.B block_validity
+The file system will be mounted with the block_validity option enabled,
+which causes extra checks to be performed after reading or writing from
+the file system. This prevents corrupted metadata blocks from causing
+file system damage by overwriting parts of the inode table or block
+group descriptors. This comes at the cost of increased memory and CPU
+overhead, so it is enabled only for debugging purposes. (This option is
+currently only supported by the ext4 file system driver in 2.6.35+
+kernels.)
+.TP
+.B discard
+The file system will be mounted with the discard mount option. This will
+cause the file system driver to attempt to use the trim/discard feature
+of some storage devices (such as SSD's and thin-provisioned drives
+available in some enterprise storage arrays) to inform the storage
+device that blocks belonging to deleted files can be reused for other
+purposes. (This option is currently only supported by the ext4 file
+system driver in 2.6.35+ kernels.)
+.TP
+.B nodelalloc
+The file system will be mounted with the nodelalloc mount option. This
+will disable the delayed allocation feature. (This option is currently
+only supported by the ext4 file system driver in 2.6.35+ kernels.)
+.RE
+.TP
+.BR \-O " [^]\fIfeature\fR[,...]"
+Set or clear the indicated file system features (options) in the file system.
+More than one file system feature can be cleared or set by separating
+features with commas. File System features prefixed with a
+caret character ('^') will be cleared in the file system's superblock;
+file system features without a prefix character or prefixed with a plus
+character ('+') will be added to the file system. For a detailed
+description of the file system features, please see the man page
+.BR ext4 (5).
+.IP
+The following file system features can be set or cleared using
+.BR tune2fs :
+.RS 1.2i
+.TP
+.B 64bit
+Enable the file system to be larger than 2^32 blocks.
+.TP
+.B casefold
+Enable support for file system level casefolding.
+The option can be cleared only if filesystem has no
+directories with
+.B F
+attribute.
+.TP
+.B dir_index
+Use hashed b-trees to speed up lookups for large directories.
+.TP
+.B dir_nlink
+Allow more than 65000 subdirectories per directory.
+.TP
+.B ea_inode
+Allow the value of each extended attribute to be placed in the data blocks of a
+separate inode if necessary, increasing the limit on the size and number of
+extended attributes per file.
+.B Tune2fs
+currently only supports setting this file system feature.
+.TP
+.B encrypt
+Enable support for file system level encryption.
+.B Tune2fs
+currently only supports setting this file system feature.
+.TP
+.B extent
+Enable the use of extent trees to store the location of data blocks in inodes.
+.B Tune2fs
+currently only supports setting this file system feature.
+.TP
+.B extra_isize
+Enable the extended inode fields used by ext4.
+.TP
+.B filetype
+Store file type information in directory entries.
+.TP
+.B flex_bg
+Allow bitmaps and inode tables for a block group to be placed
+anywhere on the storage media. \fBTune2fs\fR will not reorganize
+the location of the inode tables and allocation bitmaps, as
+.BR mke2fs (8)
+will do when it creates a freshly formatted file system with
+.B flex_bg
+enabled.
+.TP
+.B has_journal
+Use a journal to ensure file system consistency even across unclean shutdowns.
+Setting the file system feature is equivalent to using the
+.B \-j
+option.
+.TP
+.TP
+.B fast_commit
+Enable fast commit journaling feature to improve fsync latency.
+.TP
+.B large_dir
+Increase the limit on the number of files per directory.
+.B Tune2fs
+currently only supports setting this file system feature.
+.TP
+.B huge_file
+Support files larger than 2 terabytes in size.
+.TP
+.B large_file
+File System can contain files that are greater than 2GB.
+.TP
+.B metadata_csum
+Store a checksum to protect the contents in each metadata block.
+.TP
+.B metadata_csum_seed
+Allow the file system to store the metadata checksum seed in the
+superblock, enabling the administrator to change the UUID of a file system
+using the
+.B metadata_csum
+feature while it is mounted.
+.TP
+.B mmp
+Enable or disable multiple mount protection (MMP) feature.
+.TP
+.B project
+Enable project ID tracking. This is used for project quota tracking.
+.TP
+.B quota
+Enable internal file system quota inodes.
+.TP
+.B read-only
+Force the kernel to mount the file system read-only.
+.TP
+.B resize_inode
+Reserve space so the block group descriptor table may grow in the
+future.
+.B Tune2fs
+only supports clearing this file system feature.
+.TP
+.B sparse_super
+Limit the number of backup superblocks to save space on large file systems.
+.B Tune2fs
+currently only supports setting this file system feature.
+.TP
+.B stable_inodes
+Prevent the file system from being shrunk or having its UUID changed, in order to
+allow the use of specialized encryption settings that make use of the inode
+numbers and UUID.
+.B Tune2fs
+currently only supports setting this file system feature.
+.TP
+.B uninit_bg
+Allow the kernel to initialize bitmaps and inode tables lazily, and to
+keep a high watermark for the unused inodes in a file system, to reduce
+.BR e2fsck (8)
+time. The first e2fsck run after enabling this feature will take the
+full time, but subsequent e2fsck runs will take only a fraction of the
+original time, depending on how full the file system is.
+.TP
+.B verity
+Enable support for verity protected files.
+.B Tune2fs
+currently only supports setting this file system feature.
+.RE
+.IP
+After setting or clearing
+.BR sparse_super ,
+.BR uninit_bg ,
+.BR filetype ,
+or
+.B resize_inode
+file system features,
+the file system may require being checked using
+.BR e2fsck (8)
+to return the file system to a consistent state.
+.B Tune2fs
+will print a message requesting that the system administrator run
+.BR e2fsck (8)
+if necessary. After setting the
+.B dir_index
+feature,
+.B e2fsck -D
+can be run to convert existing directories to the hashed B-tree format.
+Enabling certain file system features may prevent the file system from being
+mounted by kernels which do not support those features. In particular, the
+.B uninit_bg
+and
+.B flex_bg
+features are only supported by the ext4 file system.
+.TP
+.BI \-r " reserved-blocks-count"
+Set the number of reserved file system blocks.
+.TP
+.BI \-Q " quota-options"
+Sets 'quota' feature on the superblock and works on the quota files for the
+given quota type. Quota options could be one or more of the following:
+.RS 1.2i
+.TP
+.B [^]usrquota
+Sets/clears user quota inode in the superblock.
+.TP
+.B [^]grpquota
+Sets/clears group quota inode in the superblock.
+.TP
+.B [^]prjquota
+Sets/clears project quota inode in the superblock.
+.RE
+.TP
+.BI \-T " time-last-checked"
+Set the time the file system was last checked using
+.BR e2fsck .
+The time is interpreted using the current (local) timezone.
+This can be useful in scripts which use a Logical Volume Manager to make
+a consistent snapshot of a file system, and then check the file system
+during off hours to make sure it hasn't been corrupted due to
+hardware problems, etc. If the file system was clean, then this option can
+be used to set the last checked time on the original file system. The format
+of
+.I time-last-checked
+is the international date format, with an optional time specifier, i.e.
+YYYYMMDD[HH[MM[SS]]]. The keyword
+.B now
+is also accepted, in which case the last checked time will be set to the
+current time.
+.TP
+.BI \-u " user"
+Set the user who can use the reserved file system blocks.
+.I user
+can be a numerical uid or a user name. If a user name is given, it
+is converted to a numerical uid before it is stored in the superblock.
+.TP
+.BI \-U " UUID"
+Set the universally unique identifier (UUID) of the file system to
+.IR UUID .
+The format of the UUID is a series of hex digits separated by hyphens,
+like this:
+"c1b9d5a2-f162-11cf-9ece-0020afc76f16".
+The
+.I UUID
+parameter may also be one of the following:
+.RS 1.2i
+.TP
+.I clear
+clear the file system UUID
+.TP
+.I random
+generate a new randomly-generated UUID
+.TP
+.I time
+generate a new time-based UUID
+.RE
+.IP
+The UUID may be used by
+.BR mount (8),
+.BR fsck (8),
+and
+.BR /etc/fstab (5)
+(and possibly others) by specifying
+.BI UUID= uuid
+instead of a block special device name like
+.BR /dev/hda1 .
+.IP
+See
+.BR uuidgen (8)
+for more information.
+If the system does not have a good random number generator such as
+.I /dev/random
+or
+.IR /dev/urandom ,
+.B tune2fs
+will automatically use a time-based UUID instead of a randomly-generated UUID.
+.TP
+.BI \-z " undo_file"
+Before overwriting a file system block, write the old contents of the block to
+an undo file. This undo file can be used with e2undo(8) to restore the old
+contents of the file system should something go wrong. If the empty string is
+passed as the undo_file argument, the undo file will be written to a file named
+tune2fs-\fIdevice\fR.e2undo in the directory specified via the
+\fIE2FSPROGS_UNDO_DIR\fR environment variable.
+
+WARNING: The undo file cannot be used to recover from a power or system crash.
+.SH BUGS
+We haven't found any bugs yet. That doesn't mean there aren't any...
+.SH AUTHOR
+.B tune2fs
+was written by Remy Card <Remy.Card@linux.org>. It is currently being
+maintained by Theodore Ts'o <tytso@alum.mit.edu>.
+.B tune2fs
+uses the ext2fs library written by Theodore Ts'o <tytso@mit.edu>.
+This manual page was written by Christian Kuhtz <chk@data-hh.Hanse.DE>.
+Time-dependent checking was added by Uwe Ohse <uwe@tirka.gun.de>.
+.SH AVAILABILITY
+.B tune2fs
+is part of the e2fsprogs package and is available from
+http://e2fsprogs.sourceforge.net.
+.SH SEE ALSO
+.BR debugfs (8),
+.BR dumpe2fs (8),
+.BR e2fsck (8),
+.BR mke2fs (8),
+.BR ext4 (5)
diff --git a/misc/tune2fs.c b/misc/tune2fs.c
new file mode 100644
index 0000000..458f7cf
--- /dev/null
+++ b/misc/tune2fs.c
@@ -0,0 +1,3769 @@
+/*
+ * tune2fs.c - Change the file system parameters on an ext2 file system
+ *
+ * Copyright (C) 1992, 1993, 1994 Remy Card <card@masi.ibp.fr>
+ * Laboratoire MASI, Institut Blaise Pascal
+ * Universite Pierre et Marie Curie (Paris VI)
+ *
+ * Copyright 1995, 1996, 1997, 1998, 1999, 2000 by Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ * %End-Header%
+ */
+
+/*
+ * History:
+ * 93/06/01 - Creation
+ * 93/10/31 - Added the -c option to change the maximal mount counts
+ * 93/12/14 - Added -l flag to list contents of superblock
+ * M.J.E. Mol (marcel@duteca.et.tudelft.nl)
+ * F.W. ten Wolde (franky@duteca.et.tudelft.nl)
+ * 93/12/29 - Added the -e option to change errors behavior
+ * 94/02/27 - Ported to use the ext2fs library
+ * 94/03/06 - Added the checks interval from Uwe Ohse (uwe@tirka.gun.de)
+ */
+
+#define _XOPEN_SOURCE 600 /* for inclusion of strptime() */
+#include "config.h"
+#include <fcntl.h>
+#include <grp.h>
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#else
+extern char *optarg;
+extern int optind;
+#endif
+#include <pwd.h>
+#include <stdio.h>
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#ifdef HAVE_STRINGS_H
+#include <strings.h> /* for strcasecmp() */
+#else
+#define _BSD_SOURCE /* for inclusion of strcasecmp() via <string.h> */
+#define _DEFAULT_SOURCE /* since glibc 2.20 _BSD_SOURCE is deprecated */
+#endif
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <libgen.h>
+#include <limits.h>
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+
+#include "ext2fs/ext2_fs.h"
+#include "ext2fs/ext2fs.h"
+#include "ext2fs/kernel-jbd.h"
+#include "et/com_err.h"
+#include "support/plausible.h"
+#include "support/quotaio.h"
+#include "support/devname.h"
+#include "uuid/uuid.h"
+#include "e2p/e2p.h"
+#include "util.h"
+#include "blkid/blkid.h"
+
+#include "../version.h"
+#include "support/nls-enable.h"
+
+#define QOPT_ENABLE (1)
+#define QOPT_DISABLE (-1)
+
+#ifndef FS_IOC_SETFSLABEL
+#define FSLABEL_MAX 256
+#define FS_IOC_SETFSLABEL _IOW(0x94, 50, char[FSLABEL_MAX])
+#endif
+
+#ifndef FS_IOC_GETFSLABEL
+#define FS_IOC_GETFSLABEL _IOR(0x94, 49, char[FSLABEL_MAX])
+#endif
+
+struct fsuuid {
+ __u32 fsu_len;
+ __u32 fsu_flags;
+ __u8 fsu_uuid[];
+};
+
+#ifndef EXT4_IOC_GETFSUUID
+#define EXT4_IOC_GETFSUUID _IOR('f', 44, struct fsuuid)
+#endif
+
+#ifndef EXT4_IOC_SETFSUUID
+#define EXT4_IOC_SETFSUUID _IOW('f', 44, struct fsuuid)
+#endif
+
+extern int ask_yn(const char *string, int def);
+
+const char *program_name = "tune2fs";
+char *device_name;
+char *new_label, *new_last_mounted, *requested_uuid;
+char *io_options;
+static int c_flag, C_flag, e_flag, f_flag, g_flag, i_flag, l_flag, L_flag;
+static int m_flag, M_flag, Q_flag, r_flag, s_flag = -1, u_flag, U_flag, T_flag;
+static int I_flag;
+static int clear_mmp;
+static time_t last_check_time;
+static int print_label;
+static int max_mount_count, mount_count, mount_flags;
+static unsigned long interval;
+static blk64_t reserved_blocks;
+static double reserved_ratio;
+static unsigned long resgid, resuid;
+static unsigned short errors;
+static int open_flag;
+static char *features_cmd;
+static char *mntopts_cmd;
+static int stride, stripe_width;
+static int stride_set, stripe_width_set;
+static char *extended_cmd;
+static unsigned long new_inode_size;
+static char *ext_mount_opts;
+static int quota_enable[MAXQUOTAS];
+static int rewrite_checksums;
+static int feature_64bit;
+static int fsck_requested;
+static char *undo_file;
+int enabling_casefold;
+
+int journal_size, journal_fc_size, journal_flags;
+char *journal_device;
+static blk64_t journal_location = ~0LL;
+static e2_blkcnt_t orphan_file_blocks;
+
+static struct list_head blk_move_list;
+
+struct blk_move {
+ struct list_head list;
+ blk64_t old_loc;
+ blk64_t new_loc;
+};
+
+errcode_t ext2fs_run_ext3_journal(ext2_filsys *fs);
+
+static const char *fsck_explain = N_("\nThis operation requires a freshly checked filesystem.\n");
+
+static const char *please_fsck = N_("Please run e2fsck -f on the filesystem.\n");
+static const char *please_dir_fsck =
+ N_("Please run e2fsck -fD on the filesystem.\n");
+
+#ifdef CONFIG_BUILD_FINDFS
+void do_findfs(int argc, char **argv);
+#endif
+
+#ifdef CONFIG_JBD_DEBUG /* Enabled by configure --enable-jbd-debug */
+int journal_enable_debug = -1;
+#endif
+
+static void usage(void)
+{
+ fprintf(stderr,
+ _("Usage: %s [-c max_mounts_count] [-e errors_behavior] [-f] "
+ "[-g group]\n"
+ "\t[-i interval[d|m|w]] [-j] [-J journal_options] [-l]\n"
+ "\t[-m reserved_blocks_percent] [-o [^]mount_options[,...]]\n"
+ "\t[-r reserved_blocks_count] [-u user] [-C mount_count]\n"
+ "\t[-L volume_label] [-M last_mounted_dir]\n"
+ "\t[-O [^]feature[,...]] [-Q quota_options]\n"
+ "\t[-E extended-option[,...]] [-T last_check_time] "
+ "[-U UUID]\n\t[-I new_inode_size] [-z undo_file] device\n"),
+ program_name);
+ exit(1);
+}
+
+static __u32 ok_features[3] = {
+ /* Compat */
+ EXT3_FEATURE_COMPAT_HAS_JOURNAL |
+ EXT2_FEATURE_COMPAT_DIR_INDEX |
+ EXT4_FEATURE_COMPAT_FAST_COMMIT |
+ EXT4_FEATURE_COMPAT_STABLE_INODES |
+ EXT4_FEATURE_COMPAT_ORPHAN_FILE,
+ /* Incompat */
+ EXT2_FEATURE_INCOMPAT_FILETYPE |
+ EXT3_FEATURE_INCOMPAT_EXTENTS |
+ EXT4_FEATURE_INCOMPAT_FLEX_BG |
+ EXT4_FEATURE_INCOMPAT_EA_INODE|
+ EXT4_FEATURE_INCOMPAT_MMP |
+ EXT4_FEATURE_INCOMPAT_64BIT |
+ EXT4_FEATURE_INCOMPAT_ENCRYPT |
+ EXT4_FEATURE_INCOMPAT_CSUM_SEED |
+ EXT4_FEATURE_INCOMPAT_LARGEDIR |
+ EXT4_FEATURE_INCOMPAT_CASEFOLD,
+ /* R/O compat */
+ EXT2_FEATURE_RO_COMPAT_LARGE_FILE |
+ EXT4_FEATURE_RO_COMPAT_HUGE_FILE|
+ EXT4_FEATURE_RO_COMPAT_DIR_NLINK|
+ EXT4_FEATURE_RO_COMPAT_EXTRA_ISIZE|
+ EXT4_FEATURE_RO_COMPAT_GDT_CSUM |
+ EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER |
+ EXT4_FEATURE_RO_COMPAT_QUOTA |
+ EXT4_FEATURE_RO_COMPAT_METADATA_CSUM |
+ EXT4_FEATURE_RO_COMPAT_READONLY |
+ EXT4_FEATURE_RO_COMPAT_PROJECT |
+ EXT4_FEATURE_RO_COMPAT_VERITY
+};
+
+static __u32 clear_ok_features[3] = {
+ /* Compat */
+ EXT3_FEATURE_COMPAT_HAS_JOURNAL |
+ EXT2_FEATURE_COMPAT_RESIZE_INODE |
+ EXT2_FEATURE_COMPAT_DIR_INDEX |
+ EXT4_FEATURE_COMPAT_FAST_COMMIT |
+ EXT4_FEATURE_COMPAT_ORPHAN_FILE,
+ /* Incompat */
+ EXT2_FEATURE_INCOMPAT_FILETYPE |
+ EXT4_FEATURE_INCOMPAT_FLEX_BG |
+ EXT4_FEATURE_INCOMPAT_MMP |
+ EXT4_FEATURE_INCOMPAT_64BIT |
+ EXT4_FEATURE_INCOMPAT_CSUM_SEED |
+ EXT4_FEATURE_INCOMPAT_CASEFOLD,
+ /* R/O compat */
+ EXT2_FEATURE_RO_COMPAT_LARGE_FILE |
+ EXT4_FEATURE_RO_COMPAT_HUGE_FILE|
+ EXT4_FEATURE_RO_COMPAT_DIR_NLINK|
+ EXT4_FEATURE_RO_COMPAT_EXTRA_ISIZE|
+ EXT4_FEATURE_RO_COMPAT_GDT_CSUM |
+ EXT4_FEATURE_RO_COMPAT_QUOTA |
+ EXT4_FEATURE_RO_COMPAT_PROJECT |
+ EXT4_FEATURE_RO_COMPAT_METADATA_CSUM |
+ EXT4_FEATURE_RO_COMPAT_READONLY
+};
+
+/**
+ * Try to get journal super block if any
+ */
+static int get_journal_sb(ext2_filsys jfs, char buf[SUPERBLOCK_SIZE])
+{
+ int retval;
+ journal_superblock_t *jsb;
+
+ if (!ext2fs_has_feature_journal_dev(jfs->super)) {
+ return EXT2_ET_UNSUPP_FEATURE;
+ }
+
+ /* Get the journal superblock */
+ if ((retval = io_channel_read_blk64(jfs->io,
+ ext2fs_journal_sb_start(jfs->blocksize), -SUPERBLOCK_SIZE, buf))) {
+ com_err(program_name, retval, "%s",
+ _("while reading journal superblock"));
+ return retval;
+ }
+
+ jsb = (journal_superblock_t *) buf;
+ if ((jsb->s_header.h_magic != (unsigned)ntohl(JBD2_MAGIC_NUMBER)) ||
+ (jsb->s_header.h_blocktype != (unsigned)ntohl(JBD2_SUPERBLOCK_V2))) {
+ fputs(_("Journal superblock not found!\n"), stderr);
+ return EXT2_ET_BAD_MAGIC;
+ }
+
+ return 0;
+}
+
+static __u8 *journal_user(__u8 uuid[UUID_SIZE], __u8 s_users[JBD2_USERS_SIZE],
+ int nr_users)
+{
+ int i;
+ for (i = 0; i < nr_users; i++) {
+ if (memcmp(uuid, &s_users[i * UUID_SIZE], UUID_SIZE) == 0)
+ return &s_users[i * UUID_SIZE];
+ }
+
+ return NULL;
+}
+
+/*
+ * Remove an external journal from the filesystem
+ */
+static int remove_journal_device(ext2_filsys fs)
+{
+ char *journal_path;
+ ext2_filsys jfs;
+ char buf[SUPERBLOCK_SIZE] __attribute__ ((aligned(8)));
+ journal_superblock_t *jsb;
+ int i, nr_users;
+ errcode_t retval;
+ int commit_remove_journal = 0;
+ io_manager io_ptr;
+
+ if (f_flag)
+ commit_remove_journal = 1; /* force removal even if error */
+
+ uuid_unparse(fs->super->s_journal_uuid, buf);
+ journal_path = blkid_get_devname(NULL, "UUID", buf);
+
+ if (!journal_path) {
+ journal_path =
+ ext2fs_find_block_device(fs->super->s_journal_dev);
+ if (!journal_path)
+ goto no_valid_journal;
+ }
+
+#ifdef CONFIG_TESTIO_DEBUG
+ if (getenv("TEST_IO_FLAGS") || getenv("TEST_IO_BLOCK")) {
+ io_ptr = test_io_manager;
+ test_io_backing_manager = unix_io_manager;
+ } else
+#endif
+ io_ptr = unix_io_manager;
+ retval = ext2fs_open(journal_path, EXT2_FLAG_RW|
+ EXT2_FLAG_JOURNAL_DEV_OK, 0,
+ fs->blocksize, io_ptr, &jfs);
+ if (retval) {
+ com_err(program_name, retval, "%s",
+ _("while trying to open external journal"));
+ goto no_valid_journal;
+ }
+
+ if ((retval = get_journal_sb(jfs, buf))) {
+ if (retval == EXT2_ET_UNSUPP_FEATURE)
+ fprintf(stderr, _("%s is not a journal device.\n"),
+ journal_path);
+ goto no_valid_journal;
+ }
+
+ jsb = (journal_superblock_t *) buf;
+ /* Find the filesystem UUID */
+ nr_users = ntohl(jsb->s_nr_users);
+ if (nr_users > JBD2_USERS_MAX) {
+ fprintf(stderr, _("Journal superblock is corrupted, nr_users\n"
+ "is too high (%d).\n"), nr_users);
+ commit_remove_journal = 1;
+ goto no_valid_journal;
+ }
+
+ if (!journal_user(fs->super->s_uuid, jsb->s_users, nr_users)) {
+ fputs(_("Filesystem's UUID not found on journal device.\n"),
+ stderr);
+ commit_remove_journal = 1;
+ goto no_valid_journal;
+ }
+ nr_users--;
+ for (i = 0; i < nr_users; i++)
+ memcpy(&jsb->s_users[i * 16], &jsb->s_users[(i + 1) * 16], 16);
+ jsb->s_nr_users = htonl(nr_users);
+
+ /* Write back the journal superblock */
+ retval = io_channel_write_blk64(jfs->io,
+ ext2fs_journal_sb_start(fs->blocksize),
+ -SUPERBLOCK_SIZE, buf);
+ if (retval) {
+ com_err(program_name, retval,
+ "while writing journal superblock.");
+ goto no_valid_journal;
+ }
+
+ commit_remove_journal = 1;
+
+no_valid_journal:
+ if (commit_remove_journal == 0) {
+ fputs(_("Cannot locate journal device. It was NOT removed\n"
+ "Use -f option to remove missing journal device.\n"),
+ stderr);
+ return 1;
+ }
+ fs->super->s_journal_dev = 0;
+ memset(fs->super->s_jnl_blocks, 0, sizeof(fs->super->s_jnl_blocks));
+ uuid_clear(fs->super->s_journal_uuid);
+ ext2fs_mark_super_dirty(fs);
+ fputs(_("Journal removed\n"), stdout);
+ free(journal_path);
+
+ return 0;
+}
+
+/* Helper function for remove_journal_inode */
+static int release_blocks_proc(ext2_filsys fs, blk64_t *blocknr,
+ e2_blkcnt_t blockcnt EXT2FS_ATTR((unused)),
+ blk64_t ref_block EXT2FS_ATTR((unused)),
+ int ref_offset EXT2FS_ATTR((unused)),
+ void *private EXT2FS_ATTR((unused)))
+{
+ blk64_t block;
+ int group;
+
+ block = *blocknr;
+ ext2fs_unmark_block_bitmap2(fs->block_map, block);
+ group = ext2fs_group_of_blk2(fs, block);
+ ext2fs_bg_free_blocks_count_set(fs, group, ext2fs_bg_free_blocks_count(fs, group) + 1);
+ ext2fs_group_desc_csum_set(fs, group);
+ ext2fs_free_blocks_count_add(fs->super, EXT2FS_CLUSTER_RATIO(fs));
+ return 0;
+}
+
+/*
+ * Remove the journal inode from the filesystem
+ */
+static errcode_t remove_journal_inode(ext2_filsys fs)
+{
+ struct ext2_inode inode;
+ errcode_t retval;
+ ext2_ino_t ino = fs->super->s_journal_inum;
+
+ retval = ext2fs_read_inode(fs, ino, &inode);
+ if (retval) {
+ com_err(program_name, retval, "%s",
+ _("while reading journal inode"));
+ return retval;
+ }
+ if (ino == EXT2_JOURNAL_INO) {
+ retval = ext2fs_read_bitmaps(fs);
+ if (retval) {
+ com_err(program_name, retval, "%s",
+ _("while reading bitmaps"));
+ return retval;
+ }
+ retval = ext2fs_block_iterate3(fs, ino,
+ BLOCK_FLAG_READ_ONLY, NULL,
+ release_blocks_proc, NULL);
+ if (retval) {
+ com_err(program_name, retval, "%s",
+ _("while clearing journal inode"));
+ return retval;
+ }
+ fs->super->s_overhead_clusters -=
+ EXT2FS_NUM_B2C(fs, EXT2_I_SIZE(&inode) / fs->blocksize);
+ memset(&inode, 0, sizeof(inode));
+ ext2fs_mark_bb_dirty(fs);
+ fs->flags &= ~EXT2_FLAG_SUPER_ONLY;
+ } else
+ inode.i_flags &= ~EXT2_IMMUTABLE_FL;
+ retval = ext2fs_write_inode(fs, ino, &inode);
+ if (retval) {
+ com_err(program_name, retval, "%s",
+ _("while writing journal inode"));
+ return retval;
+ }
+ fs->super->s_journal_inum = 0;
+ memset(fs->super->s_jnl_blocks, 0, sizeof(fs->super->s_jnl_blocks));
+ ext2fs_mark_super_dirty(fs);
+
+ return 0;
+}
+
+/*
+ * Update the default mount options
+ */
+static int update_mntopts(ext2_filsys fs, char *mntopts)
+{
+ struct ext2_super_block *sb = fs->super;
+
+ if (e2p_edit_mntopts(mntopts, &sb->s_default_mount_opts, ~0)) {
+ fprintf(stderr, _("Invalid mount option set: %s\n"),
+ mntopts);
+ return 1;
+ }
+ ext2fs_mark_super_dirty(fs);
+
+ return 0;
+}
+
+static int check_fsck_needed(ext2_filsys fs, const char *prompt)
+{
+ /* Refuse to modify anything but a freshly checked valid filesystem. */
+ if (!(fs->super->s_state & EXT2_VALID_FS) ||
+ (fs->super->s_state & EXT2_ERROR_FS) ||
+ (fs->super->s_lastcheck < fs->super->s_mtime)) {
+ puts(_(fsck_explain));
+ puts(_(please_fsck));
+ if (mount_flags & EXT2_MF_READONLY)
+ printf("%s", _("(and reboot afterwards!)\n"));
+ return 1;
+ }
+
+ /* Give the admin a few seconds to bail out of a dangerous op. */
+ if (!getenv("TUNE2FS_FORCE_PROMPT") && (!isatty(0) || !isatty(1)))
+ return 0;
+
+ puts(prompt);
+ proceed_question(5);
+
+ return 0;
+}
+
+static void request_dir_fsck_afterwards(ext2_filsys fs)
+{
+ static int requested;
+
+ if (requested++)
+ return;
+ fsck_requested++;
+ fs->super->s_state &= ~EXT2_VALID_FS;
+ puts(_(fsck_explain));
+ puts(_(please_dir_fsck));
+ if (mount_flags & EXT2_MF_READONLY)
+ printf("%s", _("(and reboot afterwards!)\n"));
+}
+
+static void request_fsck_afterwards(ext2_filsys fs)
+{
+ static int requested = 0;
+
+ if (requested++)
+ return;
+ fsck_requested++;
+ fs->super->s_state &= ~EXT2_VALID_FS;
+ printf("\n%s\n", _(please_fsck));
+ if (mount_flags & EXT2_MF_READONLY)
+ printf("%s", _("(and reboot afterwards!)\n"));
+}
+
+static void convert_64bit(ext2_filsys fs, int direction)
+{
+ /*
+ * Is resize2fs going to demand a fsck run? Might as well tell the
+ * user now.
+ */
+ if (!fsck_requested &&
+ ((fs->super->s_state & EXT2_ERROR_FS) ||
+ !(fs->super->s_state & EXT2_VALID_FS) ||
+ fs->super->s_lastcheck < fs->super->s_mtime))
+ request_fsck_afterwards(fs);
+ if (fsck_requested)
+ fprintf(stderr, _("After running e2fsck, please run `resize2fs %s %s"),
+ direction > 0 ? "-b" : "-s", fs->device_name);
+ else
+ fprintf(stderr, _("Please run `resize2fs %s %s"),
+ direction > 0 ? "-b" : "-s", fs->device_name);
+
+ if (undo_file)
+ fprintf(stderr, _(" -z \"%s\""), undo_file);
+ if (direction > 0)
+ fprintf(stderr, _("' to enable 64-bit mode.\n"));
+ else
+ fprintf(stderr, _("' to disable 64-bit mode.\n"));
+}
+
+/*
+ * Rewrite directory blocks with checksums
+ */
+struct rewrite_dir_context {
+ char *buf;
+ errcode_t errcode;
+ ext2_ino_t dir;
+ int is_htree:1;
+ int clear_htree:1;
+};
+
+static int rewrite_dir_block(ext2_filsys fs,
+ blk64_t *blocknr,
+ e2_blkcnt_t blockcnt EXT2FS_ATTR((unused)),
+ blk64_t ref_block EXT2FS_ATTR((unused)),
+ int ref_offset EXT2FS_ATTR((unused)),
+ void *priv_data)
+{
+ struct ext2_dx_countlimit *dcl = NULL;
+ struct rewrite_dir_context *ctx = priv_data;
+ int dcl_offset, changed = 0;
+
+ ctx->errcode = ext2fs_read_dir_block4(fs, *blocknr, ctx->buf, 0,
+ ctx->dir);
+ if (ctx->errcode)
+ return BLOCK_ABORT;
+
+ /*
+ * if htree node... Note that if we are clearing htree structures from
+ * the directory, we treat the htree internal block as an ordinary leaf.
+ * The code below will do the right thing and make space for checksum
+ * there.
+ */
+ if (ctx->is_htree && !ctx->clear_htree)
+ ext2fs_get_dx_countlimit(fs, (struct ext2_dir_entry *)ctx->buf,
+ &dcl, &dcl_offset);
+ if (dcl) {
+ if (!ext2fs_has_feature_metadata_csum(fs->super)) {
+ /* Ensure limit is the max size */
+ int max_entries = (fs->blocksize - dcl_offset) /
+ sizeof(struct ext2_dx_entry);
+ if (ext2fs_le16_to_cpu(dcl->limit) != max_entries) {
+ changed = 1;
+ dcl->limit = ext2fs_cpu_to_le16(max_entries);
+ }
+ } else {
+ /* If htree block is full then rebuild the dir */
+ if (ext2fs_le16_to_cpu(dcl->count) ==
+ ext2fs_le16_to_cpu(dcl->limit)) {
+ request_dir_fsck_afterwards(fs);
+ return 0;
+ }
+ /*
+ * Ensure dcl->limit is small enough to leave room for
+ * the checksum tail.
+ */
+ int max_entries = (fs->blocksize - (dcl_offset +
+ sizeof(struct ext2_dx_tail))) /
+ sizeof(struct ext2_dx_entry);
+ if (ext2fs_le16_to_cpu(dcl->limit) != max_entries)
+ dcl->limit = ext2fs_cpu_to_le16(max_entries);
+ /* Always rewrite checksum */
+ changed = 1;
+ }
+ } else {
+ unsigned int rec_len, name_size;
+ char *top = ctx->buf + fs->blocksize;
+ struct ext2_dir_entry *de = (struct ext2_dir_entry *)ctx->buf;
+ struct ext2_dir_entry *last_de = NULL, *penultimate_de = NULL;
+
+ /* Find last and penultimate dirent */
+ while ((char *)de < top) {
+ penultimate_de = last_de;
+ last_de = de;
+ ctx->errcode = ext2fs_get_rec_len(fs, de, &rec_len);
+ if (!ctx->errcode && !rec_len)
+ ctx->errcode = EXT2_ET_DIR_CORRUPTED;
+ if (ctx->errcode)
+ return BLOCK_ABORT;
+ de = (struct ext2_dir_entry *)(((char *)de) + rec_len);
+ }
+ ctx->errcode = ext2fs_get_rec_len(fs, last_de, &rec_len);
+ if (ctx->errcode)
+ return BLOCK_ABORT;
+ name_size = ext2fs_dirent_name_len(last_de);
+
+ if (!ext2fs_has_feature_metadata_csum(fs->super)) {
+ if (!penultimate_de)
+ return 0;
+ if (last_de->inode ||
+ name_size ||
+ rec_len != sizeof(struct ext2_dir_entry_tail))
+ return 0;
+ /*
+ * The last dirent is unused and the right length to
+ * have stored a checksum. Erase it.
+ */
+ ctx->errcode = ext2fs_get_rec_len(fs, penultimate_de,
+ &rec_len);
+ if (!rec_len)
+ ctx->errcode = EXT2_ET_DIR_CORRUPTED;
+ if (ctx->errcode)
+ return BLOCK_ABORT;
+ ext2fs_set_rec_len(fs, rec_len +
+ sizeof(struct ext2_dir_entry_tail),
+ penultimate_de);
+ changed = 1;
+ } else {
+ unsigned csum_size = sizeof(struct ext2_dir_entry_tail);
+ struct ext2_dir_entry_tail *t;
+
+ /*
+ * If the last dirent looks like the tail, just update
+ * the checksum.
+ */
+ if (!last_de->inode &&
+ rec_len == csum_size) {
+ t = (struct ext2_dir_entry_tail *)last_de;
+ t->det_reserved_name_len =
+ EXT2_DIR_NAME_LEN_CSUM;
+ changed = 1;
+ goto out;
+ }
+ if (name_size & 3)
+ name_size = (name_size & ~3) + 4;
+ /* If there's not enough space for the tail, e2fsck */
+ if (rec_len <= (8 + name_size + csum_size)) {
+ request_dir_fsck_afterwards(fs);
+ return 0;
+ }
+ /* Shorten that last de and insert the tail */
+ ext2fs_set_rec_len(fs, rec_len - csum_size, last_de);
+ t = EXT2_DIRENT_TAIL(ctx->buf, fs->blocksize);
+ ext2fs_initialize_dirent_tail(fs, t);
+
+ /* Always update checksum */
+ changed = 1;
+ }
+ }
+
+out:
+ if (!changed)
+ return 0;
+
+ ctx->errcode = ext2fs_write_dir_block4(fs, *blocknr, ctx->buf,
+ 0, ctx->dir);
+ if (ctx->errcode)
+ return BLOCK_ABORT;
+
+ return 0;
+}
+
+static errcode_t rewrite_directory(ext2_filsys fs, ext2_ino_t dir,
+ struct ext2_inode *inode)
+{
+ errcode_t retval;
+ struct rewrite_dir_context ctx;
+
+ retval = ext2fs_get_mem(fs->blocksize, &ctx.buf);
+ if (retval)
+ return retval;
+
+ ctx.is_htree = !!(inode->i_flags & EXT2_INDEX_FL);
+ ctx.clear_htree = !ext2fs_has_feature_dir_index(fs->super);
+ ctx.dir = dir;
+ ctx.errcode = 0;
+ retval = ext2fs_block_iterate3(fs, dir, BLOCK_FLAG_READ_ONLY |
+ BLOCK_FLAG_DATA_ONLY,
+ 0, rewrite_dir_block, &ctx);
+
+ ext2fs_free_mem(&ctx.buf);
+ if (retval)
+ return retval;
+
+ if (ctx.is_htree && ctx.clear_htree) {
+ inode->i_flags &= ~EXT2_INDEX_FL;
+ retval = ext2fs_write_inode(fs, dir, inode);
+ if (retval)
+ return retval;
+ }
+
+ return ctx.errcode;
+}
+
+/*
+ * Context information that does not change across rewrite_one_inode()
+ * invocations.
+ */
+struct rewrite_context {
+ ext2_filsys fs;
+ struct ext2_inode *zero_inode;
+ char *ea_buf;
+ int inode_size;
+};
+
+#define fatal_err(code, args...) \
+ do { \
+ com_err(__func__, code, args); \
+ exit(1); \
+ } while (0);
+
+static void update_ea_inode_hash(struct rewrite_context *ctx, ext2_ino_t ino,
+ struct ext2_inode *inode)
+{
+ errcode_t retval;
+ ext2_file_t file;
+ __u32 hash;
+
+ retval = ext2fs_file_open(ctx->fs, ino, 0, &file);
+ if (retval)
+ fatal_err(retval, "open ea_inode");
+ retval = ext2fs_file_read(file, ctx->ea_buf, inode->i_size,
+ NULL);
+ if (retval)
+ fatal_err(retval, "read ea_inode");
+ retval = ext2fs_file_close(file);
+ if (retval)
+ fatal_err(retval, "close ea_inode");
+
+ hash = ext2fs_crc32c_le(ctx->fs->csum_seed,
+ (unsigned char *) ctx->ea_buf, inode->i_size);
+ ext2fs_set_ea_inode_hash(inode, hash);
+}
+
+static int update_xattr_entry_hashes(ext2_filsys fs,
+ struct ext2_ext_attr_entry *entry,
+ struct ext2_ext_attr_entry *end)
+{
+ int modified = 0;
+ errcode_t retval;
+
+ while (entry < end && !EXT2_EXT_IS_LAST_ENTRY(entry)) {
+ if (entry->e_value_inum) {
+ retval = ext2fs_ext_attr_hash_entry2(fs, entry, NULL,
+ &entry->e_hash);
+ if (retval)
+ fatal_err(retval, "hash ea_inode entry");
+ modified = 1;
+ }
+ entry = EXT2_EXT_ATTR_NEXT(entry);
+ }
+ return modified;
+}
+
+static void update_inline_xattr_hashes(struct rewrite_context *ctx,
+ struct ext2_inode_large *inode)
+{
+ struct ext2_ext_attr_entry *start, *end;
+ __u32 *ea_magic;
+
+ if (inode->i_extra_isize == 0)
+ return;
+
+ if (inode->i_extra_isize & 3 ||
+ inode->i_extra_isize > ctx->inode_size - EXT2_GOOD_OLD_INODE_SIZE)
+ fatal_err(EXT2_ET_INODE_CORRUPTED, "bad i_extra_isize")
+
+ ea_magic = (__u32 *)((char *)inode + EXT2_GOOD_OLD_INODE_SIZE +
+ inode->i_extra_isize);
+ if (*ea_magic != EXT2_EXT_ATTR_MAGIC)
+ return;
+
+ start = (struct ext2_ext_attr_entry *)(ea_magic + 1);
+ end = (struct ext2_ext_attr_entry *)((char *)inode + ctx->inode_size);
+
+ update_xattr_entry_hashes(ctx->fs, start, end);
+}
+
+static void update_block_xattr_hashes(struct rewrite_context *ctx,
+ char *block_buf)
+{
+ struct ext2_ext_attr_header *header;
+ struct ext2_ext_attr_entry *start, *end;
+
+ header = (struct ext2_ext_attr_header *)block_buf;
+ if (header->h_magic != EXT2_EXT_ATTR_MAGIC)
+ return;
+
+ start = (struct ext2_ext_attr_entry *)(header+1);
+ end = (struct ext2_ext_attr_entry *)(block_buf + ctx->fs->blocksize);
+
+ if (update_xattr_entry_hashes(ctx->fs, start, end))
+ ext2fs_ext_attr_block_rehash(header, end);
+}
+
+static void rewrite_one_inode(struct rewrite_context *ctx, ext2_ino_t ino,
+ struct ext2_inode *inode)
+{
+ blk64_t file_acl_block;
+ errcode_t retval;
+
+ if (!ext2fs_test_inode_bitmap2(ctx->fs->inode_map, ino)) {
+ if (!memcmp(inode, ctx->zero_inode, ctx->inode_size))
+ return;
+ memset(inode, 0, ctx->inode_size);
+ }
+
+ if (inode->i_flags & EXT4_EA_INODE_FL)
+ update_ea_inode_hash(ctx, ino, inode);
+
+ if (ctx->inode_size != EXT2_GOOD_OLD_INODE_SIZE)
+ update_inline_xattr_hashes(ctx,
+ (struct ext2_inode_large *)inode);
+
+ retval = ext2fs_write_inode_full(ctx->fs, ino, inode, ctx->inode_size);
+ if (retval)
+ fatal_err(retval, "while writing inode");
+
+ retval = ext2fs_fix_extents_checksums(ctx->fs, ino, inode);
+ if (retval)
+ fatal_err(retval, "while rewriting extents");
+
+ if (LINUX_S_ISDIR(inode->i_mode) &&
+ ext2fs_inode_has_valid_blocks2(ctx->fs, inode)) {
+ retval = rewrite_directory(ctx->fs, ino, inode);
+ if (retval)
+ fatal_err(retval, "while rewriting directories");
+ }
+
+ file_acl_block = ext2fs_file_acl_block(ctx->fs, inode);
+ if (!file_acl_block)
+ return;
+
+ retval = ext2fs_read_ext_attr3(ctx->fs, file_acl_block, ctx->ea_buf,
+ ino);
+ if (retval)
+ fatal_err(retval, "while rewriting extended attribute");
+
+ update_block_xattr_hashes(ctx, ctx->ea_buf);
+ retval = ext2fs_write_ext_attr3(ctx->fs, file_acl_block, ctx->ea_buf,
+ ino);
+ if (retval)
+ fatal_err(retval, "while rewriting extended attribute");
+}
+
+#define REWRITE_EA_FL 0x01 /* Rewrite EA inodes */
+#define REWRITE_DIR_FL 0x02 /* Rewrite directories */
+#define REWRITE_NONDIR_FL 0x04 /* Rewrite other inodes */
+#define REWRITE_ALL (REWRITE_EA_FL | REWRITE_DIR_FL | REWRITE_NONDIR_FL)
+
+static void rewrite_inodes_pass(struct rewrite_context *ctx, unsigned int flags)
+{
+ ext2_inode_scan scan;
+ errcode_t retval;
+ ext2_ino_t ino;
+ struct ext2_inode *inode;
+ int rewrite;
+
+ retval = ext2fs_get_mem(ctx->inode_size, &inode);
+ if (retval)
+ fatal_err(retval, "while allocating memory");
+
+ retval = ext2fs_open_inode_scan(ctx->fs, 0, &scan);
+ if (retval)
+ fatal_err(retval, "while opening inode scan");
+
+ do {
+ retval = ext2fs_get_next_inode_full(scan, &ino, inode,
+ ctx->inode_size);
+ if (retval)
+ fatal_err(retval, "while getting next inode");
+ if (!ino)
+ break;
+
+ rewrite = 0;
+ if (inode->i_flags & EXT4_EA_INODE_FL) {
+ if (flags & REWRITE_EA_FL)
+ rewrite = 1;
+ } else if (LINUX_S_ISDIR(inode->i_mode)) {
+ if (flags & REWRITE_DIR_FL)
+ rewrite = 1;
+ } else {
+ if (flags & REWRITE_NONDIR_FL)
+ rewrite = 1;
+ }
+ if (rewrite)
+ rewrite_one_inode(ctx, ino, inode);
+ } while (ino);
+ ext2fs_close_inode_scan(scan);
+ ext2fs_free_mem(&inode);
+}
+
+/*
+ * Forcibly rewrite checksums in inodes specified by 'flags'
+ */
+static void rewrite_inodes(ext2_filsys fs, unsigned int flags)
+{
+ struct rewrite_context ctx = {
+ .fs = fs,
+ .inode_size = EXT2_INODE_SIZE(fs->super),
+ };
+ errcode_t retval;
+
+ if (fs->super->s_creator_os == EXT2_OS_HURD)
+ return;
+
+ retval = ext2fs_get_memzero(ctx.inode_size, &ctx.zero_inode);
+ if (retval)
+ fatal_err(retval, "while allocating memory");
+
+ retval = ext2fs_get_mem(64 * 1024, &ctx.ea_buf);
+ if (retval)
+ fatal_err(retval, "while allocating memory");
+
+ /*
+ * Extended attribute inodes have a lookup hash that needs to be
+ * recalculated with the new csum_seed. Other inodes referencing xattr
+ * inodes need this value to be up to date. That's why we do two passes:
+ *
+ * pass 1: update xattr inodes to update their lookup hash as well as
+ * other checksums.
+ *
+ * pass 2: go over other inodes to update their checksums.
+ */
+ if (ext2fs_has_feature_ea_inode(fs->super) && (flags & REWRITE_EA_FL))
+ rewrite_inodes_pass(&ctx, REWRITE_EA_FL);
+ flags &= ~REWRITE_EA_FL;
+ rewrite_inodes_pass(&ctx, flags);
+
+ ext2fs_free_mem(&ctx.zero_inode);
+ ext2fs_free_mem(&ctx.ea_buf);
+}
+
+static errcode_t rewrite_metadata_checksums(ext2_filsys fs, unsigned int flags)
+{
+ errcode_t retval;
+ dgrp_t i;
+
+ fs->flags |= EXT2_FLAG_IGNORE_CSUM_ERRORS;
+ ext2fs_init_csum_seed(fs);
+ for (i = 0; i < fs->group_desc_count; i++)
+ ext2fs_group_desc_csum_set(fs, i);
+ retval = ext2fs_read_bitmaps(fs);
+ if (retval)
+ fatal_err(retval, "while reading bitmaps");
+ rewrite_inodes(fs, flags);
+ ext2fs_mark_ib_dirty(fs);
+ ext2fs_mark_bb_dirty(fs);
+ retval = ext2fs_mmp_update2(fs, 1);
+ if (retval)
+ return retval;
+ fs->flags &= ~EXT2_FLAG_SUPER_ONLY;
+ fs->flags &= ~EXT2_FLAG_IGNORE_CSUM_ERRORS;
+ if (ext2fs_has_feature_metadata_csum(fs->super))
+ fs->super->s_checksum_type = EXT2_CRC32C_CHKSUM;
+ else
+ fs->super->s_checksum_type = 0;
+ ext2fs_mark_super_dirty(fs);
+ return 0;
+}
+
+static void enable_uninit_bg(ext2_filsys fs)
+{
+ struct ext2_group_desc *gd;
+ dgrp_t i;
+
+ for (i = 0; i < fs->group_desc_count; i++) {
+ gd = ext2fs_group_desc(fs, fs->group_desc, i);
+ gd->bg_itable_unused = 0;
+ gd->bg_flags = EXT2_BG_INODE_ZEROED;
+ ext2fs_group_desc_csum_set(fs, i);
+ }
+ fs->flags &= ~EXT2_FLAG_SUPER_ONLY;
+}
+
+static errcode_t zero_empty_inodes(ext2_filsys fs)
+{
+ int length = EXT2_INODE_SIZE(fs->super);
+ struct ext2_inode *inode = NULL;
+ ext2_inode_scan scan;
+ errcode_t retval;
+ ext2_ino_t ino;
+
+ retval = ext2fs_open_inode_scan(fs, 0, &scan);
+ if (retval)
+ goto out;
+
+ retval = ext2fs_get_mem(length, &inode);
+ if (retval)
+ goto out;
+
+ do {
+ retval = ext2fs_get_next_inode_full(scan, &ino, inode, length);
+ if (retval)
+ goto out;
+ if (!ino)
+ break;
+ if (!ext2fs_test_inode_bitmap2(fs->inode_map, ino)) {
+ memset(inode, 0, length);
+ retval = ext2fs_write_inode_full(fs, ino, inode,
+ length);
+ if (retval)
+ goto out;
+ }
+ } while (1);
+
+out:
+ ext2fs_free_mem(&inode);
+ ext2fs_close_inode_scan(scan);
+ return retval;
+}
+
+static int has_casefold_inode(ext2_filsys fs)
+{
+ int length = EXT2_INODE_SIZE(fs->super);
+ struct ext2_inode *inode = NULL;
+ ext2_inode_scan scan;
+ errcode_t retval;
+ ext2_ino_t ino;
+ int found_casefold = 0;
+
+ retval = ext2fs_get_mem(length, &inode);
+ if (retval)
+ fatal_err(retval, "while allocating memory");
+
+ retval = ext2fs_open_inode_scan(fs, 0, &scan);
+ if (retval)
+ fatal_err(retval, "while opening inode scan");
+
+ do {
+ retval = ext2fs_get_next_inode_full(scan, &ino, inode, length);
+ if (retval)
+ fatal_err(retval, "while getting next inode");
+ if (!ino)
+ break;
+
+ if(inode->i_flags & EXT4_CASEFOLD_FL) {
+ found_casefold = 1;
+ break;
+ }
+ } while(1);
+
+ ext2fs_free_mem(&inode);
+ ext2fs_close_inode_scan(scan);
+ return found_casefold;
+}
+
+static errcode_t disable_uninit_bg(ext2_filsys fs, __u32 csum_feature_flag)
+{
+ struct ext2_group_desc *gd;
+ dgrp_t i;
+ errcode_t retval;
+ blk64_t b, c, d;
+
+ /* Load bitmaps to ensure that the uninit ones get written out */
+ fs->super->s_feature_ro_compat |= csum_feature_flag;
+ retval = ext2fs_read_bitmaps(fs);
+ fs->super->s_feature_ro_compat &= ~csum_feature_flag;
+ if (retval) {
+ com_err("disable_uninit_bg", retval,
+ "while reading bitmaps");
+ request_fsck_afterwards(fs);
+ return retval;
+ }
+ ext2fs_mark_ib_dirty(fs);
+ ext2fs_mark_bb_dirty(fs);
+
+ /* If we're only turning off uninit_bg, zero the inodes */
+ if (csum_feature_flag == EXT4_FEATURE_RO_COMPAT_GDT_CSUM) {
+ retval = zero_empty_inodes(fs);
+ if (retval) {
+ com_err("disable_uninit_bg", retval,
+ "while zeroing unused inodes");
+ request_fsck_afterwards(fs);
+ return retval;
+ }
+ }
+
+ /* The bbitmap is zeroed; we must mark group metadata blocks in use */
+ for (i = 0; i < fs->group_desc_count; i++) {
+ b = ext2fs_block_bitmap_loc(fs, i);
+ ext2fs_mark_block_bitmap2(fs->block_map, b);
+ b = ext2fs_inode_bitmap_loc(fs, i);
+ ext2fs_mark_block_bitmap2(fs->block_map, b);
+
+ retval = ext2fs_super_and_bgd_loc2(fs, i, &b, &c, &d, NULL);
+ if (retval == 0 && b)
+ ext2fs_mark_block_bitmap2(fs->block_map, b);
+ if (retval == 0 && c)
+ ext2fs_mark_block_bitmap2(fs->block_map, c);
+ if (retval == 0 && d)
+ ext2fs_mark_block_bitmap2(fs->block_map, d);
+ if (retval) {
+ com_err("disable_uninit_bg", retval,
+ "while initializing block bitmaps");
+ request_fsck_afterwards(fs);
+ }
+
+ gd = ext2fs_group_desc(fs, fs->group_desc, i);
+ gd->bg_itable_unused = 0;
+ gd->bg_flags = 0;
+ ext2fs_group_desc_csum_set(fs, i);
+ }
+ fs->flags &= ~EXT2_FLAG_SUPER_ONLY;
+ ext2fs_mark_super_dirty(fs);
+
+ return 0;
+}
+
+static void
+try_confirm_csum_seed_support(void)
+{
+ if (access("/sys/fs/ext4/features/metadata_csum_seed", R_OK))
+ fputs(_("WARNING: Could not confirm kernel support for "
+ "metadata_csum_seed.\n This requires Linux >= "
+ "v4.4.\n"), stderr);
+}
+
+/*
+ * Update the feature set as provided by the user.
+ */
+static int update_feature_set(ext2_filsys fs, char *features)
+{
+ struct ext2_super_block *sb = fs->super;
+ __u32 old_features[3];
+ int type_err;
+ unsigned int mask_err;
+ errcode_t err;
+ enum quota_type qtype;
+
+#define FEATURE_ON(type, mask) (!(old_features[(type)] & (mask)) && \
+ ((&sb->s_feature_compat)[(type)] & (mask)))
+#define FEATURE_OFF(type, mask) ((old_features[(type)] & (mask)) && \
+ !((&sb->s_feature_compat)[(type)] & (mask)))
+#define FEATURE_CHANGED(type, mask) ((mask) & \
+ (old_features[(type)] ^ (&sb->s_feature_compat)[(type)]))
+
+ old_features[E2P_FEATURE_COMPAT] = sb->s_feature_compat;
+ old_features[E2P_FEATURE_INCOMPAT] = sb->s_feature_incompat;
+ old_features[E2P_FEATURE_RO_INCOMPAT] = sb->s_feature_ro_compat;
+
+ if (e2p_edit_feature2(features, &sb->s_feature_compat,
+ ok_features, clear_ok_features,
+ &type_err, &mask_err)) {
+ if (!mask_err)
+ fprintf(stderr,
+ _("Invalid filesystem option set: %s\n"),
+ features);
+ else if (type_err & E2P_FEATURE_NEGATE_FLAG)
+ fprintf(stderr, _("Clearing filesystem feature '%s' "
+ "not supported.\n"),
+ e2p_feature2string(type_err &
+ E2P_FEATURE_TYPE_MASK,
+ mask_err));
+ else
+ fprintf(stderr, _("Setting filesystem feature '%s' "
+ "not supported.\n"),
+ e2p_feature2string(type_err, mask_err));
+ return 1;
+ }
+
+ if (FEATURE_OFF(E2P_FEATURE_COMPAT, EXT3_FEATURE_COMPAT_HAS_JOURNAL)) {
+ if ((mount_flags & EXT2_MF_MOUNTED) &&
+ !(mount_flags & EXT2_MF_READONLY)) {
+ fputs(_("The has_journal feature may only be "
+ "cleared when the filesystem is\n"
+ "unmounted or mounted "
+ "read-only.\n"), stderr);
+ return 1;
+ }
+ if (ext2fs_has_feature_journal_needs_recovery(sb) &&
+ f_flag < 2) {
+ fputs(_("The needs_recovery flag is set. "
+ "Please run e2fsck before clearing\n"
+ "the has_journal flag.\n"), stderr);
+ return 1;
+ }
+ if (sb->s_journal_inum) {
+ if (remove_journal_inode(fs))
+ return 1;
+ }
+ if (sb->s_journal_dev) {
+ if (remove_journal_device(fs))
+ return 1;
+ }
+ }
+
+ if (FEATURE_OFF(E2P_FEATURE_COMPAT, EXT4_FEATURE_COMPAT_ORPHAN_FILE)) {
+ ext2_ino_t ino;
+
+ if (mount_flags & EXT2_MF_MOUNTED) {
+ fputs(_("The orphan_file feature may only be cleared "
+ "when the filesystem is unmounted.\n"), stderr);
+ return 1;
+ }
+ if (ext2fs_has_feature_orphan_present(sb) && f_flag < 2) {
+ fputs(_("The orphan_present feature is set. Please "
+ "run e2fsck before clearing orphan_file "
+ "feature.\n"),
+ stderr);
+ return 1;
+ }
+ err = ext2fs_read_bitmaps(fs);
+ if (err) {
+ com_err(program_name, err, "%s",
+ _("while loading bitmaps"));
+ return 1;
+ }
+ err = ext2fs_truncate_orphan_file(fs);
+ if (err) {
+ com_err(program_name, err,
+ _("\n\twhile trying to delete orphan file\n"));
+ return 1;
+ }
+ ino = sb->s_orphan_file_inum;
+ sb->s_orphan_file_inum = 0;
+ ext2fs_inode_alloc_stats2(fs, ino, -1, 0);
+ ext2fs_clear_feature_orphan_file(sb);
+ ext2fs_clear_feature_orphan_present(sb);
+ ext2fs_mark_super_dirty(fs);
+ }
+
+ if (FEATURE_ON(E2P_FEATURE_COMPAT, EXT4_FEATURE_COMPAT_ORPHAN_FILE)) {
+ if (!ext2fs_has_feature_journal(sb)) {
+ fputs(_("orphan_file feature can be set only for "
+ "filesystems with journal.\n"), stderr);
+ return 1;
+ }
+ /*
+ * If adding an orphan file, let the create orphan file
+ * code below handle setting the flag and creating it.
+ * We supply a default size if necessary.
+ */
+ orphan_file_blocks = ext2fs_default_orphan_file_blocks(fs);
+ ext2fs_set_feature_orphan_file(sb);
+ }
+
+ if (FEATURE_ON(E2P_FEATURE_RO_INCOMPAT,
+ EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER)) {
+ if (ext2fs_has_feature_meta_bg(sb)) {
+ fputs(_("Setting filesystem feature 'sparse_super' "
+ "not supported\nfor filesystems with "
+ "the meta_bg feature enabled.\n"),
+ stderr);
+ return 1;
+ }
+ }
+
+ if (FEATURE_ON(E2P_FEATURE_INCOMPAT, EXT4_FEATURE_INCOMPAT_MMP)) {
+ int error;
+
+ if ((mount_flags & EXT2_MF_MOUNTED) ||
+ (mount_flags & EXT2_MF_READONLY)) {
+ fputs(_("The multiple mount protection feature can't\n"
+ "be set if the filesystem is mounted or\n"
+ "read-only.\n"), stderr);
+ return 1;
+ }
+
+ error = ext2fs_mmp_init(fs);
+ if (error) {
+ fputs(_("\nError while enabling multiple mount "
+ "protection feature."), stderr);
+ return 1;
+ }
+
+ /*
+ * We want to update group desc with the new free blocks count
+ */
+ fs->flags &= ~EXT2_FLAG_SUPER_ONLY;
+
+ printf(_("Multiple mount protection has been enabled "
+ "with update interval %ds.\n"),
+ sb->s_mmp_update_interval);
+ }
+
+ if (FEATURE_OFF(E2P_FEATURE_INCOMPAT, EXT4_FEATURE_INCOMPAT_MMP)) {
+ int error;
+
+ if (mount_flags & EXT2_MF_READONLY) {
+ fputs(_("The multiple mount protection feature cannot\n"
+ "be disabled if the filesystem is readonly.\n"),
+ stderr);
+ return 1;
+ }
+
+ error = ext2fs_read_bitmaps(fs);
+ if (error) {
+ fputs(_("Error while reading bitmaps\n"), stderr);
+ return 1;
+ }
+
+ error = ext2fs_mmp_read(fs, sb->s_mmp_block, NULL);
+ if (error) {
+ struct mmp_struct *mmp_cmp = fs->mmp_cmp;
+
+ if (error == EXT2_ET_MMP_MAGIC_INVALID)
+ printf(_("Magic number in MMP block does not "
+ "match. expected: %x, actual: %x\n"),
+ EXT4_MMP_MAGIC, mmp_cmp->mmp_magic);
+ else
+ com_err(program_name, error, "%s",
+ _("while reading MMP block."));
+ goto mmp_error;
+ }
+
+ /* We need to force out the group descriptors as well */
+ fs->flags &= ~EXT2_FLAG_SUPER_ONLY;
+ ext2fs_block_alloc_stats2(fs, sb->s_mmp_block, -1);
+mmp_error:
+ sb->s_mmp_block = 0;
+ sb->s_mmp_update_interval = 0;
+ }
+
+ if (FEATURE_ON(E2P_FEATURE_COMPAT, EXT3_FEATURE_COMPAT_HAS_JOURNAL)) {
+ /*
+ * If adding a journal flag, let the create journal
+ * code below handle setting the flag and creating the
+ * journal. We supply a default size if necessary.
+ */
+ if (!journal_size)
+ journal_size = -1;
+ ext2fs_clear_feature_journal(sb);
+ }
+
+ if (FEATURE_ON(E2P_FEATURE_COMPAT, EXT2_FEATURE_COMPAT_DIR_INDEX)) {
+ if (!sb->s_def_hash_version)
+ sb->s_def_hash_version = EXT2_HASH_HALF_MD4;
+ if (uuid_is_null((unsigned char *) sb->s_hash_seed))
+ uuid_generate((unsigned char *) sb->s_hash_seed);
+ }
+
+ if (FEATURE_OFF(E2P_FEATURE_COMPAT, EXT2_FEATURE_COMPAT_DIR_INDEX) &&
+ ext2fs_has_feature_metadata_csum(sb)) {
+ if (check_fsck_needed(fs,
+ _("Disabling directory index on filesystem with "
+ "checksums could take some time.")))
+ return 1;
+ if (mount_flags & EXT2_MF_MOUNTED) {
+ fputs(_("Cannot disable dir_index on a mounted "
+ "filesystem!\n"), stderr);
+ exit(1);
+ }
+ /*
+ * Clearing dir_index on checksummed filesystem requires
+ * rewriting all directories to update checksums.
+ */
+ rewrite_checksums |= REWRITE_DIR_FL;
+ }
+
+ if (FEATURE_OFF(E2P_FEATURE_INCOMPAT, EXT4_FEATURE_INCOMPAT_FLEX_BG)) {
+ if (ext2fs_check_desc(fs)) {
+ fputs(_("Clearing the flex_bg flag would "
+ "cause the the filesystem to be\n"
+ "inconsistent.\n"), stderr);
+ return 1;
+ }
+ }
+
+ if (FEATURE_OFF(E2P_FEATURE_RO_INCOMPAT,
+ EXT4_FEATURE_RO_COMPAT_HUGE_FILE)) {
+ if ((mount_flags & EXT2_MF_MOUNTED) &&
+ !(mount_flags & EXT2_MF_READONLY)) {
+ fputs(_("The huge_file feature may only be "
+ "cleared when the filesystem is\n"
+ "unmounted or mounted "
+ "read-only.\n"), stderr);
+ return 1;
+ }
+ }
+
+ if (FEATURE_ON(E2P_FEATURE_RO_INCOMPAT,
+ EXT4_FEATURE_RO_COMPAT_METADATA_CSUM)) {
+ if (check_fsck_needed(fs,
+ _("Enabling checksums could take some time.")))
+ return 1;
+ if (mount_flags & EXT2_MF_MOUNTED) {
+ fputs(_("Cannot enable metadata_csum on a mounted "
+ "filesystem!\n"), stderr);
+ return 1;
+ }
+ if (!ext2fs_has_feature_extents(fs->super))
+ printf("%s",
+ _("Extents are not enabled. The file extent "
+ "tree can be checksummed, whereas block maps "
+ "cannot. Not enabling extents reduces the "
+ "coverage of metadata checksumming. "
+ "Re-run with -O extent to rectify.\n"));
+ if (!ext2fs_has_feature_64bit(fs->super))
+ printf("%s",
+ _("64-bit filesystem support is not enabled. "
+ "The larger fields afforded by this feature "
+ "enable full-strength checksumming. "
+ "Run resize2fs -b to rectify.\n"));
+ rewrite_checksums = REWRITE_ALL;
+ /* metadata_csum supersedes uninit_bg */
+ ext2fs_clear_feature_gdt_csum(fs->super);
+
+ /* if uninit_bg was previously off, rewrite group desc */
+ if (!(old_features[E2P_FEATURE_RO_INCOMPAT] &
+ EXT4_FEATURE_RO_COMPAT_GDT_CSUM))
+ enable_uninit_bg(fs);
+
+ /*
+ * Since metadata_csum supersedes uninit_bg, pretend like
+ * uninit_bg has been off all along.
+ */
+ old_features[E2P_FEATURE_RO_INCOMPAT] &=
+ ~EXT4_FEATURE_RO_COMPAT_GDT_CSUM;
+ }
+
+ if (FEATURE_OFF(E2P_FEATURE_RO_INCOMPAT,
+ EXT4_FEATURE_RO_COMPAT_METADATA_CSUM)) {
+ __u32 test_features[3];
+
+ if (check_fsck_needed(fs,
+ _("Disabling checksums could take some time.")))
+ return 1;
+ if (mount_flags & EXT2_MF_MOUNTED) {
+ fputs(_("Cannot disable metadata_csum on a mounted "
+ "filesystem!\n"), stderr);
+ return 1;
+ }
+ rewrite_checksums = REWRITE_ALL;
+
+ /* Enable uninit_bg unless the user expressly turned it off */
+ memcpy(test_features, old_features, sizeof(test_features));
+ test_features[E2P_FEATURE_RO_INCOMPAT] |=
+ EXT4_FEATURE_RO_COMPAT_GDT_CSUM;
+ e2p_edit_feature2(features, test_features, ok_features,
+ clear_ok_features, NULL, NULL);
+ if (test_features[E2P_FEATURE_RO_INCOMPAT] &
+ EXT4_FEATURE_RO_COMPAT_GDT_CSUM)
+ ext2fs_set_feature_gdt_csum(fs->super);
+
+ /*
+ * If we're turning off metadata_csum and not turning on
+ * uninit_bg, rewrite group desc.
+ */
+ if (!ext2fs_has_feature_gdt_csum(fs->super)) {
+ err = disable_uninit_bg(fs,
+ EXT4_FEATURE_RO_COMPAT_METADATA_CSUM);
+ if (err)
+ return 1;
+ } else
+ /*
+ * metadata_csum previously provided uninit_bg, so if
+ * we're also setting the uninit_bg feature bit,
+ * pretend like it was previously enabled. Checksums
+ * will be rewritten with crc16 later.
+ */
+ old_features[E2P_FEATURE_RO_INCOMPAT] |=
+ EXT4_FEATURE_RO_COMPAT_GDT_CSUM;
+ fs->super->s_checksum_seed = 0;
+ ext2fs_clear_feature_csum_seed(fs->super);
+ }
+
+ if (FEATURE_ON(E2P_FEATURE_RO_INCOMPAT,
+ EXT4_FEATURE_RO_COMPAT_GDT_CSUM)) {
+ if (mount_flags & EXT2_MF_MOUNTED) {
+ fputs(_("Cannot enable uninit_bg on a mounted "
+ "filesystem!\n"), stderr);
+ return 1;
+ }
+
+ /* Do not enable uninit_bg when metadata_csum enabled */
+ if (ext2fs_has_feature_metadata_csum(fs->super))
+ ext2fs_clear_feature_gdt_csum(fs->super);
+ else
+ enable_uninit_bg(fs);
+ }
+
+ if (FEATURE_OFF(E2P_FEATURE_RO_INCOMPAT,
+ EXT4_FEATURE_RO_COMPAT_GDT_CSUM)) {
+ if (mount_flags & EXT2_MF_MOUNTED) {
+ fputs(_("Cannot disable uninit_bg on a mounted "
+ "filesystem!\n"), stderr);
+ return 1;
+ }
+
+ err = disable_uninit_bg(fs,
+ EXT4_FEATURE_RO_COMPAT_GDT_CSUM);
+ if (err)
+ return 1;
+ }
+
+ /*
+ * We don't actually toggle 64bit; resize2fs does that. But this
+ * must come after the metadata_csum feature_on so that it won't
+ * complain about the lack of 64bit.
+ */
+ if (FEATURE_ON(E2P_FEATURE_INCOMPAT,
+ EXT4_FEATURE_INCOMPAT_64BIT)) {
+ if (mount_flags & EXT2_MF_MOUNTED) {
+ fprintf(stderr, _("Cannot enable 64-bit mode "
+ "while mounted!\n"));
+ return 1;
+ }
+ ext2fs_clear_feature_64bit(sb);
+ feature_64bit = 1;
+ }
+ if (FEATURE_OFF(E2P_FEATURE_INCOMPAT,
+ EXT4_FEATURE_INCOMPAT_64BIT)) {
+ if (mount_flags & EXT2_MF_MOUNTED) {
+ fprintf(stderr, _("Cannot disable 64-bit mode "
+ "while mounted!\n"));
+ return 1;
+ }
+ ext2fs_set_feature_64bit(sb);
+ feature_64bit = -1;
+ }
+
+ if (FEATURE_ON(E2P_FEATURE_RO_INCOMPAT,
+ EXT4_FEATURE_RO_COMPAT_QUOTA)) {
+ /*
+ * Set the Q_flag here and handle the quota options in the code
+ * below.
+ */
+ if (!Q_flag) {
+ Q_flag = 1;
+ /* Enable usr/grp quota by default */
+ for (qtype = 0; qtype < MAXQUOTAS; qtype++) {
+ if (qtype != PRJQUOTA)
+ quota_enable[qtype] = QOPT_ENABLE;
+ else
+ quota_enable[qtype] = QOPT_DISABLE;
+ }
+ }
+ ext2fs_clear_feature_quota(sb);
+ }
+
+ if (FEATURE_ON(E2P_FEATURE_RO_INCOMPAT,
+ EXT4_FEATURE_RO_COMPAT_PROJECT)) {
+ if (fs->super->s_inode_size == EXT2_GOOD_OLD_INODE_SIZE) {
+ fprintf(stderr, _("Cannot enable project feature; "
+ "inode size too small.\n"));
+ return 1;
+ }
+ Q_flag = 1;
+ quota_enable[PRJQUOTA] = QOPT_ENABLE;
+ }
+
+ if (FEATURE_OFF(E2P_FEATURE_RO_INCOMPAT,
+ EXT4_FEATURE_RO_COMPAT_PROJECT)) {
+ Q_flag = 1;
+ quota_enable[PRJQUOTA] = QOPT_DISABLE;
+ }
+
+ if (FEATURE_OFF(E2P_FEATURE_RO_INCOMPAT,
+ EXT4_FEATURE_RO_COMPAT_QUOTA)) {
+ /*
+ * Set the Q_flag here and handle the quota options in the code
+ * below.
+ */
+ if (Q_flag)
+ fputs(_("\nWarning: '^quota' option overrides '-Q'"
+ "arguments.\n"), stderr);
+ Q_flag = 1;
+ /* Disable all quota by default */
+ for (qtype = 0; qtype < MAXQUOTAS; qtype++)
+ quota_enable[qtype] = QOPT_DISABLE;
+ }
+
+ if (FEATURE_ON(E2P_FEATURE_INCOMPAT, EXT4_FEATURE_INCOMPAT_ENCRYPT)) {
+ fs->super->s_encrypt_algos[0] =
+ EXT4_ENCRYPTION_MODE_AES_256_XTS;
+ fs->super->s_encrypt_algos[1] =
+ EXT4_ENCRYPTION_MODE_AES_256_CTS;
+ }
+
+ if (FEATURE_ON(E2P_FEATURE_INCOMPAT, EXT4_FEATURE_INCOMPAT_CASEFOLD)) {
+ if (mount_flags & EXT2_MF_MOUNTED) {
+ fputs(_("The casefold feature may only be enabled when "
+ "the filesystem is unmounted.\n"), stderr);
+ return 1;
+ }
+ fs->super->s_encoding = EXT4_ENC_UTF8_12_1;
+ fs->super->s_encoding_flags = e2p_get_encoding_flags(EXT4_ENC_UTF8_12_1);
+ enabling_casefold = 1;
+ }
+
+ if (FEATURE_OFF(E2P_FEATURE_INCOMPAT, EXT4_FEATURE_INCOMPAT_CASEFOLD)) {
+ if (mount_flags & EXT2_MF_MOUNTED) {
+ fputs(_("The casefold feature may only be disabled when "
+ "the filesystem is unmounted.\n"), stderr);
+ return 1;
+ }
+ if (has_casefold_inode(fs)) {
+ fputs(_("The casefold feature can't be cleared when "
+ "there are inodes with +F flag.\n"), stderr);
+ return 1;
+ }
+ fs->super->s_encoding = 0;
+ fs->super->s_encoding_flags = 0;
+ enabling_casefold = 0;
+ }
+
+ if (FEATURE_ON(E2P_FEATURE_INCOMPAT,
+ EXT4_FEATURE_INCOMPAT_CSUM_SEED)) {
+ if (!ext2fs_has_feature_metadata_csum(sb)) {
+ fputs(_("Setting feature 'metadata_csum_seed' "
+ "is only supported\non filesystems with "
+ "the metadata_csum feature enabled.\n"),
+ stderr);
+ return 1;
+ }
+ try_confirm_csum_seed_support();
+ fs->super->s_checksum_seed = fs->csum_seed;
+ }
+
+ if (FEATURE_OFF(E2P_FEATURE_INCOMPAT,
+ EXT4_FEATURE_INCOMPAT_CSUM_SEED)) {
+ __le32 uuid_seed;
+
+ uuid_seed = ext2fs_crc32c_le(~0, fs->super->s_uuid,
+ sizeof(fs->super->s_uuid));
+ if (fs->super->s_checksum_seed != uuid_seed) {
+ if (mount_flags & (EXT2_MF_BUSY|EXT2_MF_MOUNTED)) {
+ fputs(_("UUID has changed since enabling "
+ "metadata_csum. Filesystem must be unmounted "
+ "\nto safely rewrite all metadata to match the new UUID.\n"),
+ stderr);
+ return 1;
+ }
+ if (check_fsck_needed(fs, _("Recalculating checksums "
+ "could take some time.")))
+ return 1;
+ rewrite_checksums = REWRITE_ALL;
+ }
+ }
+
+ if (sb->s_rev_level == EXT2_GOOD_OLD_REV &&
+ (sb->s_feature_compat || sb->s_feature_ro_compat ||
+ sb->s_feature_incompat))
+ ext2fs_update_dynamic_rev(fs);
+
+ if (FEATURE_CHANGED(E2P_FEATURE_RO_INCOMPAT,
+ EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER) ||
+ FEATURE_OFF(E2P_FEATURE_RO_INCOMPAT,
+ EXT4_FEATURE_RO_COMPAT_HUGE_FILE) ||
+ FEATURE_CHANGED(E2P_FEATURE_INCOMPAT,
+ EXT2_FEATURE_INCOMPAT_FILETYPE) ||
+ FEATURE_CHANGED(E2P_FEATURE_COMPAT,
+ EXT2_FEATURE_COMPAT_RESIZE_INODE) ||
+ FEATURE_OFF(E2P_FEATURE_RO_INCOMPAT,
+ EXT2_FEATURE_RO_COMPAT_LARGE_FILE))
+ request_fsck_afterwards(fs);
+
+ if ((old_features[E2P_FEATURE_COMPAT] != sb->s_feature_compat) ||
+ (old_features[E2P_FEATURE_INCOMPAT] != sb->s_feature_incompat) ||
+ (old_features[E2P_FEATURE_RO_INCOMPAT] != sb->s_feature_ro_compat))
+ ext2fs_mark_super_dirty(fs);
+
+ return 0;
+}
+
+/*
+ * Add a journal to the filesystem.
+ */
+static int add_journal(ext2_filsys fs)
+{
+ struct ext2fs_journal_params jparams;
+ errcode_t retval;
+ ext2_filsys jfs;
+ io_manager io_ptr;
+
+ if (ext2fs_has_feature_journal(fs->super)) {
+ fputs(_("The filesystem already has a journal.\n"), stderr);
+ goto err;
+ }
+ if (journal_device) {
+ if (!check_plausibility(journal_device, CHECK_BLOCK_DEV,
+ NULL))
+ proceed_question(-1);
+ check_mount(journal_device, 0, _("journal"));
+#ifdef CONFIG_TESTIO_DEBUG
+ if (getenv("TEST_IO_FLAGS") || getenv("TEST_IO_BLOCK")) {
+ io_ptr = test_io_manager;
+ test_io_backing_manager = unix_io_manager;
+ } else
+#endif
+ io_ptr = unix_io_manager;
+ retval = ext2fs_open(journal_device, EXT2_FLAG_RW|
+ EXT2_FLAG_JOURNAL_DEV_OK, 0,
+ fs->blocksize, io_ptr, &jfs);
+ if (retval) {
+ com_err(program_name, retval,
+ _("\n\twhile trying to open journal on %s\n"),
+ journal_device);
+ goto err;
+ }
+ printf(_("Creating journal on device %s: "),
+ journal_device);
+ fflush(stdout);
+
+ retval = ext2fs_add_journal_device(fs, jfs);
+ ext2fs_close_free(&jfs);
+ if (retval) {
+ com_err(program_name, retval,
+ _("while adding filesystem to journal on %s"),
+ journal_device);
+ goto err;
+ }
+ fputs(_("done\n"), stdout);
+ } else if (journal_size) {
+ fputs(_("Creating journal inode: "), stdout);
+ fflush(stdout);
+ figure_journal_size(&jparams, journal_size, journal_fc_size, fs);
+
+ if (journal_location_string)
+ journal_location =
+ parse_num_blocks2(journal_location_string,
+ fs->super->s_log_block_size);
+ retval = ext2fs_add_journal_inode3(fs, &jparams,
+ journal_location,
+ journal_flags);
+ if (retval) {
+ fprintf(stderr, "\n");
+ com_err(program_name, retval, "%s",
+ _("\n\twhile trying to create journal file"));
+ return retval;
+ }
+ fs->super->s_overhead_clusters += EXT2FS_NUM_B2C(fs,
+ jparams.num_journal_blocks + jparams.num_fc_blocks);
+ ext2fs_mark_super_dirty(fs);
+ fputs(_("done\n"), stdout);
+
+ /*
+ * If the filesystem wasn't mounted, we need to force
+ * the block group descriptors out.
+ */
+ if ((mount_flags & EXT2_MF_MOUNTED) == 0)
+ fs->flags &= ~EXT2_FLAG_SUPER_ONLY;
+ }
+ print_check_message(fs->super->s_max_mnt_count,
+ fs->super->s_checkinterval);
+ return 0;
+
+err:
+ free(journal_device);
+ return 1;
+}
+
+static int handle_quota_options(ext2_filsys fs)
+{
+ errcode_t retval;
+ quota_ctx_t qctx;
+ ext2_ino_t qf_ino;
+ enum quota_type qtype;
+ unsigned int qtype_bits = 0;
+ int need_dirty = 0;
+
+ for (qtype = 0 ; qtype < MAXQUOTAS; qtype++)
+ if (quota_enable[qtype] != 0)
+ break;
+ if (qtype == MAXQUOTAS)
+ /* Nothing to do. */
+ return 0;
+
+ if (quota_enable[PRJQUOTA] == QOPT_ENABLE &&
+ fs->super->s_inode_size == EXT2_GOOD_OLD_INODE_SIZE) {
+ fprintf(stderr, _("Cannot enable project quota; "
+ "inode size too small.\n"));
+ return 1;
+ }
+
+ for (qtype = 0; qtype < MAXQUOTAS; qtype++) {
+ if (quota_enable[qtype] == QOPT_ENABLE)
+ qtype_bits |= 1 << qtype;
+ }
+
+ retval = quota_init_context(&qctx, fs, qtype_bits);
+ if (retval) {
+ com_err(program_name, retval,
+ _("while initializing quota context in support library"));
+ return 1;
+ }
+
+ if (qtype_bits)
+ quota_compute_usage(qctx);
+
+ for (qtype = 0 ; qtype < MAXQUOTAS; qtype++) {
+ if (quota_enable[qtype] == QOPT_ENABLE &&
+ *quota_sb_inump(fs->super, qtype) == 0) {
+ if ((qf_ino = quota_file_exists(fs, qtype)) > 0) {
+ retval = quota_read_all_dquots(qctx, qf_ino,
+ qtype,
+ QREAD_LIMITS);
+ if (retval) {
+ com_err(program_name, retval,
+ _("while updating quota limits (%d)"),
+ qtype);
+ quota_errout:
+ quota_release_context(&qctx);
+ return 1;
+ }
+ }
+ retval = quota_write_inode(qctx, 1 << qtype);
+ if (retval) {
+ com_err(program_name, retval,
+ _("while writing quota file (%d)"),
+ qtype);
+ goto quota_errout;
+ }
+ /* Enable Quota feature if one of quota enabled */
+ if (!ext2fs_has_feature_quota(fs->super)) {
+ ext2fs_set_feature_quota(fs->super);
+ need_dirty = 1;
+ }
+ if (qtype == PRJQUOTA &&
+ !ext2fs_has_feature_project(fs->super)) {
+ ext2fs_set_feature_project(fs->super);
+ need_dirty = 1;
+ }
+ } else if (quota_enable[qtype] == QOPT_DISABLE) {
+ retval = quota_remove_inode(fs, qtype);
+ if (retval) {
+ com_err(program_name, retval,
+ _("while removing quota file (%d)"),
+ qtype);
+ goto quota_errout;
+ }
+ if (qtype == PRJQUOTA) {
+ ext2fs_clear_feature_project(fs->super);
+ need_dirty = 1;
+ }
+ }
+ }
+
+ quota_release_context(&qctx);
+ /* Clear Quota feature if all quota types disabled. */
+ if (!qtype_bits) {
+ for (qtype = 0 ; qtype < MAXQUOTAS; qtype++)
+ if (*quota_sb_inump(fs->super, qtype))
+ break;
+ if (qtype == MAXQUOTAS) {
+ ext2fs_clear_feature_quota(fs->super);
+ need_dirty = 1;
+ }
+
+ }
+ if (need_dirty)
+ ext2fs_mark_super_dirty(fs);
+ return 0;
+}
+
+static int option_handle_function(char *token)
+{
+ if (strncmp(token, "usr", 3) == 0) {
+ quota_enable[USRQUOTA] = QOPT_ENABLE;
+ } else if (strncmp(token, "^usr", 4) == 0) {
+ quota_enable[USRQUOTA] = QOPT_DISABLE;
+ } else if (strncmp(token, "grp", 3) == 0) {
+ quota_enable[GRPQUOTA] = QOPT_ENABLE;
+ } else if (strncmp(token, "^grp", 4) == 0) {
+ quota_enable[GRPQUOTA] = QOPT_DISABLE;
+ } else if (strncmp(token, "prj", 3) == 0) {
+ quota_enable[PRJQUOTA] = QOPT_ENABLE;
+ } else if (strncmp(token, "^prj", 4) == 0) {
+ quota_enable[PRJQUOTA] = QOPT_DISABLE;
+ } else {
+ fputs(_("\nBad quota options specified.\n\n"
+ "Following valid quota options are available "
+ "(pass by separating with comma):\n"
+ "\t[^]usr[quota]\n"
+ "\t[^]grp[quota]\n"
+ "\t[^]prj[quota]\n"
+ "\n\n"), stderr);
+ return 1;
+ }
+ return 0;
+}
+
+static void parse_e2label_options(int argc, char ** argv)
+{
+ if ((argc < 2) || (argc > 3)) {
+ fputs(_("Usage: e2label device [newlabel]\n"), stderr);
+ exit(1);
+ }
+ io_options = strchr(argv[1], '?');
+ if (io_options)
+ *io_options++ = 0;
+ device_name = get_devname(NULL, argv[1], NULL);
+ if (!device_name) {
+ com_err("e2label", 0, _("Unable to resolve '%s'"),
+ argv[1]);
+ exit(1);
+ }
+ open_flag = EXT2_FLAG_JOURNAL_DEV_OK | EXT2_FLAG_SUPER_ONLY;
+ if (argc == 3) {
+ open_flag |= EXT2_FLAG_RW;
+ L_flag = 1;
+ new_label = argv[2];
+ } else
+ print_label++;
+}
+
+static time_t parse_time(char *str)
+{
+ struct tm ts;
+
+ if (strcmp(str, "now") == 0) {
+ return (time(0));
+ }
+ memset(&ts, 0, sizeof(ts));
+#ifdef HAVE_STRPTIME
+ strptime(str, "%Y%m%d%H%M%S", &ts);
+#else
+ sscanf(str, "%4d%2d%2d%2d%2d%2d", &ts.tm_year, &ts.tm_mon,
+ &ts.tm_mday, &ts.tm_hour, &ts.tm_min, &ts.tm_sec);
+ ts.tm_year -= 1900;
+ ts.tm_mon -= 1;
+ if (ts.tm_year < 0 || ts.tm_mon < 0 || ts.tm_mon > 11 ||
+ ts.tm_mday < 0 || ts.tm_mday > 31 || ts.tm_hour > 23 ||
+ ts.tm_min > 59 || ts.tm_sec > 61)
+ ts.tm_mday = 0;
+#endif
+ if (ts.tm_mday == 0) {
+ com_err(program_name, 0,
+ _("Couldn't parse date/time specifier: %s"),
+ str);
+ usage();
+ }
+ ts.tm_isdst = -1;
+ return (mktime(&ts));
+}
+
+static void parse_tune2fs_options(int argc, char **argv)
+{
+ int c;
+ char *tmp;
+ struct group *gr;
+ struct passwd *pw;
+ int ret;
+ char optstring[100] = "c:e:fg:i:jlm:o:r:s:u:C:E:I:J:L:M:O:T:U:z:Q:";
+
+ open_flag = 0;
+ printf("tune2fs %s (%s)\n", E2FSPROGS_VERSION, E2FSPROGS_DATE);
+ while ((c = getopt(argc, argv, optstring)) != EOF)
+ switch (c) {
+ case 'c':
+ open_flag = EXT2_FLAG_RW;
+ c_flag = 1;
+ if (strcmp(optarg, "random") == 0) {
+ max_mount_count = 65536;
+ break;
+ }
+ max_mount_count = strtol(optarg, &tmp, 0);
+ if (*tmp || max_mount_count > 16000 ||
+ max_mount_count < -16000) {
+ com_err(program_name, 0,
+ _("bad mounts count - %s"),
+ optarg);
+ usage();
+ }
+ if (max_mount_count == 0)
+ max_mount_count = -1;
+ break;
+ case 'C':
+ mount_count = strtoul(optarg, &tmp, 0);
+ if (*tmp || mount_count > 16000) {
+ com_err(program_name, 0,
+ _("bad mounts count - %s"),
+ optarg);
+ usage();
+ }
+ C_flag = 1;
+ open_flag = EXT2_FLAG_RW;
+ break;
+ case 'e':
+ if (strcmp(optarg, "continue") == 0)
+ errors = EXT2_ERRORS_CONTINUE;
+ else if (strcmp(optarg, "remount-ro") == 0)
+ errors = EXT2_ERRORS_RO;
+ else if (strcmp(optarg, "panic") == 0)
+ errors = EXT2_ERRORS_PANIC;
+ else {
+ com_err(program_name, 0,
+ _("bad error behavior - %s"),
+ optarg);
+ usage();
+ }
+ e_flag = 1;
+ open_flag = EXT2_FLAG_RW;
+ break;
+ case 'E':
+ extended_cmd = optarg;
+ open_flag |= EXT2_FLAG_RW;
+ break;
+ case 'f': /* Force */
+ f_flag++;
+ break;
+ case 'g':
+ resgid = strtoul(optarg, &tmp, 0);
+ if (*tmp) {
+ gr = getgrnam(optarg);
+ if (gr == NULL)
+ tmp = optarg;
+ else {
+ resgid = gr->gr_gid;
+ *tmp = 0;
+ }
+ }
+ if (*tmp) {
+ com_err(program_name, 0,
+ _("bad gid/group name - %s"),
+ optarg);
+ usage();
+ }
+ g_flag = 1;
+ open_flag = EXT2_FLAG_RW;
+ break;
+ case 'i':
+ interval = strtoul(optarg, &tmp, 0);
+ switch (*tmp) {
+ case 's':
+ tmp++;
+ break;
+ case '\0':
+ case 'd':
+ case 'D': /* days */
+ interval *= 86400;
+ if (*tmp != '\0')
+ tmp++;
+ break;
+ case 'm':
+ case 'M': /* months! */
+ interval *= 86400 * 30;
+ tmp++;
+ break;
+ case 'w':
+ case 'W': /* weeks */
+ interval *= 86400 * 7;
+ tmp++;
+ break;
+ }
+ if (*tmp) {
+ com_err(program_name, 0,
+ _("bad interval - %s"), optarg);
+ usage();
+ }
+ i_flag = 1;
+ open_flag = EXT2_FLAG_RW;
+ break;
+ case 'j':
+ if (!journal_size)
+ journal_size = -1;
+ open_flag = EXT2_FLAG_RW;
+ break;
+ case 'J':
+ parse_journal_opts(optarg);
+ open_flag = EXT2_FLAG_RW;
+ break;
+ case 'l':
+ l_flag = 1;
+ break;
+ case 'L':
+ new_label = optarg;
+ L_flag = 1;
+ open_flag |= EXT2_FLAG_RW |
+ EXT2_FLAG_JOURNAL_DEV_OK;
+ break;
+ case 'm':
+ reserved_ratio = strtod(optarg, &tmp);
+ if (*tmp || reserved_ratio > 50 ||
+ reserved_ratio < 0) {
+ com_err(program_name, 0,
+ _("bad reserved block ratio - %s"),
+ optarg);
+ usage();
+ }
+ m_flag = 1;
+ open_flag = EXT2_FLAG_RW;
+ break;
+ case 'M':
+ new_last_mounted = optarg;
+ M_flag = 1;
+ open_flag = EXT2_FLAG_RW;
+ break;
+ case 'o':
+ if (mntopts_cmd) {
+ com_err(program_name, 0, "%s",
+ _("-o may only be specified once"));
+ usage();
+ }
+ mntopts_cmd = optarg;
+ open_flag = EXT2_FLAG_RW;
+ break;
+ case 'O':
+ if (features_cmd) {
+ com_err(program_name, 0, "%s",
+ _("-O may only be specified once"));
+ usage();
+ }
+ features_cmd = optarg;
+ open_flag = EXT2_FLAG_RW;
+ break;
+ case 'Q':
+ Q_flag = 1;
+ ret = parse_quota_opts(optarg, option_handle_function);
+ if (ret)
+ exit(1);
+ open_flag = EXT2_FLAG_RW;
+ break;
+ case 'r':
+ reserved_blocks = strtoul(optarg, &tmp, 0);
+ if (*tmp) {
+ com_err(program_name, 0,
+ _("bad reserved blocks count - %s"),
+ optarg);
+ usage();
+ }
+ r_flag = 1;
+ open_flag = EXT2_FLAG_RW;
+ break;
+ case 's': /* Deprecated */
+ s_flag = atoi(optarg);
+ open_flag = EXT2_FLAG_RW;
+ break;
+ case 'T':
+ T_flag = 1;
+ last_check_time = parse_time(optarg);
+ open_flag = EXT2_FLAG_RW;
+ break;
+ case 'u':
+ resuid = strtoul(optarg, &tmp, 0);
+ if (*tmp) {
+ pw = getpwnam(optarg);
+ if (pw == NULL)
+ tmp = optarg;
+ else {
+ resuid = pw->pw_uid;
+ *tmp = 0;
+ }
+ }
+ if (*tmp) {
+ com_err(program_name, 0,
+ _("bad uid/user name - %s"),
+ optarg);
+ usage();
+ }
+ u_flag = 1;
+ open_flag = EXT2_FLAG_RW;
+ break;
+ case 'U':
+ requested_uuid = optarg;
+ U_flag = 1;
+ open_flag = EXT2_FLAG_RW |
+ EXT2_FLAG_JOURNAL_DEV_OK;
+ break;
+ case 'I':
+ new_inode_size = strtoul(optarg, &tmp, 0);
+ if (*tmp) {
+ com_err(program_name, 0,
+ _("bad inode size - %s"),
+ optarg);
+ usage();
+ }
+ if (!((new_inode_size &
+ (new_inode_size - 1)) == 0)) {
+ com_err(program_name, 0,
+ _("Inode size must be a "
+ "power of two- %s"),
+ optarg);
+ usage();
+ }
+ open_flag = EXT2_FLAG_RW;
+ I_flag = 1;
+ break;
+ case 'z':
+ undo_file = optarg;
+ break;
+ default:
+ usage();
+ }
+ if (optind < argc - 1 || optind == argc)
+ usage();
+ if (!open_flag && !l_flag)
+ usage();
+ io_options = strchr(argv[optind], '?');
+ if (io_options)
+ *io_options++ = 0;
+ device_name = get_devname(NULL, argv[optind], NULL);
+ if (!device_name) {
+ com_err(program_name, 0, _("Unable to resolve '%s'"),
+ argv[optind]);
+ exit(1);
+ }
+}
+
+#ifdef CONFIG_BUILD_FINDFS
+void do_findfs(int argc, char **argv)
+{
+ char *dev;
+
+ if ((argc != 2) ||
+ (strncmp(argv[1], "LABEL=", 6) && strncmp(argv[1], "UUID=", 5))) {
+ fprintf(stderr, "Usage: findfs LABEL=<label>|UUID=<uuid>\n");
+ exit(2);
+ }
+ dev = blkid_get_devname(NULL, argv[1], NULL);
+ if (!dev) {
+ com_err("findfs", 0, _("Unable to resolve '%s'"),
+ argv[1]);
+ exit(1);
+ }
+ puts(dev);
+ exit(0);
+}
+#endif
+
+static int parse_extended_opts(ext2_filsys fs, const char *opts)
+{
+ struct ext2_super_block *sb = fs->super;
+ char *buf, *token, *next, *p, *arg;
+ int len, hash_alg;
+ int r_usage = 0;
+ int encoding = 0;
+ char *encoding_flags = NULL;
+
+ len = strlen(opts);
+ buf = malloc(len+1);
+ if (!buf) {
+ fprintf(stderr, "%s",
+ _("Couldn't allocate memory to parse options!\n"));
+ return 1;
+ }
+ strcpy(buf, opts);
+ for (token = buf; token && *token; token = next) {
+ p = strchr(token, ',');
+ next = 0;
+ if (p) {
+ *p = 0;
+ next = p+1;
+ }
+ arg = strchr(token, '=');
+ if (arg) {
+ *arg = 0;
+ arg++;
+ }
+ if (strcmp(token, "clear-mmp") == 0 ||
+ strcmp(token, "clear_mmp") == 0) {
+ clear_mmp = 1;
+ } else if (strcmp(token, "mmp_update_interval") == 0) {
+ unsigned long intv;
+ if (!arg) {
+ r_usage++;
+ continue;
+ }
+ intv = strtoul(arg, &p, 0);
+ if (*p) {
+ fprintf(stderr,
+ _("Invalid mmp_update_interval: %s\n"),
+ arg);
+ r_usage++;
+ continue;
+ }
+ if (intv == 0) {
+ intv = EXT4_MMP_UPDATE_INTERVAL;
+ } else if (intv > EXT4_MMP_MAX_UPDATE_INTERVAL) {
+ fprintf(stderr,
+ _("mmp_update_interval too big: %lu\n"),
+ intv);
+ r_usage++;
+ continue;
+ }
+ printf(P_("Setting multiple mount protection update "
+ "interval to %lu second\n",
+ "Setting multiple mount protection update "
+ "interval to %lu seconds\n", intv),
+ intv);
+ sb->s_mmp_update_interval = intv;
+ ext2fs_mark_super_dirty(fs);
+ } else if (!strcmp(token, "force_fsck")) {
+ sb->s_state |= EXT2_ERROR_FS;
+ printf(_("Setting filesystem error flag to force fsck.\n"));
+ ext2fs_mark_super_dirty(fs);
+ } else if (!strcmp(token, "test_fs")) {
+ sb->s_flags |= EXT2_FLAGS_TEST_FILESYS;
+ printf("Setting test filesystem flag\n");
+ ext2fs_mark_super_dirty(fs);
+ } else if (!strcmp(token, "^test_fs")) {
+ sb->s_flags &= ~EXT2_FLAGS_TEST_FILESYS;
+ printf("Clearing test filesystem flag\n");
+ ext2fs_mark_super_dirty(fs);
+ } else if (strcmp(token, "stride") == 0) {
+ if (!arg) {
+ r_usage++;
+ continue;
+ }
+ stride = strtoul(arg, &p, 0);
+ if (*p) {
+ fprintf(stderr,
+ _("Invalid RAID stride: %s\n"),
+ arg);
+ r_usage++;
+ continue;
+ }
+ stride_set = 1;
+ } else if (strcmp(token, "stripe-width") == 0 ||
+ strcmp(token, "stripe_width") == 0) {
+ if (!arg) {
+ r_usage++;
+ continue;
+ }
+ stripe_width = strtoul(arg, &p, 0);
+ if (*p) {
+ fprintf(stderr,
+ _("Invalid RAID stripe-width: %s\n"),
+ arg);
+ r_usage++;
+ continue;
+ }
+ stripe_width_set = 1;
+ } else if (strcmp(token, "hash_alg") == 0 ||
+ strcmp(token, "hash-alg") == 0) {
+ if (!arg) {
+ r_usage++;
+ continue;
+ }
+ hash_alg = e2p_string2hash(arg);
+ if (hash_alg < 0) {
+ fprintf(stderr,
+ _("Invalid hash algorithm: %s\n"),
+ arg);
+ r_usage++;
+ continue;
+ }
+ sb->s_def_hash_version = hash_alg;
+ printf(_("Setting default hash algorithm "
+ "to %s (%d)\n"),
+ arg, hash_alg);
+ ext2fs_mark_super_dirty(fs);
+ } else if (!strcmp(token, "mount_opts")) {
+ if (!arg) {
+ r_usage++;
+ continue;
+ }
+ if (strlen(arg) >= sizeof(fs->super->s_mount_opts)) {
+ fprintf(stderr,
+ "Extended mount options too long\n");
+ continue;
+ }
+ ext_mount_opts = strdup(arg);
+ } else if (!strcmp(token, "encoding")) {
+ if (!arg) {
+ r_usage++;
+ continue;
+ }
+ if (mount_flags & EXT2_MF_MOUNTED) {
+ fputs(_("The casefold feature may only be enabled when "
+ "the filesystem is unmounted.\n"), stderr);
+ r_usage++;
+ continue;
+ }
+ if (ext2fs_has_feature_casefold(sb) && !enabling_casefold) {
+ fprintf(stderr, _("Cannot alter existing encoding\n"));
+ r_usage++;
+ continue;
+ }
+ encoding = e2p_str2encoding(arg);
+ if (encoding < 0) {
+ fprintf(stderr, _("Invalid encoding: %s\n"), arg);
+ r_usage++;
+ continue;
+ }
+ enabling_casefold = 1;
+ sb->s_encoding = encoding;
+ printf(_("Setting encoding to '%s'\n"), arg);
+ sb->s_encoding_flags =
+ e2p_get_encoding_flags(sb->s_encoding);
+ } else if (!strcmp(token, "encoding_flags")) {
+ if (!arg) {
+ r_usage++;
+ continue;
+ }
+ encoding_flags = arg;
+ } else if (!strcmp(token, "orphan_file_size")) {
+ if (!arg) {
+ r_usage++;
+ continue;
+ }
+ orphan_file_blocks = parse_num_blocks2(arg,
+ fs->super->s_log_block_size);
+
+ if (orphan_file_blocks < 1) {
+ fprintf(stderr,
+ _("Invalid size of orphan file %s\n"),
+ arg);
+ r_usage++;
+ continue;
+ }
+ } else
+ r_usage++;
+ }
+
+ if (encoding > 0 && !r_usage) {
+ sb->s_encoding_flags =
+ e2p_get_encoding_flags(sb->s_encoding);
+
+ if (encoding_flags &&
+ e2p_str2encoding_flags(sb->s_encoding, encoding_flags,
+ &sb->s_encoding_flags)) {
+ fprintf(stderr, _("error: Invalid encoding flag: %s\n"),
+ encoding_flags);
+ r_usage++;
+ } else if (encoding_flags)
+ printf(_("Setting encoding_flags to '%s'\n"),
+ encoding_flags);
+ ext2fs_set_feature_casefold(sb);
+ ext2fs_mark_super_dirty(fs);
+ } else if (encoding_flags && !r_usage) {
+ fprintf(stderr, _("error: An encoding must be explicitly "
+ "specified when passing encoding-flags\n"));
+ r_usage++;
+ }
+ if (r_usage) {
+ fprintf(stderr, "%s", _("\nBad options specified.\n\n"
+ "Extended options are separated by commas, "
+ "and may take an argument which\n"
+ "\tis set off by an equals ('=') sign.\n\n"
+ "Valid extended options are:\n"
+ "\tclear_mmp\n"
+ "\thash_alg=<hash algorithm>\n"
+ "\tmount_opts=<extended default mount options>\n"
+ "\tmmp_update_interval=<mmp update interval in seconds>\n"
+ "\tstride=<RAID per-disk chunk size in blocks>\n"
+ "\tstripe_width=<RAID stride*data disks in blocks>\n"
+ "\tforce_fsck\n"
+ "\ttest_fs\n"
+ "\t^test_fs\n"
+ "\tencoding=<encoding>\n"
+ "\tencoding_flags=<flags>\n"));
+ free(buf);
+ return 1;
+ }
+ free(buf);
+
+ return 0;
+}
+
+/*
+ * Fill in the block bitmap bmap with the information regarding the
+ * blocks to be moved
+ */
+static int get_move_bitmaps(ext2_filsys fs, int new_ino_blks_per_grp,
+ ext2fs_block_bitmap bmap)
+{
+ dgrp_t i;
+ int retval;
+ ext2_badblocks_list bb_list = 0;
+ blk64_t j, needed_blocks = 0;
+ blk64_t start_blk, end_blk;
+
+ retval = ext2fs_read_bb_inode(fs, &bb_list);
+ if (retval)
+ return retval;
+
+ for (i = 0; i < fs->group_desc_count; i++) {
+ start_blk = ext2fs_inode_table_loc(fs, i) +
+ fs->inode_blocks_per_group;
+
+ end_blk = ext2fs_inode_table_loc(fs, i) +
+ new_ino_blks_per_grp;
+
+ for (j = start_blk; j < end_blk; j++) {
+ if (ext2fs_test_block_bitmap2(fs->block_map, j)) {
+ /*
+ * IF the block is a bad block we fail
+ */
+ if (ext2fs_badblocks_list_test(bb_list, j)) {
+ ext2fs_badblocks_list_free(bb_list);
+ return ENOSPC;
+ }
+
+ ext2fs_mark_block_bitmap2(bmap, j);
+ } else {
+ /*
+ * We are going to use this block for
+ * inode table. So mark them used.
+ */
+ ext2fs_mark_block_bitmap2(fs->block_map, j);
+ }
+ }
+ needed_blocks += end_blk - start_blk;
+ }
+
+ ext2fs_badblocks_list_free(bb_list);
+ if (needed_blocks > ext2fs_free_blocks_count(fs->super))
+ return ENOSPC;
+
+ return 0;
+}
+
+static int ext2fs_is_meta_block(ext2_filsys fs, blk64_t blk)
+{
+ dgrp_t group;
+ group = ext2fs_group_of_blk2(fs, blk);
+ if (ext2fs_block_bitmap_loc(fs, group) == blk)
+ return 1;
+ if (ext2fs_inode_bitmap_loc(fs, group) == blk)
+ return 1;
+ return 0;
+}
+
+static int ext2fs_is_block_in_group(ext2_filsys fs, dgrp_t group, blk64_t blk)
+{
+ blk64_t start_blk, end_blk;
+ start_blk = fs->super->s_first_data_block +
+ EXT2_GROUPS_TO_BLOCKS(fs->super, group);
+ /*
+ * We cannot get new block beyond end_blk for for the last block group
+ * so we can check with EXT2_BLOCKS_PER_GROUP even for last block group
+ */
+ end_blk = start_blk + EXT2_BLOCKS_PER_GROUP(fs->super);
+ if (blk >= start_blk && blk <= end_blk)
+ return 1;
+ return 0;
+}
+
+static int move_block(ext2_filsys fs, ext2fs_block_bitmap bmap)
+{
+
+ char *buf;
+ dgrp_t group = 0;
+ errcode_t retval;
+ int meta_data = 0;
+ blk64_t blk, new_blk, goal;
+ struct blk_move *bmv;
+
+ retval = ext2fs_get_mem(fs->blocksize, &buf);
+ if (retval)
+ return retval;
+
+ for (new_blk = blk = fs->super->s_first_data_block;
+ blk < ext2fs_blocks_count(fs->super); blk++) {
+ if (!ext2fs_test_block_bitmap2(bmap, blk))
+ continue;
+
+ if (ext2fs_is_meta_block(fs, blk)) {
+ /*
+ * If the block is mapping a fs meta data block
+ * like group desc/block bitmap/inode bitmap. We
+ * should find a block in the same group and fix
+ * the respective fs metadata pointers. Otherwise
+ * fail
+ */
+ group = ext2fs_group_of_blk2(fs, blk);
+ goal = ext2fs_group_first_block2(fs, group);
+ meta_data = 1;
+
+ } else {
+ goal = new_blk;
+ }
+ retval = ext2fs_new_block2(fs, goal, NULL, &new_blk);
+ if (retval)
+ goto err_out;
+
+ /* new fs meta data block should be in the same group */
+ if (meta_data && !ext2fs_is_block_in_group(fs, group, new_blk)) {
+ retval = ENOSPC;
+ goto err_out;
+ }
+
+ /* Mark this block as allocated */
+ ext2fs_mark_block_bitmap2(fs->block_map, new_blk);
+
+ /* Add it to block move list */
+ retval = ext2fs_get_mem(sizeof(struct blk_move), &bmv);
+ if (retval)
+ goto err_out;
+
+ bmv->old_loc = blk;
+ bmv->new_loc = new_blk;
+
+ list_add(&(bmv->list), &blk_move_list);
+
+ retval = io_channel_read_blk64(fs->io, blk, 1, buf);
+ if (retval)
+ goto err_out;
+
+ retval = io_channel_write_blk64(fs->io, new_blk, 1, buf);
+ if (retval)
+ goto err_out;
+ }
+
+err_out:
+ ext2fs_free_mem(&buf);
+ return retval;
+}
+
+static blk64_t translate_block(blk64_t blk)
+{
+ struct list_head *entry;
+ struct blk_move *bmv;
+
+ list_for_each(entry, &blk_move_list) {
+ bmv = list_entry(entry, struct blk_move, list);
+ if (bmv->old_loc == blk)
+ return bmv->new_loc;
+ }
+
+ return 0;
+}
+
+static int process_block(ext2_filsys fs EXT2FS_ATTR((unused)),
+ blk64_t *block_nr,
+ e2_blkcnt_t blockcnt EXT2FS_ATTR((unused)),
+ blk64_t ref_block EXT2FS_ATTR((unused)),
+ int ref_offset EXT2FS_ATTR((unused)),
+ void *priv_data)
+{
+ int ret = 0;
+ blk64_t new_blk;
+ ext2fs_block_bitmap bmap = (ext2fs_block_bitmap) priv_data;
+
+ if (!ext2fs_test_block_bitmap2(bmap, *block_nr))
+ return 0;
+ new_blk = translate_block(*block_nr);
+ if (new_blk) {
+ *block_nr = new_blk;
+ /*
+ * This will force the ext2fs_write_inode in the iterator
+ */
+ ret |= BLOCK_CHANGED;
+ }
+
+ return ret;
+}
+
+static int inode_scan_and_fix(ext2_filsys fs, ext2fs_block_bitmap bmap)
+{
+ errcode_t retval = 0;
+ ext2_ino_t ino;
+ blk64_t blk;
+ char *block_buf = 0;
+ struct ext2_inode inode;
+ ext2_inode_scan scan = NULL;
+
+ retval = ext2fs_get_mem(fs->blocksize * 3, &block_buf);
+ if (retval)
+ return retval;
+
+ retval = ext2fs_open_inode_scan(fs, 0, &scan);
+ if (retval)
+ goto err_out;
+
+ while (1) {
+ retval = ext2fs_get_next_inode(scan, &ino, &inode);
+ if (retval)
+ goto err_out;
+
+ if (!ino)
+ break;
+
+ if (inode.i_links_count == 0)
+ continue; /* inode not in use */
+
+ /* FIXME!!
+ * If we end up modifying the journal inode
+ * the sb->s_jnl_blocks will differ. But a
+ * subsequent e2fsck fixes that.
+ * Do we need to fix this ??
+ */
+
+ if (ext2fs_file_acl_block(fs, &inode) &&
+ ext2fs_test_block_bitmap2(bmap,
+ ext2fs_file_acl_block(fs, &inode))) {
+ blk = translate_block(ext2fs_file_acl_block(fs,
+ &inode));
+ if (!blk)
+ continue;
+
+ ext2fs_file_acl_block_set(fs, &inode, blk);
+
+ /*
+ * Write the inode to disk so that inode table
+ * resizing can work
+ */
+ retval = ext2fs_write_inode(fs, ino, &inode);
+ if (retval)
+ goto err_out;
+ }
+
+ if (!ext2fs_inode_has_valid_blocks2(fs, &inode))
+ continue;
+
+ retval = ext2fs_block_iterate3(fs, ino, 0, block_buf,
+ process_block, bmap);
+ if (retval)
+ goto err_out;
+
+ }
+
+err_out:
+ ext2fs_free_mem(&block_buf);
+ ext2fs_close_inode_scan(scan);
+
+ return retval;
+}
+
+/*
+ * We need to scan for inode and block bitmaps that may need to be
+ * moved. This can take place if the filesystem was formatted for
+ * RAID arrays using the mke2fs's extended option "stride".
+ */
+static int group_desc_scan_and_fix(ext2_filsys fs, ext2fs_block_bitmap bmap)
+{
+ dgrp_t i;
+ blk64_t blk, new_blk;
+
+ for (i = 0; i < fs->group_desc_count; i++) {
+ blk = ext2fs_block_bitmap_loc(fs, i);
+ if (ext2fs_test_block_bitmap2(bmap, blk)) {
+ new_blk = translate_block(blk);
+ if (!new_blk)
+ continue;
+ ext2fs_block_bitmap_loc_set(fs, i, new_blk);
+ }
+
+ blk = ext2fs_inode_bitmap_loc(fs, i);
+ if (ext2fs_test_block_bitmap2(bmap, blk)) {
+ new_blk = translate_block(blk);
+ if (!new_blk)
+ continue;
+ ext2fs_inode_bitmap_loc_set(fs, i, new_blk);
+ }
+ }
+ return 0;
+}
+
+static int expand_inode_table(ext2_filsys fs, unsigned long new_ino_size)
+{
+ dgrp_t i;
+ blk64_t blk;
+ errcode_t retval;
+ int new_ino_blks_per_grp;
+ unsigned int j;
+ char *old_itable = NULL, *new_itable = NULL;
+ char *tmp_old_itable = NULL, *tmp_new_itable = NULL;
+ unsigned long old_ino_size;
+ int old_itable_size, new_itable_size;
+
+ old_itable_size = fs->inode_blocks_per_group * fs->blocksize;
+ old_ino_size = EXT2_INODE_SIZE(fs->super);
+
+ new_ino_blks_per_grp = ext2fs_div_ceil(
+ EXT2_INODES_PER_GROUP(fs->super) *
+ new_ino_size,
+ fs->blocksize);
+
+ new_itable_size = new_ino_blks_per_grp * fs->blocksize;
+
+ retval = ext2fs_get_mem(old_itable_size, &old_itable);
+ if (retval)
+ return retval;
+
+ retval = ext2fs_get_mem(new_itable_size, &new_itable);
+ if (retval)
+ goto err_out;
+
+ tmp_old_itable = old_itable;
+ tmp_new_itable = new_itable;
+
+ for (i = 0; i < fs->group_desc_count; i++) {
+ blk = ext2fs_inode_table_loc(fs, i);
+ retval = io_channel_read_blk64(fs->io, blk,
+ fs->inode_blocks_per_group, old_itable);
+ if (retval)
+ goto err_out;
+
+ for (j = 0; j < EXT2_INODES_PER_GROUP(fs->super); j++) {
+ memcpy(new_itable, old_itable, old_ino_size);
+
+ memset(new_itable+old_ino_size, 0,
+ new_ino_size - old_ino_size);
+
+ new_itable += new_ino_size;
+ old_itable += old_ino_size;
+ }
+
+ /* reset the pointer */
+ old_itable = tmp_old_itable;
+ new_itable = tmp_new_itable;
+
+ retval = io_channel_write_blk64(fs->io, blk,
+ new_ino_blks_per_grp, new_itable);
+ if (retval)
+ goto err_out;
+ }
+
+ /* Update the meta data */
+ fs->inode_blocks_per_group = new_ino_blks_per_grp;
+ ext2fs_free_inode_cache(fs->icache);
+ fs->icache = 0;
+ fs->super->s_inode_size = new_ino_size;
+
+err_out:
+ if (old_itable)
+ ext2fs_free_mem(&old_itable);
+
+ if (new_itable)
+ ext2fs_free_mem(&new_itable);
+
+ return retval;
+}
+
+
+#define list_for_each_safe(pos, pnext, head) \
+ for (pos = (head)->next, pnext = pos->next; pos != (head); \
+ pos = pnext, pnext = pos->next)
+
+static void free_blk_move_list(void)
+{
+ struct list_head *entry, *tmp;
+ struct blk_move *bmv;
+
+ list_for_each_safe(entry, tmp, &blk_move_list) {
+ bmv = list_entry(entry, struct blk_move, list);
+ list_del(entry);
+ ext2fs_free_mem(&bmv);
+ }
+ return;
+}
+
+static int resize_inode(ext2_filsys fs, unsigned long new_size)
+{
+ errcode_t retval;
+ int new_ino_blks_per_grp;
+ ext2fs_block_bitmap bmap;
+
+ retval = ext2fs_read_inode_bitmap(fs);
+ if (retval) {
+ fputs(_("Failed to read inode bitmap\n"), stderr);
+ return retval;
+ }
+ retval = ext2fs_read_block_bitmap(fs);
+ if (retval) {
+ fputs(_("Failed to read block bitmap\n"), stderr);
+ return retval;
+ }
+ INIT_LIST_HEAD(&blk_move_list);
+
+
+ new_ino_blks_per_grp = ext2fs_div_ceil(
+ EXT2_INODES_PER_GROUP(fs->super)*
+ new_size,
+ fs->blocksize);
+
+ /* We may change the file system.
+ * Mark the file system as invalid so that
+ * the user is prompted to run fsck.
+ */
+ fs->super->s_state &= ~EXT2_VALID_FS;
+
+ retval = ext2fs_allocate_block_bitmap(fs, _("blocks to be moved"),
+ &bmap);
+ if (retval) {
+ fputs(_("Failed to allocate block bitmap when "
+ "increasing inode size\n"), stderr);
+ return retval;
+ }
+ retval = get_move_bitmaps(fs, new_ino_blks_per_grp, bmap);
+ if (retval) {
+ fputs(_("Not enough space to increase inode size \n"), stderr);
+ goto err_out;
+ }
+ retval = move_block(fs, bmap);
+ if (retval) {
+ fputs(_("Failed to relocate blocks during inode resize \n"),
+ stderr);
+ goto err_out;
+ }
+ retval = inode_scan_and_fix(fs, bmap);
+ if (retval)
+ goto err_out_undo;
+
+ retval = group_desc_scan_and_fix(fs, bmap);
+ if (retval)
+ goto err_out_undo;
+
+ retval = expand_inode_table(fs, new_size);
+ if (retval)
+ goto err_out_undo;
+
+ ext2fs_calculate_summary_stats(fs, 1 /* super only */);
+
+ fs->super->s_state |= EXT2_VALID_FS;
+ /* mark super block and block bitmap as dirty */
+ ext2fs_mark_super_dirty(fs);
+ ext2fs_mark_bb_dirty(fs);
+
+err_out:
+ free_blk_move_list();
+ ext2fs_free_block_bitmap(bmap);
+
+ return retval;
+
+err_out_undo:
+ free_blk_move_list();
+ ext2fs_free_block_bitmap(bmap);
+ fputs(_("Error in resizing the inode size.\n"
+ "Run e2undo to undo the "
+ "file system changes. \n"), stderr);
+
+ return retval;
+}
+
+static int tune2fs_setup_tdb(const char *name, io_manager *io_ptr)
+{
+ errcode_t retval = 0;
+ const char *tdb_dir;
+ char *tdb_file = NULL;
+ char *dev_name, *tmp_name;
+
+ /* (re)open a specific undo file */
+ if (undo_file && undo_file[0] != 0) {
+ retval = set_undo_io_backing_manager(*io_ptr);
+ if (retval)
+ goto err;
+ *io_ptr = undo_io_manager;
+ retval = set_undo_io_backup_file(undo_file);
+ if (retval)
+ goto err;
+ printf(_("Overwriting existing filesystem; this can be undone "
+ "using the command:\n"
+ " e2undo %s %s\n\n"),
+ undo_file, name);
+ return retval;
+ }
+
+ /*
+ * Configuration via a conf file would be
+ * nice
+ */
+ tdb_dir = getenv("E2FSPROGS_UNDO_DIR");
+ if (!tdb_dir)
+ tdb_dir = "/var/lib/e2fsprogs";
+
+ if (!strcmp(tdb_dir, "none") || (tdb_dir[0] == 0) ||
+ access(tdb_dir, W_OK))
+ return 0;
+
+ tmp_name = strdup(name);
+ if (!tmp_name)
+ goto errout;
+ dev_name = basename(tmp_name);
+ tdb_file = malloc(strlen(tdb_dir) + 9 + strlen(dev_name) + 7 + 1);
+ if (!tdb_file) {
+ free(tmp_name);
+ goto errout;
+ }
+ sprintf(tdb_file, "%s/tune2fs-%s.e2undo", tdb_dir, dev_name);
+ free(tmp_name);
+
+ if ((unlink(tdb_file) < 0) && (errno != ENOENT)) {
+ retval = errno;
+ com_err(program_name, retval,
+ _("while trying to delete %s"), tdb_file);
+ goto errout;
+ }
+
+ retval = set_undo_io_backing_manager(*io_ptr);
+ if (retval)
+ goto errout;
+ *io_ptr = undo_io_manager;
+ retval = set_undo_io_backup_file(tdb_file);
+ if (retval)
+ goto errout;
+ printf(_("Overwriting existing filesystem; this can be undone "
+ "using the command:\n"
+ " e2undo %s %s\n\n"),
+ tdb_file, name);
+
+ free(tdb_file);
+ return 0;
+errout:
+ free(tdb_file);
+err:
+ com_err("tune2fs", retval, "while trying to setup undo file\n");
+ return retval;
+}
+
+static int
+fs_update_journal_user(struct ext2_super_block *sb, __u8 old_uuid[UUID_SIZE])
+{
+ int retval, nr_users, start;
+ journal_superblock_t *jsb;
+ ext2_filsys jfs;
+ __u8 *j_uuid;
+ char *journal_path;
+ char uuid[UUID_STR_SIZE];
+ char buf[SUPERBLOCK_SIZE] __attribute__ ((aligned(8)));
+
+ if (!ext2fs_has_feature_journal(sb) || uuid_is_null(sb->s_journal_uuid))
+ return 0;
+
+ uuid_unparse(sb->s_journal_uuid, uuid);
+ journal_path = blkid_get_devname(NULL, "UUID", uuid);
+ if (!journal_path)
+ return 0;
+
+ retval = ext2fs_open2(journal_path, io_options,
+ EXT2_FLAG_JOURNAL_DEV_OK | EXT2_FLAG_RW,
+ 0, 0, unix_io_manager, &jfs);
+ if (retval) {
+ com_err(program_name, retval,
+ _("while trying to open %s"),
+ journal_path);
+ return retval;
+ }
+
+ retval = get_journal_sb(jfs, buf);
+ if (retval != 0) {
+ if (retval == EXT2_ET_UNSUPP_FEATURE)
+ fprintf(stderr, _("%s is not a journal device.\n"),
+ journal_path);
+ return retval;
+ }
+
+ jsb = (journal_superblock_t *) buf;
+ /* Find the filesystem UUID */
+ nr_users = ntohl(jsb->s_nr_users);
+ if (nr_users > JBD2_USERS_MAX) {
+ fprintf(stderr, _("Journal superblock is corrupted, nr_users\n"
+ "is too high (%d).\n"), nr_users);
+ return EXT2_ET_CORRUPT_JOURNAL_SB;
+ }
+
+ j_uuid = journal_user(old_uuid, jsb->s_users, nr_users);
+ if (j_uuid == NULL) {
+ fputs(_("Filesystem's UUID not found on journal device.\n"),
+ stderr);
+ return EXT2_ET_LOAD_EXT_JOURNAL;
+ }
+
+ memcpy(j_uuid, sb->s_uuid, UUID_SIZE);
+
+ start = ext2fs_journal_sb_start(jfs->blocksize);
+ /* Write back the journal superblock */
+ retval = io_channel_write_blk64(jfs->io, start, -SUPERBLOCK_SIZE, buf);
+ if (retval != 0) {
+ com_err(program_name, retval,
+ "while writing journal superblock.");
+ return retval;
+ }
+
+ ext2fs_close(jfs);
+
+ return 0;
+}
+
+/*
+ * Use FS_IOC_SETFSLABEL or FS_IOC_GETFSLABEL to set/get file system label
+ * Return: 0 on success
+ * 1 on error
+ * -1 when the old method should be used
+ */
+static int handle_fslabel(int setlabel)
+{
+#ifdef __linux__
+ errcode_t ret;
+ int mnt_flags, fd;
+ char label[FSLABEL_MAX];
+ int maxlen = FSLABEL_MAX - 1;
+ char mntpt[PATH_MAX + 1];
+
+ ret = ext2fs_check_mount_point(device_name, &mnt_flags,
+ mntpt, sizeof(mntpt));
+ if (ret) {
+ com_err(device_name, ret, _("while checking mount status"));
+ return 1;
+ }
+ if (!(mnt_flags & EXT2_MF_MOUNTED) ||
+ (setlabel && (mnt_flags & EXT2_MF_READONLY)))
+ return -1;
+
+ if (!mntpt[0]) {
+ fprintf(stderr,_("Unknown mount point for %s\n"), device_name);
+ return 1;
+ }
+
+ fd = open(mntpt, O_RDONLY);
+ if (fd < 0) {
+ com_err(mntpt, errno, _("while opening mount point"));
+ return 1;
+ }
+
+ /* Get fs label */
+ if (!setlabel) {
+ if (ioctl(fd, FS_IOC_GETFSLABEL, &label)) {
+ close(fd);
+ if (errno == ENOTTY)
+ return -1;
+ com_err(mntpt, errno, _("while trying to get fs label"));
+ return 1;
+ }
+ close(fd);
+ printf("%.*s\n", EXT2_LEN_STR(label));
+ return 0;
+ }
+
+ /* If it's extN file system, truncate the label to appropriate size */
+ if (mnt_flags & EXT2_MF_EXTFS)
+ maxlen = EXT2_LABEL_LEN;
+ if (strlen(new_label) > maxlen) {
+ fputs(_("Warning: label too long, truncating.\n"),
+ stderr);
+ new_label[maxlen] = '\0';
+ }
+
+ /* Set fs label */
+ if (ioctl(fd, FS_IOC_SETFSLABEL, new_label)) {
+ close(fd);
+ if (errno == ENOTTY)
+ return -1;
+ com_err(mntpt, errno, _("while trying to set fs label"));
+ return 1;
+ }
+ close(fd);
+ return 0;
+#else
+ return -1;
+#endif
+}
+
+#ifndef BUILD_AS_LIB
+int main(int argc, char **argv)
+#else
+int tune2fs_main(int argc, char **argv)
+#endif /* BUILD_AS_LIB */
+{
+ errcode_t retval;
+ ext2_filsys fs;
+ struct ext2_super_block *sb;
+ io_manager io_ptr, io_ptr_orig = NULL;
+ int rc = 0;
+ char default_undo_file[1] = { 0 };
+ char mntpt[PATH_MAX + 1] = { 0 };
+ int fd = -1;
+ struct fsuuid *fsuuid = NULL;
+
+#ifdef ENABLE_NLS
+ setlocale(LC_MESSAGES, "");
+ setlocale(LC_CTYPE, "");
+ bindtextdomain(NLS_CAT_NAME, LOCALEDIR);
+ textdomain(NLS_CAT_NAME);
+ set_com_err_gettext(gettext);
+#endif
+ if (argc && *argv)
+ program_name = *argv;
+ else
+ usage();
+ add_error_table(&et_ext2_error_table);
+
+#ifdef CONFIG_BUILD_FINDFS
+ if (strcmp(get_progname(argv[0]), "findfs") == 0)
+ do_findfs(argc, argv);
+#endif
+ if (strcmp(get_progname(argv[0]), "e2label") == 0)
+ parse_e2label_options(argc, argv);
+ else
+ parse_tune2fs_options(argc, argv);
+
+#ifdef CONFIG_TESTIO_DEBUG
+ if (getenv("TEST_IO_FLAGS") || getenv("TEST_IO_DEBUG")) {
+ io_ptr = test_io_manager;
+ test_io_backing_manager = unix_io_manager;
+ } else
+#endif
+ io_ptr = unix_io_manager;
+
+ /*
+ * Try the get/set fs label using ioctls before we even attempt
+ * to open the file system.
+ */
+ if (L_flag || print_label) {
+ rc = handle_fslabel(L_flag);
+ if (rc != -1) {
+#ifndef BUILD_AS_LIB
+ exit(rc);
+#endif
+ return rc;
+ }
+ rc = 0;
+ }
+
+retry_open:
+ if ((open_flag & EXT2_FLAG_RW) == 0 || f_flag)
+ open_flag |= EXT2_FLAG_SKIP_MMP;
+
+ open_flag |= EXT2_FLAG_64BITS | EXT2_FLAG_THREADS |
+ EXT2_FLAG_JOURNAL_DEV_OK;
+
+ /* keep the filesystem struct around to dump MMP data */
+ open_flag |= EXT2_FLAG_NOFREE_ON_ERROR;
+
+ retval = ext2fs_open2(device_name, io_options, open_flag,
+ 0, 0, io_ptr, &fs);
+ if (retval) {
+ com_err(program_name, retval,
+ _("while trying to open %s"),
+ device_name);
+ if (retval == EXT2_ET_MMP_FSCK_ON ||
+ retval == EXT2_ET_MMP_UNKNOWN_SEQ)
+ dump_mmp_msg(fs->mmp_buf,
+ _("If you are sure the filesystem "
+ "is not in use on any node, run:\n"
+ "'tune2fs -f -E clear_mmp {device}'\n"));
+ else if (retval == EXT2_ET_MMP_FAILED)
+ dump_mmp_msg(fs->mmp_buf, NULL);
+ else if (retval == EXT2_ET_MMP_MAGIC_INVALID)
+ fprintf(stderr,
+ _("MMP block magic is bad. Try to fix it by "
+ "running:\n'e2fsck -f %s'\n"), device_name);
+ else if (retval == EXT2_ET_BAD_MAGIC)
+ check_plausibility(device_name, CHECK_FS_EXIST, NULL);
+ else if (retval != EXT2_ET_MMP_FAILED)
+ fprintf(stderr, "%s",
+ _("Couldn't find valid filesystem superblock.\n"));
+
+ ext2fs_free(fs);
+ exit(1);
+ }
+ if (ext2fs_has_feature_journal_dev(fs->super)) {
+ fprintf(stderr, "%s", _("Cannot modify a journal device.\n"));
+ ext2fs_free(fs);
+ exit(1);
+ }
+ fs->default_bitmap_type = EXT2FS_BMAP64_RBTREE;
+
+ if (I_flag) {
+ /*
+ * Check the inode size is right so we can issue an
+ * error message and bail before setting up the tdb
+ * file.
+ */
+ if (new_inode_size == EXT2_INODE_SIZE(fs->super)) {
+ fprintf(stderr, _("The inode size is already %lu\n"),
+ new_inode_size);
+ rc = 1;
+ goto closefs;
+ }
+ if (new_inode_size < EXT2_INODE_SIZE(fs->super)) {
+ fprintf(stderr, "%s",
+ _("Shrinking inode size is not supported\n"));
+ rc = 1;
+ goto closefs;
+ }
+ if (new_inode_size > fs->blocksize) {
+ fprintf(stderr, _("Invalid inode size %lu (max %d)\n"),
+ new_inode_size, fs->blocksize);
+ rc = 1;
+ goto closefs;
+ }
+ rc = check_fsck_needed(fs,
+ _("Resizing inodes could take some time."));
+ if (rc)
+ goto closefs;
+ /*
+ * If inode resize is requested use the
+ * Undo I/O manager
+ */
+ undo_file = default_undo_file;
+ }
+
+ /* Set up an undo file */
+ if (undo_file && io_ptr_orig == NULL) {
+ io_ptr_orig = io_ptr;
+ retval = tune2fs_setup_tdb(device_name, &io_ptr);
+ if (retval) {
+ rc = 1;
+ goto closefs;
+ }
+ if (io_ptr != io_ptr_orig) {
+ ext2fs_close_free(&fs);
+ goto retry_open;
+ }
+ }
+
+ sb = fs->super;
+ fs->flags &= ~EXT2_FLAG_MASTER_SB_ONLY;
+
+ if (print_label) {
+ /* For e2label emulation */
+ printf("%.*s\n", EXT2_LEN_STR(sb->s_volume_name));
+ remove_error_table(&et_ext2_error_table);
+ goto closefs;
+ }
+
+ retval = ext2fs_check_mount_point(device_name, &mount_flags,
+ mntpt, sizeof(mntpt));
+ if (retval) {
+ com_err("ext2fs_check_mount_point", retval,
+ _("while determining whether %s is mounted."),
+ device_name);
+ rc = 1;
+ goto closefs;
+ }
+
+#ifdef NO_RECOVERY
+ /* Warn if file system needs recovery and it is opened for writing. */
+ if ((open_flag & EXT2_FLAG_RW) && !(mount_flags & EXT2_MF_MOUNTED) &&
+ (sb->s_feature_compat & EXT3_FEATURE_COMPAT_HAS_JOURNAL) &&
+ (sb->s_feature_incompat & EXT3_FEATURE_INCOMPAT_RECOVER)) {
+ fprintf(stderr,
+_("Warning: The journal is dirty. You may wish to replay the journal like:\n\n"
+ "\te2fsck -E journal_only %s\n\n"
+ "then rerun this command. Otherwise, any changes made may be overwritten\n"
+ "by journal recovery.\n"), device_name);
+ }
+#else
+ /* Recover the journal if possible. */
+ if ((open_flag & EXT2_FLAG_RW) && !(mount_flags & (EXT2_MF_BUSY | EXT2_MF_MOUNTED)) &&
+ ext2fs_has_feature_journal_needs_recovery(fs->super)) {
+ printf(_("Recovering journal.\n"));
+ retval = ext2fs_run_ext3_journal(&fs);
+ if (retval) {
+ com_err("tune2fs", retval,
+ "while recovering journal.\n");
+ printf(_("Please run e2fsck -fy %s.\n"), device_name);
+ if (!fs)
+ exit(1);
+ rc = 1;
+ goto closefs;
+ }
+ sb = fs->super;
+ }
+#endif
+
+ /* Normally we only need to write out the superblock */
+ fs->flags |= EXT2_FLAG_SUPER_ONLY;
+
+ if (c_flag) {
+ if (max_mount_count == 65536)
+ max_mount_count = EXT2_DFL_MAX_MNT_COUNT +
+ (random() % EXT2_DFL_MAX_MNT_COUNT);
+ sb->s_max_mnt_count = max_mount_count;
+ ext2fs_mark_super_dirty(fs);
+ printf(_("Setting maximal mount count to %d\n"),
+ max_mount_count);
+ }
+ if (C_flag) {
+ sb->s_mnt_count = mount_count;
+ ext2fs_mark_super_dirty(fs);
+ printf(_("Setting current mount count to %d\n"), mount_count);
+ }
+ if (e_flag) {
+ sb->s_errors = errors;
+ ext2fs_mark_super_dirty(fs);
+ printf(_("Setting error behavior to %d\n"), errors);
+ }
+ if (g_flag) {
+ sb->s_def_resgid = resgid;
+ ext2fs_mark_super_dirty(fs);
+ printf(_("Setting reserved blocks gid to %lu\n"), resgid);
+ }
+ if (i_flag) {
+ if ((unsigned long long)interval >= (1ULL << 32)) {
+ com_err(program_name, 0,
+ _("interval between checks is too big (%lu)"),
+ interval);
+ rc = 1;
+ goto closefs;
+ }
+ sb->s_checkinterval = interval;
+ ext2fs_mark_super_dirty(fs);
+ printf(_("Setting interval between checks to %lu seconds\n"),
+ interval);
+ }
+ if (m_flag) {
+ ext2fs_r_blocks_count_set(sb, reserved_ratio *
+ ext2fs_blocks_count(sb) / 100.0);
+ ext2fs_mark_super_dirty(fs);
+ printf (_("Setting reserved blocks percentage to %g%% (%llu blocks)\n"),
+ reserved_ratio,
+ (unsigned long long) ext2fs_r_blocks_count(sb));
+ }
+ if (r_flag) {
+ if (reserved_blocks > ext2fs_blocks_count(sb)/2) {
+ com_err(program_name, 0,
+ _("reserved blocks count is too big (%llu)"),
+ (unsigned long long) reserved_blocks);
+ rc = 1;
+ goto closefs;
+ }
+ ext2fs_r_blocks_count_set(sb, reserved_blocks);
+ ext2fs_mark_super_dirty(fs);
+ printf(_("Setting reserved blocks count to %llu\n"),
+ (unsigned long long) reserved_blocks);
+ }
+ if (s_flag == 1) {
+ if (ext2fs_has_feature_sparse_super(sb)) {
+ fputs(_("\nThe filesystem already has sparse "
+ "superblocks.\n"), stderr);
+ } else if (ext2fs_has_feature_meta_bg(sb)) {
+ fputs(_("\nSetting the sparse superblock flag not "
+ "supported\nfor filesystems with "
+ "the meta_bg feature enabled.\n"),
+ stderr);
+ rc = 1;
+ goto closefs;
+ } else {
+ ext2fs_set_feature_sparse_super(sb);
+ sb->s_state &= ~EXT2_VALID_FS;
+ ext2fs_mark_super_dirty(fs);
+ printf(_("\nSparse superblock flag set. %s"),
+ _(please_fsck));
+ }
+ }
+ if (s_flag == 0) {
+ fputs(_("\nClearing the sparse superblock flag not supported.\n"),
+ stderr);
+ rc = 1;
+ goto closefs;
+ }
+ if (T_flag) {
+ sb->s_lastcheck = last_check_time;
+ ext2fs_mark_super_dirty(fs);
+ printf(_("Setting time filesystem last checked to %s\n"),
+ ctime(&last_check_time));
+ }
+ if (u_flag) {
+ sb->s_def_resuid = resuid;
+ ext2fs_mark_super_dirty(fs);
+ printf(_("Setting reserved blocks uid to %lu\n"), resuid);
+ }
+ if (L_flag) {
+ if (strlen(new_label) > sizeof(sb->s_volume_name))
+ fputs(_("Warning: label too long, truncating.\n"),
+ stderr);
+ memset(sb->s_volume_name, 0, sizeof(sb->s_volume_name));
+ strncpy((char *)sb->s_volume_name, new_label,
+ sizeof(sb->s_volume_name));
+ ext2fs_mark_super_dirty(fs);
+ }
+ if (M_flag) {
+ memset(sb->s_last_mounted, 0, sizeof(sb->s_last_mounted));
+ strncpy((char *)sb->s_last_mounted, new_last_mounted,
+ sizeof(sb->s_last_mounted));
+ ext2fs_mark_super_dirty(fs);
+ }
+ if (mntopts_cmd) {
+ rc = update_mntopts(fs, mntopts_cmd);
+ if (rc)
+ goto closefs;
+ }
+ if (features_cmd) {
+ rc = update_feature_set(fs, features_cmd);
+ if (rc)
+ goto closefs;
+ }
+ if (extended_cmd) {
+ rc = parse_extended_opts(fs, extended_cmd);
+ if (rc)
+ goto closefs;
+ if (clear_mmp && !f_flag) {
+ fputs(_("Error in using clear_mmp. "
+ "It must be used with -f\n"),
+ stderr);
+ rc = 1;
+ goto closefs;
+ }
+ }
+ if (clear_mmp) {
+ rc = ext2fs_mmp_clear(fs);
+ goto closefs;
+ }
+ if (journal_size || journal_device) {
+ rc = add_journal(fs);
+ if (rc)
+ goto closefs;
+ }
+ if (orphan_file_blocks) {
+ errcode_t err;
+
+ err = ext2fs_read_bitmaps(fs);
+ if (err) {
+ com_err(program_name, err, "%s",
+ _("while loading bitmaps"));
+ rc = 1;
+ goto closefs;
+ }
+ err = ext2fs_create_orphan_file(fs, orphan_file_blocks);
+ if (err) {
+ com_err(program_name, err, "%s",
+ _("while creating orphan file"));
+ rc = 1;
+ goto closefs;
+ }
+ }
+
+ if (Q_flag) {
+ if (mount_flags & EXT2_MF_MOUNTED) {
+ fputs(_("The quota feature may only be changed when "
+ "the filesystem is unmounted.\n"), stderr);
+ rc = 1;
+ goto closefs;
+ }
+ rc = handle_quota_options(fs);
+ if (rc)
+ goto closefs;
+ }
+
+ if (U_flag) {
+ int set_csum = 0;
+ dgrp_t i;
+ char buf[SUPERBLOCK_SIZE] __attribute__ ((aligned(8)));
+ __u8 old_uuid[UUID_SIZE];
+ uuid_t new_uuid;
+ errcode_t ret = -1;
+
+ if (ext2fs_has_feature_stable_inodes(fs->super)) {
+ fputs(_("Cannot change the UUID of this filesystem "
+ "because it has the stable_inodes feature "
+ "flag.\n"), stderr);
+ exit(1);
+ }
+
+ if (!ext2fs_has_feature_csum_seed(fs->super) &&
+ (ext2fs_has_feature_metadata_csum(fs->super) ||
+ ext2fs_has_feature_ea_inode(fs->super))) {
+ rc = check_fsck_needed(fs,
+ _("Setting the UUID on this "
+ "filesystem could take some time."));
+ if (rc)
+ goto closefs;
+ rewrite_checksums = REWRITE_ALL;
+ }
+
+ if (ext2fs_has_group_desc_csum(fs)) {
+ /*
+ * Changing the UUID on a metadata_csum FS requires
+ * rewriting all metadata, which can race with a
+ * mounted fs. Don't allow that unless we're saving
+ * the checksum seed.
+ */
+ if ((mount_flags & EXT2_MF_MOUNTED) &&
+ !ext2fs_has_feature_csum_seed(fs->super) &&
+ ext2fs_has_feature_metadata_csum(fs->super)) {
+ fputs(_("The UUID may only be "
+ "changed when the filesystem is "
+ "unmounted.\n"), stderr);
+ fputs(_("If you only use kernels newer than "
+ "v4.4, run 'tune2fs -O "
+ "metadata_csum_seed' and re-run this "
+ "command.\n"), stderr);
+ try_confirm_csum_seed_support();
+ rc = 1;
+ goto closefs;
+ }
+
+ /*
+ * Determine if the block group checksums are
+ * correct so we know whether or not to set
+ * them later on.
+ */
+ for (i = 0; i < fs->group_desc_count; i++)
+ if (!ext2fs_group_desc_csum_verify(fs, i))
+ break;
+ if (i >= fs->group_desc_count)
+ set_csum = 1;
+ }
+
+#ifdef __linux__
+ if ((mount_flags & EXT2_MF_MOUNTED) &&
+ !(mount_flags & EXT2_MF_READONLY) && mntpt[0]) {
+ fd = open(mntpt, O_RDONLY);
+ if (fd >= 0)
+ fsuuid = malloc(sizeof(*fsuuid) + UUID_SIZE);
+ if (fsuuid) {
+ fsuuid->fsu_len = UUID_SIZE;
+ fsuuid->fsu_flags = 0;
+ ret = ioctl(fd, EXT4_IOC_GETFSUUID, fsuuid);
+ if (ret || fsuuid->fsu_len != UUID_SIZE) {
+ free(fsuuid);
+ fsuuid = NULL;
+ }
+ }
+ }
+#endif
+
+ memcpy(old_uuid, fsuuid ? fsuuid->fsu_uuid : sb->s_uuid,
+ UUID_SIZE);
+ if ((strcasecmp(requested_uuid, "null") == 0) ||
+ (strcasecmp(requested_uuid, "clear") == 0)) {
+ uuid_clear(new_uuid);
+ } else if (strcasecmp(requested_uuid, "time") == 0) {
+ uuid_generate_time(new_uuid);
+ } else if (strcasecmp(requested_uuid, "random") == 0) {
+ uuid_generate(new_uuid);
+ } else if (uuid_parse(requested_uuid, new_uuid)) {
+ com_err(program_name, 0, "%s",
+ _("Invalid UUID format\n"));
+ rc = 1;
+ goto closefs;
+ }
+
+ ret = -1;
+#ifdef __linux__
+ if (fsuuid) {
+ fsuuid->fsu_len = UUID_SIZE;
+ fsuuid->fsu_flags = 0;
+ memcpy(&fsuuid->fsu_uuid, new_uuid, UUID_SIZE);
+ ret = ioctl(fd, EXT4_IOC_SETFSUUID, fsuuid);
+ }
+#endif
+ /*
+ * If we can't set the UUID via the ioctl, fall
+ * back to directly modifying the superblock
+ .*/
+ if (ret) {
+ memcpy(sb->s_uuid, new_uuid, UUID_SIZE);
+ ext2fs_init_csum_seed(fs);
+ if (set_csum) {
+ for (i = 0; i < fs->group_desc_count; i++)
+ ext2fs_group_desc_csum_set(fs, i);
+ fs->flags &= ~EXT2_FLAG_SUPER_ONLY;
+ }
+ ext2fs_mark_super_dirty(fs);
+ }
+
+ /* If this is a journal dev, we need to copy UUID into jsb */
+ if (!(rc = get_journal_sb(fs, buf))) {
+ journal_superblock_t *jsb;
+
+ jsb = (journal_superblock_t *) buf;
+ fputs(_("Need to update journal superblock.\n"), stdout);
+ memcpy(jsb->s_uuid, sb->s_uuid, sizeof(sb->s_uuid));
+
+ /* Writeback the journal superblock */
+ if ((rc = io_channel_write_blk64(fs->io,
+ ext2fs_journal_sb_start(fs->blocksize),
+ -SUPERBLOCK_SIZE, buf)))
+ goto closefs;
+ } else if (rc != EXT2_ET_UNSUPP_FEATURE)
+ goto closefs;
+ else {
+ rc = 0; /** Reset rc to avoid ext2fs_mmp_stop() */
+
+ if ((rc = fs_update_journal_user(sb, old_uuid)))
+ goto closefs;
+ }
+ }
+
+ if (I_flag) {
+ if (mount_flags & EXT2_MF_MOUNTED) {
+ fputs(_("The inode size may only be "
+ "changed when the filesystem is "
+ "unmounted.\n"), stderr);
+ rc = 1;
+ goto closefs;
+ }
+ if (ext2fs_has_feature_flex_bg(fs->super)) {
+ fputs(_("Changing the inode size not supported for "
+ "filesystems with the flex_bg\n"
+ "feature enabled.\n"),
+ stderr);
+ rc = 1;
+ goto closefs;
+ }
+ /*
+ * We want to update group descriptor also
+ * with the new free inode count
+ */
+ if (rewrite_checksums)
+ fs->flags |= EXT2_FLAG_IGNORE_CSUM_ERRORS;
+ fs->flags &= ~EXT2_FLAG_SUPER_ONLY;
+ retval = resize_inode(fs, new_inode_size);
+ if (rewrite_checksums)
+ fs->flags &= ~EXT2_FLAG_IGNORE_CSUM_ERRORS;
+ if (retval == 0) {
+ printf(_("Setting inode size %lu\n"),
+ new_inode_size);
+ rewrite_checksums = REWRITE_ALL;
+ } else {
+ printf("%s", _("Failed to change inode size\n"));
+ rc = 1;
+ goto closefs;
+ }
+ }
+
+ if (rewrite_checksums) {
+ retval = rewrite_metadata_checksums(fs, rewrite_checksums);
+ if (retval != 0) {
+ printf("Failed to rewrite metadata checksums\n");
+ rc = 1;
+ goto closefs;
+ }
+ }
+
+ if (l_flag)
+ list_super(sb);
+ if (stride_set) {
+ sb->s_raid_stride = stride;
+ ext2fs_mark_super_dirty(fs);
+ printf(_("Setting stride size to %d\n"), stride);
+ }
+ if (stripe_width_set) {
+ sb->s_raid_stripe_width = stripe_width;
+ ext2fs_mark_super_dirty(fs);
+ printf(_("Setting stripe width to %d\n"), stripe_width);
+ }
+ if (ext_mount_opts) {
+ strncpy((char *)(fs->super->s_mount_opts), ext_mount_opts,
+ sizeof(fs->super->s_mount_opts));
+ fs->super->s_mount_opts[sizeof(fs->super->s_mount_opts)-1] = 0;
+ ext2fs_mark_super_dirty(fs);
+ printf(_("Setting extended default mount options to '%s'\n"),
+ ext_mount_opts);
+ free(ext_mount_opts);
+ }
+
+ free(device_name);
+ remove_error_table(&et_ext2_error_table);
+
+closefs:
+ if (fd >= 0)
+ close(fd);
+ if (fsuuid)
+ free(fsuuid);
+ if (rc) {
+ ext2fs_mmp_stop(fs);
+#ifndef BUILD_AS_LIB
+ exit(1);
+#endif
+ }
+
+ if (feature_64bit)
+ convert_64bit(fs, feature_64bit);
+
+ retval = ext2fs_close_free(&fs);
+ if (retval) {
+ com_err("tune2fs", retval,
+ _("while writing out and closing file system"));
+ rc = 1;
+ }
+
+ return rc;
+}
diff --git a/misc/tune2fs.h b/misc/tune2fs.h
new file mode 100644
index 0000000..f31c832
--- /dev/null
+++ b/misc/tune2fs.h
@@ -0,0 +1,26 @@
+/*
+ * tune2fs.h - Change the file system parameters on an ext2 file system
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ * %End-Header%
+ */
+
+#ifndef _TUNE2FS_H_
+#define _TUNE2FS_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Takes exactly the same args as the tune2fs executable.
+ * Is the entry point for libtune2fs.
+ */
+int tune2fs_main(int argc, char **argv);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/misc/util.c b/misc/util.c
new file mode 100644
index 0000000..3e83169
--- /dev/null
+++ b/misc/util.c
@@ -0,0 +1,330 @@
+/*
+ * util.c --- helper functions used by tune2fs and mke2fs
+ *
+ * Copyright 1995, 1996, 1997, 1998, 1999, 2000 by Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ * %End-Header%
+ */
+
+#ifndef _LARGEFILE_SOURCE
+#define _LARGEFILE_SOURCE
+#endif
+#ifndef _LARGEFILE64_SOURCE
+#define _LARGEFILE64_SOURCE
+#endif
+
+#ifdef _WIN32
+#define _POSIX
+#define __USE_MINGW_ALARM
+#endif
+
+#include "config.h"
+#include <fcntl.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <stdio.h>
+#include <string.h>
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_LINUX_MAJOR_H
+#include <linux/major.h>
+#endif
+#include <sys/types.h>
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <time.h>
+
+#include "et/com_err.h"
+#include "e2p/e2p.h"
+#include "ext2fs/ext2_fs.h"
+#include "ext2fs/ext2fs.h"
+#include "support/nls-enable.h"
+#include "support/devname.h"
+#include "blkid/blkid.h"
+#include "util.h"
+
+char *journal_location_string = NULL;
+
+#ifndef HAVE_STRCASECMP
+int strcasecmp (char *s1, char *s2)
+{
+ while (*s1 && *s2) {
+ int ch1 = *s1++, ch2 = *s2++;
+ if (isupper (ch1))
+ ch1 = tolower (ch1);
+ if (isupper (ch2))
+ ch2 = tolower (ch2);
+ if (ch1 != ch2)
+ return ch1 - ch2;
+ }
+ return *s1 ? 1 : *s2 ? -1 : 0;
+}
+#endif
+
+/*
+ * Given argv[0], return the program name.
+ */
+char *get_progname(char *argv_zero)
+{
+ char *cp;
+
+ cp = strrchr(argv_zero, '/');
+ if (!cp )
+ return argv_zero;
+ else
+ return cp+1;
+}
+
+static jmp_buf alarm_env;
+
+static void alarm_signal(int signal EXT2FS_ATTR((unused)))
+{
+ longjmp(alarm_env, 1);
+}
+
+void proceed_question(int delay)
+{
+ char buf[256];
+ const char *short_yes = _("yY");
+ const char *english_yes = "yY";
+
+ fflush(stdout);
+ fflush(stderr);
+ if (delay > 0) {
+ if (setjmp(alarm_env)) {
+ signal(SIGALRM, SIG_IGN);
+ printf("%s", _("<proceeding>\n"));
+ return;
+ }
+ signal(SIGALRM, alarm_signal);
+ printf(_("Proceed anyway (or wait %d seconds to proceed) ? (y,N) "),
+ delay);
+ alarm(delay);
+ } else
+ fputs(_("Proceed anyway? (y,N) "), stdout);
+ buf[0] = 0;
+ if (!fgets(buf, sizeof(buf), stdin) ||
+ strchr(_("nN"), buf[0]) ||
+ !(strchr(short_yes, buf[0]) ||
+ strchr(english_yes, buf[0]))) {
+ putc('\n', stdout);
+ exit(1);
+ }
+ signal(SIGALRM, SIG_IGN);
+}
+
+void check_mount(const char *device, int force, const char *type)
+{
+ errcode_t retval;
+ int mount_flags;
+
+ retval = ext2fs_check_if_mounted(device, &mount_flags);
+ if (retval) {
+ com_err("ext2fs_check_if_mount", retval,
+ _("while determining whether %s is mounted."),
+ device);
+ return;
+ }
+ if (mount_flags & EXT2_MF_MOUNTED) {
+ fprintf(stderr, _("%s is mounted; "), device);
+ if (force >= 2) {
+ fputs(_("mke2fs forced anyway. Hope /etc/mtab is "
+ "incorrect.\n"), stderr);
+ return;
+ }
+ abort_mke2fs:
+ fprintf(stderr, _("will not make a %s here!\n"), type);
+ exit(1);
+ }
+ if (mount_flags & EXT2_MF_BUSY) {
+ fprintf(stderr, _("%s is apparently in use by the system; "),
+ device);
+ if (force >= 2) {
+ fputs(_("mke2fs forced anyway.\n"), stderr);
+ return;
+ }
+ goto abort_mke2fs;
+ }
+}
+
+void parse_journal_opts(const char *opts)
+{
+ char *buf, *token, *next, *p, *arg;
+ int len;
+ int journal_usage = 0;
+
+ len = strlen(opts);
+ buf = malloc(len+1);
+ if (!buf) {
+ fputs(_("Couldn't allocate memory to parse journal "
+ "options!\n"), stderr);
+ exit(1);
+ }
+ strcpy(buf, opts);
+ for (token = buf; token && *token; token = next) {
+ p = strchr(token, ',');
+ next = 0;
+ if (p) {
+ *p = 0;
+ next = p+1;
+ }
+ arg = strchr(token, '=');
+ if (arg) {
+ *arg = 0;
+ arg++;
+ }
+#if 0
+ printf("Journal option=%s, argument=%s\n", token,
+ arg ? arg : "NONE");
+#endif
+ if (strcmp(token, "device") == 0) {
+ journal_device = get_devname(NULL, arg, NULL);
+ if (!journal_device) {
+ if (arg)
+ fprintf(stderr, _("\nCould not find "
+ "journal device matching %s\n"),
+ arg);
+ journal_usage++;
+ continue;
+ }
+ } else if (strcmp(token, "size") == 0) {
+ if (!arg) {
+ journal_usage++;
+ continue;
+ }
+ journal_size = strtoul(arg, &p, 0);
+ if (*p)
+ journal_usage++;
+ } else if (strcmp(token, "fast_commit_size") == 0) {
+ if (!arg) {
+ journal_usage++;
+ continue;
+ }
+ journal_fc_size = strtoul(arg, &p, 0);
+ if (*p)
+ journal_usage++;
+ } else if (!strcmp(token, "location")) {
+ if (!arg) {
+ journal_usage++;
+ continue;
+ }
+ journal_location_string = strdup(arg);
+ } else if (strcmp(token, "v1_superblock") == 0) {
+ journal_flags |= EXT2_MKJOURNAL_V1_SUPER;
+ continue;
+ } else
+ journal_usage++;
+ }
+ if (journal_usage) {
+ fputs(_("\nBad journal options specified.\n\n"
+ "Journal options are separated by commas, "
+ "and may take an argument which\n"
+ "\tis set off by an equals ('=') sign.\n\n"
+ "Valid journal options are:\n"
+ "\tsize=<journal size in megabytes>\n"
+ "\tdevice=<journal device>\n"
+ "\tlocation=<journal location>\n\n"
+ "The journal size must be between "
+ "1024 and 10240000 filesystem blocks.\n\n"), stderr);
+ free(buf);
+ exit(1);
+ }
+ free(buf);
+}
+
+static inline int jsize_to_blks(ext2_filsys fs, int size)
+{
+ return (size * 1024) / (fs->blocksize / 1024);
+}
+
+/* Fast commit size is in KBs */
+static inline int fcsize_to_blks(ext2_filsys fs, int size)
+{
+ return (size * 1024) / (fs->blocksize);
+}
+
+/*
+ * Determine the number of journal blocks to use, either via
+ * user-specified # of megabytes, or via some intelligently selected
+ * defaults.
+ *
+ * Find a reasonable journal file size (in blocks) given the number of blocks in
+ * the filesystem. For very small filesystems, it is not reasonable to have a
+ * journal that fills more than half of the filesystem.
+ */
+void figure_journal_size(struct ext2fs_journal_params *jparams,
+ int requested_j_size, int requested_fc_size, ext2_filsys fs)
+{
+ int total_blocks, ret;
+
+ ret = ext2fs_get_journal_params(jparams, fs);
+ if (ret) {
+ fputs(_("\nFilesystem too small for a journal\n"), stderr);
+ return;
+ }
+
+ if (requested_j_size > 0 ||
+ (ext2fs_has_feature_fast_commit(fs->super) && requested_fc_size > 0)) {
+ if (requested_j_size > 0)
+ jparams->num_journal_blocks =
+ jsize_to_blks(fs, requested_j_size);
+ if (ext2fs_has_feature_fast_commit(fs->super) &&
+ requested_fc_size > 0)
+ jparams->num_fc_blocks =
+ fcsize_to_blks(fs, requested_fc_size);
+ else if (!ext2fs_has_feature_fast_commit(fs->super))
+ jparams->num_fc_blocks = 0;
+ total_blocks = jparams->num_journal_blocks + jparams->num_fc_blocks;
+ if (total_blocks < 1024 || total_blocks > 10240000) {
+ fprintf(stderr, _("\nThe total requested journal "
+ "size is %d blocks; it must be\n"
+ "between 1024 and 10240000 blocks. "
+ "Aborting.\n"),
+ total_blocks);
+ exit(1);
+ }
+ if ((unsigned int) total_blocks > ext2fs_free_blocks_count(fs->super) / 2) {
+ fputs(_("\nTotal journal size too big for filesystem.\n"),
+ stderr);
+ exit(1);
+ }
+ }
+}
+
+void print_check_message(int mnt, unsigned int check)
+{
+ if (mnt < 0)
+ mnt = 0;
+ if (!mnt && !check)
+ return;
+ printf(_("This filesystem will be automatically "
+ "checked every %d mounts or\n"
+ "%g days, whichever comes first. "
+ "Use tune2fs -c or -i to override.\n"),
+ mnt, ((double) check) / (3600 * 24));
+}
+
+void dump_mmp_msg(struct mmp_struct *mmp, const char *msg)
+{
+
+ if (msg)
+ printf("MMP check failed: %s\n", msg);
+ if (mmp) {
+ time_t t = mmp->mmp_time;
+
+ printf("MMP error info: node: %.*s, device: %.*s, updated: %s",
+ EXT2_LEN_STR(mmp->mmp_nodename),
+ EXT2_LEN_STR(mmp->mmp_bdevname), ctime(&t));
+ }
+}
diff --git a/misc/util.h b/misc/util.h
new file mode 100644
index 0000000..ccdc7fb
--- /dev/null
+++ b/misc/util.h
@@ -0,0 +1,29 @@
+/*
+ * util.h --- header file defining prototypes for helper functions
+ * used by tune2fs and mke2fs
+ *
+ * Copyright 2000 by Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ * %End-Header%
+ */
+
+extern int journal_size;
+extern int journal_fc_size;
+extern int journal_flags;
+extern char *journal_device;
+extern char *journal_location_string;
+
+#ifndef HAVE_STRCASECMP
+extern int strcasecmp (char *s1, char *s2);
+#endif
+extern char *get_progname(char *argv_zero);
+extern void proceed_question(int delay);
+extern void parse_journal_opts(const char *opts);
+extern void check_mount(const char *device, int force, const char *type);
+extern void figure_journal_size(struct ext2fs_journal_params *jparams,
+ int requested_j_size, int requested_fc_size, ext2_filsys fs);
+extern void print_check_message(int, unsigned int);
+extern void dump_mmp_msg(struct mmp_struct *mmp, const char *msg);
diff --git a/misc/uuidd.8.in b/misc/uuidd.8.in
new file mode 100644
index 0000000..e65e391
--- /dev/null
+++ b/misc/uuidd.8.in
@@ -0,0 +1,97 @@
+.\" -*- nroff -*-
+.\" Copyright 2007 by Theodore Ts'o. All Rights Reserved.
+.\" This file may be copied under the terms of the GNU Public License.
+.\"
+.TH UUIDD 8 "@E2FSPROGS_MONTH@ @E2FSPROGS_YEAR@" "E2fsprogs version @E2FSPROGS_VERSION@"
+.SH NAME
+uuidd \- UUID generation daemon
+.SH SYNOPSIS
+.B uuidd
+[
+.B \-d
+]
+[
+.B \-p
+.I pidfile
+]
+[
+.B \-s
+.I socketpath
+]
+[
+.B \-T
+.I timeout
+]
+
+.B uuidd
+[
+.B \-r
+|
+.B \-t
+]
+[
+.B \-n
+.I number
+]
+[
+.B \-s
+.I socketpath
+]
+
+.B uuidd \-k
+.SH DESCRIPTION
+The
+.B uuidd
+daemon is used by the UUID library to generate
+universally unique identifiers (UUIDs), especially time-based UUID's
+in a secure and guaranteed-unique fashion, even in the face of large
+numbers of threads trying to grab UUID's running on different CPU's.
+.SH OPTIONS
+.TP
+.B \-d
+Run
+.B uuidd
+in debugging mode. This prevents uuidd from running as a daemon.
+.TP
+.B \-k
+If a currently uuidd daemon is running, kill it.
+.TP
+.BI \-n " number"
+When issuing a test request to a running uuidd, request a bulk response
+of
+.I number
+UUID's.
+.TP
+.BI \-p " pidfile"
+Specify the pathname where the pid file should be written. By default,
+the pid file is written to /var/lib/libuuid/uuidd.pid.
+.TP
+.BI \-s " socketpath"
+Specify the pathname used for the unix-domain socket used by uuidd. By
+default, the pathname used is /var/lib/libuuid/request. This is primarily
+for debugging purposes, since the pathname is hard-coded in the libuuid
+library.
+.TP
+.B \-r
+Test uuidd by trying to connect to a running uuidd daemon and
+request it to return a random-based UUID.
+.TP
+.B \-t
+Test uuidd by trying to connect to a running uuidd daemon and
+request it to return a time-based UUID.
+.TP
+.BI \-T " timeout"
+Specify a timeout for uuidd. If specified, then uuidd will exit after
+.I timeout
+seconds of inactivity.
+.SH AUTHOR
+The
+.B uuidd
+daemon was written by Theodore Ts'o <tytso@mit.edu>.
+.SH AVAILABILITY
+.B uuidd
+is part of libuuid from the e2fsprogs package and is available from
+http://e2fsprogs.sourceforge.net.
+.SH "SEE ALSO"
+.BR libuuid (3),
+.BR uuidgen (1)
diff --git a/misc/uuidd.c b/misc/uuidd.c
new file mode 100644
index 0000000..4db3fa9
--- /dev/null
+++ b/misc/uuidd.c
@@ -0,0 +1,600 @@
+/*
+ * uuidd.c --- UUID-generation daemon
+ *
+ * Copyright (C) 2007 Theodore Ts'o
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ * %End-Header%
+ */
+
+#define _GNU_SOURCE /* for setres[ug]id() */
+
+#include "config.h"
+#include <stdio.h>
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#include <unistd.h>
+#include <inttypes.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <string.h>
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#else
+extern int getopt(int argc, char * const argv[], const char *optstring);
+extern char *optarg;
+extern int optind;
+#endif
+#include "uuid/uuid.h"
+#include "uuid/uuidd.h"
+#include "support/nls-enable.h"
+#include "ext2fs/ext2fs.h"
+
+#ifdef __GNUC__
+#define CODE_ATTR(x) __attribute__(x)
+#else
+#define CODE_ATTR(x)
+#endif
+
+static void usage(const char *progname)
+{
+ fprintf(stderr, _("Usage: %s [-d] [-p pidfile] [-s socketpath] "
+ "[-T timeout]\n"), progname);
+ fprintf(stderr, _(" %s [-r|t] [-n num] [-s socketpath]\n"),
+ progname);
+ fprintf(stderr, _(" %s -k\n"), progname);
+ exit(1);
+}
+
+static void die(const char *msg)
+{
+ perror(msg);
+ exit(1);
+}
+
+static void create_daemon(void)
+{
+ pid_t pid;
+ uid_t euid;
+
+ pid = fork();
+ if (pid == -1) {
+ perror("fork");
+ exit(1);
+ } else if (pid != 0) {
+ exit(0);
+ }
+
+ close(0);
+ close(1);
+ close(2);
+ open("/dev/null", O_RDWR);
+ open("/dev/null", O_RDWR);
+ open("/dev/null", O_RDWR);
+
+ if (chdir("/")) {} /* Silence warn_unused_result warning */
+ (void) setsid();
+ euid = geteuid();
+ if (setreuid(euid, euid) < 0)
+ die("setreuid");
+}
+
+static ssize_t read_all(int fd, char *buf, size_t count)
+{
+ ssize_t ret;
+ ssize_t c = 0;
+ int tries = 0;
+
+ memset(buf, 0, count);
+ while (count > 0) {
+ ret = read(fd, buf, count);
+ if (ret <= 0) {
+ if ((errno == EAGAIN || errno == EINTR || ret == 0) &&
+ (tries++ < 5))
+ continue;
+ return c ? c : -1;
+ }
+ if (ret > 0)
+ tries = 0;
+ count -= ret;
+ buf += ret;
+ c += ret;
+ }
+ return c;
+}
+
+static int write_all(int fd, char *buf, size_t count)
+{
+ ssize_t ret;
+ int c = 0;
+
+ while (count > 0) {
+ ret = write(fd, buf, count);
+ if (ret < 0) {
+ if ((errno == EAGAIN) || (errno == EINTR))
+ continue;
+ return -1;
+ }
+ count -= ret;
+ buf += ret;
+ c += ret;
+ }
+ return c;
+}
+
+static const char *cleanup_pidfile, *cleanup_socket;
+
+static void terminate_intr(int signo CODE_ATTR((unused)))
+{
+ (void) unlink(cleanup_pidfile);
+ if (cleanup_socket)
+ (void) unlink(cleanup_socket);
+ exit(0);
+}
+
+static int call_daemon(const char *socket_path, int op, char *buf,
+ int buflen, int *num, const char **err_context)
+{
+ char op_buf[8];
+ int op_len;
+ int s;
+ ssize_t ret;
+ int32_t reply_len = 0;
+ struct sockaddr_un srv_addr;
+
+ if (((op == 4) || (op == 5)) && !num) {
+ if (err_context)
+ *err_context = _("bad arguments");
+ errno = EINVAL;
+ return -1;
+ }
+
+ if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
+ if (err_context)
+ *err_context = _("socket");
+ return -1;
+ }
+
+ srv_addr.sun_family = AF_UNIX;
+ strncpy(srv_addr.sun_path, socket_path, sizeof(srv_addr.sun_path));
+ srv_addr.sun_path[sizeof(srv_addr.sun_path)-1] = '\0';
+
+ if (connect(s, (const struct sockaddr *) &srv_addr,
+ sizeof(struct sockaddr_un)) < 0) {
+ if (err_context)
+ *err_context = _("connect");
+ close(s);
+ return -1;
+ }
+
+ if (op == 5) {
+ if ((*num)*16 > buflen-4)
+ *num = (buflen-4) / 16;
+ }
+ op_buf[0] = op;
+ op_len = 1;
+ if ((op == 4) || (op == 5)) {
+ memcpy(op_buf+1, num, sizeof(int));
+ op_len += sizeof(int);
+ }
+
+ ret = write_all(s, op_buf, op_len);
+ if (ret < op_len) {
+ if (err_context)
+ *err_context = _("write");
+ close(s);
+ return -1;
+ }
+
+ ret = read_all(s, (char *) &reply_len, sizeof(reply_len));
+ if (ret < 0) {
+ if (err_context)
+ *err_context = _("read count");
+ close(s);
+ return -1;
+ }
+ if (reply_len < 0 || reply_len > buflen) {
+ if (err_context)
+ *err_context = _("bad response length");
+ close(s);
+ return -1;
+ }
+ ret = read_all(s, (char *) buf, reply_len);
+
+ if ((ret > 0) && (op == 4)) {
+ if (reply_len >= (int) (16+sizeof(int)))
+ memcpy(buf+16, num, sizeof(int));
+ else
+ *num = -1;
+ }
+ if ((ret > 0) && (op == 5)) {
+ if (*num >= (int) sizeof(int))
+ memcpy(buf, num, sizeof(int));
+ else
+ *num = -1;
+ }
+
+ close(s);
+
+ return ret;
+}
+
+static void server_loop(const char *socket_path, const char *pidfile_path,
+ int debug, int timeout, int quiet)
+{
+ struct sockaddr_un my_addr, from_addr;
+ struct flock fl;
+ socklen_t fromlen;
+ int32_t reply_len = 0;
+ uuid_t uu;
+ mode_t save_umask;
+ char reply_buf[1024], *cp;
+ char op, str[UUID_STR_SIZE];
+ int i, s, ns, len, num;
+ int fd_pidfile, ret;
+
+ fd_pidfile = open(pidfile_path, O_CREAT | O_RDWR, 0664);
+ if (fd_pidfile < 0) {
+ if (!quiet)
+ fprintf(stderr, "Failed to open/create %s: %s\n",
+ pidfile_path, strerror(errno));
+ exit(1);
+ }
+ cleanup_pidfile = pidfile_path;
+ cleanup_socket = 0;
+ signal(SIGALRM, terminate_intr);
+ alarm(30);
+ fl.l_type = F_WRLCK;
+ fl.l_whence = SEEK_SET;
+ fl.l_start = 0;
+ fl.l_len = 0;
+ fl.l_pid = 0;
+ while (fcntl(fd_pidfile, F_SETLKW, &fl) < 0) {
+ if ((errno == EAGAIN) || (errno == EINTR))
+ continue;
+ if (!quiet)
+ fprintf(stderr, "Failed to lock %s: %s\n",
+ pidfile_path, strerror(errno));
+ exit(1);
+ }
+ ret = call_daemon(socket_path, 0, reply_buf, sizeof(reply_buf), 0, 0);
+ if (ret > 0) {
+ if (!quiet)
+ printf(_("uuidd daemon already running at pid %s\n"),
+ reply_buf);
+ exit(1);
+ }
+ alarm(0);
+
+ if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
+ if (!quiet)
+ fprintf(stderr, _("Couldn't create unix stream "
+ "socket: %s"), strerror(errno));
+ exit(1);
+ }
+
+ /*
+ * Make sure the socket isn't using fd numbers 0-2 to avoid it
+ * getting closed by create_daemon()
+ */
+ while (!debug && s <= 2) {
+ s = dup(s);
+ if (s < 0) {
+ perror("dup");
+ exit(1);
+ }
+ }
+
+ /*
+ * Create the address we will be binding to.
+ */
+ my_addr.sun_family = AF_UNIX;
+ strncpy(my_addr.sun_path, socket_path, sizeof(my_addr.sun_path));
+ my_addr.sun_path[sizeof(my_addr.sun_path)-1] = '\0';
+ (void) unlink(socket_path);
+ save_umask = umask(0);
+ if (bind(s, (const struct sockaddr *) &my_addr,
+ sizeof(struct sockaddr_un)) < 0) {
+ if (!quiet)
+ fprintf(stderr,
+ _("Couldn't bind unix socket %s: %s\n"),
+ socket_path, strerror(errno));
+ exit(1);
+ }
+ (void) umask(save_umask);
+
+ if (listen(s, 5) < 0) {
+ if (!quiet)
+ fprintf(stderr, _("Couldn't listen on unix "
+ "socket %s: %s\n"), socket_path,
+ strerror(errno));
+ exit(1);
+ }
+
+ cleanup_socket = socket_path;
+ if (!debug)
+ create_daemon();
+ signal(SIGHUP, terminate_intr);
+ signal(SIGINT, terminate_intr);
+ signal(SIGTERM, terminate_intr);
+ signal(SIGALRM, terminate_intr);
+ signal(SIGPIPE, SIG_IGN);
+
+ sprintf(reply_buf, "%8d\n", getpid());
+ if (ftruncate(fd_pidfile, 0)) {} /* Silence warn_unused_result */
+ write_all(fd_pidfile, reply_buf, strlen(reply_buf));
+ if (fd_pidfile > 1)
+ close(fd_pidfile); /* Unlock the pid file */
+
+ while (1) {
+ fromlen = sizeof(from_addr);
+ if (timeout > 0)
+ alarm(timeout);
+ ns = accept(s, (struct sockaddr *) &from_addr, &fromlen);
+ alarm(0);
+ if (ns < 0) {
+ if ((errno == EAGAIN) || (errno == EINTR))
+ continue;
+ perror("accept");
+ exit(1);
+ }
+ len = read(ns, &op, 1);
+ if (len != 1) {
+ if (len < 0)
+ perror("read");
+ else
+ printf(_("Error reading from client, "
+ "len = %d\n"), len);
+ goto shutdown_socket;
+ }
+ if ((op == 4) || (op == 5)) {
+ if (read_all(ns, (char *) &num, sizeof(num)) != 4)
+ goto shutdown_socket;
+ if (debug)
+ printf(_("operation %d, incoming num = %d\n"),
+ op, num);
+ } else if (debug)
+ printf("operation %d\n", op);
+
+ switch(op) {
+ case UUIDD_OP_GETPID:
+ sprintf(reply_buf, "%d", getpid());
+ reply_len = strlen(reply_buf)+1;
+ break;
+ case UUIDD_OP_GET_MAXOP:
+ sprintf(reply_buf, "%d", UUIDD_MAX_OP);
+ reply_len = strlen(reply_buf)+1;
+ break;
+ case UUIDD_OP_TIME_UUID:
+ num = 1;
+ uuid__generate_time(uu, &num);
+ if (debug) {
+ uuid_unparse(uu, str);
+ printf(_("Generated time UUID: %s\n"), str);
+ }
+ memcpy(reply_buf, uu, sizeof(uu));
+ reply_len = sizeof(uu);
+ break;
+ case UUIDD_OP_RANDOM_UUID:
+ num = 1;
+ uuid__generate_random(uu, &num);
+ if (debug) {
+ uuid_unparse(uu, str);
+ printf(_("Generated random UUID: %s\n"), str);
+ }
+ memcpy(reply_buf, uu, sizeof(uu));
+ reply_len = sizeof(uu);
+ break;
+ case UUIDD_OP_BULK_TIME_UUID:
+ uuid__generate_time(uu, &num);
+ if (debug) {
+ uuid_unparse(uu, str);
+ printf(P_("Generated time UUID %s and "
+ "subsequent UUID\n",
+ "Generated time UUID %s and %d "
+ "subsequent UUIDs\n", num),
+ str, num);
+ }
+ memcpy(reply_buf, uu, sizeof(uu));
+ reply_len = sizeof(uu);
+ memcpy(reply_buf+reply_len, &num, sizeof(num));
+ reply_len += sizeof(num);
+ break;
+ case UUIDD_OP_BULK_RANDOM_UUID:
+ if (num < 0)
+ num = 1;
+ if (num > 1000)
+ num = 1000;
+ if (num*16 > (int) (sizeof(reply_buf)-sizeof(num)))
+ num = (sizeof(reply_buf)-sizeof(num)) / 16;
+ uuid__generate_random((unsigned char *) reply_buf +
+ sizeof(num), &num);
+ if (debug) {
+ printf(_("Generated %d UUID's:\n"), num);
+ for (i=0, cp=reply_buf+sizeof(num);
+ i < num; i++, cp+=16) {
+ uuid_unparse((unsigned char *)cp, str);
+ printf("\t%s\n", str);
+ }
+ }
+ reply_len = (num*16) + sizeof(num);
+ memcpy(reply_buf, &num, sizeof(num));
+ break;
+ default:
+ if (debug)
+ printf(_("Invalid operation %d\n"), op);
+ goto shutdown_socket;
+ }
+ write_all(ns, (char *) &reply_len, sizeof(reply_len));
+ write_all(ns, reply_buf, reply_len);
+ shutdown_socket:
+ close(ns);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ const char *socket_path = UUIDD_SOCKET_PATH;
+ const char *pidfile_path = UUIDD_PIDFILE_PATH;
+ const char *err_context;
+ char buf[1024], *cp;
+ char str[37], *tmp;
+ uuid_t uu;
+ uid_t uid;
+ gid_t gid;
+ int i, c, ret;
+ int debug = 0, do_type = 0, do_kill = 0, num = 0;
+ int timeout = 0, quiet = 0, drop_privs = 0;
+
+#ifdef ENABLE_NLS
+ setlocale(LC_MESSAGES, "");
+ setlocale(LC_CTYPE, "");
+ bindtextdomain(NLS_CAT_NAME, LOCALEDIR);
+ textdomain(NLS_CAT_NAME);
+#endif
+
+ while ((c = getopt (argc, argv, "dkn:qp:s:tT:r")) != EOF) {
+ switch (c) {
+ case 'd':
+ debug++;
+ drop_privs = 1;
+ break;
+ case 'k':
+ do_kill++;
+ drop_privs = 1;
+ break;
+ case 'n':
+ num = strtol(optarg, &tmp, 0);
+ if ((num < 0) || *tmp) {
+ fprintf(stderr, _("Bad number: %s\n"), optarg);
+ exit(1);
+ }
+ break;
+ case 'p':
+ pidfile_path = optarg;
+ drop_privs = 1;
+ break;
+ case 'q':
+ quiet++;
+ break;
+ case 's':
+ socket_path = optarg;
+ drop_privs = 1;
+ break;
+ case 't':
+ do_type = UUIDD_OP_TIME_UUID;
+ drop_privs = 1;
+ break;
+ case 'T':
+ timeout = strtol(optarg, &tmp, 0);
+ if ((timeout < 0) || *tmp) {
+ fprintf(stderr, _("Bad number: %s\n"), optarg);
+ exit(1);
+ }
+ break;
+ case 'r':
+ do_type = UUIDD_OP_RANDOM_UUID;
+ drop_privs = 1;
+ break;
+ default:
+ usage(argv[0]);
+ }
+ }
+ uid = getuid();
+ if (uid && drop_privs) {
+ gid = getgid();
+#ifdef HAVE_SETRESGID
+ if (setresgid(gid, gid, gid) < 0)
+ die("setresgid");
+#else
+ if (setregid(gid, gid) < 0)
+ die("setregid");
+#endif
+
+#ifdef HAVE_SETRESUID
+ if (setresuid(uid, uid, uid) < 0)
+ die("setresuid");
+#else
+ if (setreuid(uid, uid) < 0)
+ die("setreuid");
+#endif
+ }
+ if (num && do_type) {
+ ret = call_daemon(socket_path, do_type+2, buf,
+ sizeof(buf), &num, &err_context);
+ if (ret < 0) {
+ printf(_("Error calling uuidd daemon (%s): %s\n"),
+ err_context, strerror(errno));
+ exit(1);
+ }
+ if (do_type == UUIDD_OP_TIME_UUID) {
+ if (ret != sizeof(uu) + sizeof(num))
+ goto unexpected_size;
+
+ uuid_unparse((unsigned char *) buf, str);
+
+ printf(P_("%s and subsequent UUID\n",
+ "%s and subsequent %d UUIDs\n", num),
+ str, num);
+ } else {
+ printf("%s", _("List of UUID's:\n"));
+ cp = buf + 4;
+ if (ret != (int) (sizeof(num) + num*sizeof(uu)))
+ goto unexpected_size;
+ for (i=0; i < num; i++, cp+=16) {
+ uuid_unparse((unsigned char *) cp, str);
+ printf("\t%s\n", str);
+ }
+ }
+ exit(0);
+ }
+ if (do_type) {
+ ret = call_daemon(socket_path, do_type, (char *) &uu,
+ sizeof(uu), 0, &err_context);
+ if (ret < 0) {
+ printf(_("Error calling uuidd daemon (%s): %s\n"),
+ err_context, strerror(errno));
+ exit(1);
+ }
+ if (ret != sizeof(uu)) {
+ unexpected_size:
+ printf(_("Unexpected reply length from server %d\n"),
+ ret);
+ exit(1);
+ }
+ uuid_unparse(uu, str);
+
+ printf("%s\n", str);
+ exit(0);
+ }
+
+ if (do_kill) {
+ ret = call_daemon(socket_path, 0, buf, sizeof(buf), 0, 0);
+ if ((ret > 0) && ((do_kill = atoi((char *) buf)) > 0)) {
+ ret = kill(do_kill, SIGTERM);
+ if (ret < 0) {
+ if (!quiet)
+ fprintf(stderr,
+ _("Couldn't kill uuidd running "
+ "at pid %d: %s\n"), do_kill,
+ strerror(errno));
+ exit(1);
+ }
+ if (!quiet)
+ printf(_("Killed uuidd running at pid %d\n"),
+ do_kill);
+ }
+ exit(0);
+ }
+
+ server_loop(socket_path, pidfile_path, debug, timeout, quiet);
+ return 0;
+}
diff --git a/misc/uuidd.rc b/misc/uuidd.rc
new file mode 100644
index 0000000..d35645a
--- /dev/null
+++ b/misc/uuidd.rc
@@ -0,0 +1,55 @@
+#! /bin/sh -e
+### BEGIN INIT INFO
+# Provides: uuidd
+# Required-Start: $time $local_fs
+# Required-Stop: $time $local_fs
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: uuidd daemon
+# Description: Init script for the uuid generation daemon
+### END INIT INFO
+#
+# Author: "Theodore Ts'o" <tytso@mit.edu>
+#
+set -e
+
+PATH=/bin:/usr/bin:/sbin:/usr/sbin
+DAEMON=/usr/sbin/uuidd
+PIDFILE=/var/run/uuidd/uuidd.pid
+
+test -x $DAEMON || exit 0
+
+. /lib/lsb/init-functions
+
+case "$1" in
+ start)
+ log_daemon_msg "Starting uuid generator" "uuidd"
+ start_daemon -p $PIDFILE $DAEMON
+ log_end_msg $?
+ ;;
+ stop)
+ log_daemon_msg "Stopping uuidd generator" "uuidd"
+ killproc -p $PIDFILE $DAEMON
+ log_end_msg $?
+ ;;
+ status)
+ if pidofproc -p $PIDFILE $DAEMON >& /dev/null ; then
+ echo "$DAEMON is running";
+ exit 0;
+ else
+ echo "$DAEMON is NOT running";
+ if test -f /var/run/uuidd.pid; then exit 2; fi
+ exit 3;
+ fi
+ ;;
+ force-reload|restart)
+ $0 stop
+ $0 start
+ ;;
+ *)
+ echo "Usage: /etc/init.d/uuidd {start|stop|restart|force-reload}"
+ exit 1
+ ;;
+esac
+
+exit 0
diff --git a/misc/uuidgen.1.in b/misc/uuidgen.1.in
new file mode 100644
index 0000000..cb8b3a8
--- /dev/null
+++ b/misc/uuidgen.1.in
@@ -0,0 +1,63 @@
+.\" Copyright 1999 Andreas Dilger (adilger@enel.ucalgary.ca)
+.\"
+.\" This man page was created for libuuid.so.1.1 from e2fsprogs-1.14.
+.\"
+.\" This file may be copied under the terms of the GNU Public License.
+.\"
+.\" Created Wed Mar 10 17:42:12 1999, Andreas Dilger
+.TH UUIDGEN 1 "@E2FSPROGS_MONTH@ @E2FSPROGS_YEAR@" "E2fsprogs version @E2FSPROGS_VERSION@"
+.SH NAME
+uuidgen \- command\-line utility to create a new UUID value
+.SH SYNOPSIS
+.B uuidgen
+[
+.B \-r
+|
+.B \-t
+]
+.SH DESCRIPTION
+The
+.B uuidgen
+program creates (and prints)
+a new universally unique identifier (UUID) using the
+.BR libuuid (3)
+library. The new UUID can reasonably be considered unique among
+all UUIDs created on the local system,
+and among UUIDs created on other systems in the past
+and in the future.
+.PP
+There are two types of UUID's which
+.B uuidgen
+can generate: time-based UUID's and random-based UUID's. By
+default
+.B uuidgen
+will generate a random-based UUID if a high-quality random number
+generator is present. Otherwise, it will chose a time-based UUID. It
+is possible to force the generation of one of these two
+UUID types by using the
+.B \-r
+or
+.B \-t
+options.
+.SH OPTIONS
+.TP
+.B \-r
+Generate a random-based UUID. This method creates a UUID consisting mostly
+of random bits. It requires that the operating system have a high
+quality random number generator, such as
+.IR /dev/random .
+.TP
+.B \-t
+Generate a time-based UUID. This method creates a UUID based on the system
+clock plus the system's ethernet hardware address, if present.
+.SH "CONFORMING TO"
+OSF DCE 1.1
+.SH AUTHOR
+.B uuidgen
+was written by Andreas Dilger for libuuid.
+.SH AVAILABILITY
+.B uuidgen
+is part of libuuid from the e2fsprogs package and is available from
+http://e2fsprogs.sourceforge.net.
+.SH "SEE ALSO"
+.BR libuuid (3)
diff --git a/misc/uuidgen.c b/misc/uuidgen.c
new file mode 100644
index 0000000..1233f3d
--- /dev/null
+++ b/misc/uuidgen.c
@@ -0,0 +1,80 @@
+/*
+ * gen_uuid.c --- generate a DCE-compatible uuid
+ *
+ * Copyright (C) 1999, Andreas Dilger and Theodore Ts'o
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ * %End-Header%
+ */
+
+#include "config.h"
+#include <stdio.h>
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#else
+extern int getopt(int argc, char * const argv[], const char *optstring);
+extern char *optarg;
+extern int optind;
+#endif
+#include "uuid/uuid.h"
+#include "support/nls-enable.h"
+
+#define DO_TYPE_TIME 1
+#define DO_TYPE_RANDOM 2
+
+static void usage(const char *progname)
+{
+ fprintf(stderr, _("Usage: %s [-r] [-t]\n"), progname);
+ exit(1);
+}
+
+int
+main (int argc, char *argv[])
+{
+ int c;
+ int do_type = 0;
+ char str[37];
+ uuid_t uu;
+
+#ifdef ENABLE_NLS
+ setlocale(LC_MESSAGES, "");
+ setlocale(LC_CTYPE, "");
+ bindtextdomain(NLS_CAT_NAME, LOCALEDIR);
+ textdomain(NLS_CAT_NAME);
+#endif
+
+ while ((c = getopt (argc, argv, "tr")) != EOF)
+ switch (c) {
+ case 't':
+ do_type = DO_TYPE_TIME;
+ break;
+ case 'r':
+ do_type = DO_TYPE_RANDOM;
+ break;
+ default:
+ usage(argv[0]);
+ }
+
+ switch (do_type) {
+ case DO_TYPE_TIME:
+ uuid_generate_time(uu);
+ break;
+ case DO_TYPE_RANDOM:
+ uuid_generate_random(uu);
+ break;
+ default:
+ uuid_generate(uu);
+ break;
+ }
+
+ uuid_unparse(uu, str);
+
+ printf("%s\n", str);
+
+ return 0;
+}