diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 09:25:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 09:25:10 +0000 |
commit | 5dced3d1b3deca80e01415a2e35dc7972dcbfae7 (patch) | |
tree | 6a403684e0978f0287d7f0ec0e5aab1fd31a59e1 /debugfs | |
parent | Initial commit. (diff) | |
download | e2fsprogs-5dced3d1b3deca80e01415a2e35dc7972dcbfae7.tar.xz e2fsprogs-5dced3d1b3deca80e01415a2e35dc7972dcbfae7.zip |
Adding upstream version 1.47.0.upstream/1.47.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'debugfs')
-rw-r--r-- | debugfs/Android.bp | 80 | ||||
-rw-r--r-- | debugfs/Makefile.in | 426 | ||||
-rw-r--r-- | debugfs/debug_cmds.ct | 233 | ||||
-rw-r--r-- | debugfs/debugfs.8.in | 880 | ||||
-rw-r--r-- | debugfs/debugfs.c | 2682 | ||||
-rw-r--r-- | debugfs/debugfs.h | 210 | ||||
-rw-r--r-- | debugfs/do_journal.c | 972 | ||||
-rw-r--r-- | debugfs/dump.c | 385 | ||||
-rw-r--r-- | debugfs/extent_cmds.ct | 77 | ||||
-rw-r--r-- | debugfs/extent_inode.c | 568 | ||||
-rw-r--r-- | debugfs/filefrag.c | 330 | ||||
-rw-r--r-- | debugfs/htree.c | 488 | ||||
-rw-r--r-- | debugfs/icheck.c | 176 | ||||
-rw-r--r-- | debugfs/journal.c | 947 | ||||
-rw-r--r-- | debugfs/journal.h | 25 | ||||
-rw-r--r-- | debugfs/logdump.c | 933 | ||||
-rw-r--r-- | debugfs/ls.c | 252 | ||||
-rw-r--r-- | debugfs/lsdel.c | 219 | ||||
-rw-r--r-- | debugfs/ncheck.c | 223 | ||||
-rw-r--r-- | debugfs/quota.c | 172 | ||||
-rw-r--r-- | debugfs/ro_debug_cmds.ct | 99 | ||||
-rw-r--r-- | debugfs/set_fields.c | 1024 | ||||
-rw-r--r-- | debugfs/unused.c | 60 | ||||
-rw-r--r-- | debugfs/util.c | 600 | ||||
-rw-r--r-- | debugfs/xattrs.c | 503 | ||||
-rw-r--r-- | debugfs/zap.c | 246 |
26 files changed, 12810 insertions, 0 deletions
diff --git a/debugfs/Android.bp b/debugfs/Android.bp new file mode 100644 index 0000000..4d087b3 --- /dev/null +++ b/debugfs/Android.bp @@ -0,0 +1,80 @@ +// Copyright 2017 The Android Open Source Project + +//######################## +// Build the debugfs binary + +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-GPL-2.0 + default_applicable_licenses: ["external_e2fsprogs_license"], +} + +cc_defaults { + name: "debugfs-defaults", + defaults: ["e2fsprogs-defaults"], + srcs: [ + "debug_cmds.c", + "debugfs.c", + "util.c", + "ncheck.c", + "icheck.c", + "ls.c", + "lsdel.c", + "dump.c", + "set_fields.c", + "logdump.c", + "htree.c", + "unused.c", + "e2freefrag.c", + "filefrag.c", + "extent_cmds.c", + "extent_inode.c", + "zap.c", + "quota.c", + "xattrs.c", + "journal.c", + "revoke.c", + "recovery.c", + "do_journal.c", + ], + cflags: [ + "-DDEBUGFS", + ], + include_dirs: [ + "external/e2fsprogs/misc", + "external/e2fsprogs/e2fsck", + ], +} + +debugfs_libs = [ + "libext2_misc", + "libext2fs", + "libext2_blkid", + "libext2_uuid", + "libext2_ss", + "libext2_quota", + "libext2_com_err", + "libext2_e2p", + "libext2_support", +] + +cc_binary { + name: "debugfs", + host_supported: true, + defaults: ["debugfs-defaults"], + + shared_libs: debugfs_libs, +} + +cc_binary { + name: "debugfs_static", + static_executable: true, + host_supported: true, + defaults: ["debugfs-defaults"], + + static_libs: debugfs_libs, +} diff --git a/debugfs/Makefile.in b/debugfs/Makefile.in new file mode 100644 index 0000000..67f8d0b --- /dev/null +++ b/debugfs/Makefile.in @@ -0,0 +1,426 @@ +# +# Standard e2fsprogs prologue.... +# + +srcdir = @srcdir@ +top_srcdir = @top_srcdir@ +VPATH = @srcdir@ +top_builddir = .. +my_dir = debugfs +INSTALL = @INSTALL@ +MKDIR_P = @MKDIR_P@ + +@MCONFIG@ + +PROGS= debugfs +MANPAGES= debugfs.8 + +MK_CMDS= _SS_DIR_OVERRIDE=$(srcdir)/../lib/ss ../lib/ss/mk_cmds + +DEBUG_OBJS= debug_cmds.o debugfs.o util.o ncheck.o icheck.o ls.o \ + lsdel.o dump.o set_fields.o logdump.o htree.o unused.o e2freefrag.o \ + filefrag.o extent_cmds.o extent_inode.o zap.o create_inode.o \ + quota.o xattrs.o journal.o revoke.o recovery.o do_journal.o + +RO_DEBUG_OBJS= ro_debug_cmds.o ro_debugfs.o util.o ncheck.o icheck.o ls.o \ + lsdel.o logdump.o htree.o e2freefrag.o filefrag.o extent_cmds.o \ + extent_inode.o quota.o xattrs.o + +SRCS= debug_cmds.c $(srcdir)/debugfs.c $(srcdir)/util.c $(srcdir)/ls.c \ + $(srcdir)/ncheck.c $(srcdir)/icheck.c $(srcdir)/lsdel.c \ + $(srcdir)/dump.c $(srcdir)/set_fields.c ${srcdir}/logdump.c \ + $(srcdir)/htree.c $(srcdir)/unused.c ${srcdir}/../misc/e2freefrag.c \ + $(srcdir)/filefrag.c $(srcdir)/extent_inode.c $(srcdir)/zap.c \ + $(srcdir)/../misc/create_inode.c $(srcdir)/xattrs.c $(srcdir)/quota.c \ + $(srcdir)/journal.c $(srcdir)/../e2fsck/revoke.c \ + $(srcdir)/../e2fsck/recovery.c $(srcdir)/do_journal.c + +LIBS= $(LIBSUPPORT) $(LIBEXT2FS) $(LIBE2P) $(LIBSS) $(LIBCOM_ERR) $(LIBBLKID) \ + $(LIBUUID) $(LIBMAGIC) $(SYSLIBS) +DEPLIBS= $(DEPLIBSUPPORT) $(LIBEXT2FS) $(LIBE2P) $(DEPLIBSS) $(DEPLIBCOM_ERR) \ + $(DEPLIBBLKID) $(DEPLIBUUID) + +STATIC_LIBS= $(STATIC_LIBSUPPORT) $(STATIC_LIBEXT2FS) $(STATIC_LIBSS) \ + $(STATIC_LIBCOM_ERR) $(STATIC_LIBBLKID) $(STATIC_LIBUUID) \ + $(STATIC_LIBE2P) $(LIBMAGIC) $(SYSLIBS) +STATIC_DEPLIBS= $(STATIC_LIBEXT2FS) $(DEPSTATIC_LIBSS) \ + $(DEPSTATIC_LIBCOM_ERR) $(DEPSTATIC_LIBUUID) \ + $(DEPSTATIC_LIBE2P) + +# This nastiness is needed because of jfs_user.h hackery; when we finally +# clean up this mess, we should be able to drop it +LOCAL_CFLAGS = -I$(srcdir)/../e2fsck -DDEBUGFS +DEPEND_CFLAGS = -I$(srcdir) + +.c.o: + $(E) " CC $<" + $(Q) $(CC) -c $(ALL_CFLAGS) $< -o $@ + $(Q) $(CHECK_CMD) $(ALL_CFLAGS) $< + $(Q) $(CPPCHECK_CMD) $(CPPFLAGS) $< + +all:: $(PROGS) $(MANPAGES) + +debugfs: $(DEBUG_OBJS) $(DEPLIBS) + $(E) " LD $@" + $(Q) $(CC) $(ALL_LDFLAGS) -o debugfs $(DEBUG_OBJS) $(LIBS) + +debugfs.static: $(DEBUG_OBJS) $(STATIC_DEPLIBS) + $(E) " LD $@" + $(Q) $(CC) $(LDFLAGS_STATIC) -o debugfs.static $(DEBUG_OBJS) \ + $(STATIC_LIBS) $(READLINE_LIB) + +debugfs.static-libs: $(DEBUG_OBJS) $(STATIC_DEPLIBS) + $(E) " LD $@" + $(Q) $(CC) -o debugfs.static-libs $(DEBUG_OBJS) \ + $(STATIC_LIBS) $(READLINE_LIB) + +rdebugfs: $(RO_DEBUG_OBJS) $(DEPLIBS) + $(E) " LD $@" + $(Q) $(CC) $(ALL_LDFLAGS) -o rdebugfs $(RO_DEBUG_OBJS) $(LIBS) + +debug_cmds.c debug_cmds.h: debug_cmds.ct + $(E) " MK_CMDS $@" + $(Q) $(MK_CMDS) $(srcdir)/debug_cmds.ct + +extent_cmds.c extent_cmds.h: extent_cmds.ct + $(E) " MK_CMDS $@" + $(Q) $(MK_CMDS) $(srcdir)/extent_cmds.ct + +ro_debug_cmds.c ro_debug_cmds.h: ro_debug_cmds.ct + $(E) " MK_CMDS $@" + $(Q) $(MK_CMDS) $(srcdir)/ro_debug_cmds.ct + +ro_debugfs.o: debugfs.c + $(E) " CC $@" + $(Q) $(CC) -c $(ALL_CFLAGS) $< -DREAD_ONLY -o $@ + +e2freefrag.o: $(srcdir)/../misc/e2freefrag.c + $(E) " CC $@" + $(Q) $(CC) -c $(ALL_CFLAGS) -I$(srcdir) $< -o $@ + +recovery.o: $(srcdir)/../e2fsck/recovery.c + $(E) " CC $@" + $(Q) $(CC) -c $(ALL_CFLAGS) -I$(srcdir) \ + $(srcdir)/../e2fsck/recovery.c -o $@ + +revoke.o: $(srcdir)/../e2fsck/revoke.c + $(E) " CC $@" + $(Q) $(CC) -c $(ALL_CFLAGS) -I$(srcdir) \ + $(srcdir)/../e2fsck/revoke.c -o $@ + +create_inode.o: $(srcdir)/../misc/create_inode.c + $(E) " CC $@" + $(Q) $(CC) -c $(ALL_CFLAGS) -I$(srcdir) \ + $(srcdir)/../misc/create_inode.c -o $@ + +debugfs.8: $(DEP_SUBSTITUTE) $(srcdir)/debugfs.8.in + $(E) " SUBST $@" + $(Q) $(SUBSTITUTE_UPTIME) $(srcdir)/debugfs.8.in debugfs.8 + +installdirs: + $(E) " MKDIR_P $(root_sbindir) $(man8dir)" + $(Q) $(MKDIR_P) $(DESTDIR)$(root_sbindir) \ + $(DESTDIR)$(man8dir) + +install: $(PROGS) $(MANPAGES) installdirs + $(Q) for i in $(PROGS); do \ + echo " INSTALL $(root_sbindir)/$$i"; \ + $(INSTALL_PROGRAM) $$i $(DESTDIR)$(root_sbindir)/$$i; \ + done + $(Q) for i in $(MANPAGES); do \ + for j in $(COMPRESS_EXT); do \ + $(RM) -f $(DESTDIR)$(man8dir)/$$i.$$j; \ + done; \ + echo " INSTALL_DATA $(man8dir)/$$i"; \ + $(INSTALL_DATA) $$i $(DESTDIR)$(man8dir)/$$i; \ + done + +install-strip: install + $(Q) for i in $(PROGS); do \ + echo " STRIP $(root_sbindir)/$$i"; \ + $(STRIP) $(DESTDIR)$(root_sbindir)/$$i; \ + done + +uninstall: + for i in $(PROGS); do \ + $(RM) -f $(DESTDIR)$(root_sbindir)/$$i; \ + done + for i in $(MANPAGES); do \ + $(RM) -f $(DESTDIR)$(man8dir)/$$i; \ + done + +clean:: + $(RM) -f $(PROGS) debugfs.8 \#* *.s *.o *.a *~ debug_cmds.c \ + extent_cmds.c ro_debug_cmds.c core rdebugfs debugfs.static \ + debugfs.static-libs tst_set_fields + +mostlyclean: clean +distclean: clean + $(RM) -f debug_cmds.c .depend Makefile $(srcdir)/TAGS \ + $(srcdir)/Makefile.in.old $(srcdir)/recovery.c \ + $(srcdir)/revoke.c + +tst_set_fields: set_fields.c util.c + $(E) " LD $@" + $(Q) $(CC) $(ALL_CFLAGS) $(ALL_LDFLAGS) $(SYSLIBS) -DUNITTEST \ + -o tst_set_fields $(srcdir)/set_fields.c $(srcdir)/util.c $(LIBS) + +fullcheck check:: tst_set_fields + $(TESTENV) ./tst_set_fields + +# +++ Dependency line eater +++ +# +# Makefile dependencies follow. This must be the last section in +# the Makefile.in file +# +debug_cmds.o: debug_cmds.c $(top_srcdir)/lib/ss/ss.h \ + $(top_builddir)/lib/ss/ss_err.h $(top_srcdir)/lib/et/com_err.h +debugfs.o: $(srcdir)/debugfs.c $(top_builddir)/lib/config.h \ + $(top_builddir)/lib/dirpaths.h $(srcdir)/debugfs.h $(top_srcdir)/lib/ss/ss.h \ + $(top_builddir)/lib/ss/ss_err.h $(top_srcdir)/lib/et/com_err.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 $(srcdir)/../misc/create_inode.h \ + $(top_srcdir)/lib/e2p/e2p.h $(top_srcdir)/lib/support/quotaio.h \ + $(top_srcdir)/lib/support/dqblk_v2.h \ + $(top_srcdir)/lib/support/quotaio_tree.h $(top_srcdir)/version.h \ + $(srcdir)/../e2fsck/jfs_user.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 +util.o: $(srcdir)/util.c $(top_builddir)/lib/config.h \ + $(top_builddir)/lib/dirpaths.h $(top_srcdir)/lib/ss/ss.h \ + $(top_builddir)/lib/ss/ss_err.h $(top_srcdir)/lib/et/com_err.h \ + $(srcdir)/debugfs.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 $(srcdir)/../misc/create_inode.h \ + $(top_srcdir)/lib/e2p/e2p.h $(top_srcdir)/lib/support/quotaio.h \ + $(top_srcdir)/lib/support/dqblk_v2.h \ + $(top_srcdir)/lib/support/quotaio_tree.h +ls.o: $(srcdir)/ls.c $(top_builddir)/lib/config.h \ + $(top_builddir)/lib/dirpaths.h $(srcdir)/debugfs.h $(top_srcdir)/lib/ss/ss.h \ + $(top_builddir)/lib/ss/ss_err.h $(top_srcdir)/lib/et/com_err.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 $(srcdir)/../misc/create_inode.h \ + $(top_srcdir)/lib/e2p/e2p.h $(top_srcdir)/lib/support/quotaio.h \ + $(top_srcdir)/lib/support/dqblk_v2.h \ + $(top_srcdir)/lib/support/quotaio_tree.h +ncheck.o: $(srcdir)/ncheck.c $(top_builddir)/lib/config.h \ + $(top_builddir)/lib/dirpaths.h $(srcdir)/debugfs.h $(top_srcdir)/lib/ss/ss.h \ + $(top_builddir)/lib/ss/ss_err.h $(top_srcdir)/lib/et/com_err.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 $(srcdir)/../misc/create_inode.h \ + $(top_srcdir)/lib/e2p/e2p.h $(top_srcdir)/lib/support/quotaio.h \ + $(top_srcdir)/lib/support/dqblk_v2.h \ + $(top_srcdir)/lib/support/quotaio_tree.h +icheck.o: $(srcdir)/icheck.c $(top_builddir)/lib/config.h \ + $(top_builddir)/lib/dirpaths.h $(srcdir)/debugfs.h $(top_srcdir)/lib/ss/ss.h \ + $(top_builddir)/lib/ss/ss_err.h $(top_srcdir)/lib/et/com_err.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 $(srcdir)/../misc/create_inode.h \ + $(top_srcdir)/lib/e2p/e2p.h $(top_srcdir)/lib/support/quotaio.h \ + $(top_srcdir)/lib/support/dqblk_v2.h \ + $(top_srcdir)/lib/support/quotaio_tree.h +lsdel.o: $(srcdir)/lsdel.c $(top_builddir)/lib/config.h \ + $(top_builddir)/lib/dirpaths.h $(srcdir)/debugfs.h $(top_srcdir)/lib/ss/ss.h \ + $(top_builddir)/lib/ss/ss_err.h $(top_srcdir)/lib/et/com_err.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 $(srcdir)/../misc/create_inode.h \ + $(top_srcdir)/lib/e2p/e2p.h $(top_srcdir)/lib/support/quotaio.h \ + $(top_srcdir)/lib/support/dqblk_v2.h \ + $(top_srcdir)/lib/support/quotaio_tree.h +dump.o: $(srcdir)/dump.c $(top_builddir)/lib/config.h \ + $(top_builddir)/lib/dirpaths.h $(srcdir)/debugfs.h $(top_srcdir)/lib/ss/ss.h \ + $(top_builddir)/lib/ss/ss_err.h $(top_srcdir)/lib/et/com_err.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 $(srcdir)/../misc/create_inode.h \ + $(top_srcdir)/lib/e2p/e2p.h $(top_srcdir)/lib/support/quotaio.h \ + $(top_srcdir)/lib/support/dqblk_v2.h \ + $(top_srcdir)/lib/support/quotaio_tree.h +set_fields.o: $(srcdir)/set_fields.c $(top_builddir)/lib/config.h \ + $(top_builddir)/lib/dirpaths.h $(srcdir)/debugfs.h $(top_srcdir)/lib/ss/ss.h \ + $(top_builddir)/lib/ss/ss_err.h $(top_srcdir)/lib/et/com_err.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 $(srcdir)/../misc/create_inode.h \ + $(top_srcdir)/lib/e2p/e2p.h $(top_srcdir)/lib/support/quotaio.h \ + $(top_srcdir)/lib/support/dqblk_v2.h \ + $(top_srcdir)/lib/support/quotaio_tree.h +logdump.o: $(srcdir)/logdump.c $(top_builddir)/lib/config.h \ + $(top_builddir)/lib/dirpaths.h $(srcdir)/debugfs.h $(top_srcdir)/lib/ss/ss.h \ + $(top_builddir)/lib/ss/ss_err.h $(top_srcdir)/lib/et/com_err.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 $(srcdir)/../misc/create_inode.h \ + $(top_srcdir)/lib/e2p/e2p.h $(top_srcdir)/lib/support/quotaio.h \ + $(top_srcdir)/lib/support/dqblk_v2.h \ + $(top_srcdir)/lib/support/quotaio_tree.h $(srcdir)/../e2fsck/jfs_user.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/ext2fs/fast_commit.h +htree.o: $(srcdir)/htree.c $(top_builddir)/lib/config.h \ + $(top_builddir)/lib/dirpaths.h $(srcdir)/debugfs.h $(top_srcdir)/lib/ss/ss.h \ + $(top_builddir)/lib/ss/ss_err.h $(top_srcdir)/lib/et/com_err.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 $(srcdir)/../misc/create_inode.h \ + $(top_srcdir)/lib/e2p/e2p.h $(top_srcdir)/lib/support/quotaio.h \ + $(top_srcdir)/lib/support/dqblk_v2.h \ + $(top_srcdir)/lib/support/quotaio_tree.h +unused.o: $(srcdir)/unused.c $(top_builddir)/lib/config.h \ + $(top_builddir)/lib/dirpaths.h $(srcdir)/debugfs.h $(top_srcdir)/lib/ss/ss.h \ + $(top_builddir)/lib/ss/ss_err.h $(top_srcdir)/lib/et/com_err.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 $(srcdir)/../misc/create_inode.h \ + $(top_srcdir)/lib/e2p/e2p.h $(top_srcdir)/lib/support/quotaio.h \ + $(top_srcdir)/lib/support/dqblk_v2.h \ + $(top_srcdir)/lib/support/quotaio_tree.h +e2freefrag.o: $(srcdir)/../misc/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)/../misc/e2freefrag.h \ + $(srcdir)/debugfs.h $(top_srcdir)/lib/ss/ss.h \ + $(top_builddir)/lib/ss/ss_err.h $(srcdir)/../misc/create_inode.h \ + $(top_srcdir)/lib/e2p/e2p.h $(top_srcdir)/lib/support/quotaio.h \ + $(top_srcdir)/lib/support/dqblk_v2.h \ + $(top_srcdir)/lib/support/quotaio_tree.h +filefrag.o: $(srcdir)/filefrag.c $(top_builddir)/lib/config.h \ + $(top_builddir)/lib/dirpaths.h $(srcdir)/debugfs.h $(top_srcdir)/lib/ss/ss.h \ + $(top_builddir)/lib/ss/ss_err.h $(top_srcdir)/lib/et/com_err.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 $(srcdir)/../misc/create_inode.h \ + $(top_srcdir)/lib/e2p/e2p.h $(top_srcdir)/lib/support/quotaio.h \ + $(top_srcdir)/lib/support/dqblk_v2.h \ + $(top_srcdir)/lib/support/quotaio_tree.h +extent_inode.o: $(srcdir)/extent_inode.c $(top_builddir)/lib/config.h \ + $(top_builddir)/lib/dirpaths.h $(srcdir)/debugfs.h $(top_srcdir)/lib/ss/ss.h \ + $(top_builddir)/lib/ss/ss_err.h $(top_srcdir)/lib/et/com_err.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 $(srcdir)/../misc/create_inode.h \ + $(top_srcdir)/lib/e2p/e2p.h $(top_srcdir)/lib/support/quotaio.h \ + $(top_srcdir)/lib/support/dqblk_v2.h \ + $(top_srcdir)/lib/support/quotaio_tree.h +zap.o: $(srcdir)/zap.c $(top_builddir)/lib/config.h \ + $(top_builddir)/lib/dirpaths.h $(srcdir)/debugfs.h $(top_srcdir)/lib/ss/ss.h \ + $(top_builddir)/lib/ss/ss_err.h $(top_srcdir)/lib/et/com_err.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 $(srcdir)/../misc/create_inode.h \ + $(top_srcdir)/lib/e2p/e2p.h $(top_srcdir)/lib/support/quotaio.h \ + $(top_srcdir)/lib/support/dqblk_v2.h \ + $(top_srcdir)/lib/support/quotaio_tree.h +create_inode.o: $(srcdir)/../misc/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)/../misc/create_inode.h $(top_srcdir)/lib/e2p/e2p.h \ + $(top_srcdir)/lib/support/nls-enable.h +xattrs.o: $(srcdir)/xattrs.c $(top_builddir)/lib/config.h \ + $(top_builddir)/lib/dirpaths.h $(top_srcdir)/lib/support/cstring.h \ + $(srcdir)/debugfs.h $(top_srcdir)/lib/ss/ss.h \ + $(top_builddir)/lib/ss/ss_err.h $(top_srcdir)/lib/et/com_err.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 $(srcdir)/../misc/create_inode.h \ + $(top_srcdir)/lib/e2p/e2p.h $(top_srcdir)/lib/support/quotaio.h \ + $(top_srcdir)/lib/support/dqblk_v2.h \ + $(top_srcdir)/lib/support/quotaio_tree.h +quota.o: $(srcdir)/quota.c $(top_builddir)/lib/config.h \ + $(top_builddir)/lib/dirpaths.h $(srcdir)/debugfs.h $(top_srcdir)/lib/ss/ss.h \ + $(top_builddir)/lib/ss/ss_err.h $(top_srcdir)/lib/et/com_err.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 $(srcdir)/../misc/create_inode.h \ + $(top_srcdir)/lib/e2p/e2p.h $(top_srcdir)/lib/support/quotaio.h \ + $(top_srcdir)/lib/support/dqblk_v2.h \ + $(top_srcdir)/lib/support/quotaio_tree.h +journal.o: $(srcdir)/journal.c $(top_builddir)/lib/config.h \ + $(top_builddir)/lib/dirpaths.h $(srcdir)/journal.h \ + $(srcdir)/../e2fsck/jfs_user.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 +revoke.o: $(srcdir)/../e2fsck/revoke.c $(srcdir)/../e2fsck/jfs_user.h \ + $(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 +recovery.o: $(srcdir)/../e2fsck/recovery.c $(srcdir)/../e2fsck/jfs_user.h \ + $(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 +do_journal.o: $(srcdir)/do_journal.c $(top_builddir)/lib/config.h \ + $(top_builddir)/lib/dirpaths.h $(srcdir)/debugfs.h $(top_srcdir)/lib/ss/ss.h \ + $(top_builddir)/lib/ss/ss_err.h $(top_srcdir)/lib/et/com_err.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 $(srcdir)/../misc/create_inode.h \ + $(top_srcdir)/lib/e2p/e2p.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/kernel-jbd.h $(top_srcdir)/lib/ext2fs/jfs_compat.h \ + $(top_srcdir)/lib/ext2fs/kernel-list.h $(top_srcdir)/lib/ext2fs/compiler.h \ + $(srcdir)/journal.h $(srcdir)/../e2fsck/jfs_user.h diff --git a/debugfs/debug_cmds.ct b/debugfs/debug_cmds.ct new file mode 100644 index 0000000..1ff6c9d --- /dev/null +++ b/debugfs/debug_cmds.ct @@ -0,0 +1,233 @@ +# +# Copyright (C) 1993 Theodore Ts'o. This file may be redistributed +# under the terms of the GNU Public License. +# +command_table debug_cmds; + +request do_show_debugfs_params, "Show debugfs parameters", + show_debugfs_params, params; + +request do_open_filesys, "Open a filesystem", + open_filesys, open; + +request do_close_filesys, "Close the filesystem", + close_filesys, close; + +request do_freefrag, "Report free space fragmentation", + freefrag, e2freefrag; + +request do_features, "Set/print superblock features", + feature, features; + +request do_dirty_filesys, "Mark the filesystem as dirty", + dirty_filesys, dirty; + +request do_init_filesys, "Initialize a filesystem (DESTROYS DATA)", + init_filesys; + +request do_show_super_stats, "Show superblock statistics", + show_super_stats, stats; + +request do_ncheck, "Do inode->name translation", + ncheck; + +request do_icheck, "Do block->inode translation", + icheck; + +request do_chroot, "Change root directory", + change_root_directory, chroot; + +request do_change_working_dir, "Change working directory", + change_working_directory, cd; + +request do_list_dir, "List directory", + list_directory, ls; + +request do_stat, "Show inode information ", + show_inode_info, stat; + +request do_dump_extents, "Dump extents information ", + dump_extents, extents, ex; + +request do_blocks, "Dump blocks used by an inode ", + blocks; + +request do_filefrag, "Report fragmentation information for an inode", + filefrag; + +request do_link, "Create directory link", + link, ln; + +request do_unlink, "Delete a directory link", + unlink; + +request do_mkdir, "Create a directory", + mkdir; + +request do_rmdir, "Remove a directory", + rmdir; + +request do_rm, "Remove a file (unlink and kill_file, if appropriate)", + rm; + +request do_kill_file, "Deallocate an inode and its blocks", + kill_file; + +request do_copy_inode, "Copy the inode structure", + copy_inode; + +request do_clri, "Clear an inode's contents", + clri; + +request do_freei, "Clear an inode's in-use flag", + freei; + +request do_seti, "Set an inode's in-use flag", + seti; + +request do_testi, "Test an inode's in-use flag", + testi; + +request do_freeb, "Clear a block's in-use flag", + freeb; + +request do_setb, "Set a block's in-use flag", + setb; + +request do_testb, "Test a block's in-use flag", + testb; + +request do_modify_inode, "Modify an inode by structure", + modify_inode, mi; + +request do_find_free_block, "Find free block(s)", + find_free_block, ffb; + +request do_find_free_inode, "Find free inode(s)", + find_free_inode, ffi; + +request do_print_working_directory, "Print current working directory", + print_working_directory, pwd; + +request do_expand_dir, "Expand directory", + expand_dir, expand; + +request do_mknod, "Create a special file", + mknod; + +request do_lsdel, "List deleted inodes", + list_deleted_inodes, lsdel; + +request do_undel, "Undelete file", + undelete, undel; + +request do_write, "Copy a file from your native filesystem", + write; + +request do_dump, "Dump an inode out to a file", + dump_inode, dump; + +request do_cat, "Dump an inode out to stdout", + cat; + +request do_lcd, "Change the current directory on your native filesystem", + lcd; + +request do_rdump, "Recursively dump a directory to the native filesystem", + rdump; + +request do_set_super, "Set superblock value", + set_super_value, ssv; + +request do_set_inode, "Set inode field", + set_inode_field, sif; + +request do_set_block_group_descriptor, "Set block group descriptor field", + set_block_group, set_bg; + +request do_logdump, "Dump the contents of the journal", + logdump; + +request do_htree_dump, "Dump a hash-indexed directory", + htree_dump, htree; + +request do_dx_hash, "Calculate the directory hash of a filename", + dx_hash, hash; + +request do_dirsearch, "Search a directory for a particular filename", + dirsearch; + +request do_bmap, "Calculate the logical->physical block mapping for an inode", + bmap; + +request do_fallocate, "Allocate uninitialized blocks to an inode", + fallocate; + +request do_punch, "Punch (or truncate) blocks from an inode by deallocating them", + punch, truncate; + +request do_symlink, "Create a symbolic link", + symlink; + +request do_imap, "Calculate the location of an inode", + imap; + +request do_dump_unused, "Dump unused blocks", + dump_unused; + +request do_set_current_time, "Set current time to use when setting filesystem fields", + set_current_time; + +request do_supported_features, "Print features supported by this version of e2fsprogs", + supported_features; + +request do_dump_mmp, "Dump MMP information", + dump_mmp; + +request do_set_mmp_value, "Set MMP value", + set_mmp_value, smmp; + +request do_extent_open, "Open inode for extent manipulation", + extent_open, eo; + +request do_zap_block, "Zap block: fill with 0, pattern, flip bits etc.", + zap_block, zap; + +request do_block_dump, "Dump contents of a block", + block_dump, bdump, bd; + +request do_list_xattr, "List extended attributes of an inode", + ea_list; + +request do_get_xattr, "Get an extended attribute of an inode", + ea_get; + +request do_set_xattr, "Set an extended attribute of an inode", + ea_set; + +request do_rm_xattr, "Remove an extended attribute of an inode", + ea_rm; + +request do_list_quota, "List quota", + list_quota, lq; + +request do_get_quota, "Get quota", + get_quota, gq; + +request do_idump, "Dump the inode structure in hex", + inode_dump, idump, id; + +request do_journal_open, "Open the journal", + journal_open, jo; + +request do_journal_close, "Close the journal", + journal_close, jc; + +request do_journal_write, "Write a transaction to the journal", + journal_write, jw; + +request do_journal_run, "Recover the journal", + journal_run, jr; + +end; + diff --git a/debugfs/debugfs.8.in b/debugfs/debugfs.8.in new file mode 100644 index 0000000..5b5329c --- /dev/null +++ b/debugfs/debugfs.8.in @@ -0,0 +1,880 @@ +.\" -*- 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 DEBUGFS 8 "@E2FSPROGS_MONTH@ @E2FSPROGS_YEAR@" "E2fsprogs version @E2FSPROGS_VERSION@" +.SH NAME +debugfs \- ext2/ext3/ext4 file system debugger +.SH SYNOPSIS +.B debugfs +[ +.B \-DVwcin +] +[ +.B \-b +blocksize +] +[ +.B \-s +superblock +] +[ +.B \-f +cmd_file +] +[ +.B \-R +request +] +[ +.B \-d +data_source_device +] +[ +.B \-z +.I undo_file +] +[ +device +] +.SH DESCRIPTION +The +.B debugfs +program is an interactive file system debugger. It can be used to +examine and change the state of an ext2, ext3, or ext4 file system. +.PP +.I device +is a block device (e.g., /dev/sdXX) or a file containing the file system. +.SH OPTIONS +.TP +.I \-w +Specifies that the file system should be opened in read-write mode. +Without this option, the file system is opened in read-only mode. +.TP +.I \-n +Disables metadata checksum verification. This should only be used if +you believe the metadata to be correct despite the complaints of +e2fsprogs. +.TP +.I \-c +Specifies that the file system should be opened in catastrophic mode, in +which the inode and group bitmaps are not read initially. This can be +useful for file systems with significant corruption, but because of this, +catastrophic mode forces the file system to be opened read-only. +.TP +.I \-i +Specifies that +.I device +represents an ext2 image file created by the +.B e2image +program. Since the ext2 image file only contains the superblock, block +group descriptor, block and inode allocation bitmaps, and +the inode table, many +.B debugfs +commands will not function properly. +.B Warning: +no safety checks are in place, and +.B debugfs +may fail in interesting ways if commands such as +.IR ls ", " dump ", " +etc. are tried without specifying the +.I data_source_device +using the +.I \-d +option. +.B debugfs +is a debugging tool. It has rough edges! +.TP +.I -d data_source_device +Used with the +.I \-i +option, specifies that +.I data_source_device +should be used when reading blocks not found in the ext2 image file. +This includes data, directory, and indirect blocks. +.TP +.I -b blocksize +Forces the use of the given block size (in bytes) for the file system, +rather than detecting the correct block size automatically. (This +option is rarely needed; it is used primarily when the file system is +extremely badly damaged/corrupted.) +.TP +.I -s superblock +Causes the file system superblock to be read from the given block +number, instead of using the primary superblock (located at an offset of +1024 bytes from the beginning of the file system). If you specify the +.I -s +option, you must also provide the blocksize of the file system via the +.I -b +option. (This +option is rarely needed; it is used primarily when the file system is +extremely badly damaged/corrupted.) +.TP +.I -f cmd_file +Causes +.B debugfs +to read in commands from +.IR cmd_file , +and execute them. When +.B debugfs +is finished executing those commands, it will exit. +.TP +.I -D +Causes +.B debugfs +to open the device using Direct I/O, bypassing the buffer cache. Note +that some Linux devices, notably device mapper as of this writing, do +not support Direct I/O. +.TP +.I -R request +Causes +.B debugfs +to execute the single command +.IR request , +and then exit. +.TP +.I -V +print the version number of +.B debugfs +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 +debugfs-\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 SPECIFYING FILES +Many +.B debugfs +commands take a +.I filespec +as an argument to specify an inode (as opposed to a pathname) +in the file system which is currently opened by +.BR debugfs . +The +.I filespec +argument may be specified in two forms. The first form is an inode +number surrounded by angle brackets, e.g., +.IR <2> . +The second form is a pathname; if the pathname is prefixed by a forward slash +('/'), then it is interpreted relative to the root of the file system +which is currently opened by +.BR debugfs . +If not, the pathname is +interpreted relative to the current working directory as maintained by +.BR debugfs . +This may be modified by using the +.B debugfs +command +.IR cd . +.\" +.\" +.\" +.SH COMMANDS +This is a list of the commands which +.B debugfs +supports. +.TP +.BI blocks " filespec" +Print the blocks used by the inode +.I filespec +to stdout. +.TP +.BI bmap " [ -a ] filespec logical_block [physical_block]" +Print or set the physical block number corresponding to the logical block number +.I logical_block +in the inode +.IR filespec . +If the +.I \-a +flag is specified, try to allocate a block if necessary. +.TP +.BI block_dump " '[ -x ] [-f filespec] block_num" +Dump the file system block given by +.I block_num +in hex and ASCII format to the console. If the +.I \-f +option is specified, the block number is relative to the start of the given +.BR filespec . +If the +.I \-x +option is specified, the block is interpreted as an extended attribute +block and printed to show the structure of extended attribute data +structures. +.TP +.BI cat " filespec" +Dump the contents of the inode +.I filespec +to stdout. +.TP +.BI cd " filespec" +Change the current working directory to +.IR filespec . +.TP +.BI chroot " filespec" +Change the root directory to be the directory +.IR filespec . +.TP +.BI close " [-a]" +Close the currently open file system. If the +.I -a +option is specified, write out any changes to the superblock and block +group descriptors to all of the backup superblocks, not just to the +master superblock. +.TP +.BI clri " filespec" +Clear the contents of the inode +.IR filespec . +.TP +.BI copy_inode " source_inode destination_inode" +Copy the contents of the inode structure in +.I source_inode +and use it to overwrite the inode structure at +.IR destination_inode . +.TP +.BI dirsearch " filespec filename" +Search the directory +.I filespec +for +.IR filename . +.TP +.BI dirty " [-clean]" +Mark the file system as dirty, so that the superblocks will be written on exit. +Additionally, clear the superblock's valid flag, or set it if +.I -clean +is specified. +.TP +.BI dump " [-p] filespec out_file" +Dump the contents of the inode +.I filespec +to the output file +.IR out_file . +If the +.I -p +option is given set the owner, group and permissions information on +.I out_file +to match +.IR filespec . +.TP +.BI dump_mmp " [mmp_block]" +Display the multiple-mount protection (mmp) field values. If +.I mmp_block +is specified then verify and dump the MMP values from the given block +number, otherwise use the +.B s_mmp_block +field in the superblock to locate and use the existing MMP block. +.TP +.BI dx_hash " [-h hash_alg] [-s hash_seed] filename" +Calculate the directory hash of +.IR filename . +The hash algorithm specified with +.I -h +may be +.BR legacy , " half_md4" ", or " tea . +The hash seed specified with +.I -s +must be in UUID format. +.TP +.BI dump_extents " [-n] [-l] filespec" +Dump the extent tree of the inode +.IR filespec . +The +.I -n +flag will cause +.B dump_extents +to only display the interior nodes in the extent tree. The +.I -l +flag will cause +.B dump_extents +to only display the leaf nodes in the extent tree. +.IP +(Please note that the length and range of blocks for the last extent in +an interior node is an estimate by the extents library functions, and is +not stored in file system data structures. Hence, the values displayed +may not necessarily by accurate and does not indicate a problem or +corruption in the file system.) +.TP +.B dump_unused +Dump unused blocks which contain non-null bytes. +.TP +.BI ea_get " [-f outfile]|[-xVC] [-r] filespec attr_name" +Retrieve the value of the extended attribute +.I attr_name +in the file +.I filespec +and write it either to stdout or to \fIoutfile\fR. +.TP +.BI ea_list " filespec +List the extended attributes associated with the file +.I filespec +to standard output. +.TP +.BI ea_set " [-f infile] [-r] filespec attr_name attr_value +Set the value of the extended attribute +.I attr_name +in the file +.I filespec +to the string value +.I attr_value +or read it from \fIinfile\fR. +.TP +.BI ea_rm " filespec attr_names... +Remove the extended attribute +.I attr_name +from the file \fIfilespec\fR. +.TP +.BI expand_dir " filespec" +Expand the directory +.IR filespec . +.TP +.BI fallocate " filespec start_block [end_block] +Allocate and map uninitialized blocks into \fIfilespec\fR between +logical block \fIstart_block\fR and \fIend_block\fR, inclusive. If +\fIend_block\fR is not supplied, this function maps until it runs out +of free disk blocks or the maximum file size is reached. Existing +mappings are left alone. +.TP +.BI feature " [fs_feature] [-fs_feature] ..." +Set or clear various file system features in the superblock. After setting +or clearing any file system features that were requested, print the current +state of the file system feature set. +.TP +.BI filefrag " [-dvr] filespec" +Print the number of contiguous extents in +.IR filespec . +If +.I filespec +is a directory and the +.I -d +option is not specified, +.I filefrag +will print the number of contiguous extents for each file in +the directory. The +.I -v +option will cause +.I filefrag +print a tabular listing of the contiguous extents in the +file. The +.I -r +option will cause +.I filefrag +to do a recursive listing of the directory. +.TP +.BI find_free_block " [count [goal]]" +Find the first +.I count +free blocks, starting from +.I goal +and allocate it. Also available as +.BR ffb . +.TP +.BI find_free_inode " [dir [mode]]" +Find a free inode and allocate it. If present, +.I dir +specifies the inode number of the directory +which the inode is to be located. The second +optional argument +.I mode +specifies the permissions of the new inode. (If the directory bit is set +on the mode, the allocation routine will function differently.) Also +available as +.BR ffi . +.TP +.BI freeb " block [count]" +Mark the block number +.I block +as not allocated. +If the optional argument +.I count +is present, then +.I count +blocks starting at block number +.I block +will be marked as not allocated. +.TP +.BI freefrag " [-c chunk_kb]" +Report free space fragmentation on the currently open file system. +If the +.I \-c +option is specified then the filefrag command will print how many free +chunks of size +.I chunk_kb +can be found in the file system. The chunk size must be a power of two +and be larger than the file system block size. +.TP +.BI freei " filespec [num]" +Free the inode specified by +.IR filespec . +If +.I num +is specified, also clear num-1 inodes after the specified inode. +.TP +.BI get_quota " quota_type id" +Display quota information for given quota type (user, group, or project) and ID. +.TP +.B help +Print a list of commands understood by +.BR debugfs . +.TP +.BI htree_dump " filespec" +Dump the hash-indexed directory +.IR filespec , +showing its tree structure. +.TP +.BI icheck " block ..." +Print a listing of the inodes which use the one or more blocks specified +on the command line. +.TP +.BI inode_dump " [-b]|[-e]|[-x] filespec" +Print the contents of the inode data structure in hex and ASCII format. +The +.I \-b +option causes the command to only dump the contents of the +.B i_blocks +array. The +.I \-e +option causes the command to only dump the contents of the extra inode +space, which is used to store in-line extended attributes. The +.I \-x +option causes the command to dump the extra inode space interpreted and +extended attributes. This is useful to debug corrupted inodes +containing extended attributes. +.TP +.BI imap " filespec" +Print the location of the inode data structure (in the inode table) +of the inode +.IR filespec . +.TP +.BI init_filesys " device blocksize" +Create an ext2 file system on +.I device +with device size +.IR blocksize . +Note that this does not fully initialize all of the data structures; +to do this, use the +.BR mke2fs (8) +program. This is just a call to the low-level library, which sets up +the superblock and block descriptors. +.TP +.BI journal_close +Close the open journal. +.TP +.BI journal_open " [-c] [-v ver] [-f ext_jnl] +Opens the journal for reading and writing. Journal checksumming can +be enabled by supplying \fI-c\fR; checksum formats 2 and 3 can be +selected with the \fI-v\fR option. An external journal can be loaded +from \fIext_jnl\fR. +.TP +.BI journal_run +Replay all transactions in the open journal. +.TP +.BI journal_write " [-b blocks] [-r revoke] [-c] file +Write a transaction to the open journal. The list of blocks to write +should be supplied as a comma-separated list in \fIblocks\fR; the +blocks themselves should be readable from \fIfile\fR. A list of +blocks to revoke can be supplied as a comma-separated list in +\fIrevoke\fR. By default, a commit record is written at the end; the +\fI-c\fR switch writes an uncommitted transaction. +.TP +.BI kill_file " filespec" +Deallocate the inode +.I filespec +and its blocks. Note that this does not remove any directory +entries (if any) to this inode. See the +.BR rm (1) +command if you wish to unlink a file. +.TP +.BI lcd " directory" +Change the current working directory of the +.B debugfs +process to +.I directory +on the native file system. +.TP +.BI list_quota " quota_type" +Display quota information for given quota type (user, group, or project). +.TP +.BI ln " filespec dest_file" +Create a link named +.I dest_file +which is a hard link to +.IR filespec . +Note this does not adjust the inode reference counts. +.TP +.BI logdump " [-acsOS] [-b block] [-n num_trans ] [-i filespec] [-f journal_file] [output_file]" +Dump the contents of the ext3 journal. By default, dump the journal inode as +specified in the superblock. However, this can be overridden with the +.I \-i +option, which dumps the journal from the internal inode given by +.IR filespec . +A regular file containing journal data can be specified using the +.I \-f +option. Finally, the +.I \-s +option utilizes the backup information in the superblock to locate the +journal. +.IP +The +.I \-S +option causes +.B logdump +to print the contents of the journal superblock. +.IP +The +.I \-a +option causes the +.B logdump +to print the contents of all of the descriptor blocks. +The +.I \-b +option causes +.B logdump +to print all journal records that refer to the specified block. +The +.I \-c +option will print out the contents of all of the data blocks selected by +the +.I \-a +and +.I \-b +options. +.IP +The +.I \-O +option causes logdump to display old (checkpointed) journal entries. +This can be used to try to track down journal problems even after the +journal has been replayed. +.IP +The +.I \-n +option causes +.B logdump +to continue past a journal block which is missing a magic number. +Instead, it will stop only when the entire log is printed or after +.I num_trans +transactions. +.TP +.BI ls " [-l] [-c] [-d] [-p] [-r] filespec" +Print a listing of the files in the directory +.IR filespec . +The +.I \-c +flag causes directory block checksums (if present) to be displayed. +The +.I \-d +flag will list deleted entries in the directory. +The +.I \-l +flag will list files using a more verbose format. +The +.I \-p +flag will list the files in a format which is more easily parsable by +scripts, as well as making it more clear when there are spaces or other +non-printing characters at the end of filenames. +The +.I \-r +flag will force the printing of the filename, even if it is encrypted. +.TP +.BI list_deleted_inodes " [limit]" +List deleted inodes, optionally limited to those deleted within +.I limit +seconds ago. Also available as +.BR lsdel . +.IP +This command was useful for recovering from accidental file deletions +for ext2 file systems. Unfortunately, it is not useful for this purpose +if the files were deleted using ext3 or ext4, since the inode's +data blocks are no longer available after the inode is released. +.TP +.BI modify_inode " filespec" +Modify the contents of the inode structure in the inode +.IR filespec . +Also available as +.BR mi . +.TP +.BI mkdir " filespec" +Make a directory. +.TP +.BI mknod " filespec [p|[[c|b] major minor]]" +Create a special device file (a named pipe, character or block device). +If a character or block device is to be made, the +.I major +and +.I minor +device numbers must be specified. +.TP +.BI ncheck " [-c] inode_num ..." +Take the requested list of inode numbers, and print a listing of pathnames +to those inodes. The +.I -c +flag will enable checking the file type information in the directory +entry to make sure it matches the inode's type. +.TP +.BI open " [-weficD] [-b blocksize] [-d image_filename] [-s superblock] [-z undo_file] device" +Open a file system for editing. The +.I -f +flag forces the file system to be opened even if there are some unknown +or incompatible file system features which would normally +prevent the file system from being opened. The +.I -e +flag causes the file system to be opened in exclusive mode. The +.IR -b ", " -c ", " -d ", " -i ", " -s ", " -w ", and " -D +options behave the same as the command-line options to +.BR debugfs . +.TP +.BI punch " filespec start_blk [end_blk]" +Delete the blocks in the inode ranging from +.I start_blk +to +.IR end_blk . +If +.I end_blk +is omitted then this command will function as a truncate command; that +is, all of the blocks starting at +.I start_blk +through to the end of the file will be deallocated. +.TP +.BI symlink " filespec target" +Make a symbolic link. +.TP +.B pwd +Print the current working directory. +.TP +.B quit +Quit +.B debugfs +.TP +.BI rdump " directory[...] destination" +Recursively dump +.IR directory , +or multiple +.IR directories , +and all its contents (including regular files, symbolic links, and other +directories) into the named +.IR destination , +which should be an existing directory on the native file system. +.TP +.BI rm " pathname" +Unlink +.IR pathname . +If this causes the inode pointed to by +.I pathname +to have no other references, deallocate the file. This command functions +as the unlink() system call. +.I +.TP +.BI rmdir " filespec" +Remove the directory +.IR filespec . +.TP +.BI setb " block [count]" +Mark the block number +.I block +as allocated. +If the optional argument +.I count +is present, then +.I count +blocks starting at block number +.I block +will be marked as allocated. +.TP +.BI set_block_group " bgnum field value" +Modify the block group descriptor specified by +.I bgnum +so that the block group descriptor field +.I field +has value +.IR value . +Also available as +.BR set_bg . +.TP +.BI set_current_time " time" +Set current time in seconds since Unix epoch to use when setting file system +fields. +.TP +.BI seti " filespec [num]" +Mark inode +.I filespec +as in use in the inode bitmap. If +.I num +is specified, also set num-1 inodes after the specified inode. +.TP +.BI set_inode_field " filespec field value" +Modify the inode specified by +.I filespec +so that the inode field +.I field +has value +.I value. +The list of valid inode fields which can be set via this command +can be displayed by using the command: +.B set_inode_field -l +Also available as +.BR sif . +.TP +.BI set_mmp_value " field value" +Modify the multiple-mount protection (MMP) data so that the MMP field +.I field +has value +.I value. +The list of valid MMP fields which can be set via this command +can be displayed by using the command: +.B set_mmp_value -l +Also available as +.BR smmp . +.TP +.BI set_super_value " field value" +Set the superblock field +.I field +to +.I value. +The list of valid superblock fields which can be set via this command +can be displayed by using the command: +.B set_super_value -l +Also available as +.BR ssv . +.TP +.B show_debugfs_params +Display +.B debugfs +parameters such as information about currently opened file system. +.TP +.BI show_super_stats " [-h]" +List the contents of the super block and the block group descriptors. If the +.I -h +flag is given, only print out the superblock contents. Also available as +.BR stats . +.TP +.BI stat " filespec" +Display the contents of the inode structure of the inode +.IR filespec . +.TP +.B supported_features +Display file system features supported by this version of +.BR debugfs . +.TP +.BI testb " block [count]" +Test if the block number +.I block +is marked as allocated in the block bitmap. +If the optional argument +.I count +is present, then +.I count +blocks starting at block number +.I block +will be tested. +.TP +.BI testi " filespec" +Test if the inode +.I filespec +is marked as allocated in the inode bitmap. +.TP +.BI undel " <inode_number> [pathname]" +Undelete the specified inode number (which must be surrounded by angle +brackets) so that it and its blocks are marked in use, and optionally +link the recovered inode to the specified pathname. The +.B e2fsck +command should always be run after using the +.B undel +command to recover deleted files. +.IP +Note that if you are recovering a large number of deleted files, linking +the inode to a directory may require the directory to be expanded, which +could allocate a block that had been used by one of the +yet-to-be-undeleted files. So it is safer to undelete all of the +inodes without specifying a destination pathname, and then in a separate +pass, use the debugfs +.B link +command to link the inode to the destination pathname, or use +.B e2fsck +to check the file system and link all of the recovered inodes to the +lost+found directory. +.TP +.BI unlink " pathname" +Remove the link specified by +.I pathname +to an inode. Note this does not adjust the inode reference counts. +.TP +.BI write " source_file out_file" +Copy the contents of +.I source_file +into a newly-created file in the file system named +.IR out_file . +.TP +.BI zap_block " [-f filespec] [-o offset] [-l length] [-p pattern] block_num" +Overwrite the block specified by +.I block_num +with zero (NUL) bytes, or if +.I -p +is given use the byte specified by +.IR pattern . +If +.I -f +is given then +.I block_num +is relative to the start of the file given by +.IR filespec . +The +.I -o +and +.I -l +options limit the range of bytes to zap to the specified +.I offset +and +.I length +relative to the start of the block. +.TP +.BI zap_block " [-f filespec] [-b bit] block_num" +Bit-flip portions of the physical +.IR block_num . +If +.I -f +is given, then +.I block_num +is a logical block relative to the start of +.IR filespec . +.SH ENVIRONMENT VARIABLES +.TP +.B DEBUGFS_PAGER, PAGER +The +.B debugfs +program always pipes the output of the some commands through a +pager program. These commands include: +.IR show_super_stats " (" stats ), +.IR list_directory " (" ls ), +.IR show_inode_info " (" stat ), +.IR list_deleted_inodes " (" lsdel ), +and +.IR htree_dump . +The specific pager can explicitly specified by the +.B DEBUGFS_PAGER +environment variable, and if it is not set, by the +.B PAGER +environment variable. +.IP +Note that since a pager is always used, the +.BR less (1) +pager is not particularly appropriate, since it clears the screen before +displaying the output of the command and clears the output the screen +when the pager is exited. Many users prefer to use the +.BR less (1) +pager for most purposes, which is why the +.B DEBUGFS_PAGER +environment variable is available to override the more general +.B PAGER +environment variable. +.SH AUTHOR +.B debugfs +was written by Theodore Ts'o <tytso@mit.edu>. +.SH SEE ALSO +.BR dumpe2fs (8), +.BR tune2fs (8), +.BR e2fsck (8), +.BR mke2fs (8), +.BR ext4 (5) diff --git a/debugfs/debugfs.c b/debugfs/debugfs.c new file mode 100644 index 0000000..9b6321d --- /dev/null +++ b/debugfs/debugfs.c @@ -0,0 +1,2682 @@ +/* + * debugfs.c --- a program which allows you to attach an ext2fs + * filesystem and play with it. + * + * Copyright (C) 1993 Theodore Ts'o. This file may be redistributed + * under the terms of the GNU Public License. + * + * Modifications by Robert Sanders <gt8134b@prism.gatech.edu> + */ + +#include "config.h" +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <ctype.h> +#include <string.h> +#include <time.h> +#include <libgen.h> +#ifdef HAVE_GETOPT_H +#include <getopt.h> +#else +extern int optind; +extern char *optarg; +#endif +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#include <fcntl.h> +#ifdef HAVE_SYS_SYSMACROS_H +#include <sys/sysmacros.h> +#endif + +#include "debugfs.h" +#include "uuid/uuid.h" +#include "e2p/e2p.h" + +#include <ext2fs/ext2_ext_attr.h> + +#include "../version.h" +#include "jfs_user.h" +#include "support/plausible.h" + +#ifndef BUFSIZ +#define BUFSIZ 8192 +#endif + +#ifdef CONFIG_JBD_DEBUG /* Enabled by configure --enable-jbd-debug */ +int journal_enable_debug = -1; +#endif + +/* + * There must be only one definition if we're hooking in extra commands or + * changing default prompt. Use -DSKIP_GLOBDEF for that. + */ +#ifndef SKIP_GLOBDEFS +ss_request_table *extra_cmds; +const char *debug_prog_name; +#endif +int ss_sci_idx; + +ext2_filsys current_fs; +quota_ctx_t current_qctx; +ext2_ino_t root, cwd; +int no_copy_xattrs; + +static int debugfs_setup_tdb(const char *device_name, char *undo_file, + io_manager *io_ptr) +{ + errcode_t retval = ENOMEM; + const char *tdb_dir = NULL; + 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, device_name); + return retval; + } + + /* + * Configuration via a conf file would be + * nice + */ + tdb_dir = ss_safe_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(device_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/debugfs-%s.e2undo", tdb_dir, dev_name); + free(tmp_name); + + if ((unlink(tdb_file) < 0) && (errno != ENOENT)) { + retval = errno; + com_err("debugfs", 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, device_name); + + free(tdb_file); + return 0; +errout: + free(tdb_file); +err: + com_err("debugfs", retval, "while trying to setup undo file\n"); + return retval; +} + +static void open_filesystem(char *device, int open_flags, blk64_t superblock, + blk64_t blocksize, int catastrophic, + char *data_filename, char *undo_file) +{ + int retval; + io_channel data_io = 0; + io_manager io_ptr = unix_io_manager; + + if (superblock != 0 && blocksize == 0) { + com_err(device, 0, "if you specify the superblock, you must also specify the block size"); + current_fs = NULL; + return; + } + + if (data_filename) { + if ((open_flags & EXT2_FLAG_IMAGE_FILE) == 0) { + com_err(device, 0, + "The -d option is only valid when reading an e2image file"); + current_fs = NULL; + return; + } + retval = unix_io_manager->open(data_filename, 0, &data_io); + if (retval) { + com_err(data_filename, 0, "while opening data source"); + current_fs = NULL; + return; + } + } + + if (catastrophic) + open_flags |= EXT2_FLAG_SKIP_MMP | EXT2_FLAG_IGNORE_SB_ERRORS; + + if (undo_file) { + retval = debugfs_setup_tdb(device, undo_file, &io_ptr); + if (retval) + exit(1); + } + +try_open_again: + retval = ext2fs_open(device, open_flags, superblock, blocksize, + io_ptr, ¤t_fs); + if (retval && (retval == EXT2_ET_SB_CSUM_INVALID) && + !(open_flags & EXT2_FLAG_IGNORE_CSUM_ERRORS)) { + open_flags |= EXT2_FLAG_IGNORE_CSUM_ERRORS; + printf("Checksum errors in superblock! Retrying...\n"); + goto try_open_again; + } + if (retval) { + com_err(debug_prog_name, retval, + "while trying to open %s", device); + if (retval == EXT2_ET_BAD_MAGIC) + check_plausibility(device, CHECK_FS_EXIST, NULL); + current_fs = NULL; + return; + } + current_fs->default_bitmap_type = EXT2FS_BMAP64_RBTREE; + + if (!catastrophic) { + retval = ext2fs_read_bitmaps(current_fs); + if (retval) { + com_err(device, retval, + "while reading allocation bitmaps"); + goto errout; + } + } + + if (data_io) { + retval = ext2fs_set_data_io(current_fs, data_io); + if (retval) { + com_err(device, retval, + "while setting data source"); + goto errout; + } + } + + root = cwd = EXT2_ROOT_INO; + return; + +errout: + retval = ext2fs_close_free(¤t_fs); + if (retval) + com_err(device, retval, "while trying to close filesystem"); +} + +void do_open_filesys(int argc, char **argv, int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + int c, err; + int catastrophic = 0; + blk64_t superblock = 0; + blk64_t blocksize = 0; + int open_flags = EXT2_FLAG_SOFTSUPP_FEATURES | EXT2_FLAG_64BITS | + EXT2_FLAG_THREADS; + char *data_filename = 0; + char *undo_file = NULL; + + reset_getopt(); + while ((c = getopt(argc, argv, "iwfecb:s:d:Dz:")) != EOF) { + switch (c) { + case 'i': + open_flags |= EXT2_FLAG_IMAGE_FILE; + break; + case 'w': +#ifdef READ_ONLY + goto print_usage; +#else + open_flags |= EXT2_FLAG_RW; +#endif /* READ_ONLY */ + break; + case 'f': + open_flags |= EXT2_FLAG_FORCE; + break; + case 'e': + open_flags |= EXT2_FLAG_EXCLUSIVE; + break; + case 'c': + catastrophic = 1; + break; + case 'd': + data_filename = optarg; + break; + case 'D': + open_flags |= EXT2_FLAG_DIRECT_IO; + break; + case 'b': + blocksize = parse_ulong(optarg, argv[0], + "block size", &err); + if (err) + return; + break; + case 's': + err = strtoblk(argv[0], optarg, + "superblock block number", &superblock); + if (err) + return; + break; + case 'z': +#ifdef READ_ONLY + goto print_usage; +#else + undo_file = optarg; +#endif + break; + default: + goto print_usage; + } + } + if (optind != argc-1) { + goto print_usage; + } + if (check_fs_not_open(argv[0])) + return; + open_filesystem(argv[optind], open_flags, + superblock, blocksize, catastrophic, + data_filename, undo_file); + return; + +print_usage: + fprintf(stderr, "%s: Usage: open [-s superblock] [-b blocksize] " +#ifdef READ_ONLY + "[-d image_filename] [-z undo_file] [-c] [-i] [-f] [-e] [-D] " +#else + "[-d image_filename] [-c] [-i] [-f] [-e] [-D] [-w] " +#endif + "<device>\n", argv[0]); +} + +void do_lcd(int argc, char **argv, int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + if (argc != 2) { + com_err(argv[0], 0, "Usage: %s %s", argv[0], "<native dir>"); + return; + } + + if (chdir(argv[1]) == -1) { + com_err(argv[0], errno, + "while trying to change native directory to %s", + argv[1]); + return; + } +} + +static void close_filesystem(NOARGS) +{ + int retval; + + if (current_fs->flags & EXT2_FLAG_IB_DIRTY) { + retval = ext2fs_write_inode_bitmap(current_fs); + if (retval) + com_err("ext2fs_write_inode_bitmap", retval, 0); + } + if (current_fs->flags & EXT2_FLAG_BB_DIRTY) { + retval = ext2fs_write_block_bitmap(current_fs); + if (retval) + com_err("ext2fs_write_block_bitmap", retval, 0); + } + if (current_qctx) + quota_release_context(¤t_qctx); + retval = ext2fs_close_free(¤t_fs); + if (retval) + com_err("ext2fs_close", retval, 0); + return; +} + +void do_close_filesys(int argc, char **argv, int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + int c; + + if (check_fs_open(argv[0])) + return; + + reset_getopt(); + while ((c = getopt (argc, argv, "a")) != EOF) { + switch (c) { + case 'a': + current_fs->flags &= ~EXT2_FLAG_MASTER_SB_ONLY; + break; + default: + goto print_usage; + } + } + + if (argc > optind) { + print_usage: + com_err(0, 0, "Usage: close_filesys [-a]"); + return; + } + + close_filesystem(); +} + +#ifndef READ_ONLY +void do_init_filesys(int argc, char **argv, int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + struct ext2_super_block param; + errcode_t retval; + int err; + blk64_t blocks; + + if (common_args_process(argc, argv, 3, 3, "initialize", + "<device> <blocks>", CHECK_FS_NOTOPEN)) + return; + + memset(¶m, 0, sizeof(struct ext2_super_block)); + err = strtoblk(argv[0], argv[2], "blocks count", &blocks); + if (err) + return; + ext2fs_blocks_count_set(¶m, blocks); + retval = ext2fs_initialize(argv[1], 0, ¶m, + unix_io_manager, ¤t_fs); + if (retval) { + com_err(argv[1], retval, "while initializing filesystem"); + current_fs = NULL; + return; + } + root = cwd = EXT2_ROOT_INO; + return; +} + +static void print_features(struct ext2_super_block * s, FILE *f) +{ + int i, j, printed=0; + __u32 *mask = &s->s_feature_compat, m; + + fputs("Filesystem features:", f); + for (i=0; i <3; i++,mask++) { + for (j=0,m=1; j < 32; j++, m<<=1) { + if (*mask & m) { + fprintf(f, " %s", e2p_feature2string(i, m)); + printed++; + } + } + } + if (printed == 0) + fputs("(none)", f); + fputs("\n", f); +} +#endif /* READ_ONLY */ + +static void print_bg_opts(ext2_filsys fs, dgrp_t group, int mask, + const char *str, int *first, FILE *f) +{ + if (ext2fs_bg_flags_test(fs, group, mask)) { + if (*first) { + fputs(" [", f); + *first = 0; + } else + fputs(", ", f); + fputs(str, f); + } +} + +void do_show_super_stats(int argc, char *argv[], + int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + const char *units ="block"; + dgrp_t i; + FILE *out; + int c, header_only = 0; + int numdirs = 0, first, gdt_csum; + + reset_getopt(); + while ((c = getopt (argc, argv, "h")) != EOF) { + switch (c) { + case 'h': + header_only++; + break; + default: + goto print_usage; + } + } + if (optind != argc) { + goto print_usage; + } + if (check_fs_open(argv[0])) + return; + out = open_pager(); + + if (ext2fs_has_feature_bigalloc(current_fs->super)) + units = "cluster"; + + list_super2(current_fs->super, out); + if (ext2fs_has_feature_metadata_csum(current_fs->super) && + !ext2fs_superblock_csum_verify(current_fs, + current_fs->super)) { + __u32 orig_csum = current_fs->super->s_checksum; + + ext2fs_superblock_csum_set(current_fs, + current_fs->super); + fprintf(out, "Expected Checksum: 0x%08x\n", + current_fs->super->s_checksum); + current_fs->super->s_checksum = orig_csum; + } + for (i=0; i < current_fs->group_desc_count; i++) + numdirs += ext2fs_bg_used_dirs_count(current_fs, i); + fprintf(out, "Directories: %u\n", numdirs); + + if (header_only) { + close_pager(out); + return; + } + + gdt_csum = ext2fs_has_group_desc_csum(current_fs); + for (i = 0; i < current_fs->group_desc_count; i++) { + fprintf(out, " Group %2d: block bitmap at %llu, " + "inode bitmap at %llu, " + "inode table at %llu\n" + " %u free %s%s, " + "%u free %s, " + "%u used %s%s", i, + (unsigned long long) ext2fs_block_bitmap_loc(current_fs, i), + (unsigned long long) ext2fs_inode_bitmap_loc(current_fs, i), + (unsigned long long) ext2fs_inode_table_loc(current_fs, i), + ext2fs_bg_free_blocks_count(current_fs, i), + units, + ext2fs_bg_free_blocks_count(current_fs, i) != 1 ? + "s" : "", + ext2fs_bg_free_inodes_count(current_fs, i), + ext2fs_bg_free_inodes_count(current_fs, i) != 1 ? + "inodes" : "inode", + ext2fs_bg_used_dirs_count(current_fs, i), + ext2fs_bg_used_dirs_count(current_fs, i) != 1 ? "directories" + : "directory", gdt_csum ? ", " : "\n"); + if (gdt_csum) + fprintf(out, "%u unused %s\n", + ext2fs_bg_itable_unused(current_fs, i), + ext2fs_bg_itable_unused(current_fs, i) != 1 ? + "inodes" : "inode"); + first = 1; + print_bg_opts(current_fs, i, EXT2_BG_INODE_UNINIT, "Inode not init", + &first, out); + print_bg_opts(current_fs, i, EXT2_BG_BLOCK_UNINIT, "Block not init", + &first, out); + if (gdt_csum) { + fprintf(out, "%sChecksum 0x%04x", + first ? " [":", ", ext2fs_bg_checksum(current_fs, i)); + first = 0; + } + if (!first) + fputs("]\n", out); + } + close_pager(out); + return; +print_usage: + fprintf(stderr, "%s: Usage: show_super_stats [-h]\n", argv[0]); +} + +#ifndef READ_ONLY +void do_dirty_filesys(int argc EXT2FS_ATTR((unused)), + char **argv EXT2FS_ATTR((unused)), + int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + if (check_fs_open(argv[0])) + return; + if (check_fs_read_write(argv[0])) + return; + + if (argv[1] && !strcmp(argv[1], "-clean")) + current_fs->super->s_state |= EXT2_VALID_FS; + else + current_fs->super->s_state &= ~EXT2_VALID_FS; + ext2fs_mark_super_dirty(current_fs); +} +#endif /* READ_ONLY */ + +struct list_blocks_struct { + FILE *f; + e2_blkcnt_t total; + blk64_t first_block, last_block; + e2_blkcnt_t first_bcnt, last_bcnt; + e2_blkcnt_t first; +}; + +static void finish_range(struct list_blocks_struct *lb) +{ + if (lb->first_block == 0) + return; + if (lb->first) + lb->first = 0; + else + fprintf(lb->f, ", "); + if (lb->first_block == lb->last_block) + fprintf(lb->f, "(%lld):%llu", + (long long)lb->first_bcnt, + (unsigned long long) lb->first_block); + else + fprintf(lb->f, "(%lld-%lld):%llu-%llu", + (long long)lb->first_bcnt, (long long)lb->last_bcnt, + (unsigned long long) lb->first_block, + (unsigned long long) lb->last_block); + lb->first_block = 0; +} + +static int list_blocks_proc(ext2_filsys fs EXT2FS_ATTR((unused)), + blk64_t *blocknr, e2_blkcnt_t blockcnt, + blk64_t ref_block EXT2FS_ATTR((unused)), + int ref_offset EXT2FS_ATTR((unused)), + void *private) +{ + struct list_blocks_struct *lb = (struct list_blocks_struct *) private; + + lb->total++; + if (blockcnt >= 0) { + /* + * See if we can add on to the existing range (if it exists) + */ + if (lb->first_block && + (lb->last_block+1 == *blocknr) && + (lb->last_bcnt+1 == blockcnt)) { + lb->last_block = *blocknr; + lb->last_bcnt = blockcnt; + return 0; + } + /* + * Start a new range. + */ + finish_range(lb); + lb->first_block = lb->last_block = *blocknr; + lb->first_bcnt = lb->last_bcnt = blockcnt; + return 0; + } + /* + * Not a normal block. Always force a new range. + */ + finish_range(lb); + if (lb->first) + lb->first = 0; + else + fprintf(lb->f, ", "); + if (blockcnt == -1) + fprintf(lb->f, "(IND):%llu", (unsigned long long) *blocknr); + else if (blockcnt == -2) + fprintf(lb->f, "(DIND):%llu", (unsigned long long) *blocknr); + else if (blockcnt == -3) + fprintf(lb->f, "(TIND):%llu", (unsigned long long) *blocknr); + return 0; +} + +static void internal_dump_inode_extra(FILE *out, + const char *prefix EXT2FS_ATTR((unused)), + ext2_ino_t inode_num EXT2FS_ATTR((unused)), + struct ext2_inode_large *inode) +{ + fprintf(out, "Size of extra inode fields: %u\n", inode->i_extra_isize); + if (inode->i_extra_isize > EXT2_INODE_SIZE(current_fs->super) - + EXT2_GOOD_OLD_INODE_SIZE) { + fprintf(stderr, "invalid inode->i_extra_isize (%u)\n", + inode->i_extra_isize); + return; + } +} + +static void dump_blocks(FILE *f, const char *prefix, ext2_ino_t inode) +{ + struct list_blocks_struct lb; + + fprintf(f, "%sBLOCKS:\n%s", prefix, prefix); + lb.total = 0; + lb.first_block = 0; + lb.f = f; + lb.first = 1; + ext2fs_block_iterate3(current_fs, inode, BLOCK_FLAG_READ_ONLY, NULL, + list_blocks_proc, (void *)&lb); + finish_range(&lb); + if (lb.total) + fprintf(f, "\n%sTOTAL: %lld\n", prefix, (long long)lb.total); + fprintf(f,"\n"); +} + +static int int_log10(unsigned long long arg) +{ + int l = 0; + + arg = arg / 10; + while (arg) { + l++; + arg = arg / 10; + } + return l; +} + +#define DUMP_LEAF_EXTENTS 0x01 +#define DUMP_NODE_EXTENTS 0x02 +#define DUMP_EXTENT_TABLE 0x04 + +static void dump_extents(FILE *f, const char *prefix, ext2_ino_t ino, + int flags, int logical_width, int physical_width) +{ + ext2_extent_handle_t handle; + struct ext2fs_extent extent; + struct ext2_extent_info info; + int op = EXT2_EXTENT_ROOT; + unsigned int printed = 0; + errcode_t errcode; + + errcode = ext2fs_extent_open(current_fs, ino, &handle); + if (errcode) + return; + + if (flags & DUMP_EXTENT_TABLE) + fprintf(f, "Level Entries %*s %*s Length Flags\n", + (logical_width*2)+3, "Logical", + (physical_width*2)+3, "Physical"); + else + fprintf(f, "%sEXTENTS:\n%s", prefix, prefix); + + while (1) { + errcode = ext2fs_extent_get(handle, op, &extent); + + if (errcode) + break; + + op = EXT2_EXTENT_NEXT; + + if (extent.e_flags & EXT2_EXTENT_FLAGS_SECOND_VISIT) + continue; + + if (extent.e_flags & EXT2_EXTENT_FLAGS_LEAF) { + if ((flags & DUMP_LEAF_EXTENTS) == 0) + continue; + } else { + if ((flags & DUMP_NODE_EXTENTS) == 0) + continue; + } + + errcode = ext2fs_extent_get_info(handle, &info); + if (errcode) + continue; + + if (!(extent.e_flags & EXT2_EXTENT_FLAGS_LEAF)) { + if (extent.e_flags & EXT2_EXTENT_FLAGS_SECOND_VISIT) + continue; + + if (flags & DUMP_EXTENT_TABLE) { + fprintf(f, "%2d/%2d %3d/%3d %*llu - %*llu " + "%*llu%*s %6u\n", + info.curr_level, info.max_depth, + info.curr_entry, info.num_entries, + logical_width, + (unsigned long long) extent.e_lblk, + logical_width, + (unsigned long long) extent.e_lblk + (extent.e_len - 1), + physical_width, + (unsigned long long) extent.e_pblk, + physical_width+3, "", extent.e_len); + continue; + } + + fprintf(f, "%s(ETB%d):%llu", + printed ? ", " : "", info.curr_level, + (unsigned long long) extent.e_pblk); + printed = 1; + continue; + } + + if (flags & DUMP_EXTENT_TABLE) { + fprintf(f, "%2d/%2d %3d/%3d %*llu - %*llu " + "%*llu - %*llu %6u %s\n", + info.curr_level, info.max_depth, + info.curr_entry, info.num_entries, + logical_width, + (unsigned long long) extent.e_lblk, + logical_width, + (unsigned long long) extent.e_lblk + (extent.e_len - 1), + physical_width, + (unsigned long long) extent.e_pblk, + physical_width, + (unsigned long long) extent.e_pblk + (extent.e_len - 1), + extent.e_len, + extent.e_flags & EXT2_EXTENT_FLAGS_UNINIT ? + "Uninit" : ""); + continue; + } + + if (extent.e_len == 0) + continue; + else if (extent.e_len == 1) + fprintf(f, + "%s(%lld%s):%lld", + printed ? ", " : "", + (unsigned long long) extent.e_lblk, + extent.e_flags & EXT2_EXTENT_FLAGS_UNINIT ? + "[u]" : "", + (unsigned long long) extent.e_pblk); + else + fprintf(f, + "%s(%lld-%lld%s):%lld-%lld", + printed ? ", " : "", + (unsigned long long) extent.e_lblk, + (unsigned long long) extent.e_lblk + (extent.e_len - 1), + extent.e_flags & EXT2_EXTENT_FLAGS_UNINIT ? + "[u]" : "", + (unsigned long long) extent.e_pblk, + (unsigned long long) extent.e_pblk + (extent.e_len - 1)); + printed = 1; + } + if (printed) + fprintf(f, "\n"); + ext2fs_extent_free(handle); +} + +static void dump_inline_data(FILE *out, const char *prefix, ext2_ino_t inode_num) +{ + errcode_t retval; + size_t size; + + retval = ext2fs_inline_data_size(current_fs, inode_num, &size); + if (!retval) + fprintf(out, "%sSize of inline data: %zu\n", prefix, size); +} + +static void dump_inline_symlink(FILE *out, ext2_ino_t inode_num, + struct ext2_inode *inode, const char *prefix) +{ + errcode_t retval; + char *buf = NULL; + size_t size; + + retval = ext2fs_inline_data_size(current_fs, inode_num, &size); + if (retval) + goto out; + + retval = ext2fs_get_memzero(size + 1, &buf); + if (retval) + goto out; + + retval = ext2fs_inline_data_get(current_fs, inode_num, + inode, buf, &size); + if (retval) + goto out; + + fprintf(out, "%sFast link dest: \"%.*s\"\n", prefix, + (int)size, buf); +out: + if (buf) + ext2fs_free_mem(&buf); + if (retval) + com_err(__func__, retval, "while dumping link destination"); +} + +void internal_dump_inode(FILE *out, const char *prefix, + ext2_ino_t inode_num, struct ext2_inode *inode, + int do_dump_blocks) +{ + const char *i_type; + char frag, fsize; + int os = current_fs->super->s_creator_os; + struct ext2_inode_large *large_inode; + int is_large_inode = 0; + + if (EXT2_INODE_SIZE(current_fs->super) > EXT2_GOOD_OLD_INODE_SIZE) + is_large_inode = 1; + large_inode = (struct ext2_inode_large *) inode; + + if (LINUX_S_ISDIR(inode->i_mode)) i_type = "directory"; + else if (LINUX_S_ISREG(inode->i_mode)) i_type = "regular"; + else if (LINUX_S_ISLNK(inode->i_mode)) i_type = "symlink"; + else if (LINUX_S_ISBLK(inode->i_mode)) i_type = "block special"; + else if (LINUX_S_ISCHR(inode->i_mode)) i_type = "character special"; + else if (LINUX_S_ISFIFO(inode->i_mode)) i_type = "FIFO"; + else if (LINUX_S_ISSOCK(inode->i_mode)) i_type = "socket"; + else i_type = "bad type"; + fprintf(out, "%sInode: %u Type: %s ", prefix, inode_num, i_type); + fprintf(out, "%sMode: 0%03o Flags: 0x%x\n", + prefix, inode->i_mode & 07777, inode->i_flags); + if (is_large_inode && large_inode->i_extra_isize >= 24) { + fprintf(out, "%sGeneration: %u Version: 0x%08x:%08x\n", + prefix, inode->i_generation, large_inode->i_version_hi, + inode->osd1.linux1.l_i_version); + } else { + fprintf(out, "%sGeneration: %u Version: 0x%08x\n", prefix, + inode->i_generation, inode->osd1.linux1.l_i_version); + } + fprintf(out, "%sUser: %5d Group: %5d", + prefix, inode_uid(*inode), inode_gid(*inode)); + if (is_large_inode && large_inode->i_extra_isize >= 32) + fprintf(out, " Project: %5d", large_inode->i_projid); + fputs(" Size: ", out); + if (LINUX_S_ISREG(inode->i_mode) || LINUX_S_ISDIR(inode->i_mode)) + fprintf(out, "%llu\n", (unsigned long long) EXT2_I_SIZE(inode)); + else + fprintf(out, "%u\n", inode->i_size); + if (os == EXT2_OS_HURD) + fprintf(out, + "%sFile ACL: %u Translator: %u\n", + prefix, + inode->i_file_acl, + inode->osd1.hurd1.h_i_translator); + else + fprintf(out, "%sFile ACL: %llu\n", + prefix, + inode->i_file_acl | ((long long) + (inode->osd2.linux2.l_i_file_acl_high) << 32)); + if (os != EXT2_OS_HURD) + fprintf(out, "%sLinks: %u Blockcount: %llu\n", + prefix, inode->i_links_count, + (((unsigned long long) + inode->osd2.linux2.l_i_blocks_hi << 32)) + + inode->i_blocks); + else + fprintf(out, "%sLinks: %u Blockcount: %u\n", + prefix, inode->i_links_count, inode->i_blocks); + switch (os) { + case EXT2_OS_HURD: + frag = inode->osd2.hurd2.h_i_frag; + fsize = inode->osd2.hurd2.h_i_fsize; + break; + default: + frag = fsize = 0; + } + fprintf(out, "%sFragment: Address: %u Number: %u Size: %u\n", + prefix, inode->i_faddr, frag, fsize); + if (is_large_inode && large_inode->i_extra_isize >= 24) { + fprintf(out, "%s ctime: 0x%08x:%08x -- %s", prefix, + inode->i_ctime, large_inode->i_ctime_extra, + inode_time_to_string(inode->i_ctime, + large_inode->i_ctime_extra)); + fprintf(out, "%s atime: 0x%08x:%08x -- %s", prefix, + inode->i_atime, large_inode->i_atime_extra, + inode_time_to_string(inode->i_atime, + large_inode->i_atime_extra)); + fprintf(out, "%s mtime: 0x%08x:%08x -- %s", prefix, + inode->i_mtime, large_inode->i_mtime_extra, + inode_time_to_string(inode->i_mtime, + large_inode->i_mtime_extra)); + fprintf(out, "%scrtime: 0x%08x:%08x -- %s", prefix, + large_inode->i_crtime, large_inode->i_crtime_extra, + inode_time_to_string(large_inode->i_crtime, + large_inode->i_crtime_extra)); + if (inode->i_dtime) + fprintf(out, "%s dtime: 0x%08x:(%08x) -- %s", prefix, + large_inode->i_dtime, large_inode->i_ctime_extra, + inode_time_to_string(inode->i_dtime, + large_inode->i_ctime_extra)); + } else { + fprintf(out, "%sctime: 0x%08x -- %s", prefix, inode->i_ctime, + time_to_string((__s32) inode->i_ctime)); + fprintf(out, "%satime: 0x%08x -- %s", prefix, inode->i_atime, + time_to_string((__s32) inode->i_atime)); + fprintf(out, "%smtime: 0x%08x -- %s", prefix, inode->i_mtime, + time_to_string((__s32) inode->i_mtime)); + if (inode->i_dtime) + fprintf(out, "%sdtime: 0x%08x -- %s", prefix, + inode->i_dtime, + time_to_string((__s32) inode->i_dtime)); + } + if (EXT2_INODE_SIZE(current_fs->super) > EXT2_GOOD_OLD_INODE_SIZE) + internal_dump_inode_extra(out, prefix, inode_num, + (struct ext2_inode_large *) inode); + dump_inode_attributes(out, inode_num); + if (ext2fs_has_feature_metadata_csum(current_fs->super)) { + __u32 crc = inode->i_checksum_lo; + if (is_large_inode && + large_inode->i_extra_isize >= + (offsetof(struct ext2_inode_large, + i_checksum_hi) - + EXT2_GOOD_OLD_INODE_SIZE)) + crc |= ((__u32)large_inode->i_checksum_hi) << 16; + fprintf(out, "Inode checksum: 0x%08x\n", crc); + } + + if (LINUX_S_ISLNK(inode->i_mode) && ext2fs_is_fast_symlink(inode)) + fprintf(out, "%sFast link dest: \"%.*s\"\n", prefix, + (int)EXT2_I_SIZE(inode), (char *)inode->i_block); + else if (LINUX_S_ISLNK(inode->i_mode) && + (inode->i_flags & EXT4_INLINE_DATA_FL)) + dump_inline_symlink(out, inode_num, inode, prefix); + else if (LINUX_S_ISBLK(inode->i_mode) || LINUX_S_ISCHR(inode->i_mode)) { + int major, minor; + const char *devnote; + + if (inode->i_block[0]) { + major = (inode->i_block[0] >> 8) & 255; + minor = inode->i_block[0] & 255; + devnote = ""; + } else { + major = (inode->i_block[1] & 0xfff00) >> 8; + minor = ((inode->i_block[1] & 0xff) | + ((inode->i_block[1] >> 12) & 0xfff00)); + devnote = "(New-style) "; + } + fprintf(out, "%sDevice major/minor number: %02d:%02d (hex %02x:%02x)\n", + devnote, major, minor, major, minor); + } else if (do_dump_blocks) { + if (inode->i_flags & EXT4_EXTENTS_FL) + dump_extents(out, prefix, inode_num, + DUMP_LEAF_EXTENTS|DUMP_NODE_EXTENTS, 0, 0); + else if (inode->i_flags & EXT4_INLINE_DATA_FL) + dump_inline_data(out, prefix, inode_num); + else + dump_blocks(out, prefix, inode_num); + } +} + +static void dump_inode(ext2_ino_t inode_num, struct ext2_inode *inode) +{ + FILE *out; + + out = open_pager(); + internal_dump_inode(out, "", inode_num, inode, 1); + close_pager(out); +} + +void do_stat(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + ext2_ino_t inode; + struct ext2_inode * inode_buf; + + if (check_fs_open(argv[0])) + return; + + inode_buf = (struct ext2_inode *) + malloc(EXT2_INODE_SIZE(current_fs->super)); + if (!inode_buf) { + fprintf(stderr, "do_stat: can't allocate buffer\n"); + return; + } + + if (common_inode_args_process(argc, argv, &inode, 0)) { + free(inode_buf); + return; + } + + if (debugfs_read_inode2(inode, inode_buf, argv[0], + EXT2_INODE_SIZE(current_fs->super), 0)) { + free(inode_buf); + return; + } + + dump_inode(inode, inode_buf); + free(inode_buf); + return; +} + +void do_dump_extents(int argc, char **argv, int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + struct ext2_inode inode; + ext2_ino_t ino; + FILE *out; + int c, flags = 0; + int logical_width; + int physical_width; + + reset_getopt(); + while ((c = getopt(argc, argv, "nl")) != EOF) { + switch (c) { + case 'n': + flags |= DUMP_NODE_EXTENTS; + break; + case 'l': + flags |= DUMP_LEAF_EXTENTS; + break; + } + } + + if (argc != optind + 1) { + com_err(0, 0, "Usage: dump_extents [-n] [-l] file"); + return; + } + + if (flags == 0) + flags = DUMP_NODE_EXTENTS | DUMP_LEAF_EXTENTS; + flags |= DUMP_EXTENT_TABLE; + + if (check_fs_open(argv[0])) + return; + + ino = string_to_inode(argv[optind]); + if (ino == 0) + return; + + if (debugfs_read_inode(ino, &inode, argv[0])) + return; + + if ((inode.i_flags & EXT4_EXTENTS_FL) == 0) { + fprintf(stderr, "%s: does not uses extent block maps\n", + argv[optind]); + return; + } + + logical_width = int_log10((EXT2_I_SIZE(&inode)+current_fs->blocksize-1)/ + current_fs->blocksize) + 1; + if (logical_width < 5) + logical_width = 5; + physical_width = int_log10(ext2fs_blocks_count(current_fs->super)) + 1; + if (physical_width < 5) + physical_width = 5; + + out = open_pager(); + dump_extents(out, "", ino, flags, logical_width, physical_width); + close_pager(out); + return; +} + +static int print_blocks_proc(ext2_filsys fs EXT2FS_ATTR((unused)), + 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))) +{ + printf("%llu ", (unsigned long long) *blocknr); + return 0; +} + +void do_blocks(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + ext2_ino_t inode; + + if (check_fs_open(argv[0])) + return; + + if (common_inode_args_process(argc, argv, &inode, 0)) { + return; + } + + ext2fs_block_iterate3(current_fs, inode, BLOCK_FLAG_READ_ONLY, NULL, + print_blocks_proc, NULL); + fputc('\n', stdout); + return; +} + +void do_chroot(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + ext2_ino_t inode; + int retval; + + if (common_inode_args_process(argc, argv, &inode, 0)) + return; + + retval = ext2fs_check_directory(current_fs, inode); + if (retval) { + com_err(argv[1], retval, 0); + return; + } + root = inode; +} + +#ifndef READ_ONLY +void do_clri(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + ext2_ino_t inode; + struct ext2_inode inode_buf; + + if (common_inode_args_process(argc, argv, &inode, CHECK_FS_RW)) + return; + + if (debugfs_read_inode(inode, &inode_buf, argv[0])) + return; + memset(&inode_buf, 0, sizeof(inode_buf)); + if (debugfs_write_inode(inode, &inode_buf, argv[0])) + return; +} + +void do_freei(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + unsigned int len = 1; + int err = 0; + ext2_ino_t inode; + + if (common_args_process(argc, argv, 2, 3, argv[0], "<file> [num]", + CHECK_FS_RW | CHECK_FS_BITMAPS)) + return; + if (check_fs_read_write(argv[0])) + return; + + inode = string_to_inode(argv[1]); + if (!inode) + return; + + if (argc == 3) { + len = parse_ulong(argv[2], argv[0], "length", &err); + if (err) + return; + } + + if (len == 1 && + !ext2fs_test_inode_bitmap2(current_fs->inode_map,inode)) + com_err(argv[0], 0, "Warning: inode already clear"); + while (len-- > 0) + ext2fs_unmark_inode_bitmap2(current_fs->inode_map, inode++); + ext2fs_mark_ib_dirty(current_fs); +} + +void do_seti(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + unsigned int len = 1; + int err = 0; + ext2_ino_t inode; + + if (common_args_process(argc, argv, 2, 3, argv[0], "<file> [num]", + CHECK_FS_RW | CHECK_FS_BITMAPS)) + return; + if (check_fs_read_write(argv[0])) + return; + + inode = string_to_inode(argv[1]); + if (!inode) + return; + + if (argc == 3) { + len = parse_ulong(argv[2], argv[0], "length", &err); + if (err) + return; + } + + if ((len == 1) && + ext2fs_test_inode_bitmap2(current_fs->inode_map,inode)) + com_err(argv[0], 0, "Warning: inode already set"); + while (len-- > 0) + ext2fs_mark_inode_bitmap2(current_fs->inode_map, inode++); + ext2fs_mark_ib_dirty(current_fs); +} +#endif /* READ_ONLY */ + +void do_testi(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + ext2_ino_t inode; + + if (common_inode_args_process(argc, argv, &inode, CHECK_FS_BITMAPS)) + return; + + if (ext2fs_test_inode_bitmap2(current_fs->inode_map,inode)) + printf("Inode %u is marked in use\n", inode); + else + printf("Inode %u is not in use\n", inode); +} + +#ifndef READ_ONLY +void do_freeb(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + blk64_t block; + blk64_t count = 1; + + if (common_block_args_process(argc, argv, &block, &count)) + return; + if (check_fs_read_write(argv[0])) + return; + while (count-- > 0) { + if (!ext2fs_test_block_bitmap2(current_fs->block_map,block)) + com_err(argv[0], 0, "Warning: block %llu already clear", + (unsigned long long) block); + ext2fs_unmark_block_bitmap2(current_fs->block_map,block); + block++; + } + ext2fs_mark_bb_dirty(current_fs); +} + +void do_setb(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + blk64_t block; + blk64_t count = 1; + + if (common_block_args_process(argc, argv, &block, &count)) + return; + if (check_fs_read_write(argv[0])) + return; + while (count-- > 0) { + if (ext2fs_test_block_bitmap2(current_fs->block_map,block)) + com_err(argv[0], 0, "Warning: block %llu already set", + (unsigned long long) block); + ext2fs_mark_block_bitmap2(current_fs->block_map,block); + block++; + } + ext2fs_mark_bb_dirty(current_fs); +} +#endif /* READ_ONLY */ + +void do_testb(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + blk64_t block; + blk64_t count = 1; + + if (common_block_args_process(argc, argv, &block, &count)) + return; + while (count-- > 0) { + if (ext2fs_test_block_bitmap2(current_fs->block_map,block)) + printf("Block %llu marked in use\n", + (unsigned long long) block); + else + printf("Block %llu not in use\n", + (unsigned long long) block); + block++; + } +} + +#ifndef READ_ONLY +static void modify_u8(char *com, const char *prompt, + const char *format, __u8 *val) +{ + char buf[200]; + unsigned long v; + char *tmp; + + sprintf(buf, format, *val); + printf("%30s [%s] ", prompt, buf); + if (!fgets(buf, sizeof(buf), stdin)) + return; + if (buf[strlen (buf) - 1] == '\n') + buf[strlen (buf) - 1] = '\0'; + if (!buf[0]) + return; + v = strtoul(buf, &tmp, 0); + if (*tmp) + com_err(com, 0, "Bad value - %s", buf); + else + *val = v; +} + +static void modify_u16(char *com, const char *prompt, + const char *format, __u16 *val) +{ + char buf[200]; + unsigned long v; + char *tmp; + + sprintf(buf, format, *val); + printf("%30s [%s] ", prompt, buf); + if (!fgets(buf, sizeof(buf), stdin)) + return; + if (buf[strlen (buf) - 1] == '\n') + buf[strlen (buf) - 1] = '\0'; + if (!buf[0]) + return; + v = strtoul(buf, &tmp, 0); + if (*tmp) + com_err(com, 0, "Bad value - %s", buf); + else + *val = v; +} + +static void modify_u32(char *com, const char *prompt, + const char *format, __u32 *val) +{ + char buf[200]; + unsigned long v; + char *tmp; + + sprintf(buf, format, *val); + printf("%30s [%s] ", prompt, buf); + if (!fgets(buf, sizeof(buf), stdin)) + return; + if (buf[strlen (buf) - 1] == '\n') + buf[strlen (buf) - 1] = '\0'; + if (!buf[0]) + return; + v = strtoul(buf, &tmp, 0); + if (*tmp) + com_err(com, 0, "Bad value - %s", buf); + else + *val = v; +} + + +void do_modify_inode(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + struct ext2_inode inode; + ext2_ino_t inode_num; + int i; + unsigned char *frag, *fsize; + char buf[80]; + int os; + const char *hex_format = "0x%x"; + const char *octal_format = "0%o"; + const char *decimal_format = "%d"; + const char *unsignedlong_format = "%lu"; + + if (common_inode_args_process(argc, argv, &inode_num, CHECK_FS_RW)) + return; + + os = current_fs->super->s_creator_os; + + if (debugfs_read_inode(inode_num, &inode, argv[1])) + return; + + modify_u16(argv[0], "Mode", octal_format, &inode.i_mode); + modify_u16(argv[0], "User ID", decimal_format, &inode.i_uid); + modify_u16(argv[0], "Group ID", decimal_format, &inode.i_gid); + modify_u32(argv[0], "Size", unsignedlong_format, &inode.i_size); + modify_u32(argv[0], "Creation time", decimal_format, &inode.i_ctime); + modify_u32(argv[0], "Modification time", decimal_format, &inode.i_mtime); + modify_u32(argv[0], "Access time", decimal_format, &inode.i_atime); + modify_u32(argv[0], "Deletion time", decimal_format, &inode.i_dtime); + modify_u16(argv[0], "Link count", decimal_format, &inode.i_links_count); + if (os == EXT2_OS_LINUX) + modify_u16(argv[0], "Block count high", unsignedlong_format, + &inode.osd2.linux2.l_i_blocks_hi); + modify_u32(argv[0], "Block count", unsignedlong_format, &inode.i_blocks); + modify_u32(argv[0], "File flags", hex_format, &inode.i_flags); + modify_u32(argv[0], "Generation", hex_format, &inode.i_generation); +#if 0 + modify_u32(argv[0], "Reserved1", decimal_format, &inode.i_reserved1); +#endif + modify_u32(argv[0], "File acl", decimal_format, &inode.i_file_acl); + + modify_u32(argv[0], "High 32bits of size", decimal_format, + &inode.i_size_high); + + if (os == EXT2_OS_HURD) + modify_u32(argv[0], "Translator Block", + decimal_format, &inode.osd1.hurd1.h_i_translator); + + modify_u32(argv[0], "Fragment address", decimal_format, &inode.i_faddr); + switch (os) { + case EXT2_OS_HURD: + frag = &inode.osd2.hurd2.h_i_frag; + fsize = &inode.osd2.hurd2.h_i_fsize; + break; + default: + frag = fsize = 0; + } + if (frag) + modify_u8(argv[0], "Fragment number", decimal_format, frag); + if (fsize) + modify_u8(argv[0], "Fragment size", decimal_format, fsize); + + for (i=0; i < EXT2_NDIR_BLOCKS; i++) { + sprintf(buf, "Direct Block #%u", i); + modify_u32(argv[0], buf, decimal_format, &inode.i_block[i]); + } + modify_u32(argv[0], "Indirect Block", decimal_format, + &inode.i_block[EXT2_IND_BLOCK]); + modify_u32(argv[0], "Double Indirect Block", decimal_format, + &inode.i_block[EXT2_DIND_BLOCK]); + modify_u32(argv[0], "Triple Indirect Block", decimal_format, + &inode.i_block[EXT2_TIND_BLOCK]); + if (debugfs_write_inode(inode_num, &inode, argv[1])) + return; +} +#endif /* READ_ONLY */ + +void do_change_working_dir(int argc, char *argv[], + int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + ext2_ino_t inode; + int retval; + + if (common_inode_args_process(argc, argv, &inode, 0)) + return; + + retval = ext2fs_check_directory(current_fs, inode); + if (retval) { + com_err(argv[1], retval, 0); + return; + } + cwd = inode; + return; +} + +void do_print_working_directory(int argc, char *argv[], + int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + int retval; + char *pathname = NULL; + + if (common_args_process(argc, argv, 1, 1, + "print_working_directory", "", 0)) + return; + + retval = ext2fs_get_pathname(current_fs, cwd, 0, &pathname); + if (retval) { + com_err(argv[0], retval, + "while trying to get pathname of cwd"); + } + printf("[pwd] INODE: %6u PATH: %s\n", + cwd, pathname ? pathname : "NULL"); + if (pathname) { + free(pathname); + pathname = NULL; + } + retval = ext2fs_get_pathname(current_fs, root, 0, &pathname); + if (retval) { + com_err(argv[0], retval, + "while trying to get pathname of root"); + } + printf("[root] INODE: %6u PATH: %s\n", + root, pathname ? pathname : "NULL"); + if (pathname) { + free(pathname); + pathname = NULL; + } + return; +} + +#ifndef READ_ONLY +static void make_link(char *sourcename, char *destname) +{ + ext2_ino_t ino; + struct ext2_inode inode; + int retval; + ext2_ino_t dir; + char *dest, *cp, *base_name; + + /* + * Get the source inode + */ + ino = string_to_inode(sourcename); + if (!ino) + return; + base_name = strrchr(sourcename, '/'); + if (base_name) + base_name++; + else + base_name = sourcename; + /* + * Figure out the destination. First see if it exists and is + * a directory. + */ + if (! (retval=ext2fs_namei(current_fs, root, cwd, destname, &dir))) + dest = base_name; + else { + /* + * OK, it doesn't exist. See if it is + * '<dir>/basename' or 'basename' + */ + cp = strrchr(destname, '/'); + if (cp) { + *cp = 0; + dir = string_to_inode(destname); + if (!dir) + return; + dest = cp+1; + } else { + dir = cwd; + dest = destname; + } + } + + if (debugfs_read_inode(ino, &inode, sourcename)) + return; + + retval = ext2fs_link(current_fs, dir, dest, ino, + ext2_file_type(inode.i_mode)); + if (retval) + com_err("make_link", retval, 0); + return; +} + + +void do_link(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + if (common_args_process(argc, argv, 3, 3, "link", + "<source file> <dest_name>", CHECK_FS_RW)) + return; + + make_link(argv[1], argv[2]); +} + +static int mark_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; + + block = *blocknr; + ext2fs_block_alloc_stats2(fs, block, +1); + return 0; +} + +void do_undel(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + ext2_ino_t ino; + struct ext2_inode inode; + + if (common_args_process(argc, argv, 2, 3, "undelete", + "<inode_num> [dest_name]", + CHECK_FS_RW | CHECK_FS_BITMAPS)) + return; + + ino = string_to_inode(argv[1]); + if (!ino) + return; + + if (debugfs_read_inode(ino, &inode, argv[1])) + return; + + if (ext2fs_test_inode_bitmap2(current_fs->inode_map, ino)) { + com_err(argv[1], 0, "Inode is not marked as deleted"); + return; + } + + /* + * XXX this function doesn't handle changing the links count on the + * parent directory when undeleting a directory. + */ + inode.i_links_count = LINUX_S_ISDIR(inode.i_mode) ? 2 : 1; + inode.i_dtime = 0; + + if (debugfs_write_inode(ino, &inode, argv[0])) + return; + + ext2fs_block_iterate3(current_fs, ino, BLOCK_FLAG_READ_ONLY, NULL, + mark_blocks_proc, NULL); + + ext2fs_inode_alloc_stats2(current_fs, ino, +1, 0); + + if (argc > 2) + make_link(argv[1], argv[2]); +} + +static void unlink_file_by_name(char *filename) +{ + int retval; + ext2_ino_t dir; + char *base_name; + + base_name = strrchr(filename, '/'); + if (base_name) { + *base_name++ = '\0'; + dir = string_to_inode(filename); + if (!dir) + return; + } else { + dir = cwd; + base_name = filename; + } + retval = ext2fs_unlink(current_fs, dir, base_name, 0, 0); + if (retval) + com_err("unlink_file_by_name", retval, 0); + return; +} + +void do_unlink(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + if (common_args_process(argc, argv, 2, 2, "link", + "<pathname>", CHECK_FS_RW)) + return; + + unlink_file_by_name(argv[1]); +} + +void do_copy_inode(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + ext2_ino_t src_ino, dest_ino; + unsigned char buf[4096]; + + if (common_args_process(argc, argv, 3, 3, "copy_inode", + "<source file> <dest_name>", CHECK_FS_RW)) + return; + + src_ino = string_to_inode(argv[1]); + if (!src_ino) + return; + + dest_ino = string_to_inode(argv[2]); + if (!dest_ino) + return; + + if (debugfs_read_inode2(src_ino, (struct ext2_inode *) buf, + argv[0], sizeof(buf), 0)) + return; + + if (debugfs_write_inode2(dest_ino, (struct ext2_inode *) buf, + argv[0], sizeof(buf), 0)) + return; +} + +#endif /* READ_ONLY */ + +void do_find_free_block(int argc, char *argv[], + int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + blk64_t free_blk, goal, first_free = 0; + int count; + errcode_t retval; + char *tmp; + + if ((argc > 3) || (argc==2 && *argv[1] == '?')) { + com_err(argv[0], 0, "Usage: find_free_block [count [goal]]"); + return; + } + if (check_fs_open(argv[0])) + return; + + if (argc > 1) { + count = strtol(argv[1],&tmp,0); + if (*tmp) { + com_err(argv[0], 0, "Bad count - %s", argv[1]); + return; + } + } else + count = 1; + + if (argc > 2) { + goal = strtol(argv[2], &tmp, 0); + if (*tmp) { + com_err(argv[0], 0, "Bad goal - %s", argv[1]); + return; + } + } + else + goal = current_fs->super->s_first_data_block; + + printf("Free blocks found: "); + free_blk = goal - 1; + while (count-- > 0) { + retval = ext2fs_new_block2(current_fs, free_blk + 1, 0, + &free_blk); + if (first_free) { + if (first_free == free_blk) + break; + } else + first_free = free_blk; + if (retval) { + com_err("ext2fs_new_block", retval, 0); + return; + } else + printf("%llu ", (unsigned long long) free_blk); + } + printf("\n"); +} + +void do_find_free_inode(int argc, char *argv[], + int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + ext2_ino_t free_inode, dir; + int mode; + int retval; + char *tmp; + + if (argc > 3 || (argc>1 && *argv[1] == '?')) { + com_err(argv[0], 0, "Usage: find_free_inode [dir [mode]]"); + return; + } + if (check_fs_open(argv[0])) + return; + + if (argc > 1) { + dir = strtol(argv[1], &tmp, 0); + if (*tmp) { + com_err(argv[0], 0, "Bad dir - %s", argv[1]); + return; + } + } + else + dir = root; + if (argc > 2) { + mode = strtol(argv[2], &tmp, 0); + if (*tmp) { + com_err(argv[0], 0, "Bad mode - %s", argv[2]); + return; + } + } else + mode = 010755; + + retval = ext2fs_new_inode(current_fs, dir, mode, 0, &free_inode); + if (retval) + com_err("ext2fs_new_inode", retval, 0); + else + printf("Free inode found: %u\n", free_inode); +} + +#ifndef READ_ONLY +void do_write(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + errcode_t retval; + + if (common_args_process(argc, argv, 3, 3, "write", + "<native file> <new file>", CHECK_FS_RW)) + return; + + retval = do_write_internal(current_fs, cwd, argv[1], argv[2], root); + if (retval) + com_err(argv[0], retval, 0); +} + +void do_mknod(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + unsigned long major, minor; + errcode_t retval; + int nr; + struct stat st; + + if (check_fs_open(argv[0])) + return; + if (argc < 3 || argv[2][1]) { + usage: + com_err(argv[0], 0, "Usage: mknod <name> [p| [c|b] <major> <minor>]"); + return; + } + + minor = major = 0; + switch (argv[2][0]) { + case 'p': + st.st_mode = S_IFIFO; + nr = 3; + break; + case 'c': + st.st_mode = S_IFCHR; + nr = 5; + break; + case 'b': + st.st_mode = S_IFBLK; + nr = 5; + break; + default: + nr = 0; + } + + if (nr == 5) { + major = strtoul(argv[3], argv+3, 0); + minor = strtoul(argv[4], argv+4, 0); + if (major > 65535 || minor > 65535 || argv[3][0] || argv[4][0]) + nr = 0; + } + + if (argc != nr) + goto usage; + + st.st_rdev = makedev(major, minor); + retval = do_mknod_internal(current_fs, cwd, argv[1], + st.st_mode, st.st_rdev); + if (retval) + com_err(argv[0], retval, 0); +} + +void do_mkdir(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + errcode_t retval; + + if (common_args_process(argc, argv, 2, 2, "mkdir", + "<filename>", CHECK_FS_RW)) + return; + + retval = do_mkdir_internal(current_fs, cwd, argv[1], root); + if (retval) + com_err(argv[0], retval, 0); + +} + +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) +{ + blk64_t block = *blocknr; + blk64_t *last_cluster = (blk64_t *)private; + blk64_t cluster = EXT2FS_B2C(fs, block); + + if (cluster == *last_cluster) + return 0; + + *last_cluster = cluster; + + ext2fs_block_alloc_stats2(fs, block, -1); + return 0; +} + +static void kill_file_by_inode(ext2_ino_t inode) +{ + struct ext2_inode inode_buf; + + if (debugfs_read_inode(inode, &inode_buf, 0)) + return; + inode_buf.i_dtime = current_fs->now ? current_fs->now : time(0); + if (debugfs_write_inode(inode, &inode_buf, 0)) + return; + if (ext2fs_inode_has_valid_blocks2(current_fs, &inode_buf)) { + blk64_t last_cluster = 0; + ext2fs_block_iterate3(current_fs, inode, BLOCK_FLAG_READ_ONLY, + NULL, release_blocks_proc, &last_cluster); + } + printf("\n"); + ext2fs_inode_alloc_stats2(current_fs, inode, -1, + LINUX_S_ISDIR(inode_buf.i_mode)); +} + + +void do_kill_file(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + ext2_ino_t inode_num; + + if (common_inode_args_process(argc, argv, &inode_num, CHECK_FS_RW)) + return; + + kill_file_by_inode(inode_num); +} + +void do_rm(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + int retval; + ext2_ino_t inode_num; + struct ext2_inode inode; + + if (common_args_process(argc, argv, 2, 2, "rm", + "<filename>", CHECK_FS_RW)) + return; + + retval = ext2fs_namei(current_fs, root, cwd, argv[1], &inode_num); + if (retval) { + com_err(argv[0], retval, "while trying to resolve filename"); + return; + } + + if (debugfs_read_inode(inode_num, &inode, argv[0])) + return; + + if (LINUX_S_ISDIR(inode.i_mode)) { + com_err(argv[0], 0, "file is a directory"); + return; + } + + --inode.i_links_count; + if (debugfs_write_inode(inode_num, &inode, argv[0])) + return; + + unlink_file_by_name(argv[1]); + if (inode.i_links_count == 0) + kill_file_by_inode(inode_num); +} + +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 ((ext2fs_dirent_name_len(dirent) == 1) && (dirent->name[0] == '.')) + return 0; + if ((ext2fs_dirent_name_len(dirent) == 2) && (dirent->name[0] == '.') && + (dirent->name[1] == '.')) { + rds->parent = dirent->inode; + return 0; + } + rds->empty = 0; + return 0; +} + +void do_rmdir(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + int retval; + ext2_ino_t inode_num; + struct ext2_inode inode; + struct rd_struct rds; + + if (common_args_process(argc, argv, 2, 2, "rmdir", + "<filename>", CHECK_FS_RW)) + return; + + retval = ext2fs_namei(current_fs, root, cwd, argv[1], &inode_num); + if (retval) { + com_err(argv[0], retval, "while trying to resolve filename"); + return; + } + + if (debugfs_read_inode(inode_num, &inode, argv[0])) + return; + + if (!LINUX_S_ISDIR(inode.i_mode)) { + com_err(argv[0], 0, "file is not a directory"); + return; + } + + rds.parent = 0; + rds.empty = 1; + + retval = ext2fs_dir_iterate2(current_fs, inode_num, 0, + 0, rmdir_proc, &rds); + if (retval) { + com_err(argv[0], retval, "while iterating over directory"); + return; + } + if (rds.empty == 0) { + com_err(argv[0], 0, "directory not empty"); + return; + } + + inode.i_links_count = 0; + if (debugfs_write_inode(inode_num, &inode, argv[0])) + return; + + unlink_file_by_name(argv[1]); + kill_file_by_inode(inode_num); + + if (rds.parent) { + if (debugfs_read_inode(rds.parent, &inode, argv[0])) + return; + if (inode.i_links_count > 1) + inode.i_links_count--; + if (debugfs_write_inode(rds.parent, &inode, argv[0])) + return; + } +} +#endif /* READ_ONLY */ + +void do_show_debugfs_params(int argc EXT2FS_ATTR((unused)), + char *argv[] EXT2FS_ATTR((unused)), + int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + if (current_fs) + printf("Open mode: read-%s\n", + current_fs->flags & EXT2_FLAG_RW ? "write" : "only"); + printf("Filesystem in use: %s\n", + current_fs ? current_fs->device_name : "--none--"); +} + +#ifndef READ_ONLY +void do_expand_dir(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + ext2_ino_t inode; + int retval; + + if (common_inode_args_process(argc, argv, &inode, CHECK_FS_RW)) + return; + + retval = ext2fs_expand_dir(current_fs, inode); + if (retval) + com_err("ext2fs_expand_dir", retval, 0); + return; +} + +void do_features(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + int i; + + if (check_fs_open(argv[0])) + return; + + if ((argc != 1) && check_fs_read_write(argv[0])) + return; + for (i=1; i < argc; i++) { + if (e2p_edit_feature(argv[i], + ¤t_fs->super->s_feature_compat, 0)) + com_err(argv[0], 0, "Unknown feature: %s\n", + argv[i]); + else + ext2fs_mark_super_dirty(current_fs); + } + print_features(current_fs->super, stdout); +} +#endif /* READ_ONLY */ + +void do_bmap(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + ext2_ino_t ino; + blk64_t blk, pblk = 0; + int c, err, flags = 0, ret_flags = 0; + errcode_t errcode; + + if (check_fs_open(argv[0])) + return; + + reset_getopt(); + while ((c = getopt (argc, argv, "a")) != EOF) { + switch (c) { + case 'a': + flags |= BMAP_ALLOC; + break; + default: + goto print_usage; + } + } + + if (argc <= optind+1) { + print_usage: + com_err(0, 0, + "Usage: bmap [-a] <file> logical_blk [physical_blk]"); + return; + } + + ino = string_to_inode(argv[optind++]); + if (!ino) + return; + err = strtoblk(argv[0], argv[optind++], "logical block", &blk); + if (err) + return; + + if (argc > optind+1) + goto print_usage; + + if (argc == optind+1) { + err = strtoblk(argv[0], argv[optind++], + "physical block", &pblk); + if (err) + return; + if (flags & BMAP_ALLOC) { + com_err(0, 0, "Can't set and allocate a block"); + return; + } + flags |= BMAP_SET; + } + + errcode = ext2fs_bmap2(current_fs, ino, 0, 0, flags, blk, + &ret_flags, &pblk); + if (errcode) { + com_err(argv[0], errcode, + "while mapping logical block %llu\n", + (unsigned long long) blk); + return; + } + printf("%llu", (unsigned long long) pblk); + if (ret_flags & BMAP_RET_UNINIT) + fputs(" (uninit)", stdout); + fputc('\n', stdout); +} + +void do_imap(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + ext2_ino_t ino; + unsigned long group, block, block_nr, offset; + + if (common_args_process(argc, argv, 2, 2, argv[0], + "<file>", 0)) + return; + ino = string_to_inode(argv[1]); + if (!ino) + return; + + group = (ino - 1) / EXT2_INODES_PER_GROUP(current_fs->super); + offset = ((ino - 1) % EXT2_INODES_PER_GROUP(current_fs->super)) * + EXT2_INODE_SIZE(current_fs->super); + block = offset >> EXT2_BLOCK_SIZE_BITS(current_fs->super); + if (!ext2fs_inode_table_loc(current_fs, (unsigned)group)) { + com_err(argv[0], 0, "Inode table for group %lu is missing\n", + group); + return; + } + block_nr = ext2fs_inode_table_loc(current_fs, (unsigned)group) + + block; + offset &= (EXT2_BLOCK_SIZE(current_fs->super) - 1); + + printf("Inode %u is part of block group %lu\n" + "\tlocated at block %lu, offset 0x%04lx\n", ino, group, + block_nr, offset); + +} + +void do_idump(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + struct ext2_inode_large *inode; + ext2_ino_t ino; + unsigned char *buf; + errcode_t err; + unsigned int isize, size, offset = 0; + int c, mode = 0; + + reset_getopt(); + while ((c = getopt (argc, argv, "bex")) != EOF) { + if (mode || c == '?') { + com_err(argv[0], 0, + "Usage: inode_dump [-b]|[-e] <file>"); + return; + } + mode = c; + } + if (optind != argc-1) + return; + + if (check_fs_open(argv[0])) + return; + + ino = string_to_inode(argv[optind]); + if (!ino) + return; + + isize = EXT2_INODE_SIZE(current_fs->super); + err = ext2fs_get_mem(isize, &buf); + if (err) { + com_err(argv[0], err, "while allocating memory"); + return; + } + + err = ext2fs_read_inode_full(current_fs, ino, + (struct ext2_inode *)buf, isize); + if (err) { + com_err(argv[0], err, "while reading inode %u", ino); + goto err; + } + + inode = (struct ext2_inode_large *) buf; + size = isize; + switch (mode) { + case 'b': + offset = ((char *) (&inode->i_block)) - ((char *) buf); + size = sizeof(inode->i_block); + break; + case 'x': + case 'e': + if (size <= EXT2_GOOD_OLD_INODE_SIZE) { + com_err(argv[0], 0, "No extra space in inode"); + goto err; + } + offset = EXT2_GOOD_OLD_INODE_SIZE + inode->i_extra_isize; + if (offset > size) + goto err; + size -= offset; + break; + } + if (mode == 'x') + raw_inode_xattr_dump(stdout, buf + offset, size); + else + do_byte_hexdump(stdout, buf + offset, size); +err: + ext2fs_free_mem(&buf); +} + +#ifndef READ_ONLY +void do_set_current_time(int argc, char *argv[], + int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + __s64 now; + + if (common_args_process(argc, argv, 2, 2, argv[0], + "<time>", 0)) + return; + + now = string_to_time(argv[1]); + if (now == -1) { + com_err(argv[0], 0, "Couldn't parse argument as a time: %s\n", + argv[1]); + return; + + } else { + printf("Setting current time to %s\n", time_to_string(now)); + current_fs->now = now; + } +} +#endif /* READ_ONLY */ + +static int find_supp_feature(__u32 *supp, int feature_type, char *name) +{ + int compat, bit, ret; + unsigned int feature_mask; + + if (name) { + if (feature_type == E2P_FS_FEATURE) + ret = e2p_string2feature(name, &compat, &feature_mask); + else + ret = e2p_jrnl_string2feature(name, &compat, + &feature_mask); + if (ret) + return ret; + + if (!(supp[compat] & feature_mask)) + return 1; + } else { + for (compat = 0; compat < 3; compat++) { + for (bit = 0, feature_mask = 1; bit < 32; + bit++, feature_mask <<= 1) { + if (supp[compat] & feature_mask) { + if (feature_type == E2P_FS_FEATURE) + fprintf(stdout, " %s", + e2p_feature2string(compat, + feature_mask)); + else + fprintf(stdout, " %s", + e2p_jrnl_feature2string(compat, + feature_mask)); + } + } + } + fprintf(stdout, "\n"); + } + + return 0; +} + +void do_supported_features(int argc, char *argv[], + int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + int ret; + __u32 supp[3] = { EXT2_LIB_FEATURE_COMPAT_SUPP, + EXT2_LIB_FEATURE_INCOMPAT_SUPP, + EXT2_LIB_FEATURE_RO_COMPAT_SUPP }; + __u32 jrnl_supp[3] = { JBD2_KNOWN_COMPAT_FEATURES, + JBD2_KNOWN_INCOMPAT_FEATURES, + JBD2_KNOWN_ROCOMPAT_FEATURES }; + + if (argc > 1) { + ret = find_supp_feature(supp, E2P_FS_FEATURE, argv[1]); + if (ret) { + ret = find_supp_feature(jrnl_supp, E2P_JOURNAL_FEATURE, + argv[1]); + } + if (ret) + com_err(argv[0], 0, "Unknown feature: %s\n", argv[1]); + else + fprintf(stdout, "Supported feature: %s\n", argv[1]); + } else { + fprintf(stdout, "Supported features:"); + ret = find_supp_feature(supp, E2P_FS_FEATURE, NULL); + ret = find_supp_feature(jrnl_supp, E2P_JOURNAL_FEATURE, NULL); + } +} + +#ifndef READ_ONLY +void do_punch(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + ext2_ino_t ino; + blk64_t start, end; + int err; + errcode_t errcode; + + if (common_args_process(argc, argv, 3, 4, argv[0], + "<file> start_blk [end_blk]", + CHECK_FS_RW | CHECK_FS_BITMAPS)) + return; + + ino = string_to_inode(argv[1]); + if (!ino) + return; + err = strtoblk(argv[0], argv[2], "logical block", &start); + if (err) + return; + if (argc == 4) { + err = strtoblk(argv[0], argv[3], "logical block", &end); + if (err) + return; + } else + end = ~0; + + errcode = ext2fs_punch(current_fs, ino, 0, 0, start, end); + + if (errcode) { + com_err(argv[0], errcode, + "while truncating inode %u from %llu to %llu\n", ino, + (unsigned long long) start, (unsigned long long) end); + return; + } +} + +void do_fallocate(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + ext2_ino_t ino; + blk64_t start, end; + int err; + errcode_t errcode; + + if (common_args_process(argc, argv, 3, 4, argv[0], + "<file> start_blk [end_blk]", + CHECK_FS_RW | CHECK_FS_BITMAPS)) + return; + + ino = string_to_inode(argv[1]); + if (!ino) + return; + err = strtoblk(argv[0], argv[2], "logical block", &start); + if (err) + return; + if (argc == 4) { + err = strtoblk(argv[0], argv[3], "logical block", &end); + if (err) + return; + } else + end = ~0; + + errcode = ext2fs_fallocate(current_fs, EXT2_FALLOCATE_INIT_BEYOND_EOF, + ino, NULL, ~0ULL, start, end - start + 1); + + if (errcode) { + com_err(argv[0], errcode, + "while fallocating inode %u from %llu to %llu\n", ino, + (unsigned long long) start, (unsigned long long) end); + return; + } +} + +void do_symlink(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + errcode_t retval; + + if (common_args_process(argc, argv, 3, 3, "symlink", + "<filename> <target>", CHECK_FS_RW)) + return; + + retval = do_symlink_internal(current_fs, cwd, argv[1], argv[2], root); + if (retval) + com_err(argv[0], retval, 0); + +} +#endif /* READ_ONLY */ + +#if CONFIG_MMP +void do_dump_mmp(int argc EXT2FS_ATTR((unused)), char *argv[], + int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + struct mmp_struct *mmp_s; + unsigned long long mmp_block; + time_t t; + errcode_t retval = 0; + + if (check_fs_open(argv[0])) + return; + + if (argc > 1) { + char *end = NULL; + mmp_block = strtoull(argv[1], &end, 0); + if (end == argv[0] || mmp_block == 0) { + fprintf(stderr, "%s: invalid MMP block '%s' given\n", + argv[0], argv[1]); + return; + } + } else { + mmp_block = current_fs->super->s_mmp_block; + } + + if (mmp_block == 0) { + fprintf(stderr, "%s: MMP: not active on this filesystem.\n", + argv[0]); + return; + } + + if (current_fs->mmp_buf == NULL) { + retval = ext2fs_get_mem(current_fs->blocksize, + ¤t_fs->mmp_buf); + if (retval) { + com_err(argv[0], retval, "allocating MMP buffer.\n"); + return; + } + } + + mmp_s = current_fs->mmp_buf; + + retval = ext2fs_mmp_read(current_fs, mmp_block, current_fs->mmp_buf); + if (retval) { + com_err(argv[0], retval, "reading MMP block %llu.\n", + (unsigned long long) mmp_block); + return; + } + + t = mmp_s->mmp_time; + fprintf(stdout, "block_number: %llu\n", + (unsigned long long) current_fs->super->s_mmp_block); + fprintf(stdout, "update_interval: %d\n", + current_fs->super->s_mmp_update_interval); + fprintf(stdout, "check_interval: %d\n", mmp_s->mmp_check_interval); + fprintf(stdout, "sequence: %08x\n", mmp_s->mmp_seq); + fprintf(stdout, "time: %llu -- %s", + (unsigned long long) mmp_s->mmp_time, ctime(&t)); + fprintf(stdout, "node_name: %.*s\n", + EXT2_LEN_STR(mmp_s->mmp_nodename)); + fprintf(stdout, "device_name: %.*s\n", + EXT2_LEN_STR(mmp_s->mmp_bdevname)); + fprintf(stdout, "magic: 0x%x\n", mmp_s->mmp_magic); + fprintf(stdout, "checksum: 0x%08x\n", mmp_s->mmp_checksum); +} +#else +void do_dump_mmp(int argc EXT2FS_ATTR((unused)), + char *argv[] EXT2FS_ATTR((unused)), + int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + fprintf(stdout, "MMP is unsupported, please recompile with " + "--enable-mmp\n"); +} +#endif + +static int source_file(const char *cmd_file, int ss_idx) +{ + FILE *f; + char buf[BUFSIZ]; + char *cp; + int exit_status = 0; + int retval; + + if (strcmp(cmd_file, "-") == 0) + f = stdin; + else { + f = fopen(cmd_file, "r"); + if (!f) { + perror(cmd_file); + exit(1); + } + } + fflush(stdout); + fflush(stderr); + setbuf(stdout, NULL); + setbuf(stderr, NULL); + while (!feof(f)) { + if (fgets(buf, sizeof(buf), f) == NULL) + break; + if (buf[0] == '#') { + printf("%s", buf); + continue; + } + cp = strchr(buf, '\n'); + if (cp) + *cp = 0; + cp = strchr(buf, '\r'); + if (cp) + *cp = 0; + printf("debugfs: %s\n", buf); + retval = ss_execute_line(ss_idx, buf); + if (retval) { + ss_perror(ss_idx, retval, buf); + exit_status++; + } + } + if (f != stdin) + fclose(f); + return exit_status; +} + +int main(int argc, char **argv) +{ + int retval; + const char *usage = + "Usage: %s [-b blocksize] [-s superblock] [-f cmd_file] " + "[-R request] [-d data_source_device] [-i] [-n] [-D] [-V] [" +#ifndef READ_ONLY + "[-w] [-z undo_file] " +#endif + "[-c]] [device]"; + int c; + int open_flags = EXT2_FLAG_SOFTSUPP_FEATURES | + EXT2_FLAG_64BITS | EXT2_FLAG_THREADS; + char *request = 0; + int exit_status = 0; + char *cmd_file = 0; + blk64_t superblock = 0; + blk64_t blocksize = 0; + int catastrophic = 0; + char *data_filename = 0; +#ifdef READ_ONLY + const char *opt_string = "nicR:f:b:s:Vd:D"; +#else + const char *opt_string = "niwcR:f:b:s:Vd:Dz:"; +#endif + char *undo_file = NULL; +#ifdef CONFIG_JBD_DEBUG + char *jbd_debug; +#endif + + if (debug_prog_name == 0) +#ifdef READ_ONLY + debug_prog_name = "rdebugfs"; +#else + debug_prog_name = "debugfs"; +#endif + add_error_table(&et_ext2_error_table); + fprintf (stderr, "%s %s (%s)\n", debug_prog_name, + E2FSPROGS_VERSION, E2FSPROGS_DATE); + +#ifdef CONFIG_JBD_DEBUG + jbd_debug = ss_safe_getenv("DEBUGFS_JBD_DEBUG"); + if (jbd_debug) { + int res = sscanf(jbd_debug, "%d", &journal_enable_debug); + + if (res != 1) { + fprintf(stderr, + "DEBUGFS_JBD_DEBUG \"%s\" not an integer\n\n", + jbd_debug); + exit(1); + } + } +#endif + while ((c = getopt (argc, argv, opt_string)) != EOF) { + switch (c) { + case 'R': + request = optarg; + break; + case 'f': + cmd_file = optarg; + break; + case 'd': + data_filename = optarg; + break; + case 'i': + open_flags |= EXT2_FLAG_IMAGE_FILE; + break; + case 'n': + open_flags |= EXT2_FLAG_IGNORE_CSUM_ERRORS; + break; +#ifndef READ_ONLY + case 'w': + open_flags |= EXT2_FLAG_RW; + break; +#endif + case 'D': + open_flags |= EXT2_FLAG_DIRECT_IO; + break; + case 'b': + blocksize = parse_ulong(optarg, argv[0], + "block size", 0); + break; + case 's': + retval = strtoblk(argv[0], optarg, + "superblock block number", + &superblock); + if (retval) + return 1; + break; + case 'c': + catastrophic = 1; + break; + case 'V': + /* Print version number and exit */ + fprintf(stderr, "\tUsing %s\n", + error_message(EXT2_ET_BASE)); + exit(0); +#ifndef READ_ONLY + case 'z': + undo_file = optarg; + break; +#endif + default: + com_err(argv[0], 0, usage, debug_prog_name); + return 1; + } + } + if (optind < argc) + open_filesystem(argv[optind], open_flags, + superblock, blocksize, catastrophic, + data_filename, undo_file); + + ss_sci_idx = ss_create_invocation(debug_prog_name, "0.0", (char *) NULL, + &debug_cmds, &retval); + if (retval) { + ss_perror(ss_sci_idx, retval, "creating invocation"); + exit(1); + } + ss_get_readline(ss_sci_idx); + + (void) ss_add_request_table(ss_sci_idx, &ss_std_requests, 1, &retval); + if (retval) { + ss_perror(ss_sci_idx, retval, "adding standard requests"); + exit (1); + } + if (extra_cmds) + ss_add_request_table(ss_sci_idx, extra_cmds, 1, &retval); + if (retval) { + ss_perror(ss_sci_idx, retval, "adding extra requests"); + exit (1); + } + if (request) { + retval = 0; + retval = ss_execute_line(ss_sci_idx, request); + if (retval) { + ss_perror(ss_sci_idx, retval, request); + exit_status++; + } + } else if (cmd_file) { + exit_status = source_file(cmd_file, ss_sci_idx); + } else { + ss_listen(ss_sci_idx); + } + + ss_delete_invocation(ss_sci_idx); + + if (current_fs) + close_filesystem(); + + remove_error_table(&et_ext2_error_table); + return exit_status; +} diff --git a/debugfs/debugfs.h b/debugfs/debugfs.h new file mode 100644 index 0000000..39bc024 --- /dev/null +++ b/debugfs/debugfs.h @@ -0,0 +1,210 @@ +/* + * debugfs.h --- header file for the debugfs program + */ + +#include "ss/ss.h" +#include "ext2fs/ext2_fs.h" +#include "ext2fs/ext2fs.h" +#include "../misc/create_inode.h" +#include "support/quotaio.h" + +#ifdef __STDC__ +#define NOARGS void +#else +#define NOARGS +#define const +#endif + +/* + * Flags used by the common argument processing functions + */ +#define CHECK_FS_RW 0x0001 +#define CHECK_FS_BITMAPS 0x0002 +#define CHECK_FS_NOTOPEN 0x0004 + +extern ext2_filsys current_fs; +extern quota_ctx_t current_qctx; +extern ext2_ino_t root, cwd; +extern int ss_sci_idx; +extern ss_request_table debug_cmds, extent_cmds; + +extern void reset_getopt(void); +extern FILE *open_pager(void); +extern void close_pager(FILE *stream); +extern int check_fs_open(char *name); +extern int check_fs_not_open(char *name); +extern int check_fs_read_write(char *name); +extern int check_fs_bitmaps(char *name); +extern ext2_ino_t string_to_inode(char *str); +extern char *inode_time_to_string(__u32 xtime, __u32 xtime_extra); +extern char *time_to_string(__s64); +extern __s64 string_to_time(const char *); +extern unsigned long parse_ulong(const char *str, const char *cmd, + const char *descr, int *err); +extern unsigned long long parse_ulonglong(const char *str, const char *cmd, + const char *descr, int *err); +extern int strtoblk(const char *cmd, const char *str, const char *errmsg, + blk64_t *ret); +extern int common_args_process(int argc, char *argv[], int min_argc, + int max_argc, const char *cmd, + const char *usage, int flags); +extern int common_inode_args_process(int argc, char *argv[], + ext2_ino_t *inode, int flags); +extern int common_block_args_process(int argc, char *argv[], + blk64_t *block, blk64_t *count); +extern int debugfs_read_inode(ext2_ino_t ino, struct ext2_inode * inode, + const char *cmd); +extern int debugfs_read_inode2(ext2_ino_t ino, struct ext2_inode * inode, + const char *cmd, int bufsize, int flags); +extern int debugfs_write_inode(ext2_ino_t ino, struct ext2_inode * inode, + const char *cmd); +extern int debugfs_write_inode2(ext2_ino_t ino, struct ext2_inode * inode, + const char *cmd, int bufsize, int flags); +extern int debugfs_write_new_inode(ext2_ino_t ino, struct ext2_inode * inode, + const char *cmd); +extern int ext2_file_type(unsigned int mode); + +/* ss command functions */ + +/* dump.c */ +extern void do_dump(int argc, char **argv, int sci_idx, void *infop); +extern void do_cat(int argc, char **argv, int sci_idx, void *infop); +extern void do_rdump(int argc, char **argv, int sci_idx, void *infop); + +/* extent_inode.c */ +extern void do_extent_open(int argc, char **argv, int sci_idx, void *infop); +extern void do_extent_close(int argc, char **argv, int sci_idx, void *infop); +extern void do_current_node(int argc, char **argv, int sci_idx, void *infop); +extern void do_root_node(int argc, char **argv, int sci_idx, void *infop); +extern void do_last_leaf(int argc, char **argv, int sci_idx, void *infop); +extern void do_first_sib(int argc, char **argv, int sci_idx, void *infop); +extern void do_last_sib(int argc, char **argv, int sci_idx, void *infop); +extern void do_next_sib(int argc, char **argv, int sci_idx, void *infop); +extern void do_prev_sib(int argc, char **argv, int sci_idx, void *infop); +extern void do_next_leaf(int argc, char **argv, int sci_idx, void *infop); +extern void do_prev_leaf(int argc, char **argv, int sci_idx, void *infop); +extern void do_next(int argc, char **argv, int sci_idx, void *infop); +extern void do_prev(int argc, char **argv, int sci_idx, void *infop); +extern void do_up(int argc, char **argv, int sci_idx, void *infop); +extern void do_down(int argc, char **argv, int sci_idx, void *infop); +extern void do_delete_node(int argc, char **argv, int sci_idx, void *infop); +extern void do_replace_node(int argc, char **argv, int sci_idx, void *infop); +extern void do_split_node(int argc, char **argv, int sci_idx, void *infop); +extern void do_insert_node(int argc, char **argv, int sci_idx, void *infop); +extern void do_set_bmap(int argc, char **argv, int sci_idx, void *infop); +extern void do_print_all(int argc, char **argv, int sci_idx, void *infop); +extern void do_fix_parents(int argc, char **argv, int sci_idx, void *infop); +extern void do_info(int argc, char **argv, int sci_idx, void *infop); +extern void do_goto_block(int argc, char **argv, int sci_idx, void *infop); + +/* htree.c */ +extern void do_htree_dump(int argc, char **argv, int sci_idx, void *infop); +extern void do_dx_hash(int argc, char **argv, int sci_idx, void *infop); +extern void do_dirsearch(int argc, char **argv, int sci_idx, void *infop); + +/* logdump.c */ +extern void do_logdump(int argc, char **argv, int sci_idx, void *infop); + +/* lsdel.c */ +extern void do_lsdel(int argc, char **argv, int sci_idx, void *infop); + +/* icheck.c */ +extern void do_icheck(int argc, char **argv, int sci_idx, void *infop); + +/* ncheck.c */ +extern void do_ncheck(int argc, char **argv, int sci_idx, void *infop); + +/* set_fields.c */ +extern void do_set_super(int argc, char **, int sci_idx, void *infop); +extern void do_set_inode(int argc, char **, int sci_idx, void *infop); +extern void do_set_block_group_descriptor(int argc, char **, int sci_idx, void *infop); + +/* unused.c */ +extern void do_dump_unused(int argc, char **argv, int sci_idx, void *infop); + +/* debugfs.c */ +extern ss_request_table *extra_cmds; +extern const char *debug_prog_name; +extern void internal_dump_inode(FILE *, const char *, ext2_ino_t, + struct ext2_inode *, int); + +extern void do_dirty_filesys(int argc, char **argv, int sci_idx, void *infop); +extern void do_open_filesys(int argc, char **argv, int sci_idx, void *infop); +extern void do_close_filesys(int argc, char **argv, int sci_idx, void *infop); +extern void do_lcd(int argc, char **argv, int sci_idx, void *infop); +extern void do_init_filesys(int argc, char **argv, int sci_idx, void *infop); +extern void do_show_super_stats(int argc, char **argv, int sci_idx, void *infop); +extern void do_kill_file(int argc, char **argv, int sci_idx, void *infop); +extern void do_rm(int argc, char **argv, int sci_idx, void *infop); +extern void do_link(int argc, char **argv, int sci_idx, void *infop); +extern void do_undel(int argc, char **argv, int sci_idx, void *infop); +extern void do_unlink(int argc, char **argv, int sci_idx, void *infop); +extern void do_copy_inode(int argc, char *argv[], int sci_idx, void *infop); +extern void do_find_free_block(int argc, char **argv, int sci_idx, void *infop); +extern void do_find_free_inode(int argc, char **argv, int sci_idx, void *infop); +extern void do_stat(int argc, char **argv, int sci_idx, void *infop); +extern void do_dump_extents(int argc, char **argv, int sci_idx, void *infop); +extern void do_blocks(int argc, char *argv[], int sci_idx, void *infop); + +extern void do_chroot(int argc, char **argv, int sci_idx, void *infop); +extern void do_clri(int argc, char **argv, int sci_idx, void *infop); +extern void do_freei(int argc, char **argv, int sci_idx, void *infop); +extern void do_seti(int argc, char **argv, int sci_idx, void *infop); +extern void do_testi(int argc, char **argv, int sci_idx, void *infop); +extern void do_freeb(int argc, char **argv, int sci_idx, void *infop); +extern void do_setb(int argc, char **argv, int sci_idx, void *infop); +extern void do_testb(int argc, char **argv, int sci_idx, void *infop); +extern void do_modify_inode(int argc, char **argv, int sci_idx, void *infop); +extern void do_list_dir(int argc, char **argv, int sci_idx, void *infop); +extern void do_change_working_dir(int argc, char **argv, int sci_idx, void *infop); +extern void do_print_working_directory(int argc, char **argv, int sci_idx, void *infop); +extern void do_write(int argc, char **argv, int sci_idx, void *infop); +extern void do_mknod(int argc, char **argv, int sci_idx, void *infop); +extern void do_mkdir(int argc, char **argv, int sci_idx, void *infop); +extern void do_rmdir(int argc, char **argv, int sci_idx, void *infop); +extern void do_show_debugfs_params(int argc, char **argv, int sci_idx, void *infop); +extern void do_expand_dir(int argc, char **argv, int sci_idx, void *infop); +extern void do_features(int argc, char **argv, int sci_idx, void *infop); +extern void do_bmap(int argc, char **argv, int sci_idx, void *infop); +extern void do_imap(int argc, char **argv, int sci_idx, void *infop); +extern void do_idump(int argc, char *argv[], int sci_idx, void *infop); +extern void do_set_current_time(int argc, char **argv, int sci_idx, void *infop); +extern void do_supported_features(int argc, char **argv, int sci_idx, void *infop); +extern void do_punch(int argc, char **argv, int sci_idx, void *infop); +extern void do_fallocate(int argc, char **argv, int sci_idx, void *infop); +extern void do_symlink(int argc, char **argv, int sci_idx, void *infop); + +extern void do_dump_mmp(int argc, char **argv, int sci_idx, void *infop); +extern void do_set_mmp_value(int argc, char **argv, int sci_idx, void *infop); + +extern void do_freefrag(int argc, char **argv, int sci_idx, void *infop); +extern void do_filefrag(int argc, char *argv[], int sci_idx, void *infop); + +/* do_journal.c */ + +extern void do_journal_write(int argc, char *argv[], int sci_idx, void *infop); +extern void do_journal_open(int argc, char *argv[], int sci_idx, void *infop); +extern void do_journal_close(int argc, char *argv[], int sci_idx, void *infop); +extern void do_journal_run(int argc, char *argv[], int sci_idx, void *infop); + +/* quota.c */ +extern void do_list_quota(int argc, char *argv[], int sci_idx, void *infop); +extern void do_get_quota(int argc, char *argv[], int sci_idx, void *infop); + +/* util.c */ +extern __s64 string_to_time(const char *arg); +extern errcode_t read_list(char *str, blk64_t **list, size_t *len); +extern void do_byte_hexdump(FILE *fp, unsigned char *buf, size_t bufsize); + +/* xattrs.c */ +void dump_inode_attributes(FILE *out, ext2_ino_t ino); +void do_get_xattr(int argc, char **argv, int sci_idx, void *infop); +void do_set_xattr(int argc, char **argv, int sci_idx, void *infop); +void do_rm_xattr(int argc, char **argv, int sci_idx, void *infop); +void do_list_xattr(int argc, char **argv, int sci_idx, void *infop); +void raw_inode_xattr_dump(FILE *f, unsigned char *buf, unsigned int len); +void block_xattr_dump(FILE *f, unsigned char *buf, unsigned int len); + +/* zap.c */ +extern void do_zap_block(int argc, char **argv, int sci_idx, void *infop); +extern void do_block_dump(int argc, char **argv, int sci_idx, void *infop); diff --git a/debugfs/do_journal.c b/debugfs/do_journal.c new file mode 100644 index 0000000..38439c6 --- /dev/null +++ b/debugfs/do_journal.c @@ -0,0 +1,972 @@ +/* + * do_journal.c --- Scribble onto the journal! + * + * Copyright (C) 2014 Oracle. This file may be redistributed + * under the terms of the GNU Public License. + */ + +#include "config.h" +#include <stdio.h> +#ifdef HAVE_GETOPT_H +#include <getopt.h> +#else +extern int optind; +extern char *optarg; +#endif +#include <ctype.h> +#include <unistd.h> +#ifdef HAVE_SYS_TIME_H +#include <sys/time.h> +#endif + +#include "debugfs.h" +#include "ext2fs/kernel-jbd.h" +#include "journal.h" + +#undef DEBUG + +#ifdef DEBUG +# define dbg_printf(f, a...) do {printf("JFS DEBUG: " f, ## a); \ + fflush(stdout); \ +} while (0) +#else +# define dbg_printf(f, a...) +#endif + +#define JOURNAL_CHECK_TRANS_MAGIC(x) \ + do { \ + if ((x)->magic != J_TRANS_MAGIC) \ + return EXT2_ET_INVALID_ARGUMENT; \ + } while (0) + +#define J_TRANS_MAGIC 0xD15EA5ED +#define J_TRANS_OPEN 1 +#define J_TRANS_COMMITTED 2 +struct journal_transaction_s { + unsigned int magic; + ext2_filsys fs; + journal_t *journal; + blk64_t block; + blk64_t start, end; + tid_t tid; + int flags; +}; + +typedef struct journal_transaction_s journal_transaction_t; + +static journal_t *current_journal = NULL; + +static void journal_dump_trans(journal_transaction_t *trans EXT2FS_ATTR((unused)), + const char *tag EXT2FS_ATTR((unused))) +{ + dbg_printf("TRANS %p(%s): tid=%u start=%llu block=%llu end=%llu " + "flags=0x%x\n", trans, tag, trans->tid, trans->start, + trans->block, trans->end, trans->flags); +} + +static errcode_t journal_commit_trans(journal_transaction_t *trans) +{ + struct buffer_head *bh, *cbh = NULL; + struct commit_header *commit; +#ifdef HAVE_SYS_TIME_H + struct timeval tv; +#endif + errcode_t err; + + JOURNAL_CHECK_TRANS_MAGIC(trans); + + if ((trans->flags & J_TRANS_COMMITTED) || + !(trans->flags & J_TRANS_OPEN)) + return EXT2_ET_INVALID_ARGUMENT; + + bh = getblk(trans->journal->j_dev, 0, trans->journal->j_blocksize); + if (bh == NULL) + return ENOMEM; + + /* write the descriptor block header */ + commit = (struct commit_header *)bh->b_data; + commit->h_magic = ext2fs_cpu_to_be32(JBD2_MAGIC_NUMBER); + commit->h_blocktype = ext2fs_cpu_to_be32(JBD2_COMMIT_BLOCK); + commit->h_sequence = ext2fs_cpu_to_be32(trans->tid); + if (jbd2_has_feature_checksum(trans->journal)) { + __u32 csum_v1 = ~0; + blk64_t cblk; + + cbh = getblk(trans->journal->j_dev, 0, + trans->journal->j_blocksize); + if (cbh == NULL) { + err = ENOMEM; + goto error; + } + + for (cblk = trans->start; cblk < trans->block; cblk++) { + err = jbd2_journal_bmap(trans->journal, cblk, + &cbh->b_blocknr); + if (err) + goto error; + mark_buffer_uptodate(cbh, 0); + ll_rw_block(REQ_OP_READ, 0, 1, &cbh); + err = cbh->b_err; + if (err) + goto error; + csum_v1 = ext2fs_crc32_be(csum_v1, + (unsigned char const *)cbh->b_data, + cbh->b_size); + } + + commit->h_chksum_type = JBD2_CRC32_CHKSUM; + commit->h_chksum_size = JBD2_CRC32_CHKSUM_SIZE; + commit->h_chksum[0] = ext2fs_cpu_to_be32(csum_v1); + } else { + commit->h_chksum_type = 0; + commit->h_chksum_size = 0; + commit->h_chksum[0] = 0; + } +#ifdef HAVE_SYS_TIME_H + gettimeofday(&tv, NULL); + commit->h_commit_sec = ext2fs_cpu_to_be32(tv.tv_sec); + commit->h_commit_nsec = ext2fs_cpu_to_be32(tv.tv_usec * 1000); +#else + commit->h_commit_sec = 0; + commit->h_commit_nsec = 0; +#endif + + /* Write block */ + jbd2_commit_block_csum_set(trans->journal, bh); + err = jbd2_journal_bmap(trans->journal, trans->block, &bh->b_blocknr); + if (err) + goto error; + + dbg_printf("Writing commit block at %llu:%llu\n", trans->block, + bh->b_blocknr); + mark_buffer_dirty(bh); + ll_rw_block(REQ_OP_WRITE, 0, 1, &bh); + err = bh->b_err; + if (err) + goto error; + trans->flags |= J_TRANS_COMMITTED; + trans->flags &= ~J_TRANS_OPEN; + trans->block++; + + ext2fs_set_feature_journal_needs_recovery(trans->fs->super); + ext2fs_mark_super_dirty(trans->fs); +error: + if (cbh) + brelse(cbh); + brelse(bh); + return err; +} + +static errcode_t journal_add_revoke_to_trans(journal_transaction_t *trans, + blk64_t *revoke_list, + size_t revoke_len) +{ + jbd2_journal_revoke_header_t *jrb; + void *buf; + size_t i, offset; + blk64_t curr_blk; + unsigned int sz; + unsigned csum_size = 0; + struct buffer_head *bh; + errcode_t err; + + JOURNAL_CHECK_TRANS_MAGIC(trans); + + if ((trans->flags & J_TRANS_COMMITTED) || + !(trans->flags & J_TRANS_OPEN)) + return EXT2_ET_INVALID_ARGUMENT; + + if (revoke_len == 0) + return 0; + + /* Do we need to leave space at the end for a checksum? */ + if (jbd2_journal_has_csum_v2or3(trans->journal)) + csum_size = sizeof(struct jbd2_journal_block_tail); + + curr_blk = trans->block; + + bh = getblk(trans->journal->j_dev, curr_blk, + trans->journal->j_blocksize); + if (bh == NULL) + return ENOMEM; + jrb = buf = bh->b_data; + jrb->r_header.h_magic = ext2fs_cpu_to_be32(JBD2_MAGIC_NUMBER); + jrb->r_header.h_blocktype = ext2fs_cpu_to_be32(JBD2_REVOKE_BLOCK); + jrb->r_header.h_sequence = ext2fs_cpu_to_be32(trans->tid); + offset = sizeof(*jrb); + + if (jbd2_has_feature_64bit(trans->journal)) + sz = 8; + else + sz = 4; + + for (i = 0; i < revoke_len; i++) { + /* Block full, write to journal */ + if (offset + sz > trans->journal->j_blocksize - csum_size) { + jrb->r_count = ext2fs_cpu_to_be32(offset); + jbd2_revoke_csum_set(trans->journal, bh); + + err = jbd2_journal_bmap(trans->journal, curr_blk, + &bh->b_blocknr); + if (err) + goto error; + dbg_printf("Writing revoke block at %llu:%llu\n", + curr_blk, bh->b_blocknr); + mark_buffer_dirty(bh); + ll_rw_block(REQ_OP_WRITE, 0, 1, &bh); + err = bh->b_err; + if (err) + goto error; + + offset = sizeof(*jrb); + curr_blk++; + } + + if (revoke_list[i] >= + ext2fs_blocks_count(trans->journal->j_fs_dev->k_fs->super)) { + err = EXT2_ET_BAD_BLOCK_NUM; + goto error; + } + + if (jbd2_has_feature_64bit(trans->journal)) + *((__u64 *)(&((char *)buf)[offset])) = + ext2fs_cpu_to_be64(revoke_list[i]); + else + *((__u32 *)(&((char *)buf)[offset])) = + ext2fs_cpu_to_be32(revoke_list[i]); + offset += sz; + } + + if (offset > 0) { + jrb->r_count = ext2fs_cpu_to_be32(offset); + jbd2_revoke_csum_set(trans->journal, bh); + + err = jbd2_journal_bmap(trans->journal, curr_blk, + &bh->b_blocknr); + if (err) + goto error; + dbg_printf("Writing revoke block at %llu:%llu\n", + curr_blk, bh->b_blocknr); + mark_buffer_dirty(bh); + ll_rw_block(REQ_OP_WRITE, 0, 1, &bh); + err = bh->b_err; + if (err) + goto error; + curr_blk++; + } + +error: + trans->block = curr_blk; + brelse(bh); + return err; +} + +static errcode_t journal_add_blocks_to_trans(journal_transaction_t *trans, + blk64_t *block_list, size_t block_len, + FILE *fp) +{ + blk64_t curr_blk, jdb_blk; + size_t i, j; + int csum_size = 0; + journal_header_t *jdb; + journal_block_tag_t *jdbt; + int tag_bytes; + void *buf = NULL, *jdb_buf = NULL; + struct buffer_head *bh = NULL, *data_bh; + errcode_t err; + + JOURNAL_CHECK_TRANS_MAGIC(trans); + + if ((trans->flags & J_TRANS_COMMITTED) || + !(trans->flags & J_TRANS_OPEN)) + return EXT2_ET_INVALID_ARGUMENT; + + if (block_len == 0) + return 0; + + /* Do we need to leave space at the end for a checksum? */ + if (jbd2_journal_has_csum_v2or3(trans->journal)) + csum_size = sizeof(struct jbd2_journal_block_tail); + + curr_blk = jdb_blk = trans->block; + + data_bh = getblk(trans->journal->j_dev, curr_blk, + trans->journal->j_blocksize); + if (data_bh == NULL) + return ENOMEM; + buf = data_bh->b_data; + + /* write the descriptor block header */ + bh = getblk(trans->journal->j_dev, curr_blk, + trans->journal->j_blocksize); + if (bh == NULL) { + err = ENOMEM; + goto error; + } + jdb = jdb_buf = bh->b_data; + jdb->h_magic = ext2fs_cpu_to_be32(JBD2_MAGIC_NUMBER); + jdb->h_blocktype = ext2fs_cpu_to_be32(JBD2_DESCRIPTOR_BLOCK); + jdb->h_sequence = ext2fs_cpu_to_be32(trans->tid); + jdbt = (journal_block_tag_t *)(jdb + 1); + + curr_blk++; + for (i = 0; i < block_len; i++) { + j = fread(data_bh->b_data, trans->journal->j_blocksize, 1, fp); + if (j != 1) { + err = errno; + goto error; + } + + tag_bytes = journal_tag_bytes(trans->journal); + + /* No space left in descriptor block, write it out */ + if ((char *)jdbt + tag_bytes > + (char *)jdb_buf + trans->journal->j_blocksize - csum_size) { + jbd2_descr_block_csum_set(trans->journal, bh); + err = jbd2_journal_bmap(trans->journal, jdb_blk, + &bh->b_blocknr); + if (err) + goto error; + dbg_printf("Writing descriptor block at %llu:%llu\n", + jdb_blk, bh->b_blocknr); + mark_buffer_dirty(bh); + ll_rw_block(REQ_OP_WRITE, 0, 1, &bh); + err = bh->b_err; + if (err) + goto error; + + jdbt = (journal_block_tag_t *)(jdb + 1); + jdb_blk = curr_blk; + curr_blk++; + } + + if (block_list[i] >= + ext2fs_blocks_count(trans->journal->j_fs_dev->k_fs->super)) { + err = EXT2_ET_BAD_BLOCK_NUM; + goto error; + } + + /* Fill out the block tag */ + jdbt->t_blocknr = ext2fs_cpu_to_be32(block_list[i] & 0xFFFFFFFF); + jdbt->t_flags = 0; + if (jdbt != (journal_block_tag_t *)(jdb + 1)) + jdbt->t_flags |= ext2fs_cpu_to_be16(JBD2_FLAG_SAME_UUID); + else { + memcpy(jdbt + tag_bytes, + trans->journal->j_superblock->s_uuid, + sizeof(trans->journal->j_superblock->s_uuid)); + tag_bytes += 16; + } + if (i == block_len - 1) + jdbt->t_flags |= ext2fs_cpu_to_be16(JBD2_FLAG_LAST_TAG); + if (*((__u32 *)buf) == ext2fs_cpu_to_be32(JBD2_MAGIC_NUMBER)) { + *((__u32 *)buf) = 0; + jdbt->t_flags |= ext2fs_cpu_to_be16(JBD2_FLAG_ESCAPE); + } + if (jbd2_has_feature_64bit(trans->journal)) + jdbt->t_blocknr_high = ext2fs_cpu_to_be32(block_list[i] >> 32); + jbd2_block_tag_csum_set(trans->journal, jdbt, data_bh, + trans->tid); + + /* Write the data block */ + err = jbd2_journal_bmap(trans->journal, curr_blk, + &data_bh->b_blocknr); + if (err) + goto error; + dbg_printf("Writing data block %llu at %llu:%llu tag %d\n", + block_list[i], curr_blk, data_bh->b_blocknr, + tag_bytes); + mark_buffer_dirty(data_bh); + ll_rw_block(REQ_OP_WRITE, 0, 1, &data_bh); + err = data_bh->b_err; + if (err) + goto error; + + curr_blk++; + jdbt = (journal_block_tag_t *)(((char *)jdbt) + tag_bytes); + } + + /* Write out the last descriptor block */ + if (jdbt != (journal_block_tag_t *)(jdb + 1)) { + jbd2_descr_block_csum_set(trans->journal, bh); + err = jbd2_journal_bmap(trans->journal, jdb_blk, + &bh->b_blocknr); + if (err) + goto error; + dbg_printf("Writing descriptor block at %llu:%llu\n", + jdb_blk, bh->b_blocknr); + mark_buffer_dirty(bh); + ll_rw_block(REQ_OP_WRITE, 0, 1, &bh); + err = bh->b_err; + if (err) + goto error; + } + +error: + trans->block = curr_blk; + if (bh) + brelse(bh); + brelse(data_bh); + return err; +} + +static blk64_t journal_guess_blocks(journal_t *journal, blk64_t data_blocks, + blk64_t revoke_blocks) +{ + blk64_t ret = 1; + unsigned int bs, sz; + + /* Estimate # of revoke blocks */ + bs = journal->j_blocksize; + if (jbd2_journal_has_csum_v2or3(journal)) + bs -= sizeof(struct jbd2_journal_block_tail); + sz = jbd2_has_feature_64bit(journal) ? sizeof(__u64) : sizeof(__u32); + ret += revoke_blocks * sz / bs; + + /* Estimate # of data blocks */ + bs = journal->j_blocksize - 16; + if (jbd2_journal_has_csum_v2or3(journal)) + bs -= sizeof(struct jbd2_journal_block_tail); + sz = journal_tag_bytes(journal); + ret += data_blocks * sz / bs; + + ret += data_blocks; + + return ret; +} + +static errcode_t journal_open_trans(journal_t *journal, + journal_transaction_t *trans, + blk64_t blocks) +{ + trans->fs = journal->j_fs_dev->k_fs; + trans->journal = journal; + trans->flags = J_TRANS_OPEN; + + if (journal->j_tail == 0) { + /* Clean journal, start at the tail */ + trans->tid = journal->j_tail_sequence; + trans->start = journal->j_first; + } else { + /* Put new transaction at the head of the list */ + trans->tid = journal->j_transaction_sequence; + trans->start = journal->j_head; + } + + trans->block = trans->start; + if (trans->start + blocks > journal->j_last) + return ENOSPC; + trans->end = trans->block + blocks; + journal_dump_trans(trans, "new transaction"); + + trans->magic = J_TRANS_MAGIC; + return 0; +} + +static errcode_t journal_close_trans(journal_transaction_t *trans) +{ + journal_t *journal; + + JOURNAL_CHECK_TRANS_MAGIC(trans); + + if (!(trans->flags & J_TRANS_COMMITTED)) + return 0; + + journal = trans->journal; + if (journal->j_tail == 0) { + /* Update the tail */ + journal->j_tail_sequence = trans->tid; + journal->j_tail = trans->start; + journal->j_superblock->s_start = ext2fs_cpu_to_be32(trans->start); + } + + /* Update the head */ + journal->j_head = trans->end + 1; + journal->j_transaction_sequence = trans->tid + 1; + + trans->magic = 0; + + /* Mark ourselves as needing recovery */ + if (!ext2fs_has_feature_journal_needs_recovery(trans->fs->super)) { + ext2fs_set_feature_journal_needs_recovery(trans->fs->super); + ext2fs_mark_super_dirty(trans->fs); + } + + return 0; +} + +#define JOURNAL_WRITE_NO_COMMIT 1 +static errcode_t journal_write(journal_t *journal, + int flags, blk64_t *block_list, + size_t block_len, blk64_t *revoke_list, + size_t revoke_len, FILE *fp) +{ + blk64_t blocks; + journal_transaction_t trans; + errcode_t err; + + if (revoke_len > 0) { + jbd2_set_feature_revoke(journal); + mark_buffer_dirty(journal->j_sb_buffer); + } + + blocks = journal_guess_blocks(journal, block_len, revoke_len); + err = journal_open_trans(journal, &trans, blocks); + if (err) + goto error; + + err = journal_add_blocks_to_trans(&trans, block_list, block_len, fp); + if (err) + goto error; + + err = journal_add_revoke_to_trans(&trans, revoke_list, revoke_len); + if (err) + goto error; + + if (!(flags & JOURNAL_WRITE_NO_COMMIT)) { + err = journal_commit_trans(&trans); + if (err) + goto error; + } + + err = journal_close_trans(&trans); +error: + return err; +} + +void do_journal_write(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + blk64_t *blist = NULL, *rlist = NULL; + size_t bn = 0, rn = 0; + FILE *fp = NULL; + int opt; + int flags = 0; + errcode_t err; + + if (current_journal == NULL) { + printf("Journal not open.\n"); + return; + } + + reset_getopt(); + while ((opt = getopt(argc, argv, "b:r:c")) != -1) { + switch (opt) { + case 'b': + err = read_list(optarg, &blist, &bn); + if (err) { + com_err(argv[0], err, + "while reading block list"); + goto out; + } + break; + case 'r': + err = read_list(optarg, &rlist, &rn); + if (err) { + com_err(argv[0], err, + "while reading revoke list"); + goto out; + } + break; + case 'c': + flags |= JOURNAL_WRITE_NO_COMMIT; + break; + default: + printf("%s [-b blocks] [-r revoke] [-c] file\n", + argv[0]); + printf("-b: Write these blocks into transaction.\n"); + printf("-c: Do not commit transaction.\n"); + printf("-r: Revoke these blocks from transaction.\n"); + + goto out; + } + } + + if (bn > 0 && optind != argc - 1) { + printf("Need a file to read blocks from.\n"); + return; + } + + if (bn > 0) { + fp = fopen(argv[optind], "r"); + if (fp == NULL) { + com_err(argv[0], errno, + "while opening journal data file"); + goto out; + } + } + + err = journal_write(current_journal, flags, blist, bn, + rlist, rn, fp); + if (err) + com_err("journal_write", err, "while writing journal"); + + if (fp) + fclose(fp); +out: + if (blist) + free(blist); + if (rlist) + free(rlist); +} + +/* Make sure we wrap around the log correctly! */ +#define wrap(journal, var) \ +do { \ + if (var >= (journal)->j_last) \ + var -= ((journal)->j_last - (journal)->j_first); \ +} while (0) + +/* + * Count the number of in-use tags in a journal descriptor block. + */ + +static int count_tags(journal_t *journal, char *buf) +{ + char *tagp; + journal_block_tag_t *tag; + int nr = 0, size = journal->j_blocksize; + int tag_bytes = journal_tag_bytes(journal); + + if (jbd2_journal_has_csum_v2or3(journal)) + size -= sizeof(struct jbd2_journal_block_tail); + + tagp = buf + sizeof(journal_header_t); + + while ((tagp - buf + tag_bytes) <= size) { + tag = (journal_block_tag_t *) tagp; + + nr++; + tagp += tag_bytes; + if (!(tag->t_flags & ext2fs_cpu_to_be16(JBD2_FLAG_SAME_UUID))) + tagp += 16; + + if (tag->t_flags & ext2fs_cpu_to_be16(JBD2_FLAG_LAST_TAG)) + break; + } + + return nr; +} + +static errcode_t journal_find_head(journal_t *journal) +{ + unsigned int next_commit_ID; + blk64_t next_log_block, head_block; + int err; + journal_superblock_t *sb; + journal_header_t *tmp; + struct buffer_head *bh; + unsigned int sequence; + int blocktype; + + /* + * First thing is to establish what we expect to find in the log + * (in terms of transaction IDs), and where (in terms of log + * block offsets): query the superblock. + */ + + sb = journal->j_superblock; + next_commit_ID = ext2fs_be32_to_cpu(sb->s_sequence); + next_log_block = ext2fs_be32_to_cpu(sb->s_start); + head_block = next_log_block; + + if (next_log_block == 0) + return 0; + + bh = getblk(journal->j_dev, 0, journal->j_blocksize); + if (bh == NULL) + return ENOMEM; + + /* + * Now we walk through the log, transaction by transaction, + * making sure that each transaction has a commit block in the + * expected place. Each complete transaction gets replayed back + * into the main filesystem. + */ + while (1) { + dbg_printf("Scanning for sequence ID %u at %lu/%lu\n", + next_commit_ID, (unsigned long)next_log_block, + journal->j_last); + + /* Skip over each chunk of the transaction looking + * either the next descriptor block or the final commit + * record. */ + err = jbd2_journal_bmap(journal, next_log_block, + &bh->b_blocknr); + if (err) + goto err; + mark_buffer_uptodate(bh, 0); + ll_rw_block(REQ_OP_READ, 0, 1, &bh); + err = bh->b_err; + if (err) + goto err; + + next_log_block++; + wrap(journal, next_log_block); + + /* What kind of buffer is it? + * + * If it is a descriptor block, check that it has the + * expected sequence number. Otherwise, we're all done + * here. */ + + tmp = (journal_header_t *)bh->b_data; + + if (tmp->h_magic != ext2fs_cpu_to_be32(JBD2_MAGIC_NUMBER)) { + dbg_printf("JBD2: wrong magic 0x%x\n", tmp->h_magic); + goto err; + } + + blocktype = ext2fs_be32_to_cpu(tmp->h_blocktype); + sequence = ext2fs_be32_to_cpu(tmp->h_sequence); + dbg_printf("Found magic %d, sequence %d\n", + blocktype, sequence); + + if (sequence != next_commit_ID) { + dbg_printf("JBD2: Wrong sequence %d (wanted %d)\n", + sequence, next_commit_ID); + goto err; + } + + /* OK, we have a valid descriptor block which matches + * all of the sequence number checks. What are we going + * to do with it? That depends on the pass... */ + + switch (blocktype) { + case JBD2_DESCRIPTOR_BLOCK: + next_log_block += count_tags(journal, bh->b_data); + wrap(journal, next_log_block); + continue; + + case JBD2_COMMIT_BLOCK: + head_block = next_log_block; + next_commit_ID++; + continue; + + case JBD2_REVOKE_BLOCK: + continue; + + default: + dbg_printf("Unrecognised magic %d, end of scan.\n", + blocktype); + err = -EINVAL; + goto err; + } + } + +err: + if (err == 0) { + dbg_printf("head seq=%d blk=%llu\n", next_commit_ID, + head_block); + journal->j_transaction_sequence = next_commit_ID; + journal->j_head = head_block; + } + brelse(bh); + return err; +} + +static void update_journal_csum(journal_t *journal, int ver) +{ + journal_superblock_t *jsb; + + if (journal->j_format_version < 2) + return; + + if (journal->j_tail != 0 || + ext2fs_has_feature_journal_needs_recovery( + journal->j_fs_dev->k_fs->super)) { + printf("Journal needs recovery, will not add csums.\n"); + return; + } + + /* metadata_csum implies journal csum v3 */ + jsb = journal->j_superblock; + if (ext2fs_has_feature_metadata_csum(journal->j_fs_dev->k_fs->super)) { + printf("Setting csum v%d\n", ver); + switch (ver) { + case 2: + jbd2_clear_feature_csum3(journal); + jbd2_set_feature_csum2(journal); + jbd2_clear_feature_checksum(journal); + break; + case 3: + jbd2_set_feature_csum3(journal); + jbd2_clear_feature_csum2(journal); + jbd2_clear_feature_checksum(journal); + break; + default: + printf("Unknown checksum v%d\n", ver); + break; + } + journal->j_superblock->s_checksum_type = JBD2_CRC32C_CHKSUM; + journal->j_csum_seed = jbd2_chksum(journal, ~0, jsb->s_uuid, + sizeof(jsb->s_uuid)); + } else { + jbd2_clear_feature_csum3(journal); + jbd2_clear_feature_csum2(journal); + jbd2_set_feature_checksum(journal); + } +} + +static void update_uuid(journal_t *journal) +{ + size_t z; + ext2_filsys fs; + + if (journal->j_format_version < 2) + return; + + for (z = 0; z < sizeof(journal->j_superblock->s_uuid); z++) + if (journal->j_superblock->s_uuid[z]) + break; + if (z == 0) + return; + + fs = journal->j_fs_dev->k_fs; + if (!ext2fs_has_feature_64bit(fs->super)) + return; + + if (jbd2_has_feature_64bit(journal) && + ext2fs_has_feature_64bit(fs->super)) + return; + + if (journal->j_tail != 0 || + ext2fs_has_feature_journal_needs_recovery(fs->super)) { + printf("Journal needs recovery, will not set 64bit.\n"); + return; + } + + memcpy(journal->j_superblock->s_uuid, fs->super->s_uuid, + sizeof(fs->super->s_uuid)); +} + +static void update_64bit_flag(journal_t *journal) +{ + if (journal->j_format_version < 2) + return; + + if (!ext2fs_has_feature_64bit(journal->j_fs_dev->k_fs->super)) + return; + + if (jbd2_has_feature_64bit(journal) && + ext2fs_has_feature_64bit(journal->j_fs_dev->k_fs->super)) + return; + + if (journal->j_tail != 0 || + ext2fs_has_feature_journal_needs_recovery( + journal->j_fs_dev->k_fs->super)) { + printf("Journal needs recovery, will not set 64bit.\n"); + return; + } + + jbd2_set_feature_64bit(journal); +} + +void do_journal_open(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + int opt, enable_csum = 0, csum_ver = 3; + journal_t *journal; + errcode_t err; + + if (check_fs_open(argv[0])) + return; + if (check_fs_read_write(argv[0])) + return; + if (check_fs_bitmaps(argv[0])) + return; + if (current_journal) { + printf("Journal is already open.\n"); + return; + } + if (!ext2fs_has_feature_journal(current_fs->super)) { + printf("Journalling is not enabled on this filesystem.\n"); + return; + } + + reset_getopt(); + while ((opt = getopt(argc, argv, "cv:f:")) != -1) { + switch (opt) { + case 'c': + enable_csum = 1; + break; + case 'f': + if (current_fs->journal_name) + free(current_fs->journal_name); + current_fs->journal_name = strdup(optarg); + break; + case 'v': + csum_ver = atoi(optarg); + if (csum_ver != 2 && csum_ver != 3) { + printf("Unknown journal csum v%d\n", csum_ver); + csum_ver = 3; + } + break; + default: + printf("%s: [-c] [-v ver] [-f ext_jnl]\n", argv[0]); + printf("-c: Enable journal checksumming.\n"); + printf("-v: Use this version checksum format.\n"); + printf("-f: Load this external journal.\n"); + } + } + + err = ext2fs_open_journal(current_fs, ¤t_journal); + if (err) { + com_err(argv[0], err, "while opening journal"); + return; + } + journal = current_journal; + + dbg_printf("JOURNAL: seq=%u tailseq=%u start=%lu first=%lu " + "maxlen=%lu\n", journal->j_tail_sequence, + journal->j_transaction_sequence, journal->j_tail, + journal->j_first, journal->j_last); + + update_uuid(journal); + update_64bit_flag(journal); + if (enable_csum) + update_journal_csum(journal, csum_ver); + + err = journal_find_head(journal); + if (err) + com_err(argv[0], err, "while examining journal"); +} + +void do_journal_close(int argc EXT2FS_ATTR((unused)), + char *argv[] EXT2FS_ATTR((unused)), + int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + if (current_journal == NULL) { + printf("Journal not open.\n"); + return; + } + + ext2fs_close_journal(current_fs, ¤t_journal); +} + +void do_journal_run(int argc EXT2FS_ATTR((unused)), char *argv[], + int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + errcode_t err; + + if (check_fs_open(argv[0])) + return; + if (check_fs_read_write(argv[0])) + return; + if (check_fs_bitmaps(argv[0])) + return; + if (current_journal) { + printf("Please close the journal before recovering it.\n"); + return; + } + + err = ext2fs_run_ext3_journal(¤t_fs); + if (err) + com_err("journal_run", err, "while recovering journal"); + else { + ext2fs_clear_feature_journal_needs_recovery(current_fs->super); + ext2fs_mark_super_dirty(current_fs); + } +} diff --git a/debugfs/dump.c b/debugfs/dump.c new file mode 100644 index 0000000..42f5204 --- /dev/null +++ b/debugfs/dump.c @@ -0,0 +1,385 @@ +/* + * dump.c --- dump the contents of an inode out to a file + * + * Copyright (C) 1994 Theodore Ts'o. This file may be redistributed + * under the terms of the GNU Public License. + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE /* for O_LARGEFILE */ +#endif + +#include "config.h" +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#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 "debugfs.h" + +#ifndef O_LARGEFILE +#define O_LARGEFILE 0 +#endif + +/* + * The mode_xlate function translates a linux mode into a native-OS mode_t. + */ +static struct { + __u16 lmask; + mode_t mask; +} mode_table[] = { + { LINUX_S_IRUSR, S_IRUSR }, + { LINUX_S_IWUSR, S_IWUSR }, + { LINUX_S_IXUSR, S_IXUSR }, + { LINUX_S_IRGRP, S_IRGRP }, + { LINUX_S_IWGRP, S_IWGRP }, + { LINUX_S_IXGRP, S_IXGRP }, + { LINUX_S_IROTH, S_IROTH }, + { LINUX_S_IWOTH, S_IWOTH }, + { LINUX_S_IXOTH, S_IXOTH }, + { 0, 0 } +}; + +static mode_t mode_xlate(__u16 lmode) +{ + mode_t mode = 0; + int i; + + for (i=0; mode_table[i].lmask; i++) { + if (lmode & mode_table[i].lmask) + mode |= mode_table[i].mask; + } + return mode; +} + +static void fix_perms(const char *cmd, const struct ext2_inode *inode, + int fd, const char *name) +{ + struct utimbuf ut; + int i; + + if (fd != -1) + i = fchmod(fd, mode_xlate(inode->i_mode)); + else + i = chmod(name, mode_xlate(inode->i_mode)); + if (i == -1) + com_err(cmd, errno, "while setting permissions of %s", name); + +#ifndef HAVE_FCHOWN + i = chown(name, inode_uid(*inode), inode_gid(*inode)); +#else + if (fd != -1) + i = fchown(fd, inode_uid(*inode), inode_gid(*inode)); + else + i = chown(name, inode_uid(*inode), inode_gid(*inode)); +#endif + if (i == -1) + com_err(cmd, errno, "while changing ownership of %s", name); + + ut.actime = inode->i_atime; + ut.modtime = inode->i_mtime; + if (utime(name, &ut) == -1) + com_err(cmd, errno, "while setting times of %s", name); +} + +static void dump_file(const char *cmdname, ext2_ino_t ino, int fd, + int preserve, char *outname) +{ + errcode_t retval; + struct ext2_inode inode; + char *buf = 0; + ext2_file_t e2_file; + int nbytes; + unsigned int got, blocksize = current_fs->blocksize; + + if (debugfs_read_inode(ino, &inode, cmdname)) + return; + + retval = ext2fs_file_open(current_fs, ino, 0, &e2_file); + if (retval) { + com_err(cmdname, retval, "while opening ext2 file"); + return; + } + retval = ext2fs_get_mem(blocksize, &buf); + if (retval) { + com_err(cmdname, retval, "while allocating memory"); + return; + } + while (1) { + retval = ext2fs_file_read(e2_file, buf, blocksize, &got); + if (retval) + com_err(cmdname, retval, "while reading ext2 file"); + if (got == 0) + break; + nbytes = write(fd, buf, got); + if ((unsigned) nbytes != got) + com_err(cmdname, errno, "while writing file"); + } + if (buf) + ext2fs_free_mem(&buf); + retval = ext2fs_file_close(e2_file); + if (retval) { + com_err(cmdname, retval, "while closing ext2 file"); + return; + } + + if (preserve) + fix_perms("dump_file", &inode, fd, outname); + + return; +} + +void do_dump(int argc, char **argv, int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + ext2_ino_t inode; + int fd; + int c; + int preserve = 0; + char *in_fn, *out_fn; + + reset_getopt(); + while ((c = getopt (argc, argv, "p")) != EOF) { + switch (c) { + case 'p': + preserve++; + break; + default: + print_usage: + com_err(argv[0], 0, "Usage: dump_inode [-p] " + "<file> <output_file>"); + return; + } + } + if (optind != argc-2) + goto print_usage; + + if (check_fs_open(argv[0])) + return; + + in_fn = argv[optind]; + out_fn = argv[optind+1]; + + inode = string_to_inode(in_fn); + if (!inode) + return; + + fd = open(out_fn, O_CREAT | O_WRONLY | O_TRUNC | O_LARGEFILE, 0666); + if (fd < 0) { + com_err(argv[0], errno, "while opening %s for dump_inode", + out_fn); + return; + } + + dump_file(argv[0], inode, fd, preserve, out_fn); + if (close(fd) != 0) { + com_err(argv[0], errno, "while closing %s for dump_inode", + out_fn); + return; + } + + return; +} + +static void rdump_symlink(ext2_ino_t ino, struct ext2_inode *inode, + const char *fullname) +{ + ext2_file_t e2_file; + char *buf; + errcode_t retval; + + buf = malloc(inode->i_size + 1); + if (!buf) { + com_err("rdump", errno, "while allocating for symlink"); + goto errout; + } + + if (ext2fs_is_fast_symlink(inode)) + strcpy(buf, (char *) inode->i_block); + else { + unsigned bytes = inode->i_size; + char *p = buf; + retval = ext2fs_file_open(current_fs, ino, 0, &e2_file); + if (retval) { + com_err("rdump", retval, "while opening symlink"); + goto errout; + } + for (;;) { + unsigned int got; + retval = ext2fs_file_read(e2_file, p, bytes, &got); + if (retval) { + com_err("rdump", retval, "while reading symlink"); + goto errout; + } + bytes -= got; + p += got; + if (got == 0 || bytes == 0) + break; + } + buf[inode->i_size] = 0; + retval = ext2fs_file_close(e2_file); + if (retval) + com_err("rdump", retval, "while closing symlink"); + } + + if (symlink(buf, fullname) == -1) { + com_err("rdump", errno, "while creating symlink %s -> %s", buf, fullname); + goto errout; + } + +errout: + free(buf); +} + +static int rdump_dirent(struct ext2_dir_entry *, int, int, char *, void *); + +static void rdump_inode(ext2_ino_t ino, struct ext2_inode *inode, + const char *name, const char *dumproot) +{ + char *fullname; + + /* There are more efficient ways to do this, but this method + * requires only minimal debugging. */ + fullname = malloc(strlen(dumproot) + strlen(name) + 2); + if (!fullname) { + com_err("rdump", errno, "while allocating memory"); + return; + } + sprintf(fullname, "%s/%s", dumproot, name); + + if (LINUX_S_ISLNK(inode->i_mode)) + rdump_symlink(ino, inode, fullname); + else if (LINUX_S_ISREG(inode->i_mode)) { + int fd; + fd = open(fullname, O_WRONLY | O_CREAT | O_TRUNC | O_LARGEFILE, S_IRWXU); + if (fd == -1) { + com_err("rdump", errno, "while opening %s", fullname); + goto errout; + } + dump_file("rdump", ino, fd, 1, fullname); + if (close(fd) != 0) { + com_err("rdump", errno, "while closing %s", fullname); + goto errout; + } + } + else if (LINUX_S_ISDIR(inode->i_mode) && strcmp(name, ".") && strcmp(name, "..")) { + errcode_t retval; + + /* Create the directory with 0700 permissions, because we + * expect to have to create entries it. Then fix its perms + * once we've done the traversal. */ + if (name[0] && mkdir(fullname, S_IRWXU) == -1) { + com_err("rdump", errno, "while making directory %s", fullname); + goto errout; + } + + retval = ext2fs_dir_iterate(current_fs, ino, 0, 0, + rdump_dirent, (void *) fullname); + if (retval) + com_err("rdump", retval, "while dumping %s", fullname); + + fix_perms("rdump", inode, -1, fullname); + } + /* else do nothing (don't dump device files, sockets, fifos, etc.) */ + +errout: + free(fullname); +} + +static int rdump_dirent(struct ext2_dir_entry *dirent, + int offset EXT2FS_ATTR((unused)), + int blocksize EXT2FS_ATTR((unused)), + char *buf EXT2FS_ATTR((unused)), void *private) +{ + char name[EXT2_NAME_LEN + 1]; + int thislen; + const char *dumproot = private; + struct ext2_inode inode; + + thislen = ext2fs_dirent_name_len(dirent); + strncpy(name, dirent->name, thislen); + name[thislen] = 0; + + if (debugfs_read_inode(dirent->inode, &inode, name)) + return 0; + + rdump_inode(dirent->inode, &inode, name, dumproot); + + return 0; +} + +void do_rdump(int argc, char **argv, int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + struct stat st; + char *dest_dir; + int i; + + if (common_args_process(argc, argv, 3, INT_MAX, "rdump", + "<directory>... <native directory>", 0)) + return; + + /* Pull out last argument */ + dest_dir = argv[argc - 1]; + argc--; + + /* Ensure last arg is a directory. */ + if (stat(dest_dir, &st) == -1) { + com_err("rdump", errno, "while statting %s", dest_dir); + return; + } + if (!S_ISDIR(st.st_mode)) { + com_err("rdump", 0, "%s is not a directory", dest_dir); + return; + } + + for (i = 1; i < argc; i++) { + char *arg = argv[i], *basename; + struct ext2_inode inode; + ext2_ino_t ino = string_to_inode(arg); + if (!ino) + continue; + + if (debugfs_read_inode(ino, &inode, arg)) + continue; + + basename = strrchr(arg, '/'); + if (basename) + basename++; + else + basename = arg; + + rdump_inode(ino, &inode, basename, dest_dir); + } +} + +void do_cat(int argc, char **argv, int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + ext2_ino_t inode; + + if (common_inode_args_process(argc, argv, &inode, 0)) + return; + + fflush(stdout); + fflush(stderr); + dump_file(argv[0], inode, 1, 0, argv[2]); + + return; +} + diff --git a/debugfs/extent_cmds.ct b/debugfs/extent_cmds.ct new file mode 100644 index 0000000..e1c4395 --- /dev/null +++ b/debugfs/extent_cmds.ct @@ -0,0 +1,77 @@ +# +# Copyright (C) 1993 Theodore Ts'o. This file may be redistributed +# under the terms of the GNU Public License. +# +command_table extent_cmds; + +request do_current_node, "Current extent node", + current_node, current; + +request do_root_node, "Goto root extent", + root_node, root; + +request do_last_leaf, "Goto last leaf", + last_leaf; + +request do_first_sib, "Goto first sibling", + first_sibling, first_sib; + +request do_last_sib, "Goto last sibling", + last_sibling, last_sib; + +request do_next_sib, "Goto next sibling", + next_sibling, next_sib, ns; + +request do_prev_sib, "Goto previous sibling", + prev_sibling, prev_sib, ps; + +request do_next_leaf, "Goto next leaf", + next_leaf, nl; + +request do_prev_leaf, "Goto previous leaf", + prev_leaf, pl; + +request do_next, "Goto next node", + next, n; + +request do_prev, "Goto previous node", + previous, prev, p; + +request do_up, "Up node", + up_node, up, u; + +request do_down, "Down node", + down_node, down, d; + +request do_delete_node, "Delete node", + delete_node, delete; + +request do_insert_node, "Insert node", + insert_node, insert; + +request do_split_node, "Split node", + split_node, split; + +request do_fix_parents, "Fix parents", + fix_parents, fixp; + +request do_set_bmap, "Set block mapping", + set_bmap; + +request do_replace_node, "Insert node", + replace_node, replace; + +request do_print_all, "Iterate over all nodes and print them", + print_all, all; + +request do_goto_block, "Goto extent containing specified block", + goto_block, goto; + +request do_info, "Print extent info", + info; + +request do_extent_close, "Close extent handle", + extent_close, ec; + +end; + diff --git a/debugfs/extent_inode.c b/debugfs/extent_inode.c new file mode 100644 index 0000000..e4e815e --- /dev/null +++ b/debugfs/extent_inode.c @@ -0,0 +1,568 @@ +/* + * extent_inode.c --- direct extent tree manipulation + * + * Copyright (C) 2012 Theodore Ts'o. This file may be redistributed + * under the terms of the GNU Public License. + */ + +#include "config.h" +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <ctype.h> +#include <string.h> +#include <time.h> +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#include <sys/types.h> +#ifdef HAVE_GETOPT_H +#include <getopt.h> +#else +extern int optind; +extern char *optarg; +#endif + +#include "debugfs.h" + +static ext2_ino_t current_ino; +static ext2_extent_handle_t current_handle; + +static void dbg_print_extent(char *desc, struct ext2fs_extent *extent) +{ + if (desc) + printf("%s: ", desc); + printf("extent: lblk %llu--%llu, len %u, pblk %llu, flags: ", + (unsigned long long) extent->e_lblk, + (unsigned long long) extent->e_lblk + extent->e_len - 1, + extent->e_len, (unsigned long long) extent->e_pblk); + if (extent->e_flags & EXT2_EXTENT_FLAGS_LEAF) + fputs("LEAF ", stdout); + if (extent->e_flags & EXT2_EXTENT_FLAGS_UNINIT) + fputs("UNINIT ", stdout); + if (extent->e_flags & EXT2_EXTENT_FLAGS_SECOND_VISIT) + fputs("2ND_VISIT ", stdout); + if (!extent->e_flags) + fputs("(none)", stdout); + fputc('\n', stdout); + +} + +static int common_extent_args_process(int argc, char *argv[], int min_argc, + int max_argc, const char *cmd, + const char *usage, int flags) +{ + if (common_args_process(argc, argv, min_argc, max_argc, cmd, + usage, flags)) + return 1; + + if (!current_handle) { + com_err(cmd, 0, "Extent handle not open"); + return 1; + } + return 0; +} + +static char *orig_prompt, *extent_prompt; + +void do_extent_open(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + ext2_ino_t inode; + int ret; + errcode_t retval; + char *cp; + + if (check_fs_open(argv[0])) + return; + + if (argc == 1) { + if (current_ino) + printf("Current inode is %u\n", current_ino); + else + printf("No current inode\n"); + return; + } + + if (common_inode_args_process(argc, argv, &inode, 0)) + return; + + current_ino = 0; + + retval = ext2fs_extent_open(current_fs, inode, ¤t_handle); + if (retval) { + com_err(argv[1], retval, "while opening extent handle"); + return; + } + + current_ino = inode; + + orig_prompt = ss_get_prompt(sci_idx); + extent_prompt = malloc(strlen(orig_prompt) + 32); + if (extent_prompt == NULL) { + com_err(argv[1], retval, "out of memory"); + return; + } + + strcpy(extent_prompt, orig_prompt); + cp = strchr(extent_prompt, ':'); + if (cp) + *cp = 0; + sprintf(extent_prompt + strlen(extent_prompt), " (extent ino %u): ", + current_ino); + ss_add_request_table(sci_idx, &extent_cmds, 1, &ret); + ss_set_prompt(sci_idx, extent_prompt); + return; +} + +void do_extent_close(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + int ret; + + if (common_args_process(argc, argv, 1, 1, + "extent_close", "", 0)) + return; + + if (!current_handle) { + com_err(argv[0], 0, "Extent handle not open"); + return; + } + + ext2fs_extent_free(current_handle); + current_handle = NULL; + current_ino = 0; + ss_delete_request_table(sci_idx, &extent_cmds, &ret); + ss_set_prompt(sci_idx, orig_prompt); + free(extent_prompt); + extent_prompt = NULL; +} + +static void generic_goto_node(const char *my_name, int argc, + char **argv, int op) +{ + struct ext2fs_extent extent; + errcode_t retval; + + if (my_name && common_args_process(argc, argv, 1, 1, + my_name, "", 0)) + return; + + if (!current_handle) { + com_err(argv[0], 0, "Extent handle not open"); + return; + } + + retval = ext2fs_extent_get(current_handle, op, &extent); + if (retval) { + com_err(argv[0], retval, 0); + return; + } + dbg_print_extent(0, &extent); +} + +void do_current_node(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + generic_goto_node("current_node", argc, argv, EXT2_EXTENT_CURRENT); +} + +void do_root_node(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + generic_goto_node("root_node", argc, argv, EXT2_EXTENT_ROOT); +} + +void do_last_leaf(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + generic_goto_node("last_leaf", argc, argv, EXT2_EXTENT_LAST_LEAF); +} + +void do_first_sib(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + generic_goto_node("first_sib", argc, argv, EXT2_EXTENT_FIRST_SIB); +} + +void do_last_sib(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + generic_goto_node("next_sib", argc, argv, EXT2_EXTENT_LAST_SIB); +} + +void do_next_sib(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + generic_goto_node("next_sib", argc, argv, EXT2_EXTENT_NEXT_SIB); +} + +void do_prev_sib(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + generic_goto_node("prev_sib", argc, argv, EXT2_EXTENT_PREV_SIB); +} + +void do_next_leaf(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + generic_goto_node("next_leaf", argc, argv, EXT2_EXTENT_NEXT_LEAF); +} + +void do_prev_leaf(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + generic_goto_node("prev_leaf", argc, argv, EXT2_EXTENT_PREV_LEAF); +} + +void do_next(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + generic_goto_node("next", argc, argv, EXT2_EXTENT_NEXT); +} + +void do_prev(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + generic_goto_node("prev", argc, argv, EXT2_EXTENT_PREV); +} + +void do_up(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + generic_goto_node("up", argc, argv, EXT2_EXTENT_UP); +} + +void do_down(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + generic_goto_node("down", argc, argv, EXT2_EXTENT_DOWN); +} + +void do_delete_node(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + struct ext2fs_extent extent; + errcode_t retval; + + if (common_extent_args_process(argc, argv, 1, 1, "delete_node", + "", CHECK_FS_RW | CHECK_FS_BITMAPS)) + return; + + retval = ext2fs_extent_delete(current_handle, 0); + if (retval) { + com_err(argv[0], retval, 0); + return; + } + + retval = ext2fs_extent_get(current_handle, EXT2_EXTENT_CURRENT, + &extent); + if (retval) + return; + dbg_print_extent(0, &extent); +} + +void do_replace_node(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + const char *usage = "[--uninit] <lblk> <len> <pblk>"; + errcode_t retval; + struct ext2fs_extent extent; + int err; + + if (common_extent_args_process(argc, argv, 3, 5, "replace_node", + usage, CHECK_FS_RW | CHECK_FS_BITMAPS)) + return; + + extent.e_flags = 0; + + if (!strcmp(argv[1], "--uninit")) { + argc--; + argv++; + extent.e_flags |= EXT2_EXTENT_FLAGS_UNINIT; + } + + if (argc != 4) { + fprintf(stderr, "Usage: %s %s\n", argv[0], usage); + return; + } + + err = strtoblk(argv[0], argv[1], "logical block", &extent.e_lblk); + if (err) + return; + + extent.e_len = parse_ulong(argv[2], argv[0], "length", &err); + if (err) + return; + + err = strtoblk(argv[0], argv[3], "physical block", &extent.e_pblk); + if (err) + return; + + retval = ext2fs_extent_replace(current_handle, 0, &extent); + if (retval) { + com_err(argv[0], retval, 0); + return; + } + generic_goto_node(NULL, argc, argv, EXT2_EXTENT_CURRENT); +} + +void do_split_node(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + errcode_t retval; + + if (common_extent_args_process(argc, argv, 1, 1, "split_node", + "", CHECK_FS_RW | CHECK_FS_BITMAPS)) + return; + + retval = ext2fs_extent_node_split(current_handle); + if (retval) { + com_err(argv[0], retval, 0); + return; + } + generic_goto_node(NULL, argc, argv, EXT2_EXTENT_CURRENT); +} + +void do_insert_node(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + const char *usage = "[--after] [--uninit] <lblk> <len> <pblk>"; + errcode_t retval; + struct ext2fs_extent extent; + char *cmd; + int err; + int flags = 0; + + if (common_extent_args_process(argc, argv, 3, 6, "insert_node", + usage, CHECK_FS_RW | CHECK_FS_BITMAPS)) + return; + + cmd = argv[0]; + + extent.e_flags = 0; + + while (argc > 2) { + if (!strcmp(argv[1], "--after")) { + argc--; + argv++; + flags |= EXT2_EXTENT_INSERT_AFTER; + continue; + } + if (!strcmp(argv[1], "--uninit")) { + argc--; + argv++; + extent.e_flags |= EXT2_EXTENT_FLAGS_UNINIT; + continue; + } + break; + } + + if (argc != 4) { + fprintf(stderr, "usage: %s %s\n", cmd, usage); + return; + } + + err = strtoblk(cmd, argv[1], "logical block", &extent.e_lblk); + if (err) + return; + + extent.e_len = parse_ulong(argv[2], cmd, "length", &err); + if (err) + return; + + err = strtoblk(cmd, argv[3], "physical block", &extent.e_pblk); + if (err) + return; + + retval = ext2fs_extent_insert(current_handle, flags, &extent); + if (retval) { + com_err(cmd, retval, 0); + return; + } + generic_goto_node(NULL, argc, argv, EXT2_EXTENT_CURRENT); +} + +void do_set_bmap(int argc, char **argv, int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + const char *usage = "[--uninit] <lblk> <pblk>"; + struct ext2fs_extent extent; + errcode_t retval; + blk64_t logical; + blk64_t physical; + char *cmd = argv[0]; + int flags = 0; + int err; + + if (common_extent_args_process(argc, argv, 3, 5, "set_bmap", + usage, CHECK_FS_RW | CHECK_FS_BITMAPS)) + return; + + if (argc > 2 && !strcmp(argv[1], "--uninit")) { + argc--; + argv++; + flags |= EXT2_EXTENT_SET_BMAP_UNINIT; + } + + if (argc != 3) { + fprintf(stderr, "Usage: %s %s\n", cmd, usage); + return; + } + + err = strtoblk(cmd, argv[1], "logical block", &logical); + if (err) + return; + + err = strtoblk(cmd, argv[2], "physical block", &physical); + if (err) + return; + + retval = ext2fs_extent_set_bmap(current_handle, logical, + physical, flags); + if (retval) { + com_err(cmd, retval, 0); + return; + } + + retval = ext2fs_extent_get(current_handle, EXT2_EXTENT_CURRENT, + &extent); + if (retval) + return; + dbg_print_extent(0, &extent); +} + +void do_print_all(int argc, char **argv, int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + const char *usage = "[--leaf-only|--reverse|--reverse-leaf]"; + struct ext2fs_extent extent; + errcode_t retval; + errcode_t end_err = EXT2_ET_EXTENT_NO_NEXT; + int op = EXT2_EXTENT_NEXT; + int first_op = EXT2_EXTENT_ROOT; + + + if (common_extent_args_process(argc, argv, 1, 2, "print_all", + usage, 0)) + return; + + if (argc == 2) { + if (!strcmp(argv[1], "--leaf-only")) + op = EXT2_EXTENT_NEXT_LEAF; + else if (!strcmp(argv[1], "--reverse")) { + op = EXT2_EXTENT_PREV; + first_op = EXT2_EXTENT_LAST_LEAF; + end_err = EXT2_ET_EXTENT_NO_PREV; + } else if (!strcmp(argv[1], "--reverse-leaf")) { + op = EXT2_EXTENT_PREV_LEAF; + first_op = EXT2_EXTENT_LAST_LEAF; + end_err = EXT2_ET_EXTENT_NO_PREV; + } else { + fprintf(stderr, "Usage: %s %s\n", argv[0], usage); + return; + } + } + + retval = ext2fs_extent_get(current_handle, first_op, &extent); + if (retval) { + com_err(argv[0], retval, 0); + return; + } + dbg_print_extent(0, &extent); + + while (1) { + retval = ext2fs_extent_get(current_handle, op, &extent); + if (retval == end_err) + break; + + if (retval) { + com_err(argv[0], retval, 0); + return; + } + dbg_print_extent(0, &extent); + } +} + +void do_fix_parents(int argc, char **argv, int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + errcode_t retval; + + if (common_extent_args_process(argc, argv, 1, 1, "fix_parents", "", + CHECK_FS_RW)) + return; + + retval = ext2fs_extent_fix_parents(current_handle); + if (retval) { + com_err(argv[0], retval, 0); + return; + } +} + +void do_info(int argc, char **argv, int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + struct ext2fs_extent extent; + struct ext2_extent_info info; + errcode_t retval; + + if (common_extent_args_process(argc, argv, 1, 1, "info", "", 0)) + return; + + retval = ext2fs_extent_get_info(current_handle, &info); + if (retval) { + com_err(argv[0], retval, 0); + return; + } + + retval = ext2fs_extent_get(current_handle, + EXT2_EXTENT_CURRENT, &extent); + if (retval) { + com_err(argv[0], retval, 0); + return; + } + + dbg_print_extent(0, &extent); + + printf("Current handle location: %d/%d (max: %d, bytes %d), level %d/%d\n", + info.curr_entry, info.num_entries, info.max_entries, + info.bytes_avail, info.curr_level, info.max_depth); + printf("\tmax lblk: %llu, max pblk: %llu\n", + (unsigned long long) info.max_lblk, + (unsigned long long) info.max_pblk); + printf("\tmax_len: %u, max_uninit_len: %u\n", info.max_len, + info.max_uninit_len); +} + +void do_goto_block(int argc, char **argv, int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + errcode_t retval; + blk64_t blk; + int level = 0, err; + + if (common_extent_args_process(argc, argv, 2, 3, "goto_block", + "block [level]", 0)) + return; + + if (strtoblk(argv[0], argv[1], NULL, &blk)) + return; + + if (argc == 3) { + level = parse_ulong(argv[2], argv[0], "level", &err); + if (err) + return; + } + + retval = ext2fs_extent_goto2(current_handle, level, (blk64_t) blk); + + if (retval) { + com_err(argv[0], retval, + "while trying to go to block %llu, level %d", + (unsigned long long) blk, level); + return; + } + + generic_goto_node(NULL, argc, argv, EXT2_EXTENT_CURRENT); +} diff --git a/debugfs/filefrag.c b/debugfs/filefrag.c new file mode 100644 index 0000000..31c1440 --- /dev/null +++ b/debugfs/filefrag.c @@ -0,0 +1,330 @@ +/* + * filefrag.c --- display the fragmentation information for a file + * + * Copyright (C) 2011 Theodore Ts'o. This file may be redistributed + * under the terms of the GNU Public License. + */ + +#include "config.h" +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#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 "debugfs.h" + +#define VERBOSE_OPT 0x0001 +#define DIR_OPT 0x0002 +#define RECURSIVE_OPT 0x0004 + +struct dir_list { + char *name; + ext2_ino_t ino; + struct dir_list *next; +}; + +struct filefrag_struct { + FILE *f; + const char *name; + const char *dir_name; + int options; + int logical_width; + int physical_width; + int ext; + int cont_ext; + e2_blkcnt_t num; + e2_blkcnt_t logical_start; + blk64_t physical_start; + blk64_t expected; + struct dir_list *dir_list, *dir_last; +}; + +static int int_log10(unsigned long long arg) +{ + int l = 0; + + arg = arg / 10; + while (arg) { + l++; + arg = arg / 10; + } + return l; +} + +static void print_header(struct filefrag_struct *fs) +{ + if (fs->options & VERBOSE_OPT) { + fprintf(fs->f, "%4s %*s %*s %*s %*s\n", "ext", + fs->logical_width, "logical", fs->physical_width, + "physical", fs->physical_width, "expected", + fs->logical_width, "length"); + } +} + +static void report_filefrag(struct filefrag_struct *fs) +{ + if (fs->num == 0) + return; + if (fs->options & VERBOSE_OPT) { + if (fs->expected) + fprintf(fs->f, "%4d %*lu %*llu %*llu %*lu\n", fs->ext, + fs->logical_width, + (unsigned long) fs->logical_start, + fs->physical_width, + (unsigned long long) fs->physical_start, + fs->physical_width, + (unsigned long long) fs->expected, + fs->logical_width, (unsigned long) fs->num); + else + fprintf(fs->f, "%4d %*lu %*llu %*s %*lu\n", fs->ext, + fs->logical_width, + (unsigned long) fs->logical_start, + fs->physical_width, + (unsigned long long) fs->physical_start, + fs->physical_width, "", + fs->logical_width, (unsigned long) fs->num); + } + fs->ext++; +} + +static int filefrag_blocks_proc(ext2_filsys ext4_fs EXT2FS_ATTR((unused)), + blk64_t *blocknr, e2_blkcnt_t blockcnt, + blk64_t ref_block EXT2FS_ATTR((unused)), + int ref_offset EXT2FS_ATTR((unused)), + void *private) +{ + struct filefrag_struct *fs = private; + + if (blockcnt < 0 || *blocknr == 0) + return 0; + + if ((fs->num == 0) || (blockcnt != fs->logical_start + fs->num) || + (*blocknr != fs->physical_start + fs->num)) { + report_filefrag(fs); + if (blockcnt == fs->logical_start + fs->num) + fs->expected = fs->physical_start + fs->num; + else + fs->expected = 0; + fs->logical_start = blockcnt; + fs->physical_start = *blocknr; + fs->num = 1; + fs->cont_ext++; + } else + fs->num++; + return 0; +} + +static void filefrag(ext2_ino_t ino, struct ext2_inode *inode, + struct filefrag_struct *fs) +{ + errcode_t retval; + int blocksize = current_fs->blocksize; + + fs->logical_width = int_log10((EXT2_I_SIZE(inode) + blocksize - 1) / + blocksize) + 1; + if (fs->logical_width < 7) + fs->logical_width = 7; + fs->ext = 0; + fs->cont_ext = 0; + fs->logical_start = 0; + fs->physical_start = 0; + fs->num = 0; + + if (fs->options & VERBOSE_OPT) { + blk64_t num_blocks = ext2fs_inode_i_blocks(current_fs, inode); + + if (!ext2fs_has_feature_huge_file(current_fs->super) || + !(inode->i_flags & EXT4_HUGE_FILE_FL)) + num_blocks /= current_fs->blocksize / 512; + + fprintf(fs->f, "\n%s has %llu block(s), i_size is %llu\n", + fs->name, (unsigned long long) num_blocks, + (unsigned long long) EXT2_I_SIZE(inode)); + } + print_header(fs); + if (ext2fs_inode_has_valid_blocks2(current_fs, inode)) { + retval = ext2fs_block_iterate3(current_fs, ino, + BLOCK_FLAG_READ_ONLY, NULL, + filefrag_blocks_proc, fs); + if (retval) + com_err("ext2fs_block_iterate3", retval, 0); + } + + report_filefrag(fs); + fprintf(fs->f, "%s: %d contiguous extents%s\n", fs->name, fs->ext, + LINUX_S_ISDIR(inode->i_mode) ? " (dir)" : ""); +} + +static int filefrag_dir_proc(ext2_ino_t dir EXT2FS_ATTR((unused)), + int entry, + struct ext2_dir_entry *dirent, + int offset EXT2FS_ATTR((unused)), + int blocksize EXT2FS_ATTR((unused)), + char *buf EXT2FS_ATTR((unused)), + void *private) +{ + struct filefrag_struct *fs = private; + struct ext2_inode inode; + ext2_ino_t ino; + char name[EXT2_NAME_LEN + 1]; + char *cp; + int thislen; + + if (entry == DIRENT_DELETED_FILE) + return 0; + + thislen = ext2fs_dirent_name_len(dirent); + strncpy(name, dirent->name, thislen); + name[thislen] = '\0'; + ino = dirent->inode; + + if (!strcmp(name, ".") || !strcmp(name, "..")) + return 0; + + cp = malloc(strlen(fs->dir_name) + strlen(name) + 2); + if (!cp) { + fprintf(stderr, "Couldn't allocate memory for %s/%s\n", + fs->dir_name, name); + return 0; + } + + sprintf(cp, "%s/%s", fs->dir_name, name); + fs->name = cp; + + if (debugfs_read_inode(ino, &inode, fs->name)) + goto errout; + + filefrag(ino, &inode, fs); + + if ((fs->options & RECURSIVE_OPT) && LINUX_S_ISDIR(inode.i_mode)) { + struct dir_list *p; + + p = malloc(sizeof(struct dir_list)); + if (!p) { + fprintf(stderr, "Couldn't allocate dir_list for %s\n", + fs->name); + goto errout; + } + memset(p, 0, sizeof(struct dir_list)); + p->name = cp; + p->ino = ino; + if (fs->dir_last) + fs->dir_last->next = p; + else + fs->dir_list = p; + fs->dir_last = p; + return 0; + } +errout: + free(cp); + fs->name = 0; + return 0; +} + + +static void dir_iterate(ext2_ino_t ino, struct filefrag_struct *fs) +{ + errcode_t retval; + struct dir_list *p = NULL; + + fs->dir_name = fs->name; + + while (1) { + retval = ext2fs_dir_iterate2(current_fs, ino, 0, + 0, filefrag_dir_proc, fs); + if (retval) + com_err("ext2fs_dir_iterate2", retval, 0); + if (p) { + free(p->name); + fs->dir_list = p->next; + if (!fs->dir_list) + fs->dir_last = 0; + free(p); + } + p = fs->dir_list; + if (!p) + break; + ino = p->ino; + fs->dir_name = p->name; + } +} + +void do_filefrag(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + struct filefrag_struct fs; + struct ext2_inode inode; + ext2_ino_t ino; + int c; + + memset(&fs, 0, sizeof(fs)); + if (check_fs_open(argv[0])) + return; + + reset_getopt(); + while ((c = getopt(argc, argv, "dvr")) != EOF) { + switch (c) { + case 'd': + fs.options |= DIR_OPT; + break; + case 'v': + fs.options |= VERBOSE_OPT; + break; + case 'r': + fs.options |= RECURSIVE_OPT; + break; + default: + goto print_usage; + } + } + + if (argc > optind+1) { + print_usage: + com_err(0, 0, "Usage: filefrag [-dvr] file"); + return; + } + + if (argc == optind) { + ino = cwd; + fs.name = "."; + } else { + ino = string_to_inode(argv[optind]); + fs.name = argv[optind]; + } + if (!ino) + return; + + if (debugfs_read_inode(ino, &inode, argv[0])) + return; + + fs.f = open_pager(); + fs.physical_width = int_log10(ext2fs_blocks_count(current_fs->super)); + fs.physical_width++; + if (fs.physical_width < 8) + fs.physical_width = 8; + + if (!LINUX_S_ISDIR(inode.i_mode) || (fs.options & DIR_OPT)) + filefrag(ino, &inode, &fs); + else + dir_iterate(ino, &fs); + + fprintf(fs.f, "\n"); + close_pager(fs.f); + + return; +} diff --git a/debugfs/htree.c b/debugfs/htree.c new file mode 100644 index 0000000..a9f9211 --- /dev/null +++ b/debugfs/htree.c @@ -0,0 +1,488 @@ +/* + * htree.c --- hash tree routines + * + * Copyright (C) 2002 Theodore Ts'o. This file may be redistributed + * under the terms of the GNU Public License. + */ + +#include "config.h" +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <ctype.h> +#include <string.h> +#include <time.h> +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#include <sys/types.h> +#ifdef HAVE_GETOPT_H +#include <getopt.h> +#else +extern int optind; +extern char *optarg; +#endif + +#include "debugfs.h" +#include "uuid/uuid.h" +#include "e2p/e2p.h" + +static FILE *pager; + +static void htree_dump_leaf_node(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode *inode, + struct ext2_dx_root_info * rootnode, + blk64_t blk, char *buf) +{ + errcode_t errcode; + struct ext2_dir_entry *dirent; + int thislen, col = 0; + unsigned int offset = 0; + char name[EXT2_NAME_LEN + 1]; + char tmp[EXT2_NAME_LEN + 64]; + blk64_t pblk; + ext2_dirhash_t hash, minor_hash; + unsigned int rec_len; + int hash_alg; + int hash_flags = inode->i_flags & EXT4_CASEFOLD_FL; + int csum_size = 0; + + if (ext2fs_has_feature_metadata_csum(fs->super)) + csum_size = sizeof(struct ext2_dir_entry_tail); + + errcode = ext2fs_bmap2(fs, ino, inode, buf, 0, blk, 0, &pblk); + if (errcode) { + com_err("htree_dump_leaf_node", errcode, + "while mapping logical block %llu\n", + (unsigned long long) blk); + return; + } + + fprintf(pager, "Reading directory block %llu, phys %llu\n", + (unsigned long long) blk, (unsigned long long) pblk); + errcode = ext2fs_read_dir_block4(current_fs, pblk, buf, 0, ino); + if (errcode) { + com_err("htree_dump_leaf_node", errcode, + "while reading block %llu (%llu)\n", + (unsigned long long) blk, (unsigned long long) pblk); + return; + } + hash_alg = rootnode->hash_version; + if ((hash_alg <= EXT2_HASH_TEA) && + (fs->super->s_flags & EXT2_FLAGS_UNSIGNED_HASH)) + hash_alg += 3; + + while (offset < fs->blocksize) { + dirent = (struct ext2_dir_entry *) (buf + offset); + errcode = ext2fs_get_rec_len(fs, dirent, &rec_len); + if (errcode) { + com_err("htree_dump_leaf_inode", errcode, + "while getting rec_len for block %lu", + (unsigned long) blk); + return; + } + thislen = ext2fs_dirent_name_len(dirent); + if (((offset + rec_len) > fs->blocksize) || + (rec_len < 8) || + ((rec_len % 4) != 0) || + ((unsigned) thislen + 8 > rec_len)) { + fprintf(pager, "Corrupted directory block (%llu)!\n", + (unsigned long long) blk); + break; + } + strncpy(name, dirent->name, thislen); + name[thislen] = '\0'; + errcode = ext2fs_dirhash2(hash_alg, name, thislen, + fs->encoding, hash_flags, + fs->super->s_hash_seed, + &hash, &minor_hash); + if (errcode) + com_err("htree_dump_leaf_node", errcode, + "while calculating hash"); + if ((offset == fs->blocksize - csum_size) && + (dirent->inode == 0) && + (dirent->rec_len == csum_size) && + (dirent->name_len == EXT2_DIR_NAME_LEN_CSUM)) { + struct ext2_dir_entry_tail *t; + + t = (struct ext2_dir_entry_tail *) dirent; + + snprintf(tmp, EXT2_NAME_LEN + 64, + "leaf block checksum: 0x%08x ", + t->det_checksum); + } else { + snprintf(tmp, EXT2_NAME_LEN + 64, + "%u 0x%08x-%08x (%d) %s ", + dirent->inode, hash, minor_hash, + rec_len, name); + } + thislen = strlen(tmp); + if (col + thislen > 80) { + fprintf(pager, "\n"); + col = 0; + } + fprintf(pager, "%s", tmp); + col += thislen; + offset += rec_len; + } + fprintf(pager, "\n"); +} + + +static void htree_dump_int_block(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode *inode, + struct ext2_dx_root_info * rootnode, + blk64_t blk, char *buf, int level); + + +static void htree_dump_int_node(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode *inode, + struct ext2_dx_root_info * rootnode, + struct ext2_dx_entry *ent, __u32 crc, + char *buf, int level) +{ + struct ext2_dx_countlimit dx_countlimit; + struct ext2_dx_tail *tail; + int hash, i; + int limit, count; + int remainder; + + dx_countlimit = *((struct ext2_dx_countlimit *) ent); + count = ext2fs_le16_to_cpu(dx_countlimit.count); + limit = ext2fs_le16_to_cpu(dx_countlimit.limit); + + fprintf(pager, "Number of entries (count): %d\n", count); + fprintf(pager, "Number of entries (limit): %d\n", limit); + + remainder = fs->blocksize - (limit * sizeof(struct ext2_dx_entry)); + if (ent == (struct ext2_dx_entry *)(rootnode + 1)) + remainder -= sizeof(struct ext2_dx_root_info) + 24; + else + remainder -= 8; + if (ext2fs_has_feature_metadata_csum(fs->super) && + remainder == sizeof(struct ext2_dx_tail)) { + tail = (struct ext2_dx_tail *)(ent + limit); + fprintf(pager, "Checksum: 0x%08x", + ext2fs_le32_to_cpu(tail->dt_checksum)); + if (tail->dt_checksum != crc) + fprintf(pager, " --- EXPECTED: 0x%08x", crc); + fputc('\n', pager); + } + + for (i=0; i < count; i++) { + hash = i ? ext2fs_le32_to_cpu(ent[i].hash) : 0; + fprintf(pager, "Entry #%d: Hash 0x%08x%s, block %u\n", i, + hash, (hash & 1) ? " (**)" : "", + ext2fs_le32_to_cpu(ent[i].block)); + } + + fprintf(pager, "\n"); + + for (i=0; i < count; i++) { + unsigned int hashval, block; + + hashval = ext2fs_le32_to_cpu(ent[i].hash); + block = ext2fs_le32_to_cpu(ent[i].block); + fprintf(pager, "Entry #%d: Hash 0x%08x, block %u\n", i, + i ? hashval : 0, block); + if (level) + htree_dump_int_block(fs, ino, inode, rootnode, + block, buf, level-1); + else + htree_dump_leaf_node(fs, ino, inode, rootnode, + block, buf); + } + + fprintf(pager, "---------------------\n"); +} + +static void htree_dump_int_block(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode *inode, + struct ext2_dx_root_info * rootnode, + blk64_t blk, char *buf, int level) +{ + char *cbuf; + errcode_t errcode; + blk64_t pblk; + __u32 crc; + + cbuf = malloc(fs->blocksize); + if (!cbuf) { + fprintf(pager, "Couldn't allocate child block.\n"); + return; + } + + errcode = ext2fs_bmap2(fs, ino, inode, buf, 0, blk, 0, &pblk); + if (errcode) { + com_err("htree_dump_int_block", errcode, + "while mapping logical block %llu\n", + (unsigned long long) blk); + goto errout; + } + + errcode = io_channel_read_blk64(current_fs->io, pblk, 1, buf); + if (errcode) { + com_err("htree_dump_int_block", errcode, + "while reading block %llu\n", + (unsigned long long) blk); + goto errout; + } + + errcode = ext2fs_dx_csum(current_fs, ino, + (struct ext2_dir_entry *) buf, &crc, NULL); + if (errcode) { + com_err("htree_dump_int_block", errcode, + "while calculating checksum for logical block %llu\n", + (unsigned long long) blk); + crc = (unsigned int) -1; + } + htree_dump_int_node(fs, ino, inode, rootnode, + (struct ext2_dx_entry *) (buf+8), + crc, cbuf, level); +errout: + free(cbuf); +} + + + +void do_htree_dump(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + ext2_ino_t ino; + struct ext2_inode inode; + blk64_t blk; + char *buf = NULL; + struct ext2_dx_root_info *rootnode; + struct ext2_dx_entry *ent; + errcode_t errcode; + __u32 crc; + + if (check_fs_open(argv[0])) + return; + + pager = open_pager(); + + if (common_inode_args_process(argc, argv, &ino, 0)) + goto errout; + + if (debugfs_read_inode(ino, &inode, argv[1])) + goto errout; + + if (!LINUX_S_ISDIR(inode.i_mode)) { + com_err(argv[0], 0, "Not a directory"); + goto errout; + } + + if ((inode.i_flags & EXT2_BTREE_FL) == 0) { + com_err(argv[0], 0, "Not a hash-indexed directory"); + goto errout; + } + + buf = malloc(2*current_fs->blocksize); + if (!buf) { + com_err(argv[0], 0, "Couldn't allocate htree buffer"); + goto errout; + } + + errcode = ext2fs_bmap2(current_fs, ino, &inode, buf, 0, 0, 0, &blk); + if (errcode) { + com_err("do_htree_block", errcode, + "while mapping logical block 0\n"); + goto errout; + } + + errcode = io_channel_read_blk64(current_fs->io, blk, + 1, buf); + if (errcode) { + com_err(argv[0], errcode, "Error reading root node"); + goto errout; + } + + rootnode = (struct ext2_dx_root_info *) (buf + 24); + + fprintf(pager, "Root node dump:\n"); + fprintf(pager, "\t Reserved zero: %u\n", rootnode->reserved_zero); + fprintf(pager, "\t Hash Version: %d\n", rootnode->hash_version); + fprintf(pager, "\t Info length: %d\n", rootnode->info_length); + fprintf(pager, "\t Indirect levels: %d\n", rootnode->indirect_levels); + fprintf(pager, "\t Flags: %#x\n", rootnode->unused_flags); + + ent = (struct ext2_dx_entry *) + ((char *)rootnode + rootnode->info_length); + + errcode = ext2fs_dx_csum(current_fs, ino, + (struct ext2_dir_entry *) buf, &crc, NULL); + if (errcode) { + com_err("htree_dump_int_block", errcode, + "while calculating checksum for htree root\n"); + crc = (unsigned int) -1; + } + htree_dump_int_node(current_fs, ino, &inode, rootnode, ent, crc, + buf + current_fs->blocksize, + rootnode->indirect_levels); + +errout: + free(buf); + close_pager(pager); +} + +/* + * This function prints the hash of a given file. + */ +void do_dx_hash(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + ext2_dirhash_t hash, minor_hash; + errcode_t err; + int c; + int hash_version = 0; + __u32 hash_seed[4]; + int hash_flags = 0; + const struct ext2fs_nls_table *encoding = NULL; + + hash_seed[0] = hash_seed[1] = hash_seed[2] = hash_seed[3] = 0; + + reset_getopt(); + while ((c = getopt(argc, argv, "h:s:ce:")) != EOF) { + switch (c) { + case 'h': + hash_version = e2p_string2hash(optarg); + if (hash_version < 0) + hash_version = atoi(optarg); + break; + case 's': + if (uuid_parse(optarg, (unsigned char *) hash_seed)) { + fprintf(stderr, "Invalid UUID format: %s\n", + optarg); + return; + } + break; + case 'c': + hash_flags |= EXT4_CASEFOLD_FL; + break; + case 'e': + encoding = ext2fs_load_nls_table(e2p_str2encoding(optarg)); + if (!encoding) { + fprintf(stderr, "Invalid encoding: %s\n", + optarg); + return; + } + break; + default: + goto print_usage; + } + } + if (optind != argc-1) { + print_usage: + com_err(argv[0], 0, "usage: dx_hash [-h hash_alg] " + "[-s hash_seed] [-c] [-e encoding] filename"); + return; + } + err = ext2fs_dirhash2(hash_version, argv[optind], + strlen(argv[optind]), encoding, hash_flags, + hash_seed, &hash, &minor_hash); + + if (err) { + com_err(argv[0], err, "while calculating hash"); + return; + } + printf("Hash of %s is 0x%0x (minor 0x%0x)\n", argv[optind], + hash, minor_hash); +} + +/* + * Search for particular directory entry (useful for debugging very + * large hash tree directories that have lost some blocks from the + * btree index). + */ +struct process_block_struct { + char *search_name; + char *buf; + int len; +}; + +static int search_dir_block(ext2_filsys fs, blk64_t *blocknr, + e2_blkcnt_t blockcnt, blk64_t ref_blk, + int ref_offset, void *priv_data); + +void do_dirsearch(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + ext2_ino_t inode; + struct process_block_struct pb; + + if (check_fs_open(argv[0])) + return; + + if (argc != 3) { + com_err(0, 0, "Usage: dirsearch dir filename"); + return; + } + + inode = string_to_inode(argv[1]); + if (!inode) + return; + + pb.buf = malloc(current_fs->blocksize); + if (!pb.buf) { + com_err("dirsearch", 0, "Couldn't allocate buffer"); + return; + } + pb.search_name = argv[2]; + pb.len = strlen(pb.search_name); + + ext2fs_block_iterate3(current_fs, inode, BLOCK_FLAG_READ_ONLY, 0, + search_dir_block, &pb); + + free(pb.buf); +} + + +static int search_dir_block(ext2_filsys fs, blk64_t *blocknr, + e2_blkcnt_t blockcnt, + blk64_t ref_blk EXT2FS_ATTR((unused)), + int ref_offset EXT2FS_ATTR((unused)), + void *priv_data) +{ + struct process_block_struct *p; + struct ext2_dir_entry *dirent; + errcode_t errcode; + unsigned int offset = 0; + unsigned int rec_len; + + if (blockcnt < 0) + return 0; + + p = (struct process_block_struct *) priv_data; + + errcode = io_channel_read_blk64(current_fs->io, *blocknr, 1, p->buf); + if (errcode) { + com_err("search_dir_block", errcode, + "while reading block %lu", (unsigned long) *blocknr); + return BLOCK_ABORT; + } + + while (offset < fs->blocksize) { + dirent = (struct ext2_dir_entry *) (p->buf + offset); + errcode = ext2fs_get_rec_len(fs, dirent, &rec_len); + if (errcode) { + com_err("htree_dump_leaf_inode", errcode, + "while getting rec_len for block %lu", + (unsigned long) *blocknr); + return BLOCK_ABORT; + } + if (dirent->inode && + p->len == ext2fs_dirent_name_len(dirent) && + strncmp(p->search_name, dirent->name, + p->len) == 0) { + printf("Entry found at logical block %lld, " + "phys %llu, offset %u\n", (long long)blockcnt, + (unsigned long long) *blocknr, offset); + printf("offset %u\n", offset); + return BLOCK_ABORT; + } + offset += rec_len; + } + return 0; +} + diff --git a/debugfs/icheck.c b/debugfs/icheck.c new file mode 100644 index 0000000..ed6e950 --- /dev/null +++ b/debugfs/icheck.c @@ -0,0 +1,176 @@ +/* + * icheck.c --- given a list of blocks, generate a list of inodes + * + * Copyright (C) 1994 Theodore Ts'o. This file may be redistributed + * under the terms of the GNU Public License. + */ + +#include "config.h" +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <ctype.h> +#include <string.h> +#include <time.h> +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#include <sys/types.h> + +#include "debugfs.h" + +struct block_info { + blk64_t blk; + ext2_ino_t ino; +}; + +struct block_walk_struct { + struct block_info *barray; + e2_blkcnt_t blocks_left; + e2_blkcnt_t num_blocks; + ext2_ino_t inode; +}; + +static int icheck_proc(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 *private) +{ + struct block_walk_struct *bw = (struct block_walk_struct *) private; + e2_blkcnt_t i; + + for (i=0; i < bw->num_blocks; i++) { + if (!bw->barray[i].ino && bw->barray[i].blk == *block_nr) { + bw->barray[i].ino = bw->inode; + bw->blocks_left--; + } + } + if (!bw->blocks_left) + return BLOCK_ABORT; + + return 0; +} + +void do_icheck(int argc, char **argv, int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + struct block_walk_struct bw; + struct block_info *binfo; + int i; + ext2_inode_scan scan = 0; + ext2_ino_t ino; + struct ext2_inode inode; + errcode_t retval; + char *block_buf; + + if (argc < 2) { + com_err(argv[0], 0, "Usage: icheck <block number> ..."); + return; + } + if (check_fs_open(argv[0])) + return; + + bw.barray = malloc(sizeof(struct block_info) * argc); + if (!bw.barray) { + com_err("icheck", ENOMEM, + "while allocating inode info array"); + return; + } + memset(bw.barray, 0, sizeof(struct block_info) * argc); + + block_buf = malloc(current_fs->blocksize * 3); + if (!block_buf) { + com_err("icheck", ENOMEM, "while allocating block buffer"); + goto error_out; + } + + for (i=1; i < argc; i++) { + if (strtoblk(argv[0], argv[i], NULL, &bw.barray[i-1].blk)) + goto error_out; + } + + bw.num_blocks = bw.blocks_left = argc-1; + + retval = ext2fs_open_inode_scan(current_fs, 0, &scan); + if (retval) { + com_err("icheck", retval, "while opening inode scan"); + goto error_out; + } + + do { + retval = ext2fs_get_next_inode(scan, &ino, &inode); + } while (retval == EXT2_ET_BAD_BLOCK_IN_INODE_TABLE); + if (retval) { + com_err("icheck", retval, "while starting inode scan"); + goto error_out; + } + + while (ino) { + blk64_t blk; + + if (!inode.i_links_count) + goto next; + + bw.inode = ino; + + blk = ext2fs_file_acl_block(current_fs, &inode); + if (blk) { + icheck_proc(current_fs, &blk, 0, + 0, 0, &bw); + if (bw.blocks_left == 0) + break; + ext2fs_file_acl_block_set(current_fs, &inode, blk); + } + + if (!ext2fs_inode_has_valid_blocks2(current_fs, &inode)) + goto next; + /* + * To handle filesystems touched by 0.3c extfs; can be + * removed later. + */ + if (inode.i_dtime) + goto next; + + retval = ext2fs_block_iterate3(current_fs, ino, + BLOCK_FLAG_READ_ONLY, block_buf, + icheck_proc, &bw); + if (retval) { + com_err("icheck", retval, + "while calling ext2fs_block_iterate"); + goto next; + } + + if (bw.blocks_left == 0) + break; + + next: + do { + retval = ext2fs_get_next_inode(scan, &ino, &inode); + } while (retval == EXT2_ET_BAD_BLOCK_IN_INODE_TABLE); + if (retval) { + com_err("icheck", retval, + "while doing inode scan"); + goto error_out; + } + } + + printf("Block\tInode number\n"); + for (i=0, binfo = bw.barray; i < bw.num_blocks; i++, binfo++) { + if (binfo->ino == 0) { + printf("%llu\t<block not found>\n", + (unsigned long long) binfo->blk); + continue; + } + printf("%llu\t%u\n", (unsigned long long) binfo->blk, + binfo->ino); + } + +error_out: + free(bw.barray); + free(block_buf); + if (scan) + ext2fs_close_inode_scan(scan); + return; +} diff --git a/debugfs/journal.c b/debugfs/journal.c new file mode 100644 index 0000000..5bac0d3 --- /dev/null +++ b/debugfs/journal.c @@ -0,0 +1,947 @@ +/* + * journal.c --- code for handling the "ext3" journal + * + * Copyright (C) 2000 Andreas Dilger + * Copyright (C) 2000 Theodore Ts'o + * + * Parts of the code are based on fs/jfs/journal.c by Stephen C. Tweedie + * Copyright (C) 1999 Red Hat Software + * + * This file may be redistributed under the terms of the + * GNU General Public License version 2 or at your discretion + * any later version. + */ + +#include "config.h" +#ifdef HAVE_SYS_MOUNT_H +#include <sys/param.h> +#include <sys/mount.h> +#define MNT_FL (MS_MGC_VAL | MS_RDONLY) +#endif +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif + +#define E2FSCK_INCLUDE_INLINE_FUNCS +#include "uuid/uuid.h" +#include "journal.h" + +static int bh_count = 0; + +#if EXT2_FLAT_INCLUDES +#include "blkid.h" +#else +#include "blkid/blkid.h" +#endif + +/* + * Define USE_INODE_IO to use the inode_io.c / fileio.c codepaths. + * This creates a larger static binary, and a smaller binary using + * shared libraries. It's also probably slightly less CPU-efficient, + * which is why it's not on by default. But, it's a good way of + * testing the functions in inode_io.c and fileio.c. + */ +#undef USE_INODE_IO + +/* Checksumming functions */ +static int ext2fs_journal_verify_csum_type(journal_t *j, + journal_superblock_t *jsb) +{ + if (!jbd2_journal_has_csum_v2or3(j)) + return 1; + + return jsb->s_checksum_type == JBD2_CRC32C_CHKSUM; +} + +static __u32 ext2fs_journal_sb_csum(journal_superblock_t *jsb) +{ + __u32 crc, old_crc; + + old_crc = jsb->s_checksum; + jsb->s_checksum = 0; + crc = ext2fs_crc32c_le(~0, (unsigned char *)jsb, + sizeof(journal_superblock_t)); + jsb->s_checksum = old_crc; + + return crc; +} + +static int ext2fs_journal_sb_csum_verify(journal_t *j, + journal_superblock_t *jsb) +{ + __u32 provided, calculated; + + if (!jbd2_journal_has_csum_v2or3(j)) + return 1; + + provided = ext2fs_be32_to_cpu(jsb->s_checksum); + calculated = ext2fs_journal_sb_csum(jsb); + + return provided == calculated; +} + +static errcode_t ext2fs_journal_sb_csum_set(journal_t *j, + journal_superblock_t *jsb) +{ + __u32 crc; + + if (!jbd2_journal_has_csum_v2or3(j)) + return 0; + + crc = ext2fs_journal_sb_csum(jsb); + jsb->s_checksum = ext2fs_cpu_to_be32(crc); + return 0; +} + +/* Kernel compatibility functions for handling the journal. These allow us + * to use the recovery.c file virtually unchanged from the kernel, so we + * don't have to do much to keep kernel and user recovery in sync. + */ +int jbd2_journal_bmap(journal_t *journal, unsigned long block, + unsigned long long *phys) +{ +#ifdef USE_INODE_IO + *phys = block; + return 0; +#else + struct inode *inode = journal->j_inode; + errcode_t retval; + blk64_t pblk; + + if (!inode) { + *phys = block; + return 0; + } + + retval = ext2fs_bmap2(inode->i_fs, inode->i_ino, + &inode->i_ext2, NULL, 0, (blk64_t) block, + 0, &pblk); + *phys = pblk; + return (int) retval; +#endif +} + +struct buffer_head *getblk(kdev_t kdev, unsigned long long blocknr, + int blocksize) +{ + struct buffer_head *bh; + int bufsize = sizeof(*bh) + kdev->k_fs->blocksize - + sizeof(bh->b_data); + errcode_t retval; + + retval = ext2fs_get_memzero(bufsize, &bh); + if (retval) + return NULL; + + if (journal_enable_debug >= 3) + bh_count++; + jfs_debug(4, "getblk for block %llu (%d bytes)(total %d)\n", + blocknr, blocksize, bh_count); + + bh->b_fs = kdev->k_fs; + if (kdev->k_dev == K_DEV_FS) + bh->b_io = kdev->k_fs->io; + else + bh->b_io = kdev->k_fs->journal_io; + bh->b_size = blocksize; + bh->b_blocknr = blocknr; + + return bh; +} + +int sync_blockdev(kdev_t kdev) +{ + io_channel io; + + if (kdev->k_dev == K_DEV_FS) + io = kdev->k_fs->io; + else + io = kdev->k_fs->journal_io; + + return io_channel_flush(io) ? EIO : 0; +} + +void ll_rw_block(int rw, int op_flags EXT2FS_ATTR((unused)), int nr, + struct buffer_head *bhp[]) +{ + errcode_t retval; + struct buffer_head *bh; + + for (; nr > 0; --nr) { + bh = *bhp++; + if (rw == REQ_OP_READ && !bh->b_uptodate) { + jfs_debug(3, "reading block %llu/%p\n", + bh->b_blocknr, (void *) bh); + retval = io_channel_read_blk64(bh->b_io, + bh->b_blocknr, + 1, bh->b_data); + if (retval) { + com_err(bh->b_fs->device_name, retval, + "while reading block %llu\n", + bh->b_blocknr); + bh->b_err = (int) retval; + continue; + } + bh->b_uptodate = 1; + } else if (rw == REQ_OP_WRITE && bh->b_dirty) { + jfs_debug(3, "writing block %llu/%p\n", + bh->b_blocknr, + (void *) bh); + retval = io_channel_write_blk64(bh->b_io, + bh->b_blocknr, + 1, bh->b_data); + if (retval) { + com_err(bh->b_fs->device_name, retval, + "while writing block %llu\n", + bh->b_blocknr); + bh->b_err = (int) retval; + continue; + } + bh->b_dirty = 0; + bh->b_uptodate = 1; + } else { + jfs_debug(3, "no-op %s for block %llu\n", + rw == REQ_OP_READ ? "read" : "write", + bh->b_blocknr); + } + } +} + +void mark_buffer_dirty(struct buffer_head *bh) +{ + bh->b_dirty = 1; +} + +static void mark_buffer_clean(struct buffer_head *bh) +{ + bh->b_dirty = 0; +} + +void brelse(struct buffer_head *bh) +{ + if (bh->b_dirty) + ll_rw_block(REQ_OP_WRITE, 0, 1, &bh); + jfs_debug(3, "freeing block %llu/%p (total %d)\n", + bh->b_blocknr, (void *) bh, --bh_count); + ext2fs_free_mem(&bh); +} + +int buffer_uptodate(struct buffer_head *bh) +{ + return bh->b_uptodate; +} + +void mark_buffer_uptodate(struct buffer_head *bh, int val) +{ + bh->b_uptodate = val; +} + +void wait_on_buffer(struct buffer_head *bh) +{ + if (!bh->b_uptodate) + ll_rw_block(REQ_OP_READ, 0, 1, &bh); +} + + +static void ext2fs_clear_recover(ext2_filsys fs, int error) +{ + ext2fs_clear_feature_journal_needs_recovery(fs->super); + + /* if we had an error doing journal recovery, we need a full fsck */ + if (error) + fs->super->s_state &= ~EXT2_VALID_FS; + /* + * If we replayed the journal by definition the file system + * was mounted since the last time it was checked + */ + if (fs->super->s_lastcheck >= fs->super->s_mtime) + fs->super->s_lastcheck = fs->super->s_mtime - 1; + ext2fs_mark_super_dirty(fs); +} + +/* + * This is a helper function to check the validity of the journal. + */ +struct process_block_struct { + e2_blkcnt_t last_block; +}; + +static int process_journal_block(ext2_filsys fs, + blk64_t *block_nr, + e2_blkcnt_t blockcnt, + blk64_t ref_block EXT2FS_ATTR((unused)), + int ref_offset EXT2FS_ATTR((unused)), + void *priv_data) +{ + struct process_block_struct *p; + blk64_t blk = *block_nr; + + p = (struct process_block_struct *) priv_data; + + if (!blk || blk < fs->super->s_first_data_block || + blk >= ext2fs_blocks_count(fs->super)) + return BLOCK_ABORT; + + if (blockcnt >= 0) + p->last_block = blockcnt; + return 0; +} + +static errcode_t ext2fs_get_journal(ext2_filsys fs, journal_t **ret_journal) +{ + struct process_block_struct pb; + struct ext2_super_block *sb = fs->super; + struct ext2_super_block jsuper; + struct buffer_head *bh; + struct inode *j_inode = NULL; + struct kdev_s *dev_fs = NULL, *dev_journal; + const char *journal_name = 0; + journal_t *journal = NULL; + errcode_t retval = 0; + io_manager io_ptr = 0; + unsigned long long start = 0; + int ext_journal = 0; + int tried_backup_jnl = 0; + + retval = ext2fs_get_memzero(sizeof(journal_t), &journal); + if (retval) + return retval; + + retval = ext2fs_get_memzero(2 * sizeof(struct kdev_s), &dev_fs); + if (retval) + goto errout; + dev_journal = dev_fs+1; + + dev_fs->k_fs = dev_journal->k_fs = fs; + dev_fs->k_dev = K_DEV_FS; + dev_journal->k_dev = K_DEV_JOURNAL; + + journal->j_dev = dev_journal; + journal->j_fs_dev = dev_fs; + journal->j_inode = NULL; + journal->j_blocksize = fs->blocksize; + + if (uuid_is_null(sb->s_journal_uuid)) { + if (!sb->s_journal_inum) { + retval = EXT2_ET_BAD_INODE_NUM; + goto errout; + } + retval = ext2fs_get_memzero(sizeof(*j_inode), &j_inode); + if (retval) + goto errout; + + j_inode->i_fs = fs; + j_inode->i_ino = sb->s_journal_inum; + + retval = ext2fs_read_inode(fs, sb->s_journal_inum, + &j_inode->i_ext2); + if (retval) { +try_backup_journal: + if (sb->s_jnl_backup_type != EXT3_JNL_BACKUP_BLOCKS || + tried_backup_jnl) + goto errout; + memset(&j_inode->i_ext2, 0, sizeof(struct ext2_inode)); + memcpy(&j_inode->i_ext2.i_block[0], sb->s_jnl_blocks, + EXT2_N_BLOCKS*4); + j_inode->i_ext2.i_size_high = sb->s_jnl_blocks[15]; + j_inode->i_ext2.i_size = sb->s_jnl_blocks[16]; + j_inode->i_ext2.i_links_count = 1; + j_inode->i_ext2.i_mode = LINUX_S_IFREG | 0600; + tried_backup_jnl++; + } + if (!j_inode->i_ext2.i_links_count || + !LINUX_S_ISREG(j_inode->i_ext2.i_mode)) { + retval = EXT2_ET_NO_JOURNAL; + goto try_backup_journal; + } + if (EXT2_I_SIZE(&j_inode->i_ext2) / journal->j_blocksize < + JBD2_MIN_JOURNAL_BLOCKS) { + retval = EXT2_ET_JOURNAL_TOO_SMALL; + goto try_backup_journal; + } + pb.last_block = -1; + retval = ext2fs_block_iterate3(fs, j_inode->i_ino, + BLOCK_FLAG_HOLE, 0, + process_journal_block, &pb); + if ((pb.last_block + 1) * fs->blocksize < + (int) EXT2_I_SIZE(&j_inode->i_ext2)) { + retval = EXT2_ET_JOURNAL_TOO_SMALL; + goto try_backup_journal; + } + if (tried_backup_jnl && (fs->flags & EXT2_FLAG_RW)) { + retval = ext2fs_write_inode(fs, sb->s_journal_inum, + &j_inode->i_ext2); + if (retval) + goto errout; + } + + journal->j_total_len = EXT2_I_SIZE(&j_inode->i_ext2) / + journal->j_blocksize; + +#ifdef USE_INODE_IO + retval = ext2fs_inode_io_intern2(fs, sb->s_journal_inum, + &j_inode->i_ext2, + &journal_name); + if (retval) + goto errout; + + io_ptr = inode_io_manager; +#else + journal->j_inode = j_inode; + fs->journal_io = fs->io; + retval = (errcode_t) jbd2_journal_bmap(journal, 0, &start); + if (retval) + goto errout; +#endif + } else { + ext_journal = 1; + if (!fs->journal_name) { + char uuid[37]; + blkid_cache blkid; + + blkid_get_cache(&blkid, NULL); + uuid_unparse(sb->s_journal_uuid, uuid); + fs->journal_name = blkid_get_devname(blkid, + "UUID", uuid); + if (!fs->journal_name) + fs->journal_name = blkid_devno_to_devname(sb->s_journal_dev); + blkid_put_cache(blkid); + } + journal_name = fs->journal_name; + + if (!journal_name) { + retval = EXT2_ET_LOAD_EXT_JOURNAL; + goto errout; + } + + jfs_debug(1, "Using journal file %s\n", journal_name); + io_ptr = unix_io_manager; + } + +#if 0 + test_io_backing_manager = io_ptr; + io_ptr = test_io_manager; +#endif +#ifndef USE_INODE_IO + if (ext_journal) +#endif + { + retval = io_ptr->open(journal_name, fs->flags & EXT2_FLAG_RW, + &fs->journal_io); + } + if (retval) + goto errout; + + io_channel_set_blksize(fs->journal_io, fs->blocksize); + + if (ext_journal) { + blk64_t maxlen; + + start = ext2fs_journal_sb_start(fs->blocksize) - 1; + bh = getblk(dev_journal, start, fs->blocksize); + if (!bh) { + retval = EXT2_ET_NO_MEMORY; + goto errout; + } + ll_rw_block(REQ_OP_READ, 0, 1, &bh); + retval = bh->b_err; + if (retval) { + brelse(bh); + goto errout; + } + memcpy(&jsuper, start ? bh->b_data : + bh->b_data + SUPERBLOCK_OFFSET, + sizeof(jsuper)); +#ifdef WORDS_BIGENDIAN + if (jsuper.s_magic == ext2fs_swab16(EXT2_SUPER_MAGIC)) + ext2fs_swap_super(&jsuper); +#endif + if (jsuper.s_magic != EXT2_SUPER_MAGIC || + !ext2fs_has_feature_journal_dev(&jsuper)) { + retval = EXT2_ET_LOAD_EXT_JOURNAL; + brelse(bh); + goto errout; + } + /* Make sure the journal UUID is correct */ + if (memcmp(jsuper.s_uuid, fs->super->s_journal_uuid, + sizeof(jsuper.s_uuid))) { + retval = EXT2_ET_LOAD_EXT_JOURNAL; + brelse(bh); + goto errout; + } + + /* Check the superblock checksum */ + if (ext2fs_has_feature_metadata_csum(&jsuper)) { + struct struct_ext2_filsys fsx; + struct ext2_super_block superx; + void *p; + + p = start ? bh->b_data : bh->b_data + SUPERBLOCK_OFFSET; + memcpy(&fsx, fs, sizeof(fsx)); + memcpy(&superx, fs->super, sizeof(superx)); + fsx.super = &superx; + ext2fs_set_feature_metadata_csum(fsx.super); + if (!ext2fs_superblock_csum_verify(&fsx, p)) { + retval = EXT2_ET_LOAD_EXT_JOURNAL; + brelse(bh); + goto errout; + } + } + brelse(bh); + + maxlen = ext2fs_blocks_count(&jsuper); + journal->j_total_len = (maxlen < 1ULL << 32) ? maxlen : + (1ULL << 32) - 1; + start++; + } + + bh = getblk(dev_journal, start, journal->j_blocksize); + if (!bh) { + retval = EXT2_ET_NO_MEMORY; + goto errout; + } + + journal->j_sb_buffer = bh; + journal->j_superblock = (journal_superblock_t *)bh->b_data; + +#ifdef USE_INODE_IO + if (j_inode) + ext2fs_free_mem(&j_inode); +#endif + + *ret_journal = journal; + return 0; + +errout: + if (dev_fs) + ext2fs_free_mem(&dev_fs); + if (j_inode) + ext2fs_free_mem(&j_inode); + if (journal) + ext2fs_free_mem(&journal); + return retval; +} + +static errcode_t ext2fs_journal_fix_bad_inode(ext2_filsys fs) +{ + struct ext2_super_block *sb = fs->super; + int recover = ext2fs_has_feature_journal_needs_recovery(fs->super); + int has_journal = ext2fs_has_feature_journal(fs->super); + + if (has_journal || sb->s_journal_inum) { + /* The journal inode is bogus, remove and force full fsck */ + return EXT2_ET_BAD_INODE_NUM; + } else if (recover) { + return EXT2_ET_UNSUPP_FEATURE; + } + return 0; +} + +#define V1_SB_SIZE 0x0024 +static void clear_v2_journal_fields(journal_t *journal) +{ + ext2_filsys fs = journal->j_dev->k_fs; + + memset(((char *) journal->j_superblock) + V1_SB_SIZE, 0, + fs->blocksize-V1_SB_SIZE); + mark_buffer_dirty(journal->j_sb_buffer); +} + + +static errcode_t ext2fs_journal_load(journal_t *journal) +{ + ext2_filsys fs = journal->j_dev->k_fs; + journal_superblock_t *jsb; + struct buffer_head *jbh = journal->j_sb_buffer; + + ll_rw_block(REQ_OP_READ, 0, 1, &jbh); + if (jbh->b_err) + return jbh->b_err; + + jsb = journal->j_superblock; + /* If we don't even have JBD2_MAGIC, we probably have a wrong inode */ + if (jsb->s_header.h_magic != htonl(JBD2_MAGIC_NUMBER)) + return ext2fs_journal_fix_bad_inode(fs); + + switch (ntohl(jsb->s_header.h_blocktype)) { + case JBD2_SUPERBLOCK_V1: + journal->j_format_version = 1; + if (jsb->s_feature_compat || + jsb->s_feature_incompat || + jsb->s_feature_ro_compat || + jsb->s_nr_users) + clear_v2_journal_fields(journal); + break; + + case JBD2_SUPERBLOCK_V2: + journal->j_format_version = 2; + if (ntohl(jsb->s_nr_users) > 1 && + uuid_is_null(fs->super->s_journal_uuid)) + clear_v2_journal_fields(journal); + if (ntohl(jsb->s_nr_users) > 1) + return EXT2_ET_JOURNAL_UNSUPP_VERSION; + break; + + /* + * These should never appear in a journal super block, so if + * they do, the journal is badly corrupted. + */ + case JBD2_DESCRIPTOR_BLOCK: + case JBD2_COMMIT_BLOCK: + case JBD2_REVOKE_BLOCK: + return EXT2_ET_CORRUPT_JOURNAL_SB; + + /* If we don't understand the superblock major type, but there + * is a magic number, then it is likely to be a new format we + * just don't understand, so leave it alone. */ + default: + return EXT2_ET_JOURNAL_UNSUPP_VERSION; + } + + if (JBD2_HAS_INCOMPAT_FEATURE(journal, ~JBD2_KNOWN_INCOMPAT_FEATURES)) + return EXT2_ET_UNSUPP_FEATURE; + + if (JBD2_HAS_RO_COMPAT_FEATURE(journal, ~JBD2_KNOWN_ROCOMPAT_FEATURES)) + return EXT2_ET_RO_UNSUPP_FEATURE; + + /* Checksum v1-3 are mutually exclusive features. */ + if (jbd2_has_feature_csum2(journal) && jbd2_has_feature_csum3(journal)) + return EXT2_ET_CORRUPT_JOURNAL_SB; + + if (jbd2_journal_has_csum_v2or3(journal) && + jbd2_has_feature_checksum(journal)) + return EXT2_ET_CORRUPT_JOURNAL_SB; + + if (!ext2fs_journal_verify_csum_type(journal, jsb) || + !ext2fs_journal_sb_csum_verify(journal, jsb)) + return EXT2_ET_CORRUPT_JOURNAL_SB; + + if (jbd2_journal_has_csum_v2or3(journal)) + journal->j_csum_seed = jbd2_chksum(journal, ~0, jsb->s_uuid, + sizeof(jsb->s_uuid)); + + /* We have now checked whether we know enough about the journal + * format to be able to proceed safely, so any other checks that + * fail we should attempt to recover from. */ + if (jsb->s_blocksize != htonl(journal->j_blocksize)) + return EXT2_ET_CORRUPT_JOURNAL_SB; + + if (ntohl(jsb->s_maxlen) < journal->j_total_len) + journal->j_total_len = ntohl(jsb->s_maxlen); + else if (ntohl(jsb->s_maxlen) > journal->j_total_len) + return EXT2_ET_CORRUPT_JOURNAL_SB; + + journal->j_tail_sequence = ntohl(jsb->s_sequence); + journal->j_transaction_sequence = journal->j_tail_sequence; + journal->j_tail = ntohl(jsb->s_start); + journal->j_first = ntohl(jsb->s_first); + journal->j_last = ntohl(jsb->s_maxlen); + + return 0; +} + +static void ext2fs_journal_release(ext2_filsys fs, journal_t *journal, + int reset, int drop) +{ + journal_superblock_t *jsb; + + if (drop) + mark_buffer_clean(journal->j_sb_buffer); + else if (fs->flags & EXT2_FLAG_RW) { + jsb = journal->j_superblock; + jsb->s_sequence = htonl(journal->j_tail_sequence); + if (reset) + jsb->s_start = 0; /* this marks the journal as empty */ + ext2fs_journal_sb_csum_set(journal, jsb); + mark_buffer_dirty(journal->j_sb_buffer); + } + brelse(journal->j_sb_buffer); + + if (fs && fs->journal_io) { + if (fs->io != fs->journal_io) + io_channel_close(fs->journal_io); + fs->journal_io = NULL; + free(fs->journal_name); + fs->journal_name = NULL; + } + +#ifndef USE_INODE_IO + if (journal->j_inode) + ext2fs_free_mem(&journal->j_inode); +#endif + if (journal->j_fs_dev) + ext2fs_free_mem(&journal->j_fs_dev); + ext2fs_free_mem(&journal); +} + +/* + * This function makes sure that the superblock fields regarding the + * journal are consistent. + */ +static errcode_t ext2fs_check_ext3_journal(ext2_filsys fs) +{ + struct ext2_super_block *sb = fs->super; + journal_t *journal; + int recover = ext2fs_has_feature_journal_needs_recovery(fs->super); + errcode_t retval; + + /* If we don't have any journal features, don't do anything more */ + if (!ext2fs_has_feature_journal(sb) && + !recover && sb->s_journal_inum == 0 && sb->s_journal_dev == 0 && + uuid_is_null(sb->s_journal_uuid)) + return 0; + + retval = ext2fs_get_journal(fs, &journal); + if (retval) + return retval; + + retval = ext2fs_journal_load(journal); + if (retval) + goto err; + + /* + * We want to make the flags consistent here. We will not leave with + * needs_recovery set but has_journal clear. We can't get in a loop + * with -y, -n, or -p, only if a user isn't making up their mind. + */ + if (!ext2fs_has_feature_journal(sb)) { + retval = EXT2_ET_JOURNAL_FLAGS_WRONG; + goto err; + } + + if (ext2fs_has_feature_journal(sb) && + !ext2fs_has_feature_journal_needs_recovery(sb) && + journal->j_superblock->s_start != 0) { + retval = EXT2_ET_JOURNAL_FLAGS_WRONG; + goto err; + } + + /* + * If we don't need to do replay the journal, check to see if + * the journal's errno is set; if so, we need to mark the file + * system as being corrupt and clear the journal's s_errno. + */ + if (!ext2fs_has_feature_journal_needs_recovery(sb) && + journal->j_superblock->s_errno) { + fs->super->s_state |= EXT2_ERROR_FS; + ext2fs_mark_super_dirty(fs); + journal->j_superblock->s_errno = 0; + ext2fs_journal_sb_csum_set(journal, journal->j_superblock); + mark_buffer_dirty(journal->j_sb_buffer); + } + +err: + ext2fs_journal_release(fs, journal, 0, retval ? 1 : 0); + return retval; +} + +static errcode_t recover_ext3_journal(ext2_filsys fs) +{ + journal_t *journal; + errcode_t retval; + + retval = jbd2_journal_init_revoke_record_cache(); + if (retval) + return retval; + + retval = jbd2_journal_init_revoke_table_cache(); + if (retval) + return retval; + + retval = ext2fs_get_journal(fs, &journal); + if (retval) + return retval; + + retval = ext2fs_journal_load(journal); + if (retval) + goto errout; + + retval = jbd2_journal_init_revoke(journal, 1024); + if (retval) + goto errout; + + retval = -jbd2_journal_recover(journal); + if (retval) + goto errout; + + if (journal->j_failed_commit) { + journal->j_superblock->s_errno = -EINVAL; + mark_buffer_dirty(journal->j_sb_buffer); + } + + journal->j_tail_sequence = journal->j_transaction_sequence; + +errout: + jbd2_journal_destroy_revoke(journal); + jbd2_journal_destroy_revoke_record_cache(); + jbd2_journal_destroy_revoke_table_cache(); + ext2fs_journal_release(fs, journal, 1, 0); + return retval; +} + +errcode_t ext2fs_run_ext3_journal(ext2_filsys *fsp) +{ + ext2_filsys fs = *fsp; + io_manager io_ptr = fs->io->manager; + errcode_t retval, recover_retval; + io_stats stats = 0; + unsigned long long kbytes_written = 0; + char *fsname; + int fsflags; + int fsblocksize; + + if (!(fs->flags & EXT2_FLAG_RW)) + return EXT2_ET_FILE_RO; + + if (fs->flags & EXT2_FLAG_DIRTY) + ext2fs_flush(fs); /* Force out any modifications */ + + recover_retval = recover_ext3_journal(fs); + + /* + * Reload the filesystem context to get up-to-date data from disk + * because journal recovery will change the filesystem under us. + */ + if (fs->super->s_kbytes_written && + fs->io->manager->get_stats) + fs->io->manager->get_stats(fs->io, &stats); + if (stats && stats->bytes_written) + kbytes_written = stats->bytes_written >> 10; + + ext2fs_mmp_stop(fs); + fsname = fs->device_name; + fs->device_name = NULL; + fsflags = fs->flags; + fsblocksize = fs->blocksize; + ext2fs_free(fs); + *fsp = NULL; + retval = ext2fs_open(fsname, fsflags, 0, fsblocksize, io_ptr, fsp); + ext2fs_free_mem(&fsname); + if (retval) + return retval; + + fs = *fsp; + fs->flags |= EXT2_FLAG_MASTER_SB_ONLY; + fs->super->s_kbytes_written += kbytes_written; + + /* Set the superblock flags */ + ext2fs_clear_recover(fs, recover_retval != 0); + + /* + * Do one last sanity check, and propagate journal->s_errno to + * the EXT2_ERROR_FS flag in the fs superblock if needed. + */ + retval = ext2fs_check_ext3_journal(fs); + return retval ? retval : recover_retval; +} + +errcode_t ext2fs_open_journal(ext2_filsys fs, journal_t **j) +{ + journal_t *journal; + errcode_t retval; + + retval = jbd2_journal_init_revoke_record_cache(); + if (retval) + return retval; + + retval = jbd2_journal_init_revoke_table_cache(); + if (retval) + return retval; + + retval = ext2fs_get_journal(fs, &journal); + if (retval) + return retval; + + retval = ext2fs_journal_load(journal); + if (retval) + goto errout; + + retval = jbd2_journal_init_revoke(journal, 1024); + if (retval) + goto errout; + + if (journal->j_failed_commit) { + journal->j_superblock->s_errno = -EINVAL; + mark_buffer_dirty(journal->j_sb_buffer); + } + + *j = journal; + return 0; + +errout: + jbd2_journal_destroy_revoke(journal); + jbd2_journal_destroy_revoke_record_cache(); + jbd2_journal_destroy_revoke_table_cache(); + ext2fs_journal_release(fs, journal, 1, 0); + return retval; +} + +errcode_t ext2fs_close_journal(ext2_filsys fs, journal_t **j) +{ + journal_t *journal = *j; + + jbd2_journal_destroy_revoke(journal); + jbd2_journal_destroy_revoke_record_cache(); + jbd2_journal_destroy_revoke_table_cache(); + ext2fs_journal_release(fs, journal, 0, 0); + *j = NULL; + + return 0; +} + +void jbd2_commit_block_csum_set(journal_t *j, struct buffer_head *bh) +{ + struct commit_header *h; + __u32 csum; + + if (!jbd2_journal_has_csum_v2or3(j)) + return; + + h = (struct commit_header *)(bh->b_data); + h->h_chksum_type = 0; + h->h_chksum_size = 0; + h->h_chksum[0] = 0; + csum = jbd2_chksum(j, j->j_csum_seed, bh->b_data, j->j_blocksize); + h->h_chksum[0] = ext2fs_cpu_to_be32(csum); +} + +void jbd2_revoke_csum_set(journal_t *j, struct buffer_head *bh) +{ + jbd2_descr_block_csum_set(j, bh); +} + +void jbd2_descr_block_csum_set(journal_t *j, struct buffer_head *bh) +{ + struct jbd2_journal_block_tail *tail; + __u32 csum; + + if (!jbd2_journal_has_csum_v2or3(j)) + return; + + tail = (struct jbd2_journal_block_tail *)(bh->b_data + j->j_blocksize - + sizeof(struct jbd2_journal_block_tail)); + tail->t_checksum = 0; + csum = jbd2_chksum(j, j->j_csum_seed, bh->b_data, j->j_blocksize); + tail->t_checksum = ext2fs_cpu_to_be32(csum); +} + +void jbd2_block_tag_csum_set(journal_t *j, journal_block_tag_t *tag, + struct buffer_head *bh, __u32 sequence) +{ + journal_block_tag3_t *tag3 = (journal_block_tag3_t *)tag; + __u32 csum32; + __be32 seq; + + if (!jbd2_journal_has_csum_v2or3(j)) + return; + + seq = ext2fs_cpu_to_be32(sequence); + csum32 = jbd2_chksum(j, j->j_csum_seed, (__u8 *)&seq, sizeof(seq)); + csum32 = jbd2_chksum(j, csum32, bh->b_data, bh->b_size); + + if (jbd2_has_feature_csum3(j)) + tag3->t_checksum = ext2fs_cpu_to_be32(csum32); + else + tag->t_checksum = ext2fs_cpu_to_be16(csum32); +} + diff --git a/debugfs/journal.h b/debugfs/journal.h new file mode 100644 index 0000000..10b638e --- /dev/null +++ b/debugfs/journal.h @@ -0,0 +1,25 @@ +/* + * journal.h + * + * Copyright (C) 2000 Andreas Dilger + * Copyright (C) 2000 Theodore Ts'o + * + * Parts of the code are based on fs/jfs/journal.c by Stephen C. Tweedie + * Copyright (C) 1999 Red Hat Software + * + * This file may be redistributed under the terms of the + * GNU General Public License version 2 or at your discretion + * any later version. + */ + +#include "jfs_user.h" + +/* journal.c */ +errcode_t ext2fs_open_journal(ext2_filsys fs, journal_t **j); +errcode_t ext2fs_close_journal(ext2_filsys fs, journal_t **j); +errcode_t ext2fs_run_ext3_journal(ext2_filsys *fs); +void jbd2_commit_block_csum_set(journal_t *j, struct buffer_head *bh); +void jbd2_revoke_csum_set(journal_t *j, struct buffer_head *bh); +void jbd2_descr_block_csum_set(journal_t *j, struct buffer_head *bh); +void jbd2_block_tag_csum_set(journal_t *j, journal_block_tag_t *tag, + struct buffer_head *bh, __u32 sequence); diff --git a/debugfs/logdump.c b/debugfs/logdump.c new file mode 100644 index 0000000..b600228 --- /dev/null +++ b/debugfs/logdump.c @@ -0,0 +1,933 @@ +/* + * logdump.c --- dump the contents of the journal out to a file + * + * Author: Stephen C. Tweedie, 2001 <sct@redhat.com> + * Copyright (C) 2001 Red Hat, Inc. + * Based on portions Copyright (C) 1994 Theodore Ts'o. + * + * This file may be redistributed under the terms of the GNU Public + * License. + */ + +#include "config.h" +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#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 "debugfs.h" +#include "blkid/blkid.h" +#include "jfs_user.h" +#if __GNUC_PREREQ (4, 6) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" +#endif +#include "ext2fs/fast_commit.h" +#if __GNUC_PREREQ (4, 6) +#pragma GCC diagnostic pop +#endif +#include <uuid/uuid.h> + +enum journal_location {JOURNAL_IS_INTERNAL, JOURNAL_IS_EXTERNAL}; + +#define ANY_BLOCK ((blk64_t) -1) + +static int dump_all, dump_super, dump_old, dump_contents, dump_descriptors; +static int64_t dump_counts; +static blk64_t block_to_dump, bitmap_to_dump, inode_block_to_dump; +static unsigned int group_to_dump, inode_offset_to_dump; +static ext2_ino_t inode_to_dump; + +struct journal_source +{ + enum journal_location where; + int fd; + ext2_file_t file; +}; + +static void dump_journal(char *, FILE *, struct journal_source *); + +static void dump_descriptor_block(FILE *, struct journal_source *, + char *, journal_superblock_t *, + unsigned int *, unsigned int, __u32, tid_t); + +static void dump_revoke_block(FILE *, char *, journal_superblock_t *, + unsigned int, unsigned int, tid_t); + +static void dump_metadata_block(FILE *, struct journal_source *, + journal_superblock_t*, + unsigned int, unsigned int, unsigned int, + unsigned int, tid_t); + +static void dump_fc_block(FILE *out_file, char *buf, int blocksize, + tid_t transaction, int *fc_done); + +static void do_hexdump (FILE *, char *, int); + +#define WRAP(jsb, blocknr, maxlen) \ + if (blocknr >= (maxlen)) \ + blocknr -= (maxlen - be32_to_cpu((jsb)->s_first)); + +void do_logdump(int argc, char **argv, int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + int c; + int retval; + char *out_fn; + FILE *out_file; + + char *inode_spec = NULL; + char *journal_fn = NULL; + int journal_fd = -1; + int use_sb = 0; + ext2_ino_t journal_inum; + struct ext2_inode journal_inode; + ext2_file_t journal_file = NULL; + char *tmp; + struct journal_source journal_source; + struct ext2_super_block *es = NULL; + + journal_source.where = JOURNAL_IS_INTERNAL; + journal_source.fd = 0; + journal_source.file = 0; + dump_all = 0; + dump_old = 0; + dump_contents = 0; + dump_super = 0; + dump_descriptors = 1; + block_to_dump = ANY_BLOCK; + bitmap_to_dump = -1; + inode_block_to_dump = ANY_BLOCK; + inode_to_dump = -1; + dump_counts = -1; + + reset_getopt(); + while ((c = getopt (argc, argv, "ab:ci:f:OsSn:")) != EOF) { + switch (c) { + case 'a': + dump_all++; + break; + case 'b': + block_to_dump = strtoul(optarg, &tmp, 0); + if (*tmp) { + com_err(argv[0], 0, + "Bad block number - %s", optarg); + return; + } + dump_descriptors = 0; + break; + case 'c': + dump_contents++; + break; + case 'f': + journal_fn = optarg; + break; + case 'i': + inode_spec = optarg; + dump_descriptors = 0; + break; + case 'O': + dump_old++; + break; + case 's': + use_sb++; + break; + case 'S': + dump_super++; + break; + case 'n': + dump_counts = strtol(optarg, &tmp, 10); + if (*tmp) { + com_err(argv[0], 0, + "Bad log counts number - %s", optarg); + return; + } + break; + default: + goto print_usage; + } + } + if (optind != argc && optind != argc-1) { + goto print_usage; + } + + if (inode_spec) { + int inode_group, group_offset, inodes_per_block; + + if (check_fs_open(argv[0])) + return; + + inode_to_dump = string_to_inode(inode_spec); + if (!inode_to_dump) + return; + + es = current_fs->super; + inode_group = ((inode_to_dump - 1) + / es->s_inodes_per_group); + group_offset = ((inode_to_dump - 1) + % es->s_inodes_per_group); + inodes_per_block = (current_fs->blocksize + / sizeof(struct ext2_inode)); + + inode_block_to_dump = + ext2fs_inode_table_loc(current_fs, inode_group) + + (group_offset / inodes_per_block); + inode_offset_to_dump = ((group_offset % inodes_per_block) + * sizeof(struct ext2_inode)); + printf("Inode %u is at group %u, block %llu, offset %u\n", + inode_to_dump, inode_group, + (unsigned long long) inode_block_to_dump, + inode_offset_to_dump); + } + + if (optind == argc) { + out_file = stdout; + } else { + out_fn = argv[optind]; + out_file = fopen(out_fn, "w"); + if (!out_file) { + com_err(argv[0], errno, "while opening %s for logdump", + out_fn); + goto cleanup; + } + } + + if (block_to_dump != ANY_BLOCK) { + if (check_fs_open(argv[0])) + goto cleanup; + es = current_fs->super; + group_to_dump = ((block_to_dump - + es->s_first_data_block) + / es->s_blocks_per_group); + bitmap_to_dump = ext2fs_block_bitmap_loc(current_fs, group_to_dump); + } + + if (journal_fn) { + /* Set up to read journal from a regular file somewhere */ + journal_fd = open(journal_fn, O_RDONLY, 0); + if (journal_fd < 0) { + com_err(argv[0], errno, "while opening %s for logdump", + journal_fn); + goto cleanup; + } + journal_source.where = JOURNAL_IS_EXTERNAL; + journal_source.fd = journal_fd; + dump_journal(argv[0], out_file, &journal_source); + goto cleanup; + + } + if (check_fs_open(argv[0])) + goto cleanup; + es = current_fs->super; + + if ((journal_inum = es->s_journal_inum)) { + if (use_sb) { + if (es->s_jnl_backup_type != EXT3_JNL_BACKUP_BLOCKS) { + com_err(argv[0], 0, + "no journal backup in super block\n"); + goto cleanup; + } + memset(&journal_inode, 0, sizeof(struct ext2_inode)); + memcpy(&journal_inode.i_block[0], es->s_jnl_blocks, + EXT2_N_BLOCKS*4); + journal_inode.i_size_high = es->s_jnl_blocks[15]; + journal_inode.i_size = es->s_jnl_blocks[16]; + journal_inode.i_links_count = 1; + journal_inode.i_mode = LINUX_S_IFREG | 0600; + } else { + if (debugfs_read_inode(journal_inum, &journal_inode, + argv[0])) + goto cleanup; + } + + retval = ext2fs_file_open2(current_fs, journal_inum, + &journal_inode, 0, &journal_file); + if (retval) { + com_err(argv[0], retval, "while opening ext2 file"); + goto cleanup; + } + journal_source.where = JOURNAL_IS_INTERNAL; + journal_source.file = journal_file; + } else { + char uuid[37]; + + uuid_unparse(es->s_journal_uuid, uuid); + journal_fn = blkid_get_devname(NULL, "UUID", uuid); + if (!journal_fn) + journal_fn = blkid_devno_to_devname(es->s_journal_dev); + if (!journal_fn) { + com_err(argv[0], 0, "filesystem has no journal"); + goto cleanup; + } + journal_fd = open(journal_fn, O_RDONLY, 0); + if (journal_fd < 0) { + com_err(argv[0], errno, "while opening %s for logdump", + journal_fn); + free(journal_fn); + goto cleanup; + } + fprintf(out_file, "Using external journal found at %s\n", + journal_fn); + free(journal_fn); + journal_source.where = JOURNAL_IS_EXTERNAL; + journal_source.fd = journal_fd; + } + dump_journal(argv[0], out_file, &journal_source); +cleanup: + if (journal_fd >= 0) + close(journal_fd); + if (journal_file) + ext2fs_file_close(journal_file); + if (out_file && (out_file != stdout)) + fclose(out_file); + + return; + +print_usage: + fprintf(stderr, "%s: Usage: logdump [-acsOS] [-n<num_trans>] [-b<block>] [-i<filespec>]\n\t" + "[-f<journal_file>] [output_file]\n", argv[0]); +} + + +static int read_journal_block(const char *cmd, struct journal_source *source, + ext2_loff_t offset, char *buf, unsigned int size) +{ + int retval; + unsigned int got; + + if (source->where == JOURNAL_IS_EXTERNAL) { + if (lseek(source->fd, offset, SEEK_SET) < 0) { + retval = errno; + goto seek_err; + } + retval = read(source->fd, buf, size); + if (retval < 0) { + retval = errno; + goto read_err; + } + got = retval; + retval = 0; + } else { + retval = ext2fs_file_llseek(source->file, offset, + EXT2_SEEK_SET, NULL); + if (retval) { + seek_err: + com_err(cmd, retval, "while seeking in reading journal"); + return retval; + } + retval = ext2fs_file_read(source->file, buf, size, &got); + if (retval) { + read_err: + com_err(cmd, retval, "while reading journal"); + return retval; + } + } + if (got != size) { + com_err(cmd, 0, "short read (read %u, expected %u) " + "while reading journal", got, size); + retval = -1; + } + return retval; +} + +static const char *type_to_name(int btype) +{ + switch (btype) { + case JBD2_DESCRIPTOR_BLOCK: + return "descriptor block"; + case JBD2_COMMIT_BLOCK: + return "commit block"; + case JBD2_SUPERBLOCK_V1: + return "V1 superblock"; + case JBD2_SUPERBLOCK_V2: + return "V2 superblock"; + case JBD2_REVOKE_BLOCK: + return "revoke table"; + } + return "unrecognised type"; +} + + +static void dump_journal(char *cmdname, FILE *out_file, + struct journal_source *source) +{ + struct ext2_super_block *sb; + char jsb_buffer[1024]; + char buf[EXT2_MAX_BLOCK_SIZE]; + journal_superblock_t *jsb; + unsigned int blocksize = 1024; + int retval; + __u32 magic, sequence, blocktype; + journal_header_t *header; + tid_t transaction; + unsigned int blocknr = 0; + unsigned int first_transaction_blocknr; + int fc_done; + __u64 total_len; + __u32 maxlen; + int64_t cur_counts = 0; + bool exist_no_magic = false; + + /* First, check to see if there's an ext2 superblock header */ + retval = read_journal_block(cmdname, source, 0, buf, 2048); + if (retval) + return; + + jsb = (journal_superblock_t *) buf; + sb = (struct ext2_super_block *) (buf+1024); +#ifdef WORDS_BIGENDIAN + if (sb->s_magic == ext2fs_swab16(EXT2_SUPER_MAGIC)) + ext2fs_swap_super(sb); +#endif + + if ((be32_to_cpu(jsb->s_header.h_magic) != JBD2_MAGIC_NUMBER) && + (sb->s_magic == EXT2_SUPER_MAGIC) && + ext2fs_has_feature_journal_dev(sb)) { + blocksize = EXT2_BLOCK_SIZE(sb); + blocknr = (blocksize == 1024) ? 2 : 1; + uuid_unparse(sb->s_uuid, jsb_buffer); + fprintf(out_file, "Ext2 superblock header found.\n"); + if (dump_all) { + fprintf(out_file, "\tuuid=%s\n", jsb_buffer); + fprintf(out_file, "\tblocksize=%d\n", blocksize); + fprintf(out_file, "\tjournal data size %lu\n", + (unsigned long) ext2fs_blocks_count(sb)); + } + } + + /* Next, read the journal superblock */ + retval = read_journal_block(cmdname, source, + ((ext2_loff_t) blocknr) * blocksize, + jsb_buffer, 1024); + if (retval) + return; + + if (dump_super) { + e2p_list_journal_super(out_file, jsb_buffer, + current_fs->blocksize, 0); + fputc('\n', out_file); + } + + jsb = (journal_superblock_t *) jsb_buffer; + if (be32_to_cpu(jsb->s_header.h_magic) != JBD2_MAGIC_NUMBER) { + fprintf(out_file, + "Journal superblock magic number invalid!\n"); + return; + } + blocksize = be32_to_cpu(jsb->s_blocksize); + if ((current_fs && (blocksize != current_fs->blocksize)) || + (!current_fs && (!blocksize || (blocksize & (blocksize - 1)) || + (blocksize > EXT2_MAX_BLOCK_SIZE)))) { + fprintf(out_file, + "Journal block size invalid: %u (%u)\n", + be32_to_cpu(jsb->s_blocksize), blocksize); + return; + } + transaction = be32_to_cpu(jsb->s_sequence); + blocknr = be32_to_cpu(jsb->s_start); + if (source->where == JOURNAL_IS_INTERNAL) { + retval = ext2fs_file_get_lsize(source->file, &total_len); + if (retval) { + stat_err: + com_err("dump_journal", retval, + "while getting journal inode size"); + return; + } + total_len /= blocksize; + } else { + struct stat st; + + if (fstat(source->fd, &st) < 0) + goto stat_err; + total_len = st.st_size / blocksize; + } + maxlen = be32_to_cpu(jsb->s_maxlen); + if (maxlen > total_len) + maxlen = total_len; + + fprintf(out_file, "Journal starts at block %u, transaction %u\n", + blocknr, transaction); + + if (!blocknr) { + /* Empty journal, nothing to do. */ + if (!dump_old) + goto fc; + else + blocknr = 1; + } + + first_transaction_blocknr = blocknr; + + while (1) { + if (dump_old && (dump_counts != -1) && (cur_counts >= dump_counts)) + break; + + if ((blocknr == first_transaction_blocknr) && + (cur_counts != 0) && dump_old && (dump_counts != -1)) { + fprintf(out_file, "Dump all %lld journal records.\n", + (long long) cur_counts); + break; + } + + retval = read_journal_block(cmdname, source, + ((ext2_loff_t) blocknr) * blocksize, + buf, blocksize); + if (retval) + break; + + header = (journal_header_t *) buf; + + magic = be32_to_cpu(header->h_magic); + sequence = be32_to_cpu(header->h_sequence); + blocktype = be32_to_cpu(header->h_blocktype); + + if (magic != JBD2_MAGIC_NUMBER) { + if (exist_no_magic == false) { + exist_no_magic = true; + fprintf(out_file, "No magic number at block %u: " + "end of journal.\n", blocknr); + } + if (dump_old && (dump_counts != -1)) { + blocknr++; + WRAP(jsb, blocknr, maxlen); + continue; + } + break; + } + + if (sequence != transaction) { + fprintf (out_file, "Found sequence %u (not %u) at " + "block %u: end of journal.\n", + sequence, transaction, blocknr); + if (!dump_old) + break; + } + + if (dump_descriptors) { + fprintf (out_file, "Found expected sequence %u, " + "type %u (%s) at block %u\n", + sequence, blocktype, + type_to_name(blocktype), blocknr); + } + + switch (blocktype) { + case JBD2_DESCRIPTOR_BLOCK: + dump_descriptor_block(out_file, source, buf, jsb, + &blocknr, blocksize, maxlen, + transaction); + continue; + + case JBD2_COMMIT_BLOCK: + cur_counts++; + transaction++; + blocknr++; + WRAP(jsb, blocknr, maxlen); + continue; + + case JBD2_REVOKE_BLOCK: + dump_revoke_block(out_file, buf, jsb, + blocknr, blocksize, + transaction); + blocknr++; + WRAP(jsb, blocknr, maxlen); + continue; + + default: + fprintf (out_file, "Unexpected block type %u at " + "block %u.\n", blocktype, blocknr); + break; + } + } + +fc: + blocknr = maxlen - jbd2_journal_get_num_fc_blks(jsb) + 1; + while (blocknr <= maxlen) { + retval = read_journal_block(cmdname, source, + ((ext2_loff_t) blocknr) * blocksize, + buf, blocksize); + if (retval) + return; + + dump_fc_block(out_file, buf, blocksize, transaction, &fc_done); + if (!dump_old && fc_done) + break; + blocknr++; + } +} + +static inline size_t journal_super_tag_bytes(journal_superblock_t *jsb) +{ + size_t sz; + + if (JSB_HAS_INCOMPAT_FEATURE(jsb, JBD2_FEATURE_INCOMPAT_CSUM_V3)) + return sizeof(journal_block_tag3_t); + + sz = sizeof(journal_block_tag_t); + + if (JSB_HAS_INCOMPAT_FEATURE(jsb, JBD2_FEATURE_INCOMPAT_CSUM_V2)) + sz += sizeof(__u16); + + if (JSB_HAS_INCOMPAT_FEATURE(jsb, JBD2_FEATURE_INCOMPAT_64BIT)) + return sz; + + return sz - sizeof(__u32); +} + +static void dump_fc_block(FILE *out_file, char *buf, int blocksize, + tid_t transaction, int *fc_done) +{ + struct ext4_fc_tl tl; + struct ext4_fc_head *head; + struct ext4_fc_add_range *add_range; + struct ext4_fc_del_range *del_range; + struct ext4_fc_dentry_info *dentry_info; + struct ext4_fc_tail *tail; + struct ext3_extent *ex; + __u8 *cur, *val; + + *fc_done = 0; + for (cur = (__u8 *)buf; cur < (__u8 *)buf + blocksize; + cur = cur + sizeof(tl) + le16_to_cpu(tl.fc_len)) { + memcpy(&tl, cur, sizeof(tl)); + val = cur + sizeof(tl); + + switch (le16_to_cpu(tl.fc_tag)) { + case EXT4_FC_TAG_ADD_RANGE: + add_range = (struct ext4_fc_add_range *)val; + ex = (struct ext3_extent *)add_range->fc_ex; + fprintf(out_file, + "tag %s, inode %d, lblk %u, pblk %llu, len %lu\n", + tag2str(tl.fc_tag), + le32_to_cpu(add_range->fc_ino), + le32_to_cpu(ex->ee_block), + le32_to_cpu(ex->ee_start) + + (((unsigned long long) le16_to_cpu(ex->ee_start_hi)) << 32), + le16_to_cpu(ex->ee_len) > EXT_INIT_MAX_LEN ? + le16_to_cpu(ex->ee_len) - EXT_INIT_MAX_LEN : + le16_to_cpu(ex->ee_len)); + break; + case EXT4_FC_TAG_DEL_RANGE: + del_range = (struct ext4_fc_del_range *)val; + fprintf(out_file, "tag %s, inode %d, lblk %d, len %d\n", + tag2str(tl.fc_tag), + le32_to_cpu(del_range->fc_ino), + le32_to_cpu(del_range->fc_lblk), + le32_to_cpu(del_range->fc_len)); + break; + case EXT4_FC_TAG_LINK: + case EXT4_FC_TAG_UNLINK: + case EXT4_FC_TAG_CREAT: + dentry_info = (struct ext4_fc_dentry_info *)val; + fprintf(out_file, + "tag %s, parent %d, ino %d, name \"%s\"\n", + tag2str(tl.fc_tag), + le32_to_cpu(dentry_info->fc_parent_ino), + le32_to_cpu(dentry_info->fc_ino), + dentry_info->fc_dname); + break; + case EXT4_FC_TAG_INODE: + fprintf(out_file, "tag %s, inode %d\n", + tag2str(tl.fc_tag), + le32_to_cpu(((struct ext4_fc_inode *)val)->fc_ino)); + break; + case EXT4_FC_TAG_PAD: + fprintf(out_file, "tag %s\n", tag2str(tl.fc_tag)); + break; + case EXT4_FC_TAG_TAIL: + tail = (struct ext4_fc_tail *)val; + fprintf(out_file, "tag %s, tid %d\n", + tag2str(tl.fc_tag), + le32_to_cpu(tail->fc_tid)); + if (!dump_old && + le32_to_cpu(tail->fc_tid) < transaction) { + *fc_done = 1; + return; + } + break; + case EXT4_FC_TAG_HEAD: + fprintf(out_file, "\n*** Fast Commit Area ***\n"); + head = (struct ext4_fc_head *)val; + fprintf(out_file, "tag %s, features 0x%x, tid %d\n", + tag2str(tl.fc_tag), + le32_to_cpu(head->fc_features), + le32_to_cpu(head->fc_tid)); + if (!dump_old && + le32_to_cpu(head->fc_tid) < transaction) { + *fc_done = 1; + return; + } + break; + default: + *fc_done = 1; + break; + } + } +} + +static void dump_descriptor_block(FILE *out_file, + struct journal_source *source, + char *buf, + journal_superblock_t *jsb, + unsigned int *blockp, unsigned blocksize, + __u32 maxlen, + tid_t transaction) +{ + unsigned offset, tag_size, csum_size = 0; + char *tagp; + journal_block_tag_t *tag; + unsigned int blocknr; + __u32 tag_block; + __u32 tag_flags; + + tag_size = journal_super_tag_bytes(jsb); + offset = sizeof(journal_header_t); + blocknr = *blockp; + + if (JSB_HAS_INCOMPAT_FEATURE(jsb, JBD2_FEATURE_INCOMPAT_CSUM_V3) || + JSB_HAS_INCOMPAT_FEATURE(jsb, JBD2_FEATURE_INCOMPAT_CSUM_V2)) + csum_size = sizeof(struct jbd2_journal_block_tail); + + if (dump_all) + fprintf(out_file, "Dumping descriptor block, sequence %u, at " + "block %u:\n", transaction, blocknr); + + ++blocknr; + WRAP(jsb, blocknr, maxlen); + + do { + /* Work out the location of the current tag, and skip to + * the next one... */ + tagp = &buf[offset]; + tag = (journal_block_tag_t *) tagp; + offset += tag_size; + + /* ... and if we have gone too far, then we've reached the + end of this block. */ + if (offset > blocksize - csum_size) + break; + + tag_block = be32_to_cpu(tag->t_blocknr); + tag_flags = be16_to_cpu(tag->t_flags); + + if (!(tag_flags & JBD2_FLAG_SAME_UUID)) + offset += 16; + + dump_metadata_block(out_file, source, jsb, + blocknr, tag_block, tag_flags, blocksize, + transaction); + + ++blocknr; + WRAP(jsb, blocknr, maxlen); + + } while (!(tag_flags & JBD2_FLAG_LAST_TAG)); + + *blockp = blocknr; +} + + +static void dump_revoke_block(FILE *out_file, char *buf, + journal_superblock_t *jsb EXT2FS_ATTR((unused)), + unsigned int blocknr, + unsigned int blocksize, + tid_t transaction) +{ + unsigned int offset, max; + jbd2_journal_revoke_header_t *header; + unsigned long long rblock; + int tag_size = sizeof(__u32); + + if (dump_all) + fprintf(out_file, "Dumping revoke block, sequence %u, at " + "block %u:\n", transaction, blocknr); + + if (be32_to_cpu(jsb->s_feature_incompat) & JBD2_FEATURE_INCOMPAT_64BIT) + tag_size = sizeof(__u64); + + header = (jbd2_journal_revoke_header_t *) buf; + offset = sizeof(jbd2_journal_revoke_header_t); + max = be32_to_cpu(header->r_count); + if (max > blocksize) { + fprintf(out_file, "Revoke block's r_count invalid: %u\b", + max); + max = blocksize; + } + + while (offset < max) { + if (tag_size == sizeof(__u32)) { + __u32 *entry = (__u32 *) (buf + offset); + rblock = be32_to_cpu(*entry); + } else { + __u64 *entry = (__u64 *) (buf + offset); + rblock = ext2fs_be64_to_cpu(*entry); + } + if (dump_all || rblock == block_to_dump) { + fprintf(out_file, " Revoke FS block %llu", + (unsigned long long) rblock); + if (dump_all) + fprintf(out_file, "\n"); + else + fprintf(out_file," at block %u, sequence %u\n", + blocknr, transaction); + } + offset += tag_size; + } +} + + +static void show_extent(FILE *out_file, int start_extent, int end_extent, + __u32 first_block) +{ + if (start_extent >= 0 && first_block != 0) + fprintf(out_file, "(%d+%u): %u ", + start_extent, end_extent-start_extent, first_block); +} + +static void show_indirect(FILE *out_file, const char *name, __u32 where) +{ + if (where) + fprintf(out_file, "(%s): %u ", name, where); +} + + +static void dump_metadata_block(FILE *out_file, struct journal_source *source, + journal_superblock_t *jsb EXT2FS_ATTR((unused)), + unsigned int log_blocknr, + unsigned int fs_blocknr, + unsigned int log_tag_flags, + unsigned int blocksize, + tid_t transaction) +{ + int retval; + char buf[EXT2_MAX_BLOCK_SIZE]; + + if (!(dump_all + || (fs_blocknr == block_to_dump) + || (fs_blocknr == inode_block_to_dump) + || (fs_blocknr == bitmap_to_dump))) + return; + + fprintf(out_file, " FS block %u logged at ", fs_blocknr); + if (!dump_all) + fprintf(out_file, "sequence %u, ", transaction); + fprintf(out_file, "journal block %u (flags 0x%x)\n", log_blocknr, + log_tag_flags); + + /* There are two major special cases to parse: + * + * If this block is a block + * bitmap block, we need to give it special treatment so that we + * can log any allocates and deallocates which affect the + * block_to_dump query block. + * + * If the block is an inode block for the inode being searched + * for, then we need to dump the contents of that inode + * structure symbolically. + */ + + if (!(dump_contents && dump_all) + && fs_blocknr != block_to_dump + && fs_blocknr != bitmap_to_dump + && fs_blocknr != inode_block_to_dump) + return; + + retval = read_journal_block("logdump", source, + ((ext2_loff_t) log_blocknr) * blocksize, + buf, blocksize); + if (retval) + return; + + if (fs_blocknr == bitmap_to_dump) { + struct ext2_super_block *super; + int offset; + + super = current_fs->super; + offset = ((block_to_dump - super->s_first_data_block) % + super->s_blocks_per_group); + + fprintf(out_file, " (block bitmap for block %llu: " + "block is %s)\n", + (unsigned long long) block_to_dump, + ext2fs_test_bit(offset, buf) ? "SET" : "CLEAR"); + } + + if (fs_blocknr == inode_block_to_dump) { + struct ext2_inode *inode; + int first, prev, this, start_extent, i; + + fprintf(out_file, " (inode block for inode %u):\n", + inode_to_dump); + + inode = (struct ext2_inode *) (buf + inode_offset_to_dump); + internal_dump_inode(out_file, " ", inode_to_dump, inode, 0); + + /* Dump out the direct/indirect blocks here: + * internal_dump_inode can only dump them from the main + * on-disk inode, not from the journaled copy of the + * inode. */ + + fprintf (out_file, " Blocks: "); + first = prev = start_extent = -1; + + for (i=0; i<EXT2_NDIR_BLOCKS; i++) { + this = inode->i_block[i]; + if (start_extent >= 0 && this == prev+1) { + prev = this; + continue; + } else { + show_extent(out_file, start_extent, i, first); + start_extent = i; + first = prev = this; + } + } + show_extent(out_file, start_extent, i, first); + show_indirect(out_file, "IND", inode->i_block[i++]); + show_indirect(out_file, "DIND", inode->i_block[i++]); + show_indirect(out_file, "TIND", inode->i_block[i++]); + + fprintf(out_file, "\n"); + } + + if (dump_contents) + do_hexdump(out_file, buf, blocksize); + +} + +static void do_hexdump (FILE *out_file, char *buf, int blocksize) +{ + int i,j; + int *intp; + char *charp; + unsigned char c; + + intp = (int *) buf; + charp = (char *) buf; + + for (i=0; i<blocksize; i+=16) { + fprintf(out_file, " %04x: ", i); + for (j=0; j<16; j+=4) + fprintf(out_file, "%08x ", *intp++); + for (j=0; j<16; j++) { + c = *charp++; + if (c < ' ' || c >= 127) + c = '.'; + fprintf(out_file, "%c", c); + } + fprintf(out_file, "\n"); + } +} + diff --git a/debugfs/ls.c b/debugfs/ls.c new file mode 100644 index 0000000..525f084 --- /dev/null +++ b/debugfs/ls.c @@ -0,0 +1,252 @@ +/* + * ls.c --- list directories + * + * Copyright (C) 1997 Theodore Ts'o. This file may be redistributed + * under the terms of the GNU Public License. + */ + +#include "config.h" +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <ctype.h> +#include <string.h> +#include <time.h> +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#include <sys/types.h> +#ifdef HAVE_GETOPT_H +#include <getopt.h> +#else +extern int optind; +extern char *optarg; +#endif + +#include "debugfs.h" + +/* + * list directory + */ + +#define LONG_OPT 0x0001 +#define PARSE_OPT 0x0002 +#define RAW_OPT 0x0004 +#define ENCRYPT_OPT 0x8000 + +struct list_dir_struct { + FILE *f; + int col; + int options; + int state; +}; + +static const char *monstr[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; + +static int print_filename(FILE *f, struct ext2_dir_entry *dirent, int options) +{ + unsigned char ch; + const char *cp = dirent->name; + int len = ext2fs_dirent_name_len(dirent); + int retlen = 0; + + if ((options & ENCRYPT_OPT) && !(options & RAW_OPT)) { + if (f) + return fprintf(f, "<encrypted (%d)>", len); + else + return snprintf(NULL, 0, "<encrypted (%d)>", len); + } + while (len--) { + ch = *cp++; + if (ch < 32 || ch >= 127 || ch == '\\') { + if (f) + fprintf(f, "\\x%02x", ch); + retlen += 4; + } else { + if (f) + fputc(ch, f); + retlen++; + } + } + return retlen; +} + +static int list_dir_proc(ext2_ino_t dir EXT2FS_ATTR((unused)), + int entry, + struct ext2_dir_entry *dirent, + int offset EXT2FS_ATTR((unused)), + int blocksize EXT2FS_ATTR((unused)), + char *buf EXT2FS_ATTR((unused)), + void *private) +{ + struct ext2_inode inode; + ext2_ino_t ino; + struct tm *tm_p; + time_t modtime; + char tmp[EXT2_NAME_LEN + 16]; + char datestr[80]; + char lbr, rbr; + int thislen; + int options; + struct list_dir_struct *ls = (struct list_dir_struct *) private; + struct ext2_dir_entry_tail *t = (struct ext2_dir_entry_tail *) dirent; + + thislen = ext2fs_dirent_name_len(dirent); + ino = dirent->inode; + options = ls->options; + if (ls->state < 2) { + ls->state++; + options |= RAW_OPT; + } + + if (entry == DIRENT_DELETED_FILE) { + lbr = '<'; + rbr = '>'; + ino = 0; + } else { + lbr = rbr = ' '; + } + if (options & PARSE_OPT) { + if (ino) { + if (debugfs_read_inode(ino, &inode, "ls")) + return 0; + } else + memset(&inode, 0, sizeof(struct ext2_inode)); + fprintf(ls->f,"/%u/%06o/%d/%d/%.*s/", ino, inode.i_mode, + inode_uid(inode), inode_gid(inode), thislen, dirent->name); + if (LINUX_S_ISDIR(inode.i_mode)) + fprintf(ls->f, "/"); + else + fprintf(ls->f, "%llu/", + (unsigned long long) EXT2_I_SIZE(&inode)); + fprintf(ls->f, "\n"); + } else if (options & LONG_OPT) { + if (ino) { + if (debugfs_read_inode(ino, &inode, "ls")) + return 0; + modtime = inode.i_mtime; + tm_p = localtime(&modtime); + sprintf(datestr, "%2d-%s-%4d %02d:%02d", + tm_p->tm_mday, monstr[tm_p->tm_mon], + 1900 + tm_p->tm_year, tm_p->tm_hour, + tm_p->tm_min); + } else { + strcpy(datestr, " "); + memset(&inode, 0, sizeof(struct ext2_inode)); + } + fprintf(ls->f, "%c%6u%c %6o ", lbr, ino, rbr, inode.i_mode); + if (entry == DIRENT_CHECKSUM) { + fprintf(ls->f, "(dirblock checksum: 0x%08x)\n", + t->det_checksum); + return 0; + } + fprintf(ls->f, "(%d) %5d %5d ", + ext2fs_dirent_file_type(dirent), + inode_uid(inode), inode_gid(inode)); + fprintf(ls->f, "%5llu", + (unsigned long long) EXT2_I_SIZE(&inode)); + fprintf(ls->f, " %s ", datestr); + print_filename(ls->f, dirent, options); + fputc('\n', ls->f); + } else { + if (entry == DIRENT_CHECKSUM) { + sprintf(tmp, "%c%u%c (dirblock checksum: 0x%08x) ", + lbr, dirent->inode, rbr, t->det_checksum); + thislen = strlen(tmp); + if (ls->col + thislen > 80) { + fputc('\n', ls->f); + ls->col = 0; + } + fprintf(ls->f, "%s", tmp); + ls->col += thislen; + return 0; + } + sprintf(tmp, "%c%u%c (%d) ", lbr, dirent->inode, rbr, + dirent->rec_len); + thislen = strlen(tmp) + 3; + thislen += print_filename(NULL, dirent, options); + + if (ls->col + thislen > 80) { + fputc('\n', ls->f); + ls->col = 0; + } + fprintf(ls->f, "%s", tmp); + print_filename(ls->f, dirent, options); + fputs(" ", ls->f); + ls->col += thislen; + } + return 0; +} + +void do_list_dir(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + struct ext2_inode inode; + ext2_ino_t ino; + int retval; + int c; + int flags = DIRENT_FLAG_INCLUDE_EMPTY; + struct list_dir_struct ls; + + ls.options = 0; + ls.state = 0; + if (check_fs_open(argv[0])) + return; + + reset_getopt(); + while ((c = getopt (argc, argv, "cdlpr")) != EOF) { + switch (c) { + case 'c': + flags |= DIRENT_FLAG_INCLUDE_CSUM; + break; + case 'l': + ls.options |= LONG_OPT; + break; + case 'd': + flags |= DIRENT_FLAG_INCLUDE_REMOVED; + break; + case 'p': + ls.options |= PARSE_OPT; + break; + case 'r': + ls.options |= RAW_OPT; + break; + default: + goto print_usage; + } + } + + if (argc > optind+1) { + print_usage: + com_err(0, 0, "Usage: ls [-c] [-d] [-l] [-p] [-r] file"); + return; + } + + if (argc == optind) + ino = cwd; + else + ino = string_to_inode(argv[optind]); + if (!ino) + return; + + ls.f = open_pager(); + ls.col = 0; + + if (debugfs_read_inode(ino, &inode, argv[0])) + return; + + if (inode.i_flags & EXT4_ENCRYPT_FL) + ls.options |= ENCRYPT_OPT; + + retval = ext2fs_dir_iterate2(current_fs, ino, flags, + 0, list_dir_proc, &ls); + fprintf(ls.f, "\n"); + close_pager(ls.f); + if (retval) + com_err(argv[1], retval, 0); + + return; +} + + diff --git a/debugfs/lsdel.c b/debugfs/lsdel.c new file mode 100644 index 0000000..52c7419 --- /dev/null +++ b/debugfs/lsdel.c @@ -0,0 +1,219 @@ +/* + * lsdel.c --- routines to try to help a user recover a deleted file. + * + * Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001 + * Theodore Ts'o. This file may be redistributed under the terms of + * the GNU Public License. + */ + +#include "config.h" +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#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 "debugfs.h" + +struct deleted_info { + ext2_ino_t ino; + unsigned short mode; + __u32 uid; + __u64 size; + time_t dtime; + e2_blkcnt_t num_blocks; + e2_blkcnt_t free_blocks; +}; + +struct lsdel_struct { + ext2_ino_t inode; + e2_blkcnt_t num_blocks; + e2_blkcnt_t free_blocks; + e2_blkcnt_t bad_blocks; +}; + +static int deleted_info_compare(const void *a, const void *b) +{ + const struct deleted_info *arg1, *arg2; + + arg1 = (const struct deleted_info *) a; + arg2 = (const struct deleted_info *) b; + + return arg1->dtime - arg2->dtime; +} + +static int lsdel_proc(ext2_filsys fs, + 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 *private) +{ + struct lsdel_struct *lsd = (struct lsdel_struct *) private; + + lsd->num_blocks++; + + if (*block_nr < fs->super->s_first_data_block || + *block_nr >= ext2fs_blocks_count(fs->super)) { + lsd->bad_blocks++; + return BLOCK_ABORT; + } + + if (!ext2fs_test_block_bitmap2(fs->block_map,*block_nr)) + lsd->free_blocks++; + + return 0; +} + +void do_lsdel(int argc, char **argv, int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + struct lsdel_struct lsd; + struct deleted_info *delarray; + int num_delarray, max_delarray; + ext2_inode_scan scan = 0; + ext2_ino_t ino; + struct ext2_inode inode; + errcode_t retval; + char *block_buf; + int i; + long secs = 0; + char *tmp; + time_t now; + FILE *out; + + if (common_args_process(argc, argv, 1, 2, "list_deleted_inodes", + "[secs]", 0)) + return; + + if (argc > 1) { + secs = strtol(argv[1],&tmp,0); + if (*tmp) { + com_err(argv[0], 0, "Bad time - %s",argv[1]); + return; + } + } + + now = current_fs->now ? current_fs->now : time(0); + max_delarray = 100; + num_delarray = 0; + delarray = malloc(max_delarray * sizeof(struct deleted_info)); + if (!delarray) { + com_err("ls_deleted_inodes", ENOMEM, + "while allocating deleted information storage"); + exit(1); + } + + block_buf = malloc(current_fs->blocksize * 3); + if (!block_buf) { + com_err("ls_deleted_inodes", ENOMEM, "while allocating block buffer"); + goto error_out; + } + + retval = ext2fs_open_inode_scan(current_fs, 0, &scan); + if (retval) { + com_err("ls_deleted_inodes", retval, + "while opening inode scan"); + goto error_out; + } + + do { + retval = ext2fs_get_next_inode(scan, &ino, &inode); + } while (retval == EXT2_ET_BAD_BLOCK_IN_INODE_TABLE); + if (retval) { + com_err("ls_deleted_inodes", retval, + "while starting inode scan"); + goto error_out; + } + + while (ino) { + if ((inode.i_dtime == 0) || + (secs && (labs(now - secs) > (long) inode.i_dtime))) + goto next; + + lsd.inode = ino; + lsd.num_blocks = 0; + lsd.free_blocks = 0; + lsd.bad_blocks = 0; + + if (ext2fs_inode_has_valid_blocks2(current_fs, &inode)) { + retval = ext2fs_block_iterate3(current_fs, ino, + BLOCK_FLAG_READ_ONLY, + block_buf, + lsdel_proc, &lsd); + if (retval) { + com_err("ls_deleted_inodes", retval, + "while calling ext2fs_block_iterate2"); + goto next; + } + } + if ((lsd.free_blocks && !lsd.bad_blocks) || + inode.i_flags & EXT4_INLINE_DATA_FL) { + if (num_delarray >= max_delarray) { + max_delarray += 50; + delarray = realloc(delarray, + max_delarray * sizeof(struct deleted_info)); + if (!delarray) { + com_err("ls_deleted_inodes", + ENOMEM, + "while reallocating array"); + exit(1); + } + } + + delarray[num_delarray].ino = ino; + delarray[num_delarray].mode = inode.i_mode; + delarray[num_delarray].uid = inode_uid(inode); + delarray[num_delarray].size = EXT2_I_SIZE(&inode); + delarray[num_delarray].dtime = (__s32) inode.i_dtime; + delarray[num_delarray].num_blocks = lsd.num_blocks; + delarray[num_delarray].free_blocks = lsd.free_blocks; + num_delarray++; + } + + next: + do { + retval = ext2fs_get_next_inode(scan, &ino, &inode); + } while (retval == EXT2_ET_BAD_BLOCK_IN_INODE_TABLE); + if (retval) { + com_err("ls_deleted_inodes", retval, + "while doing inode scan"); + goto error_out; + } + } + + out = open_pager(); + + fprintf(out, " Inode Owner Mode Size Blocks Time deleted\n"); + + qsort(delarray, num_delarray, sizeof(struct deleted_info), + deleted_info_compare); + + for (i = 0; i < num_delarray; i++) { + fprintf(out, "%6u %6d %6o %6llu %6lld/%6lld %s", + delarray[i].ino, + delarray[i].uid, delarray[i].mode, + (unsigned long long) delarray[i].size, + (long long) delarray[i].free_blocks, + (long long) delarray[i].num_blocks, + time_to_string(delarray[i].dtime)); + } + fprintf(out, "%d deleted inodes found.\n", num_delarray); + close_pager(out); + +error_out: + free(block_buf); + free(delarray); + if (scan) + ext2fs_close_inode_scan(scan); + return; +} + + + diff --git a/debugfs/ncheck.c b/debugfs/ncheck.c new file mode 100644 index 0000000..963b3a1 --- /dev/null +++ b/debugfs/ncheck.c @@ -0,0 +1,223 @@ +/* + * ncheck.c --- given a list of inodes, generate a list of names + * + * Copyright (C) 1994 Theodore Ts'o. This file may be redistributed + * under the terms of the GNU Public License. + */ + +#include "config.h" +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <ctype.h> +#include <string.h> +#include <time.h> +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#include <sys/types.h> +#ifdef HAVE_GETOPT_H +#include <getopt.h> +#else +extern int optind; +extern char *optarg; +#endif + +#include "debugfs.h" + +struct inode_walk_struct { + ext2_ino_t dir; + ext2_ino_t *iarray; + int names_left; + int num_inodes; + int position; + char *parent; + unsigned int get_pathname_failed:1; + unsigned int check_dirent:1; +}; + +static int ncheck_proc(struct ext2_dir_entry *dirent, + int offset EXT2FS_ATTR((unused)), + int blocksize EXT2FS_ATTR((unused)), + char *buf EXT2FS_ATTR((unused)), + void *private) +{ + struct inode_walk_struct *iw = (struct inode_walk_struct *) private; + struct ext2_inode inode; + errcode_t retval; + int filetype = ext2fs_dirent_file_type(dirent); + int i; + + iw->position++; + if (iw->position <= 2) + return 0; + for (i=0; i < iw->num_inodes; i++) { + if (iw->iarray[i] == dirent->inode) { + if (!iw->parent && !iw->get_pathname_failed) { + retval = ext2fs_get_pathname(current_fs, + iw->dir, + 0, &iw->parent); + if (retval) { + com_err("ncheck", retval, + "while calling ext2fs_get_pathname for inode #%u", iw->dir); + iw->get_pathname_failed = 1; + } + } + if (iw->parent) + printf("%u\t%s/%.*s", iw->iarray[i], + iw->parent, + ext2fs_dirent_name_len(dirent), + dirent->name); + else + printf("%u\t<%u>/%.*s", iw->iarray[i], + iw->dir, + ext2fs_dirent_name_len(dirent), + dirent->name); + if (iw->check_dirent && filetype) { + if (!debugfs_read_inode(dirent->inode, &inode, + "ncheck") && + filetype != ext2_file_type(inode.i_mode)) { + printf(" <--- BAD FILETYPE"); + } + } + putc('\n', stdout); + iw->names_left--; + } + } + if (!iw->names_left) + return DIRENT_ABORT; + + return 0; +} + +void do_ncheck(int argc, char **argv, int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + struct inode_walk_struct iw; + int c, i; + ext2_inode_scan scan = 0; + ext2_ino_t ino; + struct ext2_inode inode; + errcode_t retval; + char *tmp; + + iw.check_dirent = 0; + + reset_getopt(); + while ((c = getopt (argc, argv, "c")) != EOF) { + switch (c) { + case 'c': + iw.check_dirent = 1; + break; + default: + goto print_usage; + } + } + + if (argc <= 1) { + print_usage: + com_err(argv[0], 0, "Usage: ncheck [-c] <inode number> ..."); + return; + } + if (check_fs_open(argv[0])) + return; + + argc -= optind; + argv += optind; + iw.iarray = malloc(sizeof(ext2_ino_t) * argc); + if (!iw.iarray) { + com_err("ncheck", ENOMEM, + "while allocating inode number array"); + return; + } + memset(iw.iarray, 0, sizeof(ext2_ino_t) * argc); + + iw.names_left = 0; + for (i=0; i < argc; i++) { + char *str = argv[i]; + int len = strlen(str); + + if ((len > 2) && (str[0] == '<') && (str[len - 1] == '>')) + str++; + iw.iarray[i] = strtol(str, &tmp, 0); + if (*tmp && (str == argv[i] || *tmp != '>')) { + com_err("ncheck", 0, "Invalid inode number - '%s'", + argv[i]); + goto error_out; + } + if (debugfs_read_inode(iw.iarray[i], &inode, *argv)) + goto error_out; + if (LINUX_S_ISDIR(inode.i_mode)) + iw.names_left += 1; + else + iw.names_left += inode.i_links_count; + } + + iw.num_inodes = argc; + + retval = ext2fs_open_inode_scan(current_fs, 0, &scan); + if (retval) { + com_err("ncheck", retval, "while opening inode scan"); + goto error_out; + } + + do { + retval = ext2fs_get_next_inode(scan, &ino, &inode); + } while (retval == EXT2_ET_BAD_BLOCK_IN_INODE_TABLE); + if (retval) { + com_err("ncheck", retval, "while starting inode scan"); + goto error_out; + } + + printf("Inode\tPathname\n"); + while (ino) { + if (!inode.i_links_count) + goto next; + /* + * To handle filesystems touched by 0.3c extfs; can be + * removed later. + */ + if (inode.i_dtime) + goto next; + /* Ignore anything that isn't a directory */ + if (!LINUX_S_ISDIR(inode.i_mode)) + goto next; + + iw.position = 0; + iw.parent = 0; + iw.dir = ino; + iw.get_pathname_failed = 0; + + retval = ext2fs_dir_iterate(current_fs, ino, 0, 0, + ncheck_proc, &iw); + ext2fs_free_mem(&iw.parent); + if (retval) { + com_err("ncheck", retval, + "while calling ext2_dir_iterate"); + goto next; + } + + if (iw.names_left == 0) + break; + + next: + do { + retval = ext2fs_get_next_inode(scan, &ino, &inode); + } while (retval == EXT2_ET_BAD_BLOCK_IN_INODE_TABLE); + + if (retval) { + com_err("ncheck", retval, + "while doing inode scan"); + goto error_out; + } + } + +error_out: + free(iw.iarray); + if (scan) + ext2fs_close_inode_scan(scan); + return; +} + + + diff --git a/debugfs/quota.c b/debugfs/quota.c new file mode 100644 index 0000000..1da1e03 --- /dev/null +++ b/debugfs/quota.c @@ -0,0 +1,172 @@ +/* + * quota.c --- debugfs quota commands + * + * Copyright (C) 2014 Theodore Ts'o. This file may be redistributed + * under the terms of the GNU Public License. + */ + +#include "config.h" +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <ctype.h> +#include <string.h> +#include <time.h> +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#include <sys/types.h> +#ifdef HAVE_GETOPT_H +#include <getopt.h> +#else +extern int optind; +extern char *optarg; +#endif + +#include "debugfs.h" + +const char *quota_type[] = { "user", "group", "project", NULL }; + +static int load_quota_ctx(char *progname) +{ + errcode_t retval; + + if (check_fs_open(progname)) + return 1; + + if (!ext2fs_has_feature_quota(current_fs->super)) { + com_err(progname, 0, "quota feature not enabled"); + return 1; + } + + if (current_qctx) + return 0; + + retval = quota_init_context(¤t_qctx, current_fs, 0); + if (retval) { + com_err(current_fs->device_name, retval, + "while trying to load quota information"); + return 1; + } + return 0; +} + +static int parse_quota_type(const char *cmdname, const char *str) +{ + errcode_t retval; + char *t; + int flags = 0; + int i; + + for (i = 0; i < MAXQUOTAS; i++) { + if (strcasecmp(str, quota_type[i]) == 0) + break; + } + if (i >= MAXQUOTAS) { + i = strtol(str, &t, 0); + if (*t) + i = -1; + } + if (i < 0 || i >= MAXQUOTAS) { + com_err(0, 0, "Invalid quota type: %s", str); + printf("Valid quota types are: "); + for (i = 0; i < MAXQUOTAS; i++) + printf("%s ", quota_type[i]); + printf("\n"); + return -1; + } + + if (current_fs->flags & EXT2_FLAG_RW) + flags |= EXT2_FILE_WRITE; + + retval = quota_file_open(current_qctx, NULL, 0, i, -1, flags); + if (retval) { + com_err(cmdname, retval, + "while opening quota inode (type %d)", i); + return -1; + } + return i; +} + + +static int list_quota_callback(struct dquot *dq, + void *cb_data EXT2FS_ATTR((unused))) +{ + printf("%10u %8lld %8lld %8lld %8lld %8lld %8lld\n", + dq->dq_id, (long long)dq->dq_dqb.dqb_curspace, + (long long)dq->dq_dqb.dqb_bsoftlimit, + (long long)dq->dq_dqb.dqb_bhardlimit, + (long long)dq->dq_dqb.dqb_curinodes, + (long long)dq->dq_dqb.dqb_isoftlimit, + (long long)dq->dq_dqb.dqb_ihardlimit); + return 0; +} + +void do_list_quota(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + errcode_t retval; + int type; + struct quota_handle *qh; + + if (load_quota_ctx(argv[0])) + return; + + if (argc != 2) { + com_err(0, 0, "Usage: list_quota <quota_type>\n"); + return; + } + + type = parse_quota_type(argv[0], argv[1]); + if (type < 0) + return; + + printf("%7s %2s %8s %8s %8s %8s %8s %8s\n", + quota_type[type], "id", + "space", "quota", "limit", "inodes", "quota", "limit"); + qh = current_qctx->quota_file[type]; + retval = qh->qh_ops->scan_dquots(qh, list_quota_callback, NULL); + if (retval) { + com_err(argv[0], retval, "while scanning dquots"); + return; + } +} + +void do_get_quota(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + int err, type; + struct quota_handle *qh; + struct dquot *dq; + qid_t id; + + if (load_quota_ctx(argv[0])) + return; + + if (argc != 3) { + com_err(0, 0, "Usage: get_quota <quota_type> <id>\n"); + return; + } + + type = parse_quota_type(argv[0], argv[1]); + if (type < 0) + return; + + id = parse_ulong(argv[2], argv[0], "id", &err); + if (err) + return; + + printf("%7s %2s %8s %8s %8s %8s %8s %8s\n", + quota_type[type], "id", + "space", "quota", "limit", "inodes", "quota", "limit"); + + qh = current_qctx->quota_file[type]; + + dq = qh->qh_ops->read_dquot(qh, id); + if (dq) { + list_quota_callback(dq, NULL); + ext2fs_free_mem(&dq); + } else { + com_err(argv[0], 0, "couldn't read quota record"); + } +} diff --git a/debugfs/ro_debug_cmds.ct b/debugfs/ro_debug_cmds.ct new file mode 100644 index 0000000..736ade6 --- /dev/null +++ b/debugfs/ro_debug_cmds.ct @@ -0,0 +1,99 @@ +# +# Restricted set of debugfs commands +# +# Copyright (C) 1993 Theodore Ts'o. This file may be redistributed +# under the terms of the GNU Public License. +# +command_table debug_cmds; + +request do_show_debugfs_params, "Show debugfs parameters", + show_debugfs_params, params; + +request do_open_filesys, "Open a filesystem", + open_filesys, open; + +request do_close_filesys, "Close the filesystem", + close_filesys, close; + +request do_freefrag, "Report free space fragmentation", + freefrag, e2freefrag; + +request do_show_super_stats, "Show superblock statistics", + show_super_stats, stats; + +request do_ncheck, "Do inode->name translation", + ncheck; + +request do_icheck, "Do block->inode translation", + icheck; + +request do_chroot, "Change root directory", + change_root_directory, chroot; + +request do_change_working_dir, "Change working directory", + change_working_directory, cd; + +request do_list_dir, "List directory", + list_directory, ls; + +request do_stat, "Show inode information ", + show_inode_info, stat; + +request do_dump_extents, "Dump extents information ", + dump_extents, extents, ex; + +request do_blocks, "Dump blocks used by an inode ", + blocks; + +request do_filefrag, "Report fragmentation information for an inode", + filefrag; + +request do_testi, "Test an inode's in-use flag", + testi; + +request do_find_free_block, "Find free block(s)", + find_free_block, ffb; + +request do_find_free_inode, "Find free inode(s)", + find_free_inode, ffi; + +request do_print_working_directory, "Print current working directory", + print_working_directory, pwd; + +request do_lsdel, "List deleted inodes", + list_deleted_inodes, lsdel; + +request do_logdump, "Dump the contents of the journal", + logdump; + +request do_htree_dump, "Dump a hash-indexed directory", + htree_dump, htree; + +request do_dx_hash, "Calculate the directory hash of a filename", + dx_hash, hash; + +request do_dirsearch, "Search a directory for a particular filename", + dirsearch; + +request do_bmap, "Calculate the logical->physical block mapping for an inode", + bmap; + +request do_imap, "Calculate the location of an inode", + imap; + +request do_supported_features, "Print features supported by this version of e2fsprogs", + supported_features; + +request do_dump_mmp, "Dump MMP information", + dump_mmp; + +request do_extent_open, "Open inode for extent manipulation", + extent_open, eo; + +request do_list_quota, "List quota", + lost_quota, lq; + +request do_get_quota, "Get quota", + get_quota, gq; + +end; diff --git a/debugfs/set_fields.c b/debugfs/set_fields.c new file mode 100644 index 0000000..f916dea --- /dev/null +++ b/debugfs/set_fields.c @@ -0,0 +1,1024 @@ +/* + * set_fields.c --- set a superblock value + * + * Copyright (C) 2000, 2001, 2002, 2003, 2004 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 strptime() and strtoull */ + +#ifdef HAVE_STRTOULL +#define STRTOULL strtoull +#else +#define STRTOULL strtoul +#endif + +#include "config.h" +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <ctype.h> +#include <string.h> +#include <strings.h> +#include <time.h> +#include <sys/types.h> +#include <sys/stat.h> +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#include <assert.h> +#if HAVE_STRINGS_H +#include <strings.h> +#endif +#include <fcntl.h> +#include <utime.h> + +#include "debugfs.h" +#include "uuid/uuid.h" +#include "e2p/e2p.h" +#include "support/quotaio.h" + +static struct ext2_super_block set_sb; +static struct ext2_inode_large set_inode; +static struct ext2_group_desc set_gd; +static struct ext4_group_desc set_gd4; +static struct mmp_struct set_mmp; +static dgrp_t set_bg; +static ext2_ino_t set_ino; +static int array_idx; + +#define FLAG_ARRAY 0x0001 +#define FLAG_ALIAS 0x0002 /* Data intersects with other field */ +#define FLAG_CSUM 0x0004 + +struct field_set_info { + const char *name; + void *ptr; + void *ptr2; + unsigned int size; + errcode_t (*func)(struct field_set_info *info, char *field, char *arg); + int flags; + int max_idx; +}; + +static errcode_t parse_uint(struct field_set_info *info, char *field, char *arg); +static errcode_t parse_int(struct field_set_info *info, char *field, char *arg); +static errcode_t parse_string(struct field_set_info *info, char *field, char *arg); +static errcode_t parse_uuid(struct field_set_info *info, char *field, char *arg); +static errcode_t parse_hashalg(struct field_set_info *info, char *field, char *arg); +static errcode_t parse_encoding(struct field_set_info *info, char *field, char *arg); +static errcode_t parse_time(struct field_set_info *info, char *field, char *arg); +static errcode_t parse_bmap(struct field_set_info *info, char *field, char *arg); +static errcode_t parse_gd_csum(struct field_set_info *info, char *field, char *arg); +static errcode_t parse_inode_csum(struct field_set_info *info, char *field, + char *arg); +static errcode_t parse_mmp_clear(struct field_set_info *info, char *field, + char *arg); + +#if __GNUC_PREREQ (4, 6) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif + +static struct field_set_info super_fields[] = { + { "inodes_count", &set_sb.s_inodes_count, NULL, 4, parse_uint }, + { "blocks_count", &set_sb.s_blocks_count, &set_sb.s_blocks_count_hi, + 4, parse_uint }, + { "r_blocks_count", &set_sb.s_r_blocks_count, + &set_sb.s_r_blocks_count_hi, 4, parse_uint }, + { "free_blocks_count", &set_sb.s_free_blocks_count, + &set_sb.s_free_blocks_hi, 4, parse_uint }, + { "free_inodes_count", &set_sb.s_free_inodes_count, NULL, 4, parse_uint }, + { "first_data_block", &set_sb.s_first_data_block, NULL, 4, parse_uint }, + { "log_block_size", &set_sb.s_log_block_size, NULL, 4, parse_uint }, + { "log_cluster_size", &set_sb.s_log_cluster_size, NULL, 4, parse_int }, + { "blocks_per_group", &set_sb.s_blocks_per_group, NULL, 4, parse_uint }, + { "clusters_per_group", &set_sb.s_clusters_per_group, NULL, 4, parse_uint }, + { "inodes_per_group", &set_sb.s_inodes_per_group, NULL, 4, parse_uint }, + { "mtime", &set_sb.s_mtime, NULL, 4, parse_time }, + { "wtime", &set_sb.s_wtime, NULL, 4, parse_time }, + { "mnt_count", &set_sb.s_mnt_count, NULL, 2, parse_uint }, + { "max_mnt_count", &set_sb.s_max_mnt_count, NULL, 2, parse_int }, + /* s_magic */ + { "state", &set_sb.s_state, NULL, 2, parse_uint }, + { "errors", &set_sb.s_errors, NULL, 2, parse_uint }, + { "minor_rev_level", &set_sb.s_minor_rev_level, NULL, 2, parse_uint }, + { "lastcheck", &set_sb.s_lastcheck, NULL, 4, parse_time }, + { "checkinterval", &set_sb.s_checkinterval, NULL, 4, parse_uint }, + { "creator_os", &set_sb.s_creator_os, NULL, 4, parse_uint }, + { "rev_level", &set_sb.s_rev_level, NULL, 4, parse_uint }, + { "def_resuid", &set_sb.s_def_resuid, NULL, 2, parse_uint }, + { "def_resgid", &set_sb.s_def_resgid, NULL, 2, parse_uint }, + { "first_ino", &set_sb.s_first_ino, NULL, 4, parse_uint }, + { "inode_size", &set_sb.s_inode_size, NULL, 2, parse_uint }, + { "block_group_nr", &set_sb.s_block_group_nr, NULL, 2, parse_uint }, + { "feature_compat", &set_sb.s_feature_compat, NULL, 4, parse_uint }, + { "feature_incompat", &set_sb.s_feature_incompat, NULL, 4, parse_uint }, + { "feature_ro_compat", &set_sb.s_feature_ro_compat, NULL, 4, parse_uint }, + { "uuid", &set_sb.s_uuid, NULL, 16, parse_uuid }, + { "volume_name", &set_sb.s_volume_name, NULL, 16, parse_string }, + { "last_mounted", &set_sb.s_last_mounted, NULL, 64, parse_string }, + { "algorithm_usage_bitmap", &set_sb.s_algorithm_usage_bitmap, NULL, + 4, parse_uint }, + { "prealloc_blocks", &set_sb.s_prealloc_blocks, NULL, 1, parse_uint }, + { "prealloc_dir_blocks", &set_sb.s_prealloc_dir_blocks, NULL, 1, + parse_uint }, + { "reserved_gdt_blocks", &set_sb.s_reserved_gdt_blocks, NULL, 2, + parse_uint }, + { "journal_uuid", &set_sb.s_journal_uuid, NULL, 16, parse_uuid }, + { "journal_inum", &set_sb.s_journal_inum, NULL, 4, parse_uint }, + { "journal_dev", &set_sb.s_journal_dev, NULL, 4, parse_uint }, + { "last_orphan", &set_sb.s_last_orphan, NULL, 4, parse_uint }, + { "hash_seed", &set_sb.s_hash_seed, NULL, 16, parse_uuid }, + { "def_hash_version", &set_sb.s_def_hash_version, NULL, 1, parse_hashalg }, + { "jnl_backup_type", &set_sb.s_jnl_backup_type, NULL, 1, parse_uint }, + { "desc_size", &set_sb.s_desc_size, NULL, 2, parse_uint }, + { "default_mount_opts", &set_sb.s_default_mount_opts, NULL, 4, parse_uint }, + { "first_meta_bg", &set_sb.s_first_meta_bg, NULL, 4, parse_uint }, + { "mkfs_time", &set_sb.s_mkfs_time, NULL, 4, parse_time }, + { "jnl_blocks", &set_sb.s_jnl_blocks[0], NULL, 4, parse_uint, FLAG_ARRAY, + 17 }, + { "min_extra_isize", &set_sb.s_min_extra_isize, NULL, 2, parse_uint }, + { "want_extra_isize", &set_sb.s_want_extra_isize, NULL, 2, parse_uint }, + { "flags", &set_sb.s_flags, NULL, 4, parse_uint }, + { "raid_stride", &set_sb.s_raid_stride, NULL, 2, parse_uint }, + { "mmp_interval", &set_sb.s_mmp_update_interval, NULL, 2, parse_uint }, + { "mmp_block", &set_sb.s_mmp_block, NULL, 8, parse_uint }, + { "raid_stripe_width", &set_sb.s_raid_stripe_width, NULL, 4, parse_uint }, + { "log_groups_per_flex", &set_sb.s_log_groups_per_flex, NULL, 1, parse_uint }, + { "kbytes_written", &set_sb.s_kbytes_written, NULL, 8, parse_uint }, + { "snapshot_inum", &set_sb.s_snapshot_inum, NULL, 4, parse_uint }, + { "snapshot_id", &set_sb.s_snapshot_id, NULL, 4, parse_uint }, + { "snapshot_r_blocks_count", &set_sb.s_snapshot_r_blocks_count, + NULL, 8, parse_uint }, + { "snapshot_list", &set_sb.s_snapshot_list, NULL, 4, parse_uint }, + { "mount_opts", &set_sb.s_mount_opts, NULL, 64, parse_string }, + { "usr_quota_inum", &set_sb.s_usr_quota_inum, NULL, 4, parse_uint }, + { "grp_quota_inum", &set_sb.s_grp_quota_inum, NULL, 4, parse_uint }, + { "prj_quota_inum", &set_sb.s_prj_quota_inum, NULL, 4, parse_uint }, + { "overhead_clusters", &set_sb.s_overhead_clusters, NULL, 4, parse_uint }, + { "backup_bgs", &set_sb.s_backup_bgs[0], NULL, 4, parse_uint, + FLAG_ARRAY, 2 }, + { "checksum", &set_sb.s_checksum, NULL, 4, parse_uint }, + { "checksum_type", &set_sb.s_checksum_type, NULL, 1, parse_uint }, + { "encryption_level", &set_sb.s_encryption_level, NULL, 1, parse_uint }, + { "error_count", &set_sb.s_error_count, NULL, 4, parse_uint }, + { "first_error_time", &set_sb.s_first_error_time, NULL, 4, parse_time }, + { "first_error_ino", &set_sb.s_first_error_ino, NULL, 4, parse_uint }, + { "first_error_block", &set_sb.s_first_error_block, NULL, 8, parse_uint }, + { "first_error_func", &set_sb.s_first_error_func, NULL, 32, parse_string }, + { "first_error_line", &set_sb.s_first_error_line, NULL, 4, parse_uint }, + { "last_error_time", &set_sb.s_last_error_time, NULL, 4, parse_time }, + { "last_error_ino", &set_sb.s_last_error_ino, NULL, 4, parse_uint }, + { "last_error_block", &set_sb.s_last_error_block, NULL, 8, parse_uint }, + { "last_error_func", &set_sb.s_last_error_func, NULL, 32, parse_string }, + { "last_error_line", &set_sb.s_last_error_line, NULL, 4, parse_uint }, + { "encrypt_algos", &set_sb.s_encrypt_algos, NULL, 1, parse_uint, + FLAG_ARRAY, 4 }, + { "encrypt_pw_salt", &set_sb.s_encrypt_pw_salt, NULL, 16, parse_uuid }, + { "lpf_ino", &set_sb.s_lpf_ino, NULL, 4, parse_uint }, + { "checksum_seed", &set_sb.s_checksum_seed, NULL, 4, parse_uint }, + { "encoding", &set_sb.s_encoding, NULL, 2, parse_encoding }, + { "orphan_file_inum", &set_sb.s_orphan_file_inum, NULL, 4, parse_uint }, + { 0, 0, 0, 0 } +}; + +static struct field_set_info inode_fields[] = { + { "mode", &set_inode.i_mode, NULL, 2, parse_uint }, + { "uid", &set_inode.i_uid, &set_inode.osd2.linux2.l_i_uid_high, + 2, parse_uint }, + { "size", &set_inode.i_size, &set_inode.i_size_high, 4, parse_uint }, + { "atime", &set_inode.i_atime, &set_inode.i_atime_extra, + 4, parse_time }, + { "ctime", &set_inode.i_ctime, &set_inode.i_ctime_extra, + 4, parse_time }, + { "mtime", &set_inode.i_mtime, &set_inode.i_mtime_extra, + 4, parse_time }, + { "dtime", &set_inode.i_dtime, NULL, + 4, parse_time }, + { "gid", &set_inode.i_gid, &set_inode.osd2.linux2.l_i_gid_high, + 2, parse_uint }, + { "links_count", &set_inode.i_links_count, NULL, 2, parse_uint }, + /* Special case: i_blocks is 4 bytes, i_blocks_high is 2 bytes */ + { "blocks", &set_inode.i_blocks, &set_inode.osd2.linux2.l_i_blocks_hi, + 6, parse_uint }, + { "flags", &set_inode.i_flags, NULL, 4, parse_uint }, + { "version", &set_inode.osd1.linux1.l_i_version, + &set_inode.i_version_hi, 4, parse_uint }, + { "translator", &set_inode.osd1.hurd1.h_i_translator, NULL, + 4, parse_uint, FLAG_ALIAS }, + { "block", &set_inode.i_block[0], NULL, 4, parse_uint, FLAG_ARRAY, + EXT2_NDIR_BLOCKS }, + { "block[IND]", &set_inode.i_block[EXT2_IND_BLOCK], NULL, 4, parse_uint }, + { "block[DIND]", &set_inode.i_block[EXT2_DIND_BLOCK], NULL, 4, parse_uint }, + { "block[TIND]", &set_inode.i_block[EXT2_TIND_BLOCK], NULL, 4, parse_uint }, + { "generation", &set_inode.i_generation, NULL, 4, parse_uint }, + /* Special case: i_file_acl_high is 2 bytes */ + { "file_acl", &set_inode.i_file_acl, + &set_inode.osd2.linux2.l_i_file_acl_high, 6, parse_uint }, + { "faddr", &set_inode.i_faddr, NULL, 4, parse_uint }, + { "frag", &set_inode.osd2.hurd2.h_i_frag, NULL, 1, parse_uint, FLAG_ALIAS }, + { "fsize", &set_inode.osd2.hurd2.h_i_fsize, NULL, 1, parse_uint }, + { "checksum", &set_inode.osd2.linux2.l_i_checksum_lo, + &set_inode.i_checksum_hi, 2, parse_inode_csum, FLAG_CSUM }, + { "author", &set_inode.osd2.hurd2.h_i_author, NULL, + 4, parse_uint, FLAG_ALIAS }, + { "extra_isize", &set_inode.i_extra_isize, NULL, + 2, parse_uint }, + { "ctime_extra", &set_inode.i_ctime_extra, NULL, + 4, parse_uint, FLAG_ALIAS }, + { "mtime_extra", &set_inode.i_mtime_extra, NULL, + 4, parse_uint, FLAG_ALIAS }, + { "atime_extra", &set_inode.i_atime_extra, NULL, + 4, parse_uint, FLAG_ALIAS }, + { "crtime", &set_inode.i_crtime, &set_inode.i_crtime_extra, + 4, parse_time }, + { "crtime_extra", &set_inode.i_crtime_extra, NULL, + 4, parse_uint, FLAG_ALIAS }, + { "projid", &set_inode.i_projid, NULL, 4, parse_uint }, + { "bmap", NULL, NULL, 4, parse_bmap, FLAG_ARRAY }, + { 0, 0, 0, 0 } +}; + +static struct field_set_info ext2_bg_fields[] = { + { "block_bitmap", &set_gd.bg_block_bitmap, NULL, 4, parse_uint }, + { "inode_bitmap", &set_gd.bg_inode_bitmap, NULL, 4, parse_uint }, + { "inode_table", &set_gd.bg_inode_table, NULL, 4, parse_uint }, + { "free_blocks_count", &set_gd.bg_free_blocks_count, NULL, 2, parse_uint }, + { "free_inodes_count", &set_gd.bg_free_inodes_count, NULL, 2, parse_uint }, + { "used_dirs_count", &set_gd.bg_used_dirs_count, NULL, 2, parse_uint }, + { "flags", &set_gd.bg_flags, NULL, 2, parse_uint }, + { "itable_unused", &set_gd.bg_itable_unused, NULL, 2, parse_uint }, + { "checksum", &set_gd.bg_checksum, NULL, 2, parse_gd_csum }, + { 0, 0, 0, 0 } +}; + +static struct field_set_info ext4_bg_fields[] = { + { "block_bitmap", &set_gd4.bg_block_bitmap, + &set_gd4.bg_block_bitmap_hi, 4, parse_uint }, + { "inode_bitmap", &set_gd4.bg_inode_bitmap, + &set_gd4.bg_inode_bitmap_hi, 4, parse_uint }, + { "inode_table", &set_gd4.bg_inode_table, + &set_gd4.bg_inode_table_hi, 4, parse_uint }, + { "free_blocks_count", &set_gd4.bg_free_blocks_count, + &set_gd4.bg_free_blocks_count_hi, 2, parse_uint }, + { "free_inodes_count", &set_gd4.bg_free_inodes_count, + &set_gd4.bg_free_inodes_count_hi, 2, parse_uint }, + { "used_dirs_count", &set_gd4.bg_used_dirs_count, + &set_gd4.bg_used_dirs_count_hi, 2, parse_uint }, + { "flags", &set_gd4.bg_flags, NULL, 2, parse_uint }, + { "exclude_bitmap", &set_gd4.bg_exclude_bitmap_lo, + &set_gd4.bg_exclude_bitmap_hi, 4, parse_uint }, + { "block_bitmap_csum", &set_gd4.bg_block_bitmap_csum_lo, + &set_gd4.bg_block_bitmap_csum_hi, 2, parse_uint }, + { "inode_bitmap_csum", &set_gd4.bg_inode_bitmap_csum_lo, + &set_gd4.bg_inode_bitmap_csum_hi, 2, parse_uint }, + { "itable_unused", &set_gd4.bg_itable_unused, + &set_gd4.bg_itable_unused_hi, 2, parse_uint }, + { "checksum", &set_gd4.bg_checksum, NULL, 2, parse_gd_csum }, + { 0, 0, 0, 0 } +}; + +static struct field_set_info mmp_fields[] = { + { "clear", &set_mmp.mmp_magic, NULL, sizeof(set_mmp), + parse_mmp_clear, FLAG_ALIAS }, + { "magic", &set_mmp.mmp_magic, NULL, 4, parse_uint }, + { "seq", &set_mmp.mmp_seq, NULL, 4, parse_uint }, + { "time", &set_mmp.mmp_time, NULL, 8, parse_uint }, + { "nodename", &set_mmp.mmp_nodename, NULL, sizeof(set_mmp.mmp_nodename), + parse_string }, + { "bdevname", &set_mmp.mmp_bdevname, NULL, sizeof(set_mmp.mmp_bdevname), + parse_string }, + { "check_interval", &set_mmp.mmp_check_interval, NULL, 2, parse_uint }, + { "checksum", &set_mmp.mmp_checksum, NULL, 4, parse_uint }, + { 0, 0, 0, 0 } +}; +#if __GNUC_PREREQ (4, 6) +#pragma GCC diagnostic pop +#endif + +#ifdef UNITTEST + + +static void do_verify_field_set_info(struct field_set_info *fields, + const void *data, size_t size) +{ + struct field_set_info *ss, *ss2; + const char *begin = (char *)data; + const char *end = begin + size; + + for (ss = fields ; ss->name ; ss++) { + const char *ptr; + + /* Check pointers */ + ptr = ss->ptr; + assert(!ptr || (ptr >= begin && ptr < end)); + ptr = ss->ptr2; + assert(!ptr || (ptr >= begin && ptr < end)); + + /* Check function */ + assert(ss->func); + + for (ss2 = fields ; ss2 != ss ; ss2++) { + /* Check duplicate names */ + assert(strcmp(ss->name, ss2->name)); + + if (ss->flags & FLAG_ALIAS || ss2->flags & FLAG_ALIAS) + continue; + /* Check false aliases, might be copy-n-paste error */ + assert(!ss->ptr || (ss->ptr != ss2->ptr && + ss->ptr != ss2->ptr2)); + assert(!ss->ptr2 || (ss->ptr2 != ss2->ptr && + ss->ptr2 != ss2->ptr2)); + } + } +} + +int main(int argc, char **argv) +{ + do_verify_field_set_info(super_fields, &set_sb, sizeof(set_sb)); + do_verify_field_set_info(inode_fields, &set_inode, sizeof(set_inode)); + do_verify_field_set_info(ext2_bg_fields, &set_gd, sizeof(set_gd)); + do_verify_field_set_info(ext4_bg_fields, &set_gd4, sizeof(set_gd4)); + do_verify_field_set_info(mmp_fields, &set_mmp, sizeof(set_mmp)); + return 0; +} + +ext2_filsys current_fs; +ext2_ino_t root, cwd; + +#endif /* UNITTEST */ + +static int check_suffix(const char *field) +{ + int len = strlen(field); + + if (len <= 3) + return 0; + field += len-3; + if (!strcmp(field, "_lo")) + return 1; + if (!strcmp(field, "_hi")) + return 2; + return 0; +} + +static struct field_set_info *find_field(struct field_set_info *fields, + char *field) +{ + struct field_set_info *ss; + const char *prefix; + char *arg, *delim, *idx, *tmp; + int suffix, prefix_len; + + if (fields == super_fields) + prefix = "s_"; + else if (fields == inode_fields) + prefix = "i_"; + else + prefix = "bg_"; + prefix_len = strlen(prefix); + if (strncmp(field, prefix, prefix_len) == 0) + field += prefix_len; + + arg = malloc(strlen(field)+1); + if (!arg) + return NULL; + strcpy(arg, field); + + idx = strchr(arg, '['); + if (idx) { + *idx++ = 0; + delim = idx + strlen(idx) - 1; + if (!*idx || *delim != ']') + idx = 0; + else + *delim = 0; + } + /* + * Can we parse the number? + */ + if (idx) { + array_idx = strtol(idx, &tmp, 0); + if (*tmp) { + *(--idx) = '['; + *delim = ']'; + idx = 0; + } + } + + /* + * If there is a valid _hi or a _lo suffix, strip it off + */ + suffix = check_suffix(arg); + if (suffix > 0) + arg[strlen(arg)-3] = 0; + + for (ss = fields ; ss->name ; ss++) { + if (suffix && ss->ptr2 == 0) + continue; + if (ss->flags & FLAG_ARRAY) { + if (!idx || (strcmp(ss->name, arg) != 0)) + continue; + if (ss->max_idx > 0 && array_idx >= ss->max_idx) + continue; + } else { + if (strcmp(ss->name, arg) != 0) + continue; + } + free(arg); + return ss; + } + free(arg); + return NULL; +} + +/* + * Note: info->size == 6 is special; this means a base size 4 bytes, + * and secondary (high) size of 2 bytes. This is needed for the + * special case of i_blocks_high and i_file_acl_high. + */ +static errcode_t parse_uint(struct field_set_info *info, char *field, + char *arg) +{ + unsigned long long n, num, mask, limit; + int suffix = check_suffix(field); + char *tmp; + void *field1 = info->ptr, *field2 = info->ptr2; + int size = (info->size == 6) ? 4 : info->size; + union { + __u64 *ptr64; + __u32 *ptr32; + __u16 *ptr16; + __u8 *ptr8; + } u; + + if (suffix == 1) + field2 = 0; + if (suffix == 2) { + field1 = field2; + field2 = 0; + } + + u.ptr8 = (__u8 *) field1; + if (info->flags & FLAG_ARRAY) + u.ptr8 += array_idx * info->size; + + errno = 0; + num = STRTOULL(arg, &tmp, 0); + if (*tmp || errno) { + fprintf(stderr, "Couldn't parse '%s' for field %s.\n", + arg, info->name); + return EINVAL; + } + mask = ~0ULL >> ((8 - size) * 8); + limit = ~0ULL >> ((8 - info->size) * 8); + if (field2 && info->size != 6) + limit = ~0ULL >> ((8 - info->size*2) * 8); + + if (num > limit) { + fprintf(stderr, "Value '%s' exceeds field %s maximum %llu.\n", + arg, info->name, limit); + return EINVAL; + } + n = num & mask; + switch (size) { + case 8: + *u.ptr64 = n; + break; + case 4: + *u.ptr32 = n; + break; + case 2: + *u.ptr16 = n; + break; + case 1: + *u.ptr8 = n; + break; + } + if (!field2) + return 0; + n = (size == 8) ? 0 : (num >> (size*8)); + u.ptr8 = (__u8 *) field2; + if (info->size == 6) + size = 2; + switch (size) { + case 8: + /* Should never get here */ + fprintf(stderr, "64-bit field %s has a second 64-bit field\n" + "defined; BUG?!?\n", info->name); + *u.ptr64 = 0; + break; + case 4: + *u.ptr32 = n; + break; + case 2: + *u.ptr16 = n; + break; + case 1: + *u.ptr8 = n; + break; + } + return 0; +} + +static errcode_t parse_int(struct field_set_info *info, + char *field EXT2FS_ATTR((unused)), char *arg) +{ + long num; + char *tmp; + __s32 *ptr32; + __s16 *ptr16; + __s8 *ptr8; + + num = strtol(arg, &tmp, 0); + if (*tmp) { + fprintf(stderr, "Couldn't parse '%s' for field %s.\n", + arg, info->name); + return EINVAL; + } + switch (info->size) { + case 4: + ptr32 = (__s32 *) info->ptr; + *ptr32 = num; + break; + case 2: + ptr16 = (__s16 *) info->ptr; + *ptr16 = num; + break; + case 1: + ptr8 = (__s8 *) info->ptr; + *ptr8 = num; + break; + } + return 0; +} + +static errcode_t parse_string(struct field_set_info *info, + char *field EXT2FS_ATTR((unused)), char *arg) +{ + char *cp = (char *) info->ptr; + + if (strlen(arg) >= info->size) { + fprintf(stderr, "Error maximum size for %s is %d.\n", + info->name, info->size); + return EINVAL; + } + strcpy(cp, arg); + return 0; +} + +static errcode_t parse_time(struct field_set_info *info, + char *field, char *arg) +{ + __s64 t; + __u32 t_low, t_high; + __u32 *ptr_low, *ptr_high; + + if (check_suffix(field)) + return parse_uint(info, field, arg); + + ptr_low = (__u32 *) info->ptr; + ptr_high = (__u32 *) info->ptr2; + + t = string_to_time(arg); + + if (t == -1) { + fprintf(stderr, "Couldn't parse '%s' for field %s.\n", + arg, info->name); + return EINVAL; + } + t_low = (__u32) t; + t_high = ((t - (__s32)t) >> 32) & EXT4_EPOCH_MASK; + *ptr_low = t_low; + if (ptr_high) + *ptr_high = (*ptr_high & ~EXT4_EPOCH_MASK) | t_high; + return 0; +} + +static errcode_t parse_uuid(struct field_set_info *info, + char *field EXT2FS_ATTR((unused)), char *arg) +{ + unsigned char * p = (unsigned char *) info->ptr; + + if ((strcasecmp(arg, "null") == 0) || + (strcasecmp(arg, "clear") == 0)) { + uuid_clear(p); + } else if (strcasecmp(arg, "time") == 0) { + uuid_generate_time(p); + } else if (strcasecmp(arg, "random") == 0) { + uuid_generate(p); + } else if (uuid_parse(arg, p)) { + fprintf(stderr, "Invalid UUID format: %s\n", arg); + return EINVAL; + } + return 0; +} + +static errcode_t parse_hashalg(struct field_set_info *info, + char *field EXT2FS_ATTR((unused)), char *arg) +{ + int hashv; + unsigned char *p = (unsigned char *) info->ptr; + + hashv = e2p_string2hash(arg); + if (hashv < 0) { + fprintf(stderr, "Invalid hash algorithm: %s\n", arg); + return EINVAL; + } + *p = hashv; + return 0; +} + +static errcode_t parse_encoding(struct field_set_info *info, + char *field EXT2FS_ATTR((unused)), char *arg) +{ + int encoding; + unsigned char *p = (unsigned char *) info->ptr; + + encoding = e2p_str2encoding(arg); + if (encoding < 0) + return parse_uint(info, field, arg); + *p = encoding; + return 0; +} + +static errcode_t parse_bmap(struct field_set_info *info, + char *field EXT2FS_ATTR((unused)), char *arg) +{ + blk64_t blk; + errcode_t retval; + char *tmp; + + blk = strtoull(arg, &tmp, 0); + if (*tmp) { + fprintf(stderr, "Couldn't parse '%s' for field %s.\n", + arg, info->name); + return EINVAL; + } + + retval = ext2fs_bmap2(current_fs, set_ino, + (struct ext2_inode *) &set_inode, + NULL, BMAP_ALLOC | BMAP_SET, array_idx, NULL, + &blk); + if (retval) { + com_err("set_inode", retval, "while setting block map"); + } + return retval; +} + +static errcode_t parse_gd_csum(struct field_set_info *info, char *field, + char *arg) +{ + __u16 *checksum = info->ptr; + + if (strcmp(arg, "calc") == 0) { + *checksum = ext2fs_group_desc_csum(current_fs, set_bg); + printf("Checksum set to 0x%04x\n", *checksum); + return 0; + } + return parse_uint(info, field, arg); +} + +static errcode_t parse_inode_csum(struct field_set_info *info, char *field, + char *arg) +{ + errcode_t retval = 0; + __u32 crc; + int is_large_inode = 0; + + if (strcmp(arg, "calc") == 0) { + size_t sz = EXT2_INODE_SIZE(current_fs->super); + struct ext2_inode_large *tmp_inode = NULL; + + retval = ext2fs_get_mem(sz, &tmp_inode); + if (retval) + goto out; + + retval = ext2fs_read_inode_full(current_fs, set_ino, + (struct ext2_inode *) tmp_inode, + sz); + if (retval) + goto out; + +#ifdef WORDS_BIGENDIAN + ext2fs_swap_inode_full(current_fs, tmp_inode, + tmp_inode, 1, sz); +#endif + + if (sz > EXT2_GOOD_OLD_INODE_SIZE) + is_large_inode = 1; + + retval = ext2fs_inode_csum_set(current_fs, set_ino, + tmp_inode); + if (retval) + goto out; +#ifdef WORDS_BIGENDIAN + crc = set_inode.i_checksum_lo = + ext2fs_swab16(tmp_inode->i_checksum_lo); + +#else + crc = set_inode.i_checksum_lo = tmp_inode->i_checksum_lo; +#endif + if (is_large_inode && + set_inode.i_extra_isize >= + (offsetof(struct ext2_inode_large, + i_checksum_hi) - + EXT2_GOOD_OLD_INODE_SIZE)) { +#ifdef WORDS_BIGENDIAN + set_inode.i_checksum_lo = + ext2fs_swab16(tmp_inode->i_checksum_lo); +#else + set_inode.i_checksum_hi = tmp_inode->i_checksum_hi; +#endif + crc |= ((__u32)set_inode.i_checksum_hi) << 16; + } + printf("Checksum set to 0x%08x\n", crc); + out: + ext2fs_free_mem(&tmp_inode); + return retval; + } + return parse_uint(info, field, arg); +} + +static void print_possible_fields(struct field_set_info *fields) +{ + struct field_set_info *ss; + const char *type, *cmd; + FILE *f; + char name[40], idx[40]; + + if (fields == super_fields) { + type = "Superblock"; + cmd = "set_super_value"; + } else if (fields == inode_fields) { + type = "Inode"; + cmd = "set_inode"; + } else if (fields == mmp_fields) { + type = "MMP"; + cmd = "set_mmp_value"; + } else { + type = "Block group descriptor"; + cmd = "set_block_group"; + } + f = open_pager(); + + fprintf(f, "%s fields supported by the %s command:\n", type, cmd); + + for (ss = fields ; ss->name ; ss++) { + type = "unknown"; + if (ss->func == parse_string) + type = "string"; + else if (ss->func == parse_int) + type = "integer"; + else if (ss->func == parse_uint) + type = "unsigned integer"; + else if (ss->func == parse_uuid) + type = "UUID"; + else if (ss->func == parse_hashalg) + type = "hash algorithm"; + else if (ss->func == parse_time) + type = "date/time"; + else if (ss->func == parse_bmap) + type = "set physical->logical block map"; + else if (ss->func == parse_gd_csum) + type = "unsigned integer OR \"calc\""; + strcpy(name, ss->name); + if (ss->flags & FLAG_ARRAY) { + if (ss->max_idx > 0) + sprintf(idx, "[%d]", ss->max_idx); + else + strcpy(idx, "[]"); + strcat(name, idx); + } + if (ss->ptr2) + strcat(name, "[_hi|_lo]"); + fprintf(f, "\t%-25s\t%s\n", name, type); + } + close_pager(f); +} + + +void do_set_super(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + const char *usage = "<field> <value>\n" + "\t\"set_super_value -l\" will list the names of " + "superblock fields\n\twhich can be set."; + static struct field_set_info *ss; + + if ((argc == 2) && !strcmp(argv[1], "-l")) { + print_possible_fields(super_fields); + return; + } + + if (common_args_process(argc, argv, 3, 3, "set_super_value", + usage, CHECK_FS_RW)) + return; + + if ((ss = find_field(super_fields, argv[1])) == 0) { + com_err(argv[0], 0, "invalid field specifier: %s", argv[1]); + return; + } + set_sb = *current_fs->super; + if (ss->func(ss, argv[1], argv[2]) == 0) { + *current_fs->super = set_sb; + ext2fs_mark_super_dirty(current_fs); + } +} + +void do_set_inode(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + const char *usage = "<inode> <field> <value>\n" + "\t\"set_inode_field -l\" will list the names of " + "the fields in an ext2 inode\n\twhich can be set."; + static struct field_set_info *ss; + + if ((argc == 2) && !strcmp(argv[1], "-l")) { + print_possible_fields(inode_fields); + return; + } + + if (common_args_process(argc, argv, 4, 4, "set_inode", + usage, CHECK_FS_RW)) + return; + + if ((ss = find_field(inode_fields, argv[2])) == 0) { + com_err(argv[0], 0, "invalid field specifier: %s", argv[2]); + return; + } + + set_ino = string_to_inode(argv[1]); + if (!set_ino) + return; + + if (debugfs_read_inode2(set_ino, + (struct ext2_inode *) &set_inode, argv[1], + sizeof(set_inode), + (ss->flags & FLAG_CSUM) ? + READ_INODE_NOCSUM : 0)) + return; + + if (ss->func(ss, argv[2], argv[3]) == 0) { + debugfs_write_inode2(set_ino, + (struct ext2_inode *) &set_inode, + argv[1], sizeof(set_inode), + (ss->flags & FLAG_CSUM) ? + WRITE_INODE_NOCSUM : 0); + } +} + +void do_set_block_group_descriptor(int argc, char *argv[], + int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + const char *usage = "<bg number> <field> <value>\n" + "\t\"set_block_group -l\" will list the names of " + "the fields in a block group descriptor\n\twhich can be set."; + struct field_set_info *table; + struct field_set_info *ss; + char *end; + void *edit, *target; + int size; + + /* + * Determine whether we are editing an ext2 or ext4 block group + * descriptor. Descriptors larger than ext4_group_desc cannot + * have their fields edited yet, because they do not have any + * names assigned. When that happens, this function needs to + * be updated for the new descriptor struct and fields. + */ + if (current_fs && + EXT2_DESC_SIZE(current_fs->super) >= EXT2_MIN_DESC_SIZE_64BIT) { + table = ext4_bg_fields; + edit = &set_gd4; + size = sizeof(set_gd4); + } else { + table = ext2_bg_fields; + edit = &set_gd; + size = sizeof(set_gd); + } + + if ((argc == 2) && !strcmp(argv[1], "-l")) { + print_possible_fields(table); + return; + } + + if (common_args_process(argc, argv, 4, 4, "set_block_group", + usage, CHECK_FS_RW)) + return; + + set_bg = strtoul(argv[1], &end, 0); + if (*end) { + com_err(argv[0], 0, "invalid block group number: %s", argv[1]); + return; + } + + if (set_bg >= current_fs->group_desc_count) { + com_err(argv[0], 0, "block group number too big: %d", set_bg); + return; + } + + if ((ss = find_field(table, argv[2])) == 0) { + com_err(argv[0], 0, "invalid field specifier: %s", argv[2]); + return; + } + + target = ext2fs_group_desc(current_fs, current_fs->group_desc, set_bg); + memcpy(edit, target, size); + if (ss->func(ss, argv[2], argv[3]) == 0) { + memcpy(target, edit, size); + ext2fs_mark_super_dirty(current_fs); + } +} + +static errcode_t parse_mmp_clear(struct field_set_info *info, + char *field EXT2FS_ATTR((unused)), + char *arg EXT2FS_ATTR((unused))) +{ + errcode_t retval; + + retval = ext2fs_mmp_clear(current_fs); + if (retval != 0) + com_err("set_mmp_value", retval, "while clearing MMP block\n"); + else + memcpy(info->ptr, current_fs->mmp_buf, info->size); + + return 1; /* we don't need the MMP block written again */ +} + +#ifdef CONFIG_MMP +void do_set_mmp_value(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + const char *usage = "<field> <value>\n" + "\t\"set_mmp_value -l\" will list the names of " + "MMP fields\n\twhich can be set."; + static struct field_set_info *smmp; + struct mmp_struct *mmp_s; + errcode_t retval; + + if (argc == 2 && strcmp(argv[1], "-l") == 0) { + print_possible_fields(mmp_fields); + return; + } + + if (check_fs_open(argv[0])) + return; + + if (current_fs->super->s_mmp_block == 0) { + com_err(argv[0], 0, "no MMP block allocated\n"); + return; + } + + if (common_args_process(argc, argv, 2, 3, "set_mmp_value", + usage, CHECK_FS_RW)) + return; + + mmp_s = current_fs->mmp_buf; + if (mmp_s == NULL) { + retval = ext2fs_get_mem(current_fs->blocksize, &mmp_s); + if (retval) { + com_err(argv[0], retval, "allocating MMP buffer\n"); + return; + } + retval = ext2fs_mmp_read(current_fs, + current_fs->super->s_mmp_block, mmp_s); + if (retval) { + com_err(argv[0], retval, "reading MMP block %llu.\n", + (long long)current_fs->super->s_mmp_block); + ext2fs_free_mem(&mmp_s); + return; + } + current_fs->mmp_buf = mmp_s; + } + + smmp = find_field(mmp_fields, argv[1]); + if (smmp == 0) { + com_err(argv[0], 0, "invalid field specifier: %s", argv[1]); + return; + } + + set_mmp = *mmp_s; + if (smmp->func(smmp, argv[1], argv[2]) == 0) { + ext2fs_mmp_write(current_fs, current_fs->super->s_mmp_block, + &set_mmp); + *mmp_s = set_mmp; + } +} +#else +void do_set_mmp_value(int argc EXT2FS_ATTR((unused)), + char *argv[] EXT2FS_ATTR((unused)), + int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + fprintf(stdout, "MMP is unsupported, please recompile with " + "--enable-mmp\n"); +} +#endif + diff --git a/debugfs/unused.c b/debugfs/unused.c new file mode 100644 index 0000000..08191a0 --- /dev/null +++ b/debugfs/unused.c @@ -0,0 +1,60 @@ +/* + * unused.c --- quick and dirty unused space dumper + * + * Copyright (C) 1997 Theodore Ts'o. This file may be redistributed + * under the terms of the GNU Public License. + */ + +#include "config.h" +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <ctype.h> +#include <string.h> +#include <time.h> +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#include <sys/types.h> +#ifdef HAVE_GETOPT_H +#include <getopt.h> +#else +extern int optind; +extern char *optarg; +#endif + +#include "debugfs.h" + +void do_dump_unused(int argc EXT2FS_ATTR((unused)), char **argv, + int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + blk64_t blk; + unsigned char buf[EXT2_MAX_BLOCK_SIZE]; + unsigned int i; + errcode_t retval; + + if (common_args_process(argc, argv, 1, 1, + "dump_unused", "", 0)) + return; + + for (blk=current_fs->super->s_first_data_block; + blk < ext2fs_blocks_count(current_fs->super); blk++) { + if (ext2fs_test_block_bitmap2(current_fs->block_map,blk)) + continue; + retval = io_channel_read_blk64(current_fs->io, blk, 1, buf); + if (retval) { + com_err(argv[0], retval, "While reading block\n"); + return; + } + for (i=0; i < current_fs->blocksize; i++) + if (buf[i]) + break; + if (i >= current_fs->blocksize) + continue; + printf("\nUnused block %llu contains non-zero data:\n\n", + (unsigned long long) blk); + for (i=0; i < current_fs->blocksize; i++) + fputc(buf[i], stdout); + } +} diff --git a/debugfs/util.c b/debugfs/util.c new file mode 100644 index 0000000..9e88054 --- /dev/null +++ b/debugfs/util.c @@ -0,0 +1,600 @@ +/* + * util.c --- utilities for the debugfs program + * + * Copyright (C) 1993, 1994 Theodore Ts'o. This file may be + * redistributed under the terms of the GNU Public License. + * + */ + +#define _XOPEN_SOURCE 600 /* needed for strptime */ + +#include "config.h" +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <ctype.h> +#include <string.h> +#include <time.h> +#include <signal.h> +#ifdef HAVE_GETOPT_H +#include <getopt.h> +#else +extern int optind; +extern char *optarg; +#endif +#ifdef HAVE_OPTRESET +extern int optreset; /* defined by BSD, but not others */ +#endif + +#include "ss/ss.h" +#include "debugfs.h" + +/* + * This function resets the libc getopt() function, which keeps + * internal state. Bad design! Stupid libc API designers! No + * biscuit! + * + * BSD-derived getopt() functions require that optind be reset to 1 in + * order to reset getopt() state. This used to be generally accepted + * way of resetting getopt(). However, glibc's getopt() + * has additional getopt() state beyond optind, and requires that + * optind be set zero to reset its state. So the unfortunate state of + * affairs is that BSD-derived versions of getopt() misbehave if + * optind is set to 0 in order to reset getopt(), and glibc's getopt() + * will core dump if optind is set 1 in order to reset getopt(). + * + * More modern versions of BSD require that optreset be set to 1 in + * order to reset getopt(). Sigh. Standards, anyone? + * + * We hide the hair here. + */ +void reset_getopt(void) +{ +#if defined(__GLIBC__) || defined(__linux__) + optind = 0; +#else + optind = 1; +#endif +#ifdef HAVE_OPTRESET + optreset = 1; /* Makes BSD getopt happy */ +#endif +} + +static const char *pager_search_list[] = { "pager", "more", "less", 0 }; +static const char *pager_dir_list[] = { "/usr/bin", "/bin", 0 }; + +static const char *find_pager(char *buf) +{ + const char **i, **j; + + for (i = pager_search_list; *i; i++) { + for (j = pager_dir_list; *j; j++) { + sprintf(buf, "%s/%s", *j, *i); + if (access(buf, X_OK) == 0) + return(buf); + } + } + return 0; +} + +FILE *open_pager(void) +{ + FILE *outfile = 0; + const char *pager = ss_safe_getenv("DEBUGFS_PAGER"); + char buf[80]; + + signal(SIGPIPE, SIG_IGN); + if (!isatty(1)) + return stdout; + if (!pager) + pager = ss_safe_getenv("PAGER"); + if (!pager) + pager = find_pager(buf); + if (!pager || + (strcmp(pager, "__none__") == 0) || + ((outfile = popen(pager, "w")) == 0)) + return stdout; + return outfile; +} + +void close_pager(FILE *stream) +{ + if (stream && stream != stdout) pclose(stream); +} + +/* + * This routine is used whenever a command needs to turn a string into + * an inode. + */ +ext2_ino_t string_to_inode(char *str) +{ + ext2_ino_t ino; + int len = strlen(str); + char *end; + int retval; + + /* + * If the string is of the form <ino>, then treat it as an + * inode number. + */ + if ((len > 2) && (str[0] == '<') && (str[len-1] == '>')) { + ino = strtoul(str+1, &end, 0); + if (*end=='>' && (ino <= current_fs->super->s_inodes_count)) + return ino; + } + + retval = ext2fs_namei(current_fs, root, cwd, str, &ino); + if (retval) { + com_err(str, retval, 0); + return 0; + } + if (ino > current_fs->super->s_inodes_count) { + com_err(str, 0, "resolves to an illegal inode number: %u\n", + ino); + return 0; + } + return ino; +} + +/* + * This routine returns 1 if the filesystem is not open, and prints an + * error message to that effect. + */ +int check_fs_open(char *name) +{ + if (!current_fs) { + com_err(name, 0, "Filesystem not open"); + return 1; + } + return 0; +} + +/* + * This routine returns 1 if a filesystem is open, and prints an + * error message to that effect. + */ +int check_fs_not_open(char *name) +{ + if (current_fs) { + com_err(name, 0, + "Filesystem %s is still open. Close it first.\n", + current_fs->device_name); + return 1; + } + return 0; +} + +/* + * This routine returns 1 if a filesystem is not opened read/write, + * and prints an error message to that effect. + */ +int check_fs_read_write(char *name) +{ + if (!(current_fs->flags & EXT2_FLAG_RW)) { + com_err(name, 0, "Filesystem opened read/only"); + return 1; + } + return 0; +} + +/* + * This routine returns 1 if a filesystem is doesn't have its inode + * and block bitmaps loaded, and prints an error message to that + * effect. + */ +int check_fs_bitmaps(char *name) +{ + if (!current_fs->block_map || !current_fs->inode_map) { + com_err(name, 0, "Filesystem bitmaps not loaded"); + return 1; + } + return 0; +} + +char *inode_time_to_string(__u32 xtime, __u32 xtime_extra) +{ + __s64 t = (__s32) xtime; + + t += (__s64) (xtime_extra & EXT4_EPOCH_MASK) << 32; + return time_to_string(t); +} + +/* + * This function takes a __s64 time value and converts it to a string, + * using ctime + */ +char *time_to_string(__s64 cl) +{ + static int do_gmt = -1; + time_t t = (time_t) cl; + const char *tz; + + if (do_gmt == -1) { + /* The diet libc doesn't respect the TZ environment variable */ + tz = ss_safe_getenv("TZ"); + if (!tz) + tz = ""; + do_gmt = !strcmp(tz, "GMT") || !strcmp(tz, "GMT0"); + } + + return asctime((do_gmt) ? gmtime(&t) : localtime(&t)); +} + +/* + * Parse a string as a time. Return ((time_t)-1) if the string + * doesn't appear to be a sane time. + */ +extern __s64 string_to_time(const char *arg) +{ + struct tm ts; + __s64 ret; + char *tmp; + + if (strcmp(arg, "now") == 0) { + return time(0); + } + if (arg[0] == '@') { + /* interpret it as an integer */ + arg++; + fallback: + ret = strtoll(arg, &tmp, 0); + if (*tmp) + return -1; + return ret; + } + memset(&ts, 0, sizeof(ts)); +#ifdef HAVE_STRPTIME + tmp = strptime(arg, "%Y%m%d%H%M%S", &ts); + if (tmp == NULL) + tmp = strptime(arg, "%Y%m%d%H%M", &ts); + if (tmp == NULL) + tmp = strptime(arg, "%Y%m%d", &ts); + if (tmp == NULL) + goto fallback; +#else + sscanf(arg, "%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) + goto fallback; +#endif + ts.tm_isdst = -1; + /* strptime() may only update the specified fields, which does not + * necessarily include ts.tm_yday (%j). Calculate this if unset: + * + * Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec + * 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 + * + * Start with 31 days per month. Even months have only 30 days, but + * reverse in August, subtract one day for those months. February has + * only 28 days, not 30, subtract two days. Add day of month, minus + * one, since day is not finished yet. Leap years handled afterward. */ + if (ts.tm_yday == 0) + ts.tm_yday = (ts.tm_mon * 31) - + ((ts.tm_mon - (ts.tm_mon > 7)) / 2) - + 2 * (ts.tm_mon > 1) + ts.tm_mday - 1; + ret = ts.tm_sec + ts.tm_min*60 + ts.tm_hour*3600 + ts.tm_yday*86400 + + ((__s64) ts.tm_year-70)*31536000 + + (((__s64) ts.tm_year-69)/4)*86400 - + (((__s64) ts.tm_year-1)/100)*86400 + + (((__s64) ts.tm_year+299)/400)*86400; + return ret; +} + +/* + * This function will convert a string to an unsigned long, printing + * an error message if it fails, and returning success or failure in err. + */ +unsigned long parse_ulong(const char *str, const char *cmd, + const char *descr, int *err) +{ + char *tmp; + unsigned long ret; + + ret = strtoul(str, &tmp, 0); + if (*tmp == 0) { + if (err) + *err = 0; + return ret; + } + com_err(cmd, 0, "Bad %s - %s", descr, str); + if (err) + *err = 1; + else + exit(1); + return 0; +} + +/* + * This function will convert a string to an unsigned long long, printing + * an error message if it fails, and returning success or failure in err. + */ +unsigned long long parse_ulonglong(const char *str, const char *cmd, + const char *descr, int *err) +{ + char *tmp; + unsigned long long ret; + + ret = strtoull(str, &tmp, 0); + if (*tmp == 0) { + if (err) + *err = 0; + return ret; + } + com_err(cmd, 0, "Bad %s - %s", descr, str); + if (err) + *err = 1; + else + exit(1); + return 0; +} + +/* + * This function will convert a string to a block number. It returns + * 0 on success, 1 on failure. On failure, it outputs either an optionally + * specified error message or a default. + */ +int strtoblk(const char *cmd, const char *str, const char *errmsg, + blk64_t *ret) +{ + blk64_t blk; + int err; + + if (errmsg == NULL) + blk = parse_ulonglong(str, cmd, "block number", &err); + else + blk = parse_ulonglong(str, cmd, errmsg, &err); + *ret = blk; + return err; +} + +/* + * This is a common helper function used by the command processing + * routines + */ +int common_args_process(int argc, char *argv[], int min_argc, int max_argc, + const char *cmd, const char *usage, int flags) +{ + if (argc < min_argc || argc > max_argc) { + com_err(argv[0], 0, "Usage: %s %s", cmd, usage); + return 1; + } + if (flags & CHECK_FS_NOTOPEN) { + if (check_fs_not_open(argv[0])) + return 1; + } else { + if (check_fs_open(argv[0])) + return 1; + } + if ((flags & CHECK_FS_RW) && check_fs_read_write(argv[0])) + return 1; + if ((flags & CHECK_FS_BITMAPS) && check_fs_bitmaps(argv[0])) + return 1; + return 0; +} + +/* + * This is a helper function used by do_stat, do_freei, do_seti, and + * do_testi, etc. Basically, any command which takes a single + * argument which is a file/inode number specifier. + */ +int common_inode_args_process(int argc, char *argv[], + ext2_ino_t *inode, int flags) +{ + if (common_args_process(argc, argv, 2, 2, argv[0], "<file>", flags)) + return 1; + + *inode = string_to_inode(argv[1]); + if (!*inode) + return 1; + return 0; +} + +/* + * This is a helper function used by do_freeb, do_setb, and do_testb + */ +int common_block_args_process(int argc, char *argv[], + blk64_t *block, blk64_t *count) +{ + int err; + + if (common_args_process(argc, argv, 2, 3, argv[0], + "<block> [count]", CHECK_FS_BITMAPS)) + return 1; + + if (strtoblk(argv[0], argv[1], NULL, block)) + return 1; + if (*block == 0) { + com_err(argv[0], 0, "Invalid block number 0"); + return 1; + } + + if (argc > 2) { + err = strtoblk(argv[0], argv[2], "count", count); + if (err) + return 1; + } + return 0; +} + +int debugfs_read_inode2(ext2_ino_t ino, struct ext2_inode * inode, + const char *cmd, int bufsize, int flags) +{ + int retval; + + retval = ext2fs_read_inode2(current_fs, ino, inode, bufsize, flags); + if (retval) { + com_err(cmd, retval, "while reading inode %u", ino); + return 1; + } + return 0; +} + +int debugfs_read_inode(ext2_ino_t ino, struct ext2_inode * inode, + const char *cmd) +{ + int retval; + + retval = ext2fs_read_inode(current_fs, ino, inode); + if (retval) { + com_err(cmd, retval, "while reading inode %u", ino); + return 1; + } + return 0; +} + +int debugfs_write_inode2(ext2_ino_t ino, + struct ext2_inode *inode, + const char *cmd, + int bufsize, int flags) +{ + int retval; + + retval = ext2fs_write_inode2(current_fs, ino, inode, bufsize, flags); + if (retval) { + com_err(cmd, retval, "while writing inode %u", ino); + return 1; + } + return 0; +} + +int debugfs_write_inode(ext2_ino_t ino, struct ext2_inode * inode, + const char *cmd) +{ + int retval; + + retval = ext2fs_write_inode(current_fs, ino, inode); + if (retval) { + com_err(cmd, retval, "while writing inode %u", ino); + return 1; + } + return 0; +} + +int debugfs_write_new_inode(ext2_ino_t ino, struct ext2_inode * inode, + const char *cmd) +{ + int retval; + + retval = ext2fs_write_new_inode(current_fs, ino, inode); + if (retval) { + com_err(cmd, retval, "while creating inode %u", ino); + return 1; + } + return 0; +} + +/* + * Given a mode, return the ext2 file type + */ +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; +} + +errcode_t read_list(char *str, blk64_t **list, size_t *len) +{ + blk64_t *lst = *list; + size_t ln = *len; + char *tok, *p = str; + errcode_t retval = 0; + + while ((tok = strtok(p, ","))) { + blk64_t *l; + blk64_t x, y; + char *e; + + errno = 0; + y = x = strtoull(tok, &e, 0); + if (errno) { + retval = errno; + break; + } + if (*e == '-') { + y = strtoull(e + 1, NULL, 0); + if (errno) { + retval = errno; + break; + } + } else if (*e != 0) { + retval = EINVAL; + break; + } + if (y < x) { + retval = EINVAL; + break; + } + l = realloc(lst, sizeof(blk64_t) * (ln + y - x + 1)); + if (l == NULL) { + retval = ENOMEM; + break; + } + lst = l; + for (; x <= y; x++) + lst[ln++] = x; + p = NULL; + } + + *list = lst; + *len = ln; + return retval; +} + +void do_byte_hexdump(FILE *fp, unsigned char *buf, size_t bufsize) +{ + size_t i, j, max; + int suppress = -1; + + for (i = 0; i < bufsize; i += 16) { + max = (bufsize - i > 16) ? 16 : bufsize - i; + if (suppress < 0) { + if (i && memcmp(buf + i, buf + i - max, max) == 0) { + suppress = i; + fprintf(fp, "*\n"); + continue; + } + } else { + if (memcmp(buf + i, buf + suppress, max) == 0) + continue; + suppress = -1; + } + fprintf(fp, "%04o ", (unsigned int)i); + for (j = 0; j < 16; j++) { + if (j < max) + fprintf(fp, "%02x", buf[i+j]); + else + fprintf(fp, " "); + if ((j % 2) == 1) + fprintf(fp, " "); + } + fprintf(fp, " "); + for (j = 0; j < max; j++) + fprintf(fp, "%c", isprint(buf[i+j]) ? buf[i+j] : '.'); + fprintf(fp, "\n"); + } + fprintf(fp, "\n"); +} diff --git a/debugfs/xattrs.c b/debugfs/xattrs.c new file mode 100644 index 0000000..cd042bc --- /dev/null +++ b/debugfs/xattrs.c @@ -0,0 +1,503 @@ +/* + * xattrs.c --- Modify extended attributes via debugfs. + * + * Copyright (C) 2014 Oracle. This file may be redistributed + * under the terms of the GNU Public License. + */ + +#include "config.h" +#include <stdio.h> +#ifdef HAVE_GETOPT_H +#include <getopt.h> +#else +extern int optind; +extern char *optarg; +#endif +#include <ctype.h> +#include "support/cstring.h" + +#include "debugfs.h" + +#define PRINT_XATTR_HEX 0x01 +#define PRINT_XATTR_RAW 0x02 +#define PRINT_XATTR_C 0x04 +#define PRINT_XATTR_STATFMT 0x08 +#define PRINT_XATTR_NOQUOTES 0x10 + +/* Dump extended attributes */ +static void print_xattr_hex(FILE *f, const char *str, int len) +{ + int i; + + for (i = 0; i < len; i++) + fprintf(f, "%02x ", (unsigned char)str[i]); +} + +/* Dump extended attributes */ +static void print_xattr_string(FILE *f, const char *str, int len, int flags) +{ + int printable = 0; + int i; + + if (flags & PRINT_XATTR_RAW) { + fwrite(str, len, 1, f); + return; + } + + if ((flags & PRINT_XATTR_C) == 0) { + /* check: is string "printable enough?" */ + for (i = 0; i < len; i++) + if (isprint(str[i])) + printable++; + + if (printable <= len*7/8) + flags |= PRINT_XATTR_HEX; + } + + if (flags & PRINT_XATTR_HEX) { + print_xattr_hex(f, str, len); + } else { + if ((flags & PRINT_XATTR_NOQUOTES) == 0) + fputc('\"', f); + print_c_string(f, str, len); + if ((flags & PRINT_XATTR_NOQUOTES) == 0) + fputc('\"', f); + } +} + +static void print_xattr(FILE *f, char *name, char *value, size_t value_len, + int print_flags) +{ + print_xattr_string(f, name, strlen(name), PRINT_XATTR_NOQUOTES); + fprintf(f, " (%zu)", value_len); + if ((print_flags & PRINT_XATTR_STATFMT) && + (strcmp(name, "system.data") == 0)) + value_len = 0; + if (value_len != 0 && + (!(print_flags & PRINT_XATTR_STATFMT) || (value_len < 40))) { + fprintf(f, " = "); + print_xattr_string(f, value, value_len, print_flags); + } + fputc('\n', f); +} + +static int dump_attr(char *name, char *value, size_t value_len, void *data) +{ + FILE *out = data; + + fprintf(out, " "); + print_xattr(out, name, value, value_len, PRINT_XATTR_STATFMT); + return 0; +} + +void dump_inode_attributes(FILE *out, ext2_ino_t ino) +{ + struct ext2_xattr_handle *h; + size_t sz; + errcode_t err; + + err = ext2fs_xattrs_open(current_fs, ino, &h); + if (err) + return; + + err = ext2fs_xattrs_read(h); + if (err) + goto out; + + err = ext2fs_xattrs_count(h, &sz); + if (err || sz == 0) + goto out; + + fprintf(out, "Extended attributes:\n"); + err = ext2fs_xattrs_iterate(h, dump_attr, out); + if (err) + goto out; + +out: + err = ext2fs_xattrs_close(&h); +} + +void do_list_xattr(int argc, char **argv, int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + ext2_ino_t ino; + + if (argc != 2) { + printf("%s: Usage: %s <file>\n", argv[0], + argv[0]); + return; + } + + if (check_fs_open(argv[0])) + return; + + ino = string_to_inode(argv[1]); + if (!ino) + return; + + dump_inode_attributes(stdout, ino); +} + +void do_get_xattr(int argc, char **argv, int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + ext2_ino_t ino; + struct ext2_xattr_handle *h; + FILE *fp = NULL; + char *buf = NULL; + size_t buflen; + int i; + int print_flags = 0; + unsigned int handle_flags = 0; + errcode_t err; + + reset_getopt(); + while ((i = getopt(argc, argv, "Cf:rxV")) != -1) { + switch (i) { + case 'f': + if (fp) + fclose(fp); + fp = fopen(optarg, "w"); + if (fp == NULL) { + perror(optarg); + return; + } + break; + case 'r': + handle_flags |= XATTR_HANDLE_FLAG_RAW; + break; + case 'x': + print_flags |= PRINT_XATTR_HEX; + break; + case 'V': + print_flags |= PRINT_XATTR_RAW| + PRINT_XATTR_NOQUOTES; + break; + case 'C': + print_flags |= PRINT_XATTR_C; + break; + default: + goto usage; + } + } + + if (optind != argc - 2) { + usage: + printf("%s: Usage: %s [-f outfile]|[-xVC] [-r] <file> <attr>\n", + argv[0], argv[0]); + + goto out2; + } + + if (check_fs_open(argv[0])) + goto out2; + + ino = string_to_inode(argv[optind]); + if (!ino) + goto out2; + + err = ext2fs_xattrs_open(current_fs, ino, &h); + if (err) + goto out2; + + err = ext2fs_xattrs_flags(h, &handle_flags, NULL); + if (err) + goto out; + + err = ext2fs_xattrs_read(h); + if (err) + goto out; + + err = ext2fs_xattr_get(h, argv[optind + 1], (void **)&buf, &buflen); + if (err) + goto out; + + if (fp) { + fwrite(buf, buflen, 1, fp); + } else { + if (print_flags & PRINT_XATTR_RAW) { + if (print_flags & (PRINT_XATTR_HEX|PRINT_XATTR_C)) + print_flags &= ~PRINT_XATTR_RAW; + print_xattr_string(stdout, buf, buflen, print_flags); + } else { + print_xattr(stdout, argv[optind + 1], + buf, buflen, print_flags); + } + printf("\n"); + } + + ext2fs_free_mem(&buf); +out: + ext2fs_xattrs_close(&h); + if (err) + com_err(argv[0], err, "while getting extended attribute"); +out2: + if (fp) + fclose(fp); +} + +void do_set_xattr(int argc, char **argv, int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + ext2_ino_t ino; + struct ext2_xattr_handle *h; + FILE *fp = NULL; + char *buf = NULL; + size_t buflen; + unsigned int handle_flags = 0; + int i; + errcode_t err; + + reset_getopt(); + while ((i = getopt(argc, argv, "f:r")) != -1) { + switch (i) { + case 'f': + if (fp) + fclose(fp); + fp = fopen(optarg, "r"); + if (fp == NULL) { + perror(optarg); + return; + } + break; + case 'r': + handle_flags |= XATTR_HANDLE_FLAG_RAW; + break; + default: + goto print_usage; + } + } + + if (!(fp && optind == argc - 2) && !(!fp && optind == argc - 3)) { + print_usage: + printf("Usage:\t%s [-r] <file> <attr> <value>\n", argv[0]); + printf("\t%s -f <value_file> [-r] <file> <attr>\n", argv[0]); + goto out2; + } + + if (check_fs_open(argv[0])) + goto out2; + if (check_fs_read_write(argv[0])) + goto out2; + if (check_fs_bitmaps(argv[0])) + goto out2; + + ino = string_to_inode(argv[optind]); + if (!ino) + goto out2; + + err = ext2fs_xattrs_open(current_fs, ino, &h); + if (err) + goto out2; + + err = ext2fs_xattrs_flags(h, &handle_flags, NULL); + if (err) + goto out; + + err = ext2fs_xattrs_read(h); + if (err) + goto out; + + if (fp) { + err = ext2fs_get_mem(current_fs->blocksize, &buf); + if (err) + goto out; + buflen = fread(buf, 1, current_fs->blocksize, fp); + } else { + buf = argv[optind + 2]; + buflen = parse_c_string(buf); + } + + err = ext2fs_xattr_set(h, argv[optind + 1], buf, buflen); +out: + ext2fs_xattrs_close(&h); + if (err) + com_err(argv[0], err, "while setting extended attribute"); +out2: + if (fp) { + fclose(fp); + ext2fs_free_mem(&buf); + } +} + +void do_rm_xattr(int argc, char **argv, int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + ext2_ino_t ino; + struct ext2_xattr_handle *h; + int i; + errcode_t err; + + if (argc < 3) { + printf("%s: Usage: %s <file> <attrs>...\n", argv[0], argv[0]); + return; + } + + if (check_fs_open(argv[0])) + return; + if (check_fs_read_write(argv[0])) + return; + if (check_fs_bitmaps(argv[0])) + return; + + ino = string_to_inode(argv[1]); + if (!ino) + return; + + err = ext2fs_xattrs_open(current_fs, ino, &h); + if (err) + return; + + err = ext2fs_xattrs_read(h); + if (err) + goto out; + + for (i = 2; i < argc; i++) { + err = ext2fs_xattr_remove(h, argv[i]); + if (err) + goto out; + } +out: + ext2fs_xattrs_close(&h); + if (err) + com_err(argv[0], err, "while removing extended attribute"); +} + +/* + * Return non-zero if the string has a minimal number of non-printable + * characters. + */ +static int is_mostly_printable(const char *cp, int len) +{ + int np = 0; + + if (len < 0) + len = strlen(cp); + + while (len--) { + if (!isprint(*cp++)) { + np++; + if (np > 3) + return 0; + } + } + return 1; +} + +static void safe_print(FILE *f, const char *cp, int len) +{ + unsigned char ch; + + if (len < 0) + len = strlen(cp); + + while (len--) { + ch = *cp++; + if (ch > 128) { + fputs("M-", f); + ch -= 128; + } + if ((ch < 32) || (ch == 0x7f)) { + fputc('^', f); + ch ^= 0x40; /* ^@, ^A, ^B; ^? for DEL */ + } + fputc(ch, f); + } +} + +static void dump_xattr_raw_entries(FILE *f, unsigned char *buf, + unsigned int start, unsigned int len, + unsigned value_start) +{ + struct ext2_ext_attr_entry ent; + unsigned int off = start; + unsigned int vstart; + + while (off < len) { + if ((*(__u16 *) (buf + off)) == 0) { + fprintf(f, "last entry found at offset %u (%04o)\n", + off, off); + break; + } + if ((off + sizeof(struct ext2_ext_attr_entry)) >= len) { + fprintf(f, "xattr buffer overrun at %u (len = %u)\n", + off, len); + break; + } +#if WORDS_BIGENDIAN + ext2fs_swap_ext_attr_entry(&ent, + (struct ext2_ext_attr_entry *) (buf + off)); +#else + ent = *((struct ext2_ext_attr_entry *) (buf + off)); +#endif + fprintf(f, "offset = %d (%04o), hash = %u, name_len = %u, " + "name_index = %u\n", + off, off, ent.e_hash, ent.e_name_len, ent.e_name_index); + vstart = value_start + ent.e_value_offs; + fprintf(f, "value_offset = %d (%04o), value_inum = %u, " + "value_size = %u\n", ent.e_value_offs, + vstart, ent.e_value_inum, ent.e_value_size); + off += sizeof(struct ext2_ext_attr_entry); + fprintf(f, "name = "); + if ((off + ent.e_name_len) >= len) + fprintf(f, "<runs off end>"); + else + safe_print(f, (char *)(buf + off), ent.e_name_len); + fputc('\n', f); + if (ent.e_value_size == 0) + goto skip_value; + fprintf(f, "value = "); + if (ent.e_value_inum) + fprintf(f, "<ino %u>", ent.e_value_inum); + else if (ent.e_value_offs >= len || + (vstart + ent.e_value_size) > len) + fprintf(f, "<runs off end>"); + else if (is_mostly_printable((char *)(buf + vstart), + ent.e_value_size)) + safe_print(f, (char *)(buf + vstart), + ent.e_value_size); + else { + fprintf(f, "<hexdump>\n"); + do_byte_hexdump(f, (unsigned char *)(buf + vstart), + ent.e_value_size); + } + fputc('\n', f); + skip_value: + fputc('\n', f); + off += (ent.e_name_len + 3) & ~3; + } +} + +void raw_inode_xattr_dump(FILE *f, unsigned char *buf, unsigned int len) +{ + __u32 magic = ext2fs_le32_to_cpu(*((__le32 *) buf)); + + fprintf(f, "magic = %08x, length = %u, value_start =4 \n\n", + magic, len); + if (magic == EXT2_EXT_ATTR_MAGIC) + dump_xattr_raw_entries(f, buf, 4, len, 4); +} + +void block_xattr_dump(FILE *f, unsigned char *buf, unsigned int len) +{ + struct ext2_ext_attr_header header; + +#ifdef WORDS_BIGENDIAN + ext2fs_swap_ext_attr_header(&header, + (struct ext2_ext_attr_header *) buf); +#else + header = *((struct ext2_ext_attr_header *) buf); +#endif + fprintf(f, "magic = %08x, length = %u\n", header.h_magic, len); + if (header.h_magic != EXT2_EXT_ATTR_MAGIC) + return; + fprintf(f, "refcount = %u, blocks = %u\n", header.h_refcount, + header.h_blocks); + fprintf(f, "hash = %08x, checksum = %08x\n", header.h_hash, + header.h_checksum); + fprintf(f, "reserved: %08x %08x %08x\n\n", header.h_reserved[0], + header.h_reserved[1], header.h_reserved[2]); + + dump_xattr_raw_entries(f, buf, + sizeof(struct ext2_ext_attr_header), len, 0); +} diff --git a/debugfs/zap.c b/debugfs/zap.c new file mode 100644 index 0000000..f862482 --- /dev/null +++ b/debugfs/zap.c @@ -0,0 +1,246 @@ +/* + * zap.c --- zap block + * + * Copyright (C) 2012 Theodore Ts'o. This file may be redistributed + * under the terms of the GNU Public License. + */ + +#include "config.h" +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <ctype.h> +#include <string.h> +#include <time.h> +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#include <sys/types.h> +#ifdef HAVE_GETOPT_H +#include <getopt.h> +#else +extern int optind; +extern char *optarg; +#endif + +#include "debugfs.h" + +void do_zap_block(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + unsigned long pattern = 0; + unsigned char *buf; + ext2_ino_t inode; + errcode_t errcode; + blk64_t block; + char *file = NULL; + int c, err; + int offset = -1; + int length = -1; + int bit = -1; + + if (check_fs_open(argv[0])) + return; + if (check_fs_read_write(argv[0])) + return; + + reset_getopt(); + while ((c = getopt (argc, argv, "b:f:l:o:p:")) != EOF) { + switch (c) { + case 'f': + file = optarg; + break; + case 'b': + bit = parse_ulong(optarg, argv[0], + "bit", &err); + if (err) + return; + if (bit >= (int) current_fs->blocksize * 8) { + com_err(argv[0], 0, "The bit to flip " + "must be within a %d block\n", + current_fs->blocksize); + return; + } + break; + case 'p': + pattern = parse_ulong(optarg, argv[0], + "pattern", &err); + if (err) + return; + if (pattern >= 256) { + com_err(argv[0], 0, "The fill pattern must " + "be an 8-bit value\n"); + return; + } + break; + case 'o': + offset = parse_ulong(optarg, argv[0], + "offset", &err); + if (err) + return; + if (offset >= (int) current_fs->blocksize) { + com_err(argv[0], 0, "The offset must be " + "within a %d block\n", + current_fs->blocksize); + return; + } + break; + + break; + case 'l': + length = parse_ulong(optarg, argv[0], + "length", &err); + if (err) + return; + break; + default: + goto print_usage; + } + } + + if (bit > 0 && offset > 0) { + com_err(argv[0], 0, "The -o and -b options can not be mixed."); + return; + } + + if (offset < 0) + offset = 0; + if (length < 0) + length = current_fs->blocksize - offset; + if ((offset + length) > (int) current_fs->blocksize) { + com_err(argv[0], 0, "The specified length is too bug\n"); + return; + } + + if (argc != optind+1) { + print_usage: + com_err(0, 0, "Usage:\tzap_block [-f file] [-o offset] " + "[-l length] [-p pattern] block_num"); + com_err(0, 0, "\tzap_block [-f file] [-b bit] " + "block_num"); + return; + } + + block = parse_ulonglong(argv[optind], argv[0], "block", &err); + if (err) + return; + + if (file) { + inode = string_to_inode(file); + if (!inode) + return; + errcode = ext2fs_bmap2(current_fs, inode, 0, 0, 0, + block, 0, &block); + if (errcode) { + com_err(argv[0], errcode, + "while mapping logical block %llu\n", + (unsigned long long) block); + return; + } + } + + buf = malloc(current_fs->blocksize); + if (!buf) { + com_err(argv[0], 0, "Couldn't allocate block buffer"); + return; + } + + errcode = io_channel_read_blk64(current_fs->io, block, 1, buf); + if (errcode) { + com_err(argv[0], errcode, + "while reading block %llu\n", + (unsigned long long) block); + goto errout; + } + + if (bit >= 0) + buf[bit >> 3] ^= 1 << (bit & 7); + else + memset(buf+offset, pattern, length); + + errcode = io_channel_write_blk64(current_fs->io, block, 1, buf); + if (errcode) { + com_err(argv[0], errcode, + "while write block %llu\n", + (unsigned long long) block); + goto errout; + } + +errout: + free(buf); + return; +} + +void do_block_dump(int argc, char *argv[], int sci_idx EXT2FS_ATTR((unused)), + void *infop EXT2FS_ATTR((unused))) +{ + unsigned char *buf; + ext2_ino_t inode; + errcode_t errcode; + blk64_t block; + char *file = NULL; + int xattr_dump = 0; + int c, err; + + if (check_fs_open(argv[0])) + return; + + reset_getopt(); + while ((c = getopt (argc, argv, "f:x")) != EOF) { + switch (c) { + case 'f': + file = optarg; + break; + case 'x': + xattr_dump = 1; + break; + default: + goto print_usage; + } + } + + if (argc != optind + 1) { + print_usage: + com_err(0, 0, "Usage: block_dump [-x] [-f inode] block_num"); + return; + } + + block = parse_ulonglong(argv[optind], argv[0], "block", &err); + if (err) + return; + + if (file) { + inode = string_to_inode(file); + if (!inode) + return; + errcode = ext2fs_bmap2(current_fs, inode, 0, 0, 0, + block, 0, &block); + if (errcode) { + com_err(argv[0], errcode, + "while mapping logical block %llu\n", + (unsigned long long) block); + return; + } + } + + buf = malloc(current_fs->blocksize); + if (!buf) { + com_err(argv[0], 0, "Couldn't allocate block buffer"); + return; + } + + errcode = io_channel_read_blk64(current_fs->io, block, 1, buf); + if (errcode) { + com_err(argv[0], errcode, + "while reading block %llu\n", + (unsigned long long) block); + goto errout; + } + + if (xattr_dump) + block_xattr_dump(stdout, buf, current_fs->blocksize); + else + do_byte_hexdump(stdout, buf, current_fs->blocksize); +errout: + free(buf); +} |