diff options
Diffstat (limited to 'misc')
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, ¤t_settings); + if (rc) + return rc; + *saved_settings = current_settings; + current_settings.c_lflag &= ~ECHO; + rc = tcsetattr(0, TCSANOW, ¤t_settings); + + return rc; +} + +static void get_passphrase(char *passphrase, int len) +{ + char *p; + struct termios current_settings; + + assert(len > 0); + disable_echo(¤t_settings); + p = fgets(passphrase, len, stdin); + tcsetattr(0, TCSANOW, ¤t_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, "atype_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, + ¶m->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; +} |