From cd29ef7745a67764a1ebb413ccda496732cf14e8 Mon Sep 17 00:00:00 2001 From: "cantab.net!aia21" Date: Fri, 29 Nov 2002 12:16:35 +0000 Subject: [PATCH] mvdir (Logical change 1.23) --- ntfsprogs/Makefile.am | 77 + ntfsprogs/attrdef.c | 153 ++ ntfsprogs/boot.c | 218 +++ ntfsprogs/dumplog.c | 310 +++ ntfsprogs/mkntfs.8.in | 213 ++ ntfsprogs/mkntfs.c | 3554 ++++++++++++++++++++++++++++++++++ ntfsprogs/ntfsdump_logfile.c | 371 ++++ ntfsprogs/ntfsfix.8.in | 56 + ntfsprogs/ntfsfix.c | 317 +++ ntfsprogs/ntfsinfo.8.in | 25 + ntfsprogs/ntfsinfo.c | 195 ++ ntfsprogs/ntfslabel.8.in | 54 + ntfsprogs/ntfslabel.c | 247 +++ ntfsprogs/ntfsresize.8.in | 117 ++ ntfsprogs/ntfsresize.c | 914 +++++++++ ntfsprogs/ntfsundelete.8.in | 344 ++++ ntfsprogs/ntfsundelete.c | 2018 +++++++++++++++++++ ntfsprogs/ntfsundelete.h | 104 + ntfsprogs/ntfswipe.c | 732 +++++++ ntfsprogs/ntfswipe.h | 47 + ntfsprogs/sd.c | 200 ++ ntfsprogs/upcase.c | 82 + 22 files changed, 10348 insertions(+) diff --git a/ntfsprogs/Makefile.am b/ntfsprogs/Makefile.am index e69de29b..81600372 100644 --- a/ntfsprogs/Makefile.am +++ b/ntfsprogs/Makefile.am @@ -0,0 +1,77 @@ +# Need this to enable 64-bit (device) file access functions and parameters. +if DEBUG +AM_CFLAGS = -D_FILE_OFFSET_BITS=64 -g -DDEBUG -Wall +else +AM_CFLAGS = -D_FILE_OFFSET_BITS=64 +endif + +if REALLYSTATIC +AM_LIBS = $(top_srcdir)/libntfs/.libs/libntfs.a +AM_LFLAGS = -static +STATIC_LINK = $(CC) $(AM_CFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ +else +AM_LIBS = $(top_srcdir)/libntfs/libntfs.la +AM_LFLAGS = $(all_libraries) +LIBTOOL_LINK = $(LIBTOOL) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ +endif + +# Workaround to make REALLYSTATIC work with automake 1.5. +LINK=$(STATIC_LINK) $(LIBTOOL_LINK) + +bin_PROGRAMS = ntfsfix ntfsinfo +sbin_PROGRAMS = mkntfs ntfslabel ntfsundelete ntfsresize +EXTRA_PROGRAMS = ntfsdump_logfile dumplog ntfswipe + +man_MANS = mkntfs.8 ntfsfix.8 ntfslabel.8 ntfsinfo.8 ntfsundelete.8 \ + ntfsresize.8 +EXTRA_MANS = + +CLEANFILES = $(EXTRA_PROGRAMS) + +linux_ntfsincludedir = -I$(top_srcdir)/include + +# Set the include path. +INCLUDES = -I$(top_srcdir)/include $(all_includes) + +ntfsfix_SOURCES = ntfsfix.c +ntfsfix_LDADD = $(AM_LIBS) +ntfsfix_LDFLAGS = $(AM_LFLAGS) + +mkntfs_SOURCES = attrdef.c upcase.c boot.c sd.c mkntfs.c +mkntfs_LDADD = $(AM_LIBS) +mkntfs_LDFLAGS = $(AM_LFLAGS) + +ntfslabel_SOURCES = ntfslabel.c +ntfslabel_LDADD = $(AM_LIBS) +ntfslabel_LDFLAGS = $(AM_LFLAGS) + +ntfsinfo_SOURCES = ntfsinfo.c +ntfsinfo_LDADD = $(AM_LIBS) +ntfsinfo_LDFLAGS = $(AM_LFLAGS) + +ntfsundelete_SOURCES = ntfsundelete.c ntfsundelete.h +ntfsundelete_LDADD = $(AM_LIBS) +ntfsundelete_LDFLAGS = $(AM_LFLAGS) + +ntfsresize_SOURCES = ntfsresize.c +ntfsresize_LDADD = $(AM_LIBS) +ntfsresize_LDFLAGS = $(AM_LFLAGS) + +# We don't distribute these + +ntfswipe_SOURCES = ntfswipe.c ntfswipe.h +ntfswipe_LDADD = $(AM_LIBS) +ntfswipe_LDFLAGS = $(AM_LFLAGS) + +ntfsdump_logfile_SOURCES= ntfsdump_logfile.c +ntfsdump_logfile_LDADD = $(AM_LIBS) +ntfsdump_logfile_LDFLAGS= $(AM_LFLAGS) + +dumplog_SOURCES = dumplog.c +dumplog_LDADD = $(AM_LIBS) +dumplog_LDFLAGS = $(AM_LFLAGS) + +# Extra targets + +strip: $(bin_PROGRAMS) $(sbin_PROGRAMS) + $(STRIP) $^ diff --git a/ntfsprogs/attrdef.c b/ntfsprogs/attrdef.c index e69de29b..c9bb82c6 100644 --- a/ntfsprogs/attrdef.c +++ b/ntfsprogs/attrdef.c @@ -0,0 +1,153 @@ +const unsigned char attrdef_ntfs12_array[2400] = { + 36, 0, 83, 0, 84, 0, 65, 0, 78, 0, 68, 0, 65, 0, 82, 0, + 68, 0, 95, 0, 73, 0, 78, 0, 70, 0, 79, 0, 82, 0, 77, 0, + 65, 0, 84, 0, 73, 0, 79, 0, 78, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, + 48, 0, 0, 0, 0, 0, 0, 0, 48, 0, 0, 0, 0, 0, 0, 0, + 36, 0, 65, 0, 84, 0, 84, 0, 82, 0, 73, 0, 66, 0, 85, 0, + 84, 0, 69, 0, 95, 0, 76, 0, 73, 0, 83, 0, 84, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, + 36, 0, 70, 0, 73, 0, 76, 0, 69, 0, 95, 0, 78, 0, 65, 0, + 77, 0, 69, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 66, 0, 0, 0, + 68, 0, 0, 0, 0, 0, 0, 0, 66, 2, 0, 0, 0, 0, 0, 0, + 36, 0, 86, 0, 79, 0, 76, 0, 85, 0, 77, 0, 69, 0, 95, 0, + 86, 0, 69, 0, 82, 0, 83, 0, 73, 0, 79, 0, 78, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, + 8, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, + 36, 0, 83, 0, 69, 0, 67, 0, 85, 0, 82, 0, 73, 0, 84, 0, + 89, 0, 95, 0, 68, 0, 69, 0, 83, 0, 67, 0, 82, 0, 73, 0, + 80, 0, 84, 0, 79, 0, 82, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, + 36, 0, 86, 0, 79, 0, 76, 0, 85, 0, 77, 0, 69, 0, 95, 0, + 78, 0, 65, 0, 77, 0, 69, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, + 2, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 36, 0, 86, 0, 79, 0, 76, 0, 85, 0, 77, 0, 69, 0, 95, 0, + 73, 0, 78, 0, 70, 0, 79, 0, 82, 0, 77, 0, 65, 0, 84, 0, + 73, 0, 79, 0, 78, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, + 12, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, + 36, 0, 68, 0, 65, 0, 84, 0, 65, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, + 36, 0, 73, 0, 78, 0, 68, 0, 69, 0, 88, 0, 95, 0, 82, 0, + 79, 0, 79, 0, 84, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +144, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, + 36, 0, 73, 0, 78, 0, 68, 0, 69, 0, 88, 0, 95, 0, 65, 0, + 76, 0, 76, 0, 79, 0, 67, 0, 65, 0, 84, 0, 73, 0, 79, 0, + 78, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, + 36, 0, 66, 0, 73, 0, 84, 0, 77, 0, 65, 0, 80, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +176, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, + 36, 0, 83, 0, 89, 0, 77, 0, 66, 0, 79, 0, 76, 0, 73, 0, + 67, 0, 95, 0, 76, 0, 73, 0, 78, 0, 75, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, + 36, 0, 69, 0, 65, 0, 95, 0, 73, 0, 78, 0, 70, 0, 79, 0, + 82, 0, 77, 0, 65, 0, 84, 0, 73, 0, 79, 0, 78, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +208, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, + 8, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, + 36, 0, 69, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + diff --git a/ntfsprogs/boot.c b/ntfsprogs/boot.c index e69de29b..b47ded9c 100644 --- a/ntfsprogs/boot.c +++ b/ntfsprogs/boot.c @@ -0,0 +1,218 @@ +/* The first 3429 bytes of $Boot. The rest is just zero. Total 8192 bytes. */ +const unsigned char boot_array[3429] = { +235, 91, 144, 78, 84, 70, 83, 32, 32, 32, 32, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 250, 51, 192, +142, 208, 188, 0, 124, 251, 184, 192, 7, 142, 216, 199, 6, 84, 0, 0, + 0, 199, 6, 86, 0, 0, 0, 199, 6, 91, 0, 16, 0, 184, 0, 13, +142, 192, 43, 219, 232, 7, 0, 104, 0, 13, 104, 102, 2, 203, 80, 83, + 81, 82, 6, 102, 161, 84, 0, 102, 3, 6, 28, 0, 102, 51, 210, 102, + 15, 183, 14, 24, 0, 102, 247, 241, 254, 194, 136, 22, 90, 0, 102, 139, +208, 102, 193, 234, 16, 247, 54, 26, 0, 136, 22, 37, 0, 163, 88, 0, +161, 24, 0, 42, 6, 90, 0, 64, 59, 6, 91, 0, 118, 3, 161, 91, + 0, 80, 180, 2, 139, 22, 88, 0, 177, 6, 210, 230, 10, 54, 90, 0, +139, 202, 134, 233, 138, 54, 37, 0, 178, 128, 205, 19, 88, 114, 42, 1, + 6, 84, 0, 131, 22, 86, 0, 0, 41, 6, 91, 0, 118, 11, 193, 224, + 5, 140, 194, 3, 208, 142, 194, 235, 138, 7, 90, 89, 91, 88, 195, 190, + 89, 1, 235, 8, 190, 227, 1, 235, 3, 190, 57, 1, 232, 9, 0, 190, +173, 1, 232, 3, 0, 251, 235, 254, 172, 60, 0, 116, 9, 180, 14, 187, + 7, 0, 205, 16, 235, 242, 195, 29, 0, 65, 32, 100, 105, 115, 107, 32, +114, 101, 97, 100, 32, 101, 114, 114, 111, 114, 32, 111, 99, 99, 117, 114, +114, 101, 100, 46, 13, 10, 0, 41, 0, 65, 32, 107, 101, 114, 110, 101, +108, 32, 102, 105, 108, 101, 32, 105, 115, 32, 109, 105, 115, 115, 105, 110, +103, 32, 102, 114, 111, 109, 32, 116, 104, 101, 32, 100, 105, 115, 107, 46, + 13, 10, 0, 37, 0, 65, 32, 107, 101, 114, 110, 101, 108, 32, 102, 105, +108, 101, 32, 105, 115, 32, 116, 111, 111, 32, 100, 105, 115, 99, 111, 110, +116, 105, 103, 117, 111, 117, 115, 46, 13, 10, 0, 51, 0, 73, 110, 115, +101, 114, 116, 32, 97, 32, 115, 121, 115, 116, 101, 109, 32, 100, 105, 115, +107, 101, 116, 116, 101, 32, 97, 110, 100, 32, 114, 101, 115, 116, 97, 114, +116, 13, 10, 116, 104, 101, 32, 115, 121, 115, 116, 101, 109, 46, 13, 10, + 0, 23, 0, 92, 78, 84, 76, 68, 82, 32, 105, 115, 32, 99, 111, 109, +112, 114, 101, 115, 115, 101, 100, 46, 13, 10, 0, 0, 0, 0, 85, 170, + 5, 0, 78, 0, 84, 0, 76, 0, 68, 0, 82, 0, 4, 0, 36, 0, + 73, 0, 51, 0, 48, 0, 0, 224, 0, 0, 0, 48, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 140, 200, 142, 216, 193, 224, 4, 250, 139, 224, +251, 102, 15, 183, 6, 11, 0, 102, 15, 182, 30, 13, 0, 102, 247, 227, +102, 163, 78, 2, 102, 139, 14, 64, 0, 128, 249, 0, 15, 143, 14, 0, +246, 217, 102, 184, 1, 0, 0, 0, 102, 211, 224, 235, 8, 144, 102, 161, + 78, 2, 102, 247, 225, 102, 163, 82, 2, 102, 15, 183, 30, 11, 0, 102, + 51, 210, 102, 247, 243, 102, 163, 86, 2, 232, 44, 4, 102, 139, 14, 74, + 2, 102, 137, 14, 34, 2, 102, 3, 14, 82, 2, 102, 137, 14, 38, 2, +102, 3, 14, 82, 2, 102, 137, 14, 42, 2, 102, 3, 14, 82, 2, 102, +137, 14, 58, 2, 102, 3, 14, 82, 2, 102, 137, 14, 66, 2, 102, 184, +144, 0, 0, 0, 102, 139, 14, 34, 2, 232, 65, 9, 102, 11, 192, 15, +132, 22, 254, 102, 163, 46, 2, 102, 184, 160, 0, 0, 0, 102, 139, 14, + 38, 2, 232, 40, 9, 102, 163, 50, 2, 102, 184, 176, 0, 0, 0, 102, +139, 14, 42, 2, 232, 22, 9, 102, 163, 54, 2, 102, 161, 46, 2, 102, + 11, 192, 15, 132, 227, 253, 103, 128, 120, 8, 0, 15, 133, 218, 253, 103, +102, 141, 80, 16, 103, 3, 66, 4, 103, 102, 15, 182, 72, 12, 102, 137, + 14, 94, 2, 103, 102, 139, 72, 8, 102, 137, 14, 90, 2, 102, 161, 90, + 2, 102, 15, 183, 14, 11, 0, 102, 51, 210, 102, 247, 241, 102, 163, 98, + 2, 102, 161, 66, 2, 102, 3, 6, 90, 2, 102, 163, 70, 2, 102, 131, + 62, 50, 2, 0, 15, 132, 25, 0, 102, 131, 62, 54, 2, 0, 15, 132, +135, 253, 102, 139, 30, 54, 2, 30, 7, 102, 139, 62, 70, 2, 232, 177, + 1, 102, 15, 183, 14, 0, 2, 102, 184, 2, 2, 0, 0, 232, 153, 6, +102, 11, 192, 15, 132, 88, 253, 103, 102, 139, 0, 30, 7, 102, 139, 62, + 58, 2, 232, 209, 4, 102, 161, 58, 2, 102, 187, 128, 0, 0, 0, 102, +185, 0, 0, 0, 0, 102, 186, 0, 0, 0, 0, 232, 203, 0, 102, 11, +192, 15, 132, 42, 253, 103, 102, 15, 183, 88, 12, 102, 129, 227, 255, 0, + 0, 0, 15, 133, 30, 253, 102, 139, 216, 104, 0, 32, 7, 102, 43, 255, +232, 79, 1, 138, 22, 36, 0, 184, 232, 3, 142, 192, 141, 54, 11, 0, + 43, 192, 104, 0, 32, 80, 203, 80, 83, 81, 82, 6, 255, 54, 91, 0, +255, 54, 84, 0, 255, 54, 86, 0, 139, 195, 193, 232, 4, 140, 193, 3, +193, 37, 255, 15, 45, 0, 16, 247, 216, 139, 14, 91, 0, 193, 225, 5, + 81, 59, 193, 118, 2, 139, 193, 80, 193, 232, 5, 163, 91, 0, 232, 61, +252, 88, 89, 43, 200, 118, 11, 140, 194, 3, 208, 142, 194, 184, 0, 16, +235, 222, 143, 6, 86, 0, 143, 6, 84, 0, 143, 6, 91, 0, 7, 90, + 89, 91, 88, 195, 6, 30, 102, 96, 102, 139, 218, 102, 15, 182, 14, 13, + 0, 102, 247, 225, 102, 163, 84, 0, 102, 139, 195, 102, 247, 225, 163, 91, + 0, 139, 223, 131, 227, 15, 140, 192, 102, 193, 239, 4, 3, 199, 80, 7, +232, 116, 255, 102, 97, 144, 31, 7, 195, 103, 3, 64, 20, 103, 102, 131, + 56, 255, 15, 132, 76, 0, 103, 102, 57, 24, 15, 133, 51, 0, 102, 11, +201, 15, 133, 10, 0, 103, 128, 120, 9, 0, 15, 133, 35, 0, 195, 103, + 58, 72, 9, 15, 133, 26, 0, 102, 139, 240, 103, 3, 112, 10, 232, 61, + 5, 102, 81, 30, 7, 102, 139, 250, 243, 167, 102, 89, 15, 133, 1, 0, +195, 103, 102, 131, 120, 4, 0, 15, 132, 7, 0, 103, 102, 3, 64, 4, +235, 171, 102, 43, 192, 195, 102, 139, 243, 232, 18, 5, 103, 102, 3, 0, +103, 247, 64, 12, 2, 0, 15, 133, 52, 0, 103, 102, 141, 80, 16, 103, + 58, 74, 64, 15, 133, 24, 0, 103, 102, 141, 114, 66, 232, 239, 4, 102, + 81, 30, 7, 102, 139, 251, 243, 167, 102, 89, 15, 133, 1, 0, 195, 103, +131, 120, 8, 0, 15, 132, 6, 0, 103, 3, 64, 8, 235, 194, 102, 51, +192, 195, 103, 128, 123, 8, 0, 15, 133, 28, 0, 6, 30, 102, 96, 103, +102, 141, 83, 16, 103, 102, 139, 10, 102, 139, 243, 103, 3, 114, 4, 243, +164, 102, 97, 144, 31, 7, 195, 103, 102, 141, 83, 16, 103, 102, 139, 74, + 8, 102, 65, 102, 43, 192, 232, 1, 0, 195, 6, 30, 102, 96, 103, 128, +123, 8, 1, 15, 132, 3, 0, 233, 127, 251, 102, 131, 249, 0, 15, 133, + 6, 0, 102, 97, 144, 31, 7, 195, 102, 83, 102, 80, 102, 81, 102, 87, + 6, 232, 87, 3, 102, 139, 209, 7, 102, 95, 102, 89, 102, 59, 202, 15, +141, 3, 0, 102, 139, 209, 232, 171, 254, 102, 43, 202, 102, 139, 218, 102, +139, 194, 102, 15, 182, 22, 13, 0, 102, 247, 226, 102, 15, 183, 22, 11, + 0, 102, 247, 226, 102, 3, 248, 102, 88, 102, 3, 195, 102, 91, 235, 170, + 6, 30, 102, 96, 103, 128, 123, 8, 1, 15, 132, 3, 0, 233, 25, 251, +102, 131, 249, 0, 15, 133, 6, 0, 102, 97, 144, 31, 7, 195, 102, 83, +102, 80, 102, 81, 102, 87, 6, 102, 81, 102, 51, 210, 102, 15, 182, 14, + 13, 0, 102, 247, 241, 102, 82, 232, 225, 2, 102, 15, 182, 30, 13, 0, +102, 247, 227, 102, 90, 102, 3, 194, 102, 80, 102, 15, 182, 6, 13, 0, +102, 247, 225, 102, 139, 208, 102, 88, 102, 89, 7, 102, 95, 102, 89, 102, + 59, 202, 15, 141, 3, 0, 102, 139, 209, 102, 163, 84, 0, 137, 22, 91, + 0, 6, 30, 102, 96, 139, 223, 131, 227, 15, 140, 192, 102, 193, 239, 4, + 3, 199, 80, 7, 232, 160, 253, 102, 97, 144, 31, 7, 102, 43, 202, 102, +139, 218, 102, 139, 194, 102, 15, 183, 22, 11, 0, 102, 247, 226, 102, 3, +248, 102, 88, 102, 3, 195, 102, 91, 233, 101, 255, 6, 30, 102, 96, 38, +103, 102, 15, 183, 95, 4, 38, 103, 102, 15, 183, 79, 6, 102, 11, 201, + 15, 132, 101, 250, 102, 3, 223, 102, 131, 195, 2, 102, 129, 199, 254, 1, + 0, 0, 102, 73, 102, 11, 201, 15, 132, 23, 0, 38, 103, 139, 3, 38, +103, 137, 7, 102, 131, 195, 2, 102, 129, 199, 0, 2, 0, 0, 102, 73, +235, 226, 102, 97, 144, 31, 7, 195, 6, 30, 102, 96, 102, 184, 1, 0, + 0, 0, 102, 163, 30, 2, 102, 161, 26, 2, 102, 3, 6, 82, 2, 102, +163, 74, 2, 102, 161, 48, 0, 102, 15, 182, 30, 13, 0, 102, 247, 227, +102, 163, 84, 0, 102, 161, 86, 2, 163, 91, 0, 102, 139, 30, 26, 2, + 30, 7, 232, 242, 252, 102, 15, 183, 251, 232, 111, 255, 102, 161, 26, 2, +102, 187, 32, 0, 0, 0, 102, 185, 0, 0, 0, 0, 102, 186, 0, 0, + 0, 0, 232, 100, 253, 102, 11, 192, 15, 132, 87, 0, 102, 139, 216, 30, + 7, 102, 139, 62, 22, 2, 232, 249, 253, 102, 139, 30, 22, 2, 103, 102, +129, 59, 128, 0, 0, 0, 15, 132, 6, 0, 103, 3, 91, 4, 235, 238, +103, 102, 129, 59, 128, 0, 0, 0, 15, 133, 39, 0, 102, 83, 103, 102, +139, 67, 16, 102, 139, 62, 74, 2, 30, 7, 232, 9, 1, 102, 91, 102, +161, 82, 2, 102, 1, 6, 74, 2, 102, 255, 6, 30, 2, 103, 3, 91, + 4, 235, 205, 102, 97, 144, 31, 7, 195, 102, 139, 208, 102, 139, 14, 30, + 2, 102, 161, 26, 2, 102, 82, 102, 80, 102, 81, 102, 82, 102, 187, 128, + 0, 0, 0, 102, 185, 0, 0, 0, 0, 102, 186, 0, 0, 0, 0, 232, +215, 252, 102, 11, 192, 15, 132, 64, 249, 102, 139, 216, 102, 88, 232, 42, + 1, 102, 11, 192, 15, 132, 7, 0, 102, 91, 102, 91, 102, 91, 195, 102, + 89, 102, 88, 102, 90, 102, 3, 6, 82, 2, 226, 185, 102, 51, 192, 195, + 6, 30, 102, 96, 102, 80, 102, 81, 102, 51, 210, 102, 15, 182, 30, 13, + 0, 102, 247, 243, 102, 82, 232, 144, 255, 102, 11, 192, 15, 132, 249, 248, +102, 15, 182, 30, 13, 0, 102, 247, 227, 102, 90, 102, 3, 194, 102, 163, + 84, 0, 102, 89, 102, 15, 182, 30, 13, 0, 102, 59, 203, 15, 142, 19, + 0, 137, 30, 91, 0, 102, 43, 203, 102, 88, 102, 3, 195, 102, 80, 102, + 81, 235, 20, 144, 102, 88, 102, 3, 193, 102, 80, 137, 14, 91, 0, 102, +185, 0, 0, 0, 0, 102, 81, 6, 102, 87, 139, 223, 131, 227, 15, 140, +192, 102, 193, 239, 4, 3, 199, 80, 7, 232, 155, 251, 102, 95, 7, 102, + 3, 62, 78, 2, 102, 89, 102, 88, 102, 131, 249, 0, 15, 143, 116, 255, +102, 97, 144, 31, 7, 195, 6, 30, 102, 96, 102, 247, 38, 86, 2, 102, +139, 14, 86, 2, 232, 89, 255, 232, 241, 253, 102, 97, 144, 31, 7, 195, + 6, 30, 102, 96, 102, 247, 38, 98, 2, 102, 139, 30, 50, 2, 102, 139, + 14, 98, 2, 30, 7, 102, 139, 62, 66, 2, 232, 35, 253, 232, 203, 253, +102, 97, 144, 31, 7, 195, 102, 80, 102, 83, 102, 81, 102, 139, 30, 70, + 2, 102, 139, 200, 102, 193, 232, 3, 102, 131, 225, 7, 102, 3, 216, 102, +184, 1, 0, 0, 0, 102, 211, 224, 103, 132, 3, 15, 132, 4, 0, 248, +235, 2, 144, 249, 102, 89, 102, 91, 102, 88, 195, 103, 128, 123, 8, 1, + 15, 132, 4, 0, 102, 43, 192, 195, 103, 102, 141, 115, 16, 103, 102, 139, + 86, 8, 102, 59, 194, 15, 135, 11, 0, 103, 102, 139, 22, 102, 59, 194, + 15, 131, 4, 0, 102, 43, 192, 195, 103, 3, 94, 16, 102, 43, 246, 103, +128, 59, 0, 15, 132, 62, 0, 232, 129, 0, 102, 3, 241, 232, 57, 0, +102, 3, 202, 102, 59, 193, 15, 140, 33, 0, 102, 139, 209, 102, 80, 103, +102, 15, 182, 11, 102, 139, 193, 102, 131, 224, 15, 102, 193, 233, 4, 102, + 3, 217, 102, 3, 216, 102, 67, 102, 88, 235, 196, 102, 43, 200, 102, 43, +194, 102, 3, 198, 195, 102, 43, 192, 195, 102, 43, 201, 103, 138, 11, 128, +225, 15, 102, 131, 249, 0, 15, 133, 4, 0, 102, 43, 201, 195, 102, 83, +102, 82, 102, 3, 217, 103, 102, 15, 190, 19, 102, 73, 102, 75, 102, 131, +249, 0, 15, 132, 13, 0, 102, 193, 226, 8, 103, 138, 19, 102, 75, 102, + 73, 235, 235, 102, 139, 202, 102, 90, 102, 91, 195, 102, 83, 102, 82, 102, + 43, 210, 103, 138, 19, 102, 131, 226, 15, 102, 43, 201, 103, 138, 11, 192, +233, 4, 102, 131, 249, 0, 15, 133, 8, 0, 102, 43, 201, 102, 90, 102, + 91, 195, 102, 3, 218, 102, 3, 217, 103, 102, 15, 190, 19, 102, 73, 102, + 75, 102, 131, 249, 0, 15, 132, 13, 0, 102, 193, 226, 8, 103, 138, 19, +102, 75, 102, 73, 235, 235, 102, 139, 202, 102, 90, 102, 91, 195, 102, 11, +201, 15, 133, 1, 0, 195, 102, 81, 102, 86, 103, 131, 62, 97, 15, 140, + 12, 0, 103, 131, 62, 122, 15, 143, 4, 0, 103, 131, 46, 32, 102, 131, +198, 2, 226, 230, 102, 94, 102, 89, 195, 102, 80, 102, 81, 102, 139, 208, +102, 161, 46, 2, 103, 102, 141, 88, 16, 103, 3, 67, 4, 103, 102, 141, + 64, 16, 102, 139, 218, 232, 158, 250, 102, 11, 192, 15, 132, 5, 0, 102, + 89, 102, 89, 195, 102, 161, 50, 2, 102, 11, 192, 15, 133, 8, 0, 102, + 89, 102, 89, 102, 51, 192, 195, 102, 139, 22, 50, 2, 103, 102, 141, 82, + 16, 103, 102, 139, 66, 8, 102, 64, 102, 139, 30, 78, 2, 102, 247, 227, +102, 51, 210, 102, 247, 54, 90, 2, 102, 80, 102, 88, 102, 11, 192, 15, +132, 48, 0, 102, 72, 102, 80, 232, 28, 254, 114, 238, 232, 241, 253, 102, + 90, 102, 89, 102, 91, 102, 83, 102, 81, 102, 82, 102, 161, 66, 2, 103, +102, 141, 64, 24, 232, 47, 250, 102, 11, 192, 116, 206, 102, 89, 102, 89, +102, 89, 195, 102, 89, 102, 89, 102, 51, 192, 195, 6, 30, 102, 96, 102, +139, 54, 66, 2, 102, 185, 32, 0, 0, 0, 102, 247, 193, 3, 0, 0, + 0, 15, 133, 3, 0, 232, 13, 0, 102, 173, 232, 105, 0, 226, 235, 102, + 97, 144, 31, 7, 195, 6, 30, 102, 96, 102, 51, 192, 102, 51, 219, 176, + 13, 180, 14, 187, 7, 0, 205, 16, 176, 10, 180, 14, 187, 7, 0, 205, + 16, 102, 97, 144, 31, 7, 195, 6, 30, 102, 96, 102, 11, 201, 15, 133, + 9, 0, 232, 208, 255, 102, 97, 144, 31, 7, 195, 102, 51, 192, 102, 51, +219, 173, 180, 14, 187, 7, 0, 205, 16, 226, 240, 232, 183, 255, 102, 97, +144, 31, 7, 195, 96, 172, 60, 0, 116, 9, 180, 14, 187, 7, 0, 205, + 16, 235, 242, 97, 144, 195, 6, 30, 102, 96, 102, 185, 8, 0, 0, 0, +102, 139, 208, 102, 131, 226, 15, 102, 82, 102, 193, 232, 4, 226, 241, 102, +185, 8, 0, 0, 0, 102, 88, 102, 131, 248, 9, 15, 143, 7, 0, 102, +131, 192, 48, 235, 9, 144, 102, 131, 232, 10, 102, 131, 192, 65, 102, 51, +219, 180, 14, 187, 7, 0, 205, 16, 226, 219, 176, 32, 180, 14, 187, 7, + 0, 205, 16, 102, 97, 144, 31, 7, 232, 96, 0, 195, 6, 30, 102, 96, +102, 190, 22, 13, 0, 0, 232, 79, 245, 102, 97, 144, 31, 7, 195, 6, + 30, 102, 96, 102, 190, 38, 13, 0, 0, 232, 60, 245, 102, 97, 144, 31, + 7, 195, 6, 30, 102, 96, 102, 190, 54, 13, 0, 0, 232, 41, 245, 102, + 97, 144, 31, 7, 195, 6, 30, 102, 96, 102, 190, 70, 13, 0, 0, 232, + 22, 245, 102, 97, 144, 31, 7, 195, 6, 30, 102, 96, 102, 190, 86, 13, + 0, 0, 232, 3, 245, 102, 97, 144, 31, 7, 195, 102, 80, 102, 184, 0, + 0, 245, 255, 102, 64, 102, 11, 192, 117, 249, 102, 88, 195, 102, 81, 102, + 80, 102, 184, 5, 0, 0, 0, 30, 7, 102, 139, 249, 232, 71, 252, 102, +139, 193, 102, 91, 102, 83, 102, 15, 183, 14, 12, 2, 102, 186, 14, 2, + 0, 0, 232, 68, 248, 102, 91, 102, 89, 102, 11, 192, 15, 133, 47, 0, +102, 139, 193, 102, 139, 203, 102, 80, 102, 83, 232, 35, 0, 102, 91, 102, + 95, 102, 11, 192, 15, 132, 23, 0, 30, 7, 232, 9, 252, 102, 139, 199, +102, 15, 183, 14, 12, 2, 102, 186, 14, 2, 0, 0, 232, 10, 248, 195, +102, 81, 102, 187, 32, 0, 0, 0, 102, 185, 0, 0, 0, 0, 102, 186, + 0, 0, 0, 0, 232, 242, 247, 102, 11, 192, 15, 132, 82, 0, 102, 139, +216, 30, 7, 102, 139, 62, 22, 2, 232, 135, 248, 30, 7, 102, 139, 30, + 22, 2, 102, 89, 38, 102, 57, 15, 15, 132, 46, 0, 38, 102, 131, 63, +255, 15, 132, 45, 0, 38, 131, 127, 4, 0, 15, 132, 36, 0, 38, 102, + 15, 183, 71, 4, 3, 216, 139, 195, 37, 0, 128, 116, 215, 140, 192, 5, + 0, 8, 142, 192, 129, 227, 255, 127, 235, 202, 38, 102, 139, 71, 16, 195, +102, 89, 102, 51, 192, 195, 68, 101, 98, 117, 103, 32, 80, 111, 105, 110, +116, 32, 48, 13, 10, 0, 68, 101, 98, 117, 103, 32, 80, 111, 105, 110, +116, 32, 49, 13, 10, 0, 68, 101, 98, 117, 103, 32, 80, 111, 105, 110, +116, 32, 50, 13, 10, 0, 68, 101, 98, 117, 103, 32, 80, 111, 105, 110, +116, 32, 51, 13, 10, 0, 68, 101, 98, 117, 103, 32, 80, 111, 105, 110, +116, 32, 52, 13, 10 }; + diff --git a/ntfsprogs/dumplog.c b/ntfsprogs/dumplog.c index e69de29b..1a730c97 100644 --- a/ntfsprogs/dumplog.c +++ b/ntfsprogs/dumplog.c @@ -0,0 +1,310 @@ +const char *EXEC_NAME = "dumplog"; +const char *EXEC_VERSION = "1.0"; +/* + * $Id$ + * + * DumpLog - Part of the Linux-NTFS project. + * + * Copyright (c) 2000,2001 Anton Altaparmakov. + * + * This utility will interpret the contents of the journal ($LogFile) specified + * on the command line and display the results on stdout. Errors will be output + * to stderr. + * + * Anton Altaparmakov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program (in the main directory of the Linux-NTFS source + * in the file COPYING); if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "types.h" +#include "mst.h" +#include "logfile.h" + +int main(int argc, char **argv) +{ + s64 l, br; + unsigned char *lfd = NULL; + RESTART_PAGE_HEADER *rph; + RESTART_AREA *rr; + RESTART_CLIENT *cr; + RECORD_PAGE_HEADER *rcrd_ph; + LOG_RECORD *lr; + int pass = 1; + int i, lps, client; + int f = 0; + char zero[4096]; + struct stat sbuf; + + memset(zero, 0, sizeof(zero)); + printf("\n"); + if (argc != 2) { + printf("%s v%s - Interpret and display information about the " + "journal\ngiven on the command line.\n\n" + /* Generic copyright / disclaimer. */ + "Copyright (c) 2001 Anton Altaparmakov.\n\n" + "%s is free software, released under the GNU " + "General Public License\nand you are welcome to " + "redistribute it under certain conditions.\n" + "%s comes with ABSOLUTELY NO WARRANTY; for details " + "read the GNU\nGeneral Public License to be found " + "in the file COPYING in the main Linux-NTFS\n" + "distribution directory.\n\n" + /* Generic part ends here. */ + "Syntax: dumplog log_file_name\n" + " e.g. dumplog /mnt/ntfstest/\\$LogFile\n\n", + EXEC_NAME, EXEC_VERSION, EXEC_NAME, EXEC_NAME); + fprintf(stderr, "Error: incorrect syntax\n"); + exit(1); + } + if (stat(argv[1], &sbuf) == -1) { + if (errno == ENOENT) + fprintf(stderr, "The file doesn't exist; did you " + "specify it correctly?\n"); + else + fprintf(stderr, "Error getting information about %s: " + "%s\n", argv[1], strerror(errno)); + exit(1); + } + f = open(argv[1], O_RDONLY); + if (f == -1) { + perror("Couldn't open file"); + exit(1); + } + l = sbuf.st_size; + if (l > 0x400000LL) { + printf("Only analysing the first four megabytes of the " + "logfile (real size = 0x%Lx).\n", + (unsigned long long)l); + l = 0x400000LL; + } + lfd = (unsigned char*)calloc(1, l); + if (!lfd) { + perror("Couldn't allocate internal buffer"); + goto log_file_error; + } + /* Read in the $LogFile into the buffer. */ + if ((br = read(f, lfd, l)) == -1) { + perror("Couldn't read file"); + goto log_file_error; + } + /* Valid data length in buffer. */ + l = min(br, l); + /* Check restart area. */ + if (!is_rstr_recordp(lfd)) { + s64 _l; + + for (_l = 0LL; _l < l; _l++) + if (lfd[_l] != (unsigned char)-1) + break; + if (_l < l) + puts("Logfile contents are corrupt (magic RSTR " + "missing)!"); + else + puts("Logfile is empty."); + goto log_file_error; + } + /* Do the interpretation and display now. */ + rph = (RESTART_PAGE_HEADER*)lfd; + lps = le32_to_cpu(rph->log_page_size); +pass_loc: + if (ntfs_post_read_mst_fixup((NTFS_RECORD*)rph, lps) || + is_baad_record(rph->magic)) { + puts("Logfile incomplete multi sector transfer detected! " + "Cannot handle this yet!"); + goto log_file_error; + } + if ((pass == 2) && !memcmp(lfd, rph, lps)) { + printf("2nd restart area fully matches the 1st one. Skipping " + "display.\n"); + goto skip_rstr_pass; + } + if (le16_to_cpu(rph->major_ver != 1) || + le16_to_cpu(rph->minor_ver != 1)) { + fprintf(stderr, "$LogFile version %i.%i! Error: Unknown " + "$LogFile version!\n", + le16_to_cpu(rph->major_ver), + le16_to_cpu(rph->minor_ver)); + goto log_file_error; + } + rr = (RESTART_AREA*)((char*)rph + le16_to_cpu(rph->restart_offset)); + cr = (RESTART_CLIENT*)((char*)rr + + le16_to_cpu(rr->client_array_offset)); + /* Dump of the interpreted $LogFile restart area. */ + if (pass == 1) + printf("\n$LogFile version %i.%i.\n", + le16_to_cpu(rph->major_ver), + le16_to_cpu(rph->minor_ver)); + printf("\n%s restart area:\n", pass == 1? "1st": "2nd"); + printf("magic = RSTR\n"); + printf("ChkDskLsn = 0x%Lx\n", sle64_to_cpu(rph->chkdsk_lsn)); + printf("SystemPageSize = %u\n", le32_to_cpu(rph->system_page_size)); + printf("LogPageSize = %u\n", le32_to_cpu(rph->log_page_size)); + printf("RestartOffset = 0x%x\n", le16_to_cpu(rph->restart_offset)); + printf("\n(1st) restart record:\n"); + printf("CurrentLsn = %Lx\n", sle64_to_cpu(rr->current_lsn)); + printf("LogClients = %u\n", le16_to_cpu(rr->log_clients)); + printf("ClientFreeList = %i\n", sle16_to_cpu(rr->client_free_list)); + printf("ClientInUseList = %i\n", sle16_to_cpu(rr->client_in_use_list)); + printf("Flags = 0x%x\n", le16_to_cpu(rr->flags)); + printf("SeqNumberBits = %u (0x%x)\n", le32_to_cpu(rr->seq_number_bits), + le32_to_cpu(rr->seq_number_bits)); + printf("RestartAreaLength = 0x%x\n", + le16_to_cpu(rr->restart_area_length)); + printf("ClientArrayOffset = 0x%x\n", + le16_to_cpu(rr->client_array_offset)); + printf("FileSize = %Lu (0x%Lx)\n", le64_to_cpu(rr->file_size), + le64_to_cpu(rr->file_size)); + printf("LastLsnDataLength = 0x%x\n", + le32_to_cpu(rr->last_lsn_data_length)); + printf("RecordLength = 0x%x\n", le16_to_cpu(rr->record_length)); + printf("LogPageDataOffset = 0x%x\n", + le16_to_cpu(rr->log_page_data_offset)); + for (client = 0; client < le16_to_cpu(rr->log_clients); client++) { + printf("\nRestart client record number %i:\n", client); + printf("OldestLsn = 0x%Lx\n", sle64_to_cpu(cr->oldest_lsn)); + printf("ClientRestartLsn = 0x%Lx\n", + sle64_to_cpu(cr->client_restart_lsn)); + printf("PrevClient = %i\n", sle16_to_cpu(cr->prev_client)); + printf("NextClient = %i\n", sle16_to_cpu(cr->next_client)); + printf("SeqNumber = 0x%Lx\n", le64_to_cpu(cr->seq_number)); + printf("ClientNameLength = 0x%x\n", + le32_to_cpu(cr->client_name_length)); + if (le32_to_cpu(cr->client_name_length)) { + // convert to ascii and print out. + // printf("ClientName = %u\n", le16_to_cpu(cr->client_name)); + } + /* Size of a restart client record is fixed at 0xa0 bytes. */ + cr = (RESTART_CLIENT*)((char*)cr + 0xa0); + } +skip_rstr_pass: + if (pass == 1) { + rph = (RESTART_PAGE_HEADER*)((char*)rph + lps); + ++pass; + goto pass_loc; + } + rcrd_ph = (RECORD_PAGE_HEADER*)rph; + /* Reuse pass for log record clienter. */ + pass = 0; + printf("\nFinished with restart area. Beginning with log area.\n"); +rcrd_pass_loc: + rcrd_ph = (RECORD_PAGE_HEADER*)((char*)rcrd_ph + lps); + if ((char*)rcrd_ph + lps > (char*)lfd + l) + goto end_of_rcrd_passes; + printf("\nLog record page number %i", pass); + if (!is_rcrd_record(rcrd_ph->magic)) { + for (i = 0; i < lps; i++) + if (((char*)rcrd_ph)[i] != (char)-1) + break; + if (i < lps) + puts(" is corrupt (magic RCRD is missing)."); + else + puts(" is empty."); + pass++; + goto rcrd_pass_loc; + } else + printf(":"); + /* Dump log record page */ + printf("\nmagic = RCRD\n"); + printf("copy.last_lsn/file_offset = 0x%Lx\n", + le64_to_cpu(rcrd_ph->copy.last_lsn)); + printf("flags = 0x%x\n", le32_to_cpu(rcrd_ph->flags)); + printf("page count = %i\n", le16_to_cpu(rcrd_ph->page_count)); + printf("page position = %i\n", le16_to_cpu(rcrd_ph->page_position)); + printf("header.next_record_offset = 0x%Lx\n", + le64_to_cpu(rcrd_ph->header.packed.next_record_offset)); + printf("header.last_end_lsn = 0x%Lx\n", + le64_to_cpu(rcrd_ph->header.packed.last_end_lsn)); + /* + * Where does the 0x40 come from? Is it just usa_offset + + * usa_client * 2 + 7 & ~7 or is it derived from somewhere? + */ + lr = (LOG_RECORD*)((char*)rcrd_ph + 0x40); + client = 0; +log_record_pass: + printf("\nLog record %i:\n", client); + printf("this lsn = 0x%Lx\n", le64_to_cpu(lr->this_lsn)); + printf("client previous lsn = 0x%Lx\n", + le64_to_cpu(lr->client_previous_lsn)); + printf("client undo next lsn = 0x%Lx\n", + le64_to_cpu(lr->client_undo_next_lsn)); + printf("client data length = 0x%x\n", + le32_to_cpu(lr->client_data_length)); + printf("client_id.seq_number = 0x%x\n", + le16_to_cpu(lr->client_id.seq_number)); + printf("client_id.client_index = 0x%x\n", + le16_to_cpu(lr->client_id.client_index)); + printf("record type = 0x%x\n", le32_to_cpu(lr->record_type)); + printf("transaction_id = 0x%x\n", le32_to_cpu(lr->transaction_id)); + printf("flags = 0x%x:", lr->flags); + if (!lr->flags) + printf(" NONE\n"); + else { + int _b = 0; + + if (lr->flags & LOG_RECORD_MULTI_PAGE) { + printf(" LOG_RECORD_MULTI_PAGE"); + _b = 1; + } + if (lr->flags & ~LOG_RECORD_MULTI_PAGE) { + if (_b) + printf(" |"); + printf(" Unknown flags"); + } + printf("\n"); + } + printf("redo_operation = 0x%x\n", le16_to_cpu(lr->redo_operation)); + printf("undo_operation = 0x%x\n", le16_to_cpu(lr->undo_operation)); + printf("redo_offset = 0x%x\n", le16_to_cpu(lr->redo_offset)); + printf("redo_length = 0x%x\n", le16_to_cpu(lr->redo_length)); + printf("undo_offset = 0x%x\n", le16_to_cpu(lr->undo_offset)); + printf("undo_length = 0x%x\n", le16_to_cpu(lr->undo_length)); + printf("target_attribute = 0x%x\n", le16_to_cpu(lr->target_attribute)); + printf("lcns_to_follow = 0x%x\n", le16_to_cpu(lr->lcns_to_follow)); + printf("record_offset = 0x%x\n", le16_to_cpu(lr->record_offset)); + printf("attribute_offset = 0x%x\n", le16_to_cpu(lr->attribute_offset)); + printf("target_vcn = 0x%Lx\n", sle64_to_cpu(lr->target_vcn)); + if (le16_to_cpu(lr->lcns_to_follow) > 0) + printf("Array of lcns:\n"); + for (i = 0; i < le16_to_cpu(lr->lcns_to_follow); i++) + printf("lcn_list[%i].lcn = 0x%Lx\n", i, + sle64_to_cpu(lr->lcn_list[i].lcn)); + client++; + lr = (LOG_RECORD*)((char*)lr + 0x70); + if (((char*)lr + 0x70 <= (char*)rcrd_ph + + le64_to_cpu(rcrd_ph->header.packed.next_record_offset))) + goto log_record_pass; + pass++; + goto rcrd_pass_loc; +end_of_rcrd_passes: +log_file_error: + printf("\n"); + /* Set return code to 0. */ + i = 0; + if (lfd) + free(lfd); + if (f) + close(f); + return i; +} + diff --git a/ntfsprogs/mkntfs.8.in b/ntfsprogs/mkntfs.8.in index e69de29b..6916396d 100644 --- a/ntfsprogs/mkntfs.8.in +++ b/ntfsprogs/mkntfs.8.in @@ -0,0 +1,213 @@ +.\" -*- nroff -*- +.\" Copyright (c) 2001,2002 Anton Altaparmakov. All Rights Reserved. +.\" This file may be copied under the terms of the GNU Public License. +.\" Adapted from e2fsprogs-1.19/misc/mke2fs.8.in by Theodore Ts'o. +.\" +.TH MKNTFS 8 "March 2002" "Linux-NTFS version @VERSION@" +.SH NAME +mkntfs \- create a NTFS 1.2 (Windows NT/2000/XP) file system +.SH SYNOPSIS +.B mkntfs +[ +.B \-s +.I sector-size +] +[ +.B \-c +.I cluster-size +] +[ +.B \-L +.I volume-label +] +[ +.B \-z +.I mft-zone-multiplier +] +[ +.B \-f +| +.B \-Q +] +[ +.B -n +] +[ +.B \-q +] +[ +.B \-v +] +[ +.B \-vv +] +[ +.B \-C +] +[ +.B \-F +] +[ +.B \-I +] +[ +.B \-V +] +.I device +[ +.I number-of-sectors +] +.SH DESCRIPTION +.B mkntfs +is used to create a NTFS 1.2 (Windows NT 4.0) file system on a device (usually +a disk partition). +.I device +is the special file corresponding to the device (e.g +.IR /dev/hdXX ). +.I number-of-sectors +is the number of blocks on the device. If omitted, +.B mkntfs +automagically figures the file system size. +.SH OPTIONS +.TP +.BI \-s " sector-size" +Specify the size of sectors in bytes. Valid sector size values are 256, 512, +1024, 2048 and 4096 bytes per sector. If omitted, +.B mkntfs +.I sector-size +is determined automatically and if that fails a default of 512 +bytes per sector is used. +.TP +.BI \-c " cluster-size" +Specify the size of clusters in bytes. Valid cluster size values are powers of +two, with at least 256, and at most 65536 bytes per cluster. If omitted, +.B mkntfs +.I cluster-size +is determined by the volume size. The value is determined as +follows: +.TS +lB lB lB +l l r. +Volume size Default cluster +0 - 512MB 512 bytes +512MB - 1GB 1024 bytes +1GB - 2GB 2048 bytes +2GB + 4096 bytes +.TE + +Note that the default cluster size is set to be at least equal to the sector +size as a cluster cannot be smaller than a sector. Also, note that values +greater than 4096 have the side effect that compression is disabled on the +volume (due to limitations in the NTFS compression algorithm currently in use +by Windows). +.TP +.BI \-L " volume-label" +Set the volume label for the filesystem. +.TP +.BI \-z " mft-zone-multiplier" +Set the MFT zone multiplier, which determines the size of the MFT zone to use +on the volume. The MFT zone is the area at the beginning of the volume reserved +for the master file table (MFT), which stores the on disk inodes (MFT records). +It is noteworthy that small files are stored entirely within the inode; +thus, if you expect to use the volume for storing large numbers of very small +files, it is useful to set the zone multiplier to a higher value. Note, that +the MFT zone is resized on the fly as required during operation of the NTFS +driver but choosing a good value will reduce fragmentation. Valid values +are 1, 2, 3 and 4. The values have the following meaning: +.TS +lB lB +lB lB +c l. +MFT zone MFT zone size +multiplier (% of volume size) +1 12.5% (default) +2 25.0% +3 37.5% +4 50.0% +.TE +.TP +.B \-f +Same as +.BR \-Q . +.TP +.B \-Q +Perform quick format. This will skip both zeroing of the volume and bad sector +checking. +.TP +.B \-n +Causes +.B mkntfs +to not actually create a filesystem, but display what it would do if it were +to create a filesystem. All steps of the format are carried out except the +actual writing to the device. +.TP +.B \-q +Quiet execution; only errors are written to stderr, no output to stdout +occurs at all. Useful if +.B mkntfs +is run in a script. +.TP +.B \-v +Verbose execution. +.TP +.B \-vv +Really verbose execution; includes the verbose output from the +.B \-v +option as well as additional output useful for debugging +.B mkntfs. +.TP +.B \-C +Enable compression on the volume. +.TP +.B \-F +Force +.B mkntfs +to run, even if the specified +.I device +is not a block special device, or appears to be mounted. +.TP +.B \-I +Disable content indexing on the volume. (This is only meaningful on +Windows 2000 and later. Windows NT 4.0 and earlier ignore this as they do +not implement content indexing at all.) +.TP +.B \-V +Print the version number of +.B mkntfs +and exit. +.SH AUTHOR +This version of +.B mkntfs +has been written by Anton Altaparmakov (if that fails, use +). +.SH BUGS +.B mkntfs +writes the backup boot sector to the last sector of the block +.I device +being formatted. However, current versions of the Linux kernel (all versions +up to and including todays 2.4.18) either only report an even number of sectors +when the sector size is below 1024 bytes, which is the case for most hard +drives today (512 bytes sector size) or they return the correct number but +accessing the last sector fails. Either way, this means that when a partition +has an odd number of 512-byte sectors, the last sector is either not reported +to us at all or it is not writable by us and hence the created NTFS volume +will either have the backup boot sector placed one sector ahead of where it +should be or it cannot be written at all. For this reason, +.B mkntfs +marks the NTFS volume dirty, so that when you reboot into Windows, check disk +runs automatically and creates a copy of the backup boot sector in the correct +location. This also has the benefit of catching any bugs in +.B mkntfs +as check disk would find any corrupt structures and repair them, as well as +report them. - If you do see any problems reported, please report the messages +to the author. +.br +There may be other bugs. Please, report them to the author. +.SH AVAILABILITY +.B mkntfs +is part of the Linux-NTFS project and is available for download from +http://sf.net/project/showfiles.php?group_id=13956 in source (tar ball and +rpm) and pre-compiled binary (i386 rpm and deb) form. +.SH SEE ALSO +.BR badblocks (8) + diff --git a/ntfsprogs/mkntfs.c b/ntfsprogs/mkntfs.c index e69de29b..47357c76 100644 --- a/ntfsprogs/mkntfs.c +++ b/ntfsprogs/mkntfs.c @@ -0,0 +1,3554 @@ +/* + * $Id$ + * + * mkntfs - Part of the Linux-NTFS project. + * + * Copyright (c) 2000-2002 Anton Altaparmakov. + * Copyright (C) 2001-2002 Richard Russon. + * + * This utility will create an NTFS 1.2 (Windows NT 4.0) volume on a user + * specified (block) device. + * + * Some things (option handling and determination of mount status) have been + * adapted from e2fsprogs-1.19 and lib/ext2fs/ismounted.c and misc/mke2fs.c in + * particular. + * + * Anton Altaparmakov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program (in the main directory of the Linux-NTFS source + * in the file COPYING); if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * WARNING: This program might not work on architectures which do not allow + * unaligned access. For those, the program would need to start using + * get/put_unaligned macros (#include ), but not doing it yet, + * since NTFS really mostly applies to ia32 only, which does allow unaligned + * accesses. We might not actually have a problem though, since the structs are + * defined as being packed so that might be enough for gcc to insert the + * correct code. + * + * If anyone using a non-little endian and/or an aligned access only CPU tries + * this program please let me know whether it works or not! + * + * Anton Altaparmakov + */ + +#include "config.h" + +#ifdef HAVE_UNISTD_H +# include +#endif +#ifdef HAVE_STDLIB_H +# include +#endif +#ifdef HAVE_STDIO_H +# include +#endif +#ifdef HAVE_STDARG_H +# include +#endif +#ifdef HAVE_STRING_H +# include +#endif +#ifdef HAVE_ERRNO_H +# include +#endif +#include +#ifdef HAVE_LINUX_FD_H +# include +# include +#endif +#include +#ifdef HAVE_GETOPT_H +# include +#else + extern char *optarg; + extern int optind; +#endif +#include +#ifdef HAVE_LINUX_MAJOR_H +# include +#endif +#ifndef MAJOR +# define MAJOR(dev) ((dev) >> 8) +# define MINOR(dev) ((dev) & 0xff) +#endif +#ifndef SCSI_BLK_MAJOR +# define SCSI_BLK_MAJOR(m) ((m) == SCSI_DISK_MAJOR || \ + (m) == SCSI_CDROM_MAJOR) +#endif +#include + +#if defined(__linux__) && defined(_IO) && !defined(BLKGETSIZE) +# define BLKGETSIZE _IO(0x12,96) /* Get device size in 512byte blocks. */ +#endif + +#if defined(__linux__) && defined(_IO) && !defined(BLKSSZGET) +# define BLKSSZGET _IO(0x12,104) /* Get device sector size in bytse. */ +#endif + +#include "types.h" +#include "bootsect.h" +#include "disk_io.h" +#include "attrib.h" +#include "bitmap.h" +#include "mst.h" +#include "dir.h" +#include "runlist.h" + +extern const unsigned char attrdef_ntfs12_array[2400]; +extern const unsigned char boot_array[3429]; +extern void init_system_file_sd(int sys_file_no, char **sd_val, + int *sd_val_len); +extern void init_upcase_table(uchar_t *uc, u32 uc_len); + +/* Page size on ia32. Can change to 8192 on Alpha. */ +#define NTFS_PAGE_SIZE 4096 + +const char *EXEC_NAME = "mkntfs"; + +/* Need these global so mkntfs_exit can access them. */ +struct flock flk; +char *buf = NULL; +char *buf2 = NULL; +int buf2_size = 0; +int mft_bitmap_size, mft_bitmap_byte_size; +unsigned char *mft_bitmap = NULL; +int lcn_bitmap_byte_size; +unsigned char *lcn_bitmap = NULL; +run_list *rl = NULL, *rl_mft = NULL, *rl_mft_bmp = NULL, *rl_mftmirr = NULL; +run_list *rl_logfile = NULL, *rl_boot = NULL, *rl_bad = NULL, *rl_index; +INDEX_ALLOCATION *index_block = NULL; +ntfs_volume *vol; + +struct { + int sector_size; /* -s, in bytes, power of 2, default is + 512 bytes. */ + long long nr_sectors; /* size of device in sectors */ + long long nr_clusters; /* Note: Win2k treats clusters as + 32-bit entities! */ + long long volume_size; /* in bytes, or suffixed + with k for kB, m or M for MB, or + g or G for GB, or t or T for TB */ + int index_block_size; /* in bytes. */ + int mft_size; /* The bigger of 16kB & one cluster. */ + long long mft_lcn; /* lcn of $MFT, $DATA attribute. */ + long long mftmirr_lcn; /* lcn of $MFTMirr, $DATA. */ + long long logfile_lcn; /* lcn of $LogFile, $DATA. */ + int logfile_size; /* in bytes, determined from + volume_size. */ + char mft_zone_multiplier; /* -z, value from 1 to 4. Default is + 1. */ + long long mft_zone_end; /* Determined from volume_size and + mft_zone_multiplier, in clusters. */ + char no_action; /* -n, do not write to device, only + display what would be done. */ + char check_bad_blocks; /* read-only test for bad + clusters. */ + long long *bad_blocks; /* Array of bad clusters. */ + long long nr_bad_blocks; /* Number of bad clusters. */ + char *bad_blocks_filename; /* filename, file to read list of + bad clusters from. */ + ATTR_DEF *attr_defs; /* filename, attribute defs. */ + int attr_defs_len; /* in bytes */ + uchar_t *upcase; /* filename, upcase table. */ + u32 upcase_len; /* Determined automatically. */ + char quiet; /* -q, quiet execution. */ + char verbose; /* -v, verbose execution, given twice, + * really verbose execution (debug + * mode). */ + char force; /* -F, force fs creation. */ + char quick_format; /* -f or -Q, fast format, don't zero + the volume first. */ + char enable_compression; /* -C, enables compression of all files + on the volume by default. */ + char disable_indexing; /* -I, disables indexing of file + contents on the volume by default. */ + /* -V, print version and exit. */ +} opt; + +/* Error output. Ignores quiet (-q). */ +void Eprintf(const char *fmt, ...) +{ + va_list ap; + + fprintf(stderr, "ERROR: "); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); +} + +void err_exit(const char *fmt, ...) __attribute__ ((noreturn)); + +/* Error output and terminate. Ignores quiet (-q). */ +void err_exit(const char *fmt, ...) +{ + va_list ap; + + fprintf(stderr, "ERROR: "); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, "Aborting...\n"); + exit(1); +} + +/* Debugging output (-vv). Overriden by quiet (-q). */ +void Dprintf(const char *fmt, ...) +{ + va_list ap; + + if (!opt.quiet && opt.verbose > 1) { + printf("DEBUG: "); + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + } +} + +/* Verbose output (-v). */ +void Vprintf(const char *fmt, ...) +{ + va_list ap; + + if (!opt.quiet && opt.verbose > 0) { + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + } +} + +/* Quietable output (if not -q). */ +void Qprintf(const char *fmt, ...) +{ + va_list ap; + + if (!opt.quiet) { + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + } +} + +void append_to_bad_blocks(unsigned long block) +{ + long long *new_buf; + + if (!(opt.nr_bad_blocks & 15)) { + new_buf = realloc(opt.bad_blocks, (opt.nr_bad_blocks + 16) * + sizeof(long long)); + if (!new_buf) + err_exit("Reallocating memory for bad blocks list " + "failed: %s\n", strerror(errno)); + if (opt.bad_blocks != new_buf) + free(opt.bad_blocks); + opt.bad_blocks = new_buf; + } + opt.bad_blocks[opt.nr_bad_blocks++] = block; +} + +__inline__ long long mkntfs_write(int fd, const void *buf, long long count) +{ + long long bytes_written, total; + int retry; + + if (opt.no_action) + return count; + total = 0LL; + retry = 0; + do { + bytes_written = write(fd, buf, count); + if (bytes_written == -1LL) { + retry = errno; + Eprintf("Error writing to %s: %s\n", vol->dev_name, + strerror(errno)); + errno = retry; + return bytes_written; + } else if (!bytes_written) + ++retry; + else { + count -= bytes_written; + total += bytes_written; + } + } while (count && retry < 3); + if (count) + Eprintf("Failed to complete writing to %s after three retries." + "\n", vol->dev_name); + return total; +} + +/* + * Write to disk the clusters contained in the run list @rl taking the data + * from @val. Take @val_len bytes from @val and pad the rest with zeroes. + * + * If the @rl specifies a completely sparse file, @val is allowed to be NULL. + * + * @inited_size if not NULL points to an output variable which will contain + * the actual number of bytes written to disk. I.e. this will not include + * sparse bytes for example. + * + * Return the number of bytes written (minus padding) or -1 on error. Errno + * will be set to the error code. + */ +s64 ntfs_rlwrite(int fd, const run_list *rl, const char *val, + const s64 val_len, s64 *inited_size) +{ + s64 bytes_written, total, length, delta; + int retry, i; + + if (inited_size) + *inited_size = 0LL; + if (opt.no_action) + return val_len; + total = delta = 0LL; + for (i = 0; rl[i].length; i++) { + length = rl[i].length * vol->cluster_size; + /* Don't write sparse runs. */ + if (rl[i].lcn == -1) { + total += length; + if (!val) + continue; + // TODO: Check that *val is really zero at pos and len. + continue; + } + if (lseek(fd, rl[i].lcn * vol->cluster_size, SEEK_SET) == + (off_t)-1) + return -1LL; + retry = 0; + do { + if (total + length > val_len) { + delta = length; + length = val_len - total; + delta -= length; + } + bytes_written = write(fd, val + total, length); + if (bytes_written == -1LL) { + retry = errno; + Eprintf("Error writing to %s: %s\n", + vol->dev_name, strerror(errno)); + errno = retry; + return bytes_written; + } + if (bytes_written) { + length -= bytes_written; + total += bytes_written; + if (inited_size) + *inited_size += bytes_written; + } else + ++retry; + } while (length && retry < 3); + if (length) { + Eprintf("Failed to complete writing to %s after three " + "retries.\n", vol->dev_name); + return total; + } + } + if (delta) { + char *buf = (char*)calloc(1, delta); + if (!buf) + err_exit("Error allocating internal buffer: " + "%s\n", strerror(errno)); + bytes_written = mkntfs_write(fd, buf, delta); + free(buf); + if (bytes_written == -1LL) + return bytes_written; + } + return total; +} + +/** + * ucslen - determine the length of a fixed-size unicode-character string + * @s: pointer to unicode-character string + * + * Return the number of unicode-characters in @s up to a maximum of maxlen + * unicode-characters, not including the terminating (uchar_t)'\0'. If there + * is no (uchar_t)'\0' between s and s+maxlen, maxlen is returned. + * + * This function never looks beyond s+maxlen. + */ +int ucsnlen(const uchar_t *s, int maxlen) +{ + int i; + + for (i = 0; i < maxlen; i++) + if (!s[i]) + break; + return i; +} + +/** + * ucstos - convert unicode-character string to ASCII + * @dest: points to buffer to receive the converted string + * @src: points to string to convert + * @maxlen: size of @dest buffer in bytes + * + * Return the number of characters written to @dest, not including the + * terminating null byte. If a unicode character was encountered which could + * not be converted -1 is returned. + */ +int ucstos(char *dest, const uchar_t *src, int maxlen) +{ + uchar_t u; + int i; + + /* Need one byte for null terminator. */ + maxlen--; + for (i = 0; i < maxlen; i++) { + u = le16_to_cpu(src[i]); + if (!u) + break; + if (u & 0xff00) + return -1; + dest[i] = u & 0xff; + } + dest[i] = 0; + return i; +} + +/** + * stoucs - convert ASCII string to unicode-character string + * @dest: points to buffer to receive the converted string + * @src: points to string to convert + * @maxlen: size of @dest buffer in bytes + * + * Return the number of characters written to @dest, not including the + * terminating null unicode character. + */ +int stoucs(uchar_t *dest, const char *src, int maxlen) +{ + char c; + int i; + + /* Need two bytes for null terminator. */ + maxlen -= 2; + for (i = 0; i < maxlen; i++) { + c = src[i]; + if (!c) + break; + dest[i] = cpu_to_le16(c); + } + dest[i] = cpu_to_le16('\0'); + return i; +} + +void dump_resident_attr_val(ATTR_TYPES type, char *val, u32 val_len) +{ + const char *don_t_know = "Don't know what to do with this attribute " + "type yet."; + const char *skip = "Skipping display of $%s attribute value.\n"; + const char *todo = "This is still work in progress."; + char *buf; + int i, j; + + switch (type) { + case AT_STANDARD_INFORMATION: + // TODO + printf("%s\n", todo); + return; + case AT_ATTRIBUTE_LIST: + // TODO + printf("%s\n", todo); + return; + case AT_FILE_NAME: + // TODO + printf("%s\n", todo); + return; + case AT_OBJECT_ID: + // TODO + printf("%s\n", todo); + return; + case AT_SECURITY_DESCRIPTOR: + // TODO + printf("%s\n", todo); + return; + case AT_VOLUME_NAME: + printf("Volume name length = %i\n", val_len); + if (val_len) { + buf = calloc(1, val_len); + if (!buf) + err_exit("Failed to allocate internal buffer: " + "%s\n", strerror(errno)); + i = ucstos(buf, (uchar_t*)val, val_len); + if (i == -1) + printf("Volume name contains non-displayable " + "Unicode characters.\n"); + printf("Volume name = %s\n", buf); + free(buf); + } + return; + case AT_VOLUME_INFORMATION: +#define VOL_INF(x) ((VOLUME_INFORMATION *)(x)) + printf("NTFS version %i.%i\n", VOL_INF(val)->major_ver, + VOL_INF(val)->minor_ver); + i = VOL_INF(val)->flags; +#undef VOL_INF + printf("Volume flags = 0x%x: ", i); + if (!i) { + printf("NONE\n"); + return; + } + j = 0; + if (i & VOLUME_MODIFIED_BY_CHKDSK) { + j = 1; + printf("VOLUME_MODIFIED_BY_CHKDSK"); + } + if (i & VOLUME_REPAIR_OBJECT_ID) { + if (j) + printf(" | "); + else + j = 0; + printf("VOLUME_REPAIR_OBJECT_ID"); + } + if (i & VOLUME_DELETE_USN_UNDERWAY) { + if (j) + printf(" | "); + else + j = 0; + printf("VOLUME_DELETE_USN_UNDERWAY"); + } + if (i & VOLUME_MOUNTED_ON_NT4) { + if (j) + printf(" | "); + else + j = 0; + printf("VOLUME_MOUNTED_ON_NT4"); + } + if (i & VOLUME_UPGRADE_ON_MOUNT) { + if (j) + printf(" | "); + else + j = 0; + printf("VOLUME_UPGRADE_ON_MOUNT"); + } + if (i & VOLUME_RESIZE_LOG_FILE) { + if (j) + printf(" | "); + else + j = 0; + printf("VOLUME_RESIZE_LOG_FILE"); + } + if (i & VOLUME_IS_DIRTY) { + if (j) + printf(" | "); + else + j = 0; + printf("VOLUME_IS_DIRTY"); + } + printf("\n"); + return; + case AT_DATA: + printf(skip, "DATA"); + return; + case AT_INDEX_ROOT: + // TODO + printf("%s\n", todo); + return; + case AT_INDEX_ALLOCATION: + // TODO + printf("%s\n", todo); + return; + case AT_BITMAP: + printf(skip, "BITMAP"); + return; + case AT_REPARSE_POINT: + // TODO + printf("%s\n", todo); + return; + case AT_EA_INFORMATION: + // TODO + printf("%s\n", don_t_know); + return; + case AT_EA: + // TODO + printf("%s\n", don_t_know); + return; + case AT_LOGGED_UTILITY_STREAM: + // TODO + printf("%s\n", don_t_know); + return; + default: + i = le32_to_cpu(type); + printf("Cannot display unknown %s defined attribute type 0x%x" + ".\n", i >= + le32_to_cpu(AT_FIRST_USER_DEFINED_ATTRIBUTE) ? + "user" : "system", i); + } +} + +void dump_resident_attr(ATTR_RECORD *a) +{ + int i; + + i = le32_to_cpu(a->value_length); + printf("Attribute value length = %u (0x%x)\n", i, i); + i = le16_to_cpu(a->value_offset); + printf("Attribute value offset = %u (0x%x)\n", i, i); + i = a->resident_flags; + printf("Resident flags = 0x%x: ", i); + if (!i) + printf("NONE\n"); + else if (i & ~RESIDENT_ATTR_IS_INDEXED) + printf("UNKNOWN FLAG(S)\n"); + else + printf("RESIDENT_ATTR_IS_INDEXED\n"); + dump_resident_attr_val(a->type, (char*)a + le16_to_cpu(a->value_offset), + le32_to_cpu(a->value_length)); +} + +void dump_mapping_pairs_array(char *b, unsigned int max_len) +{ + // TODO + return; +} + +void dump_non_resident_attr(ATTR_RECORD *a) +{ + s64 l; + int i; + + l = sle64_to_cpu(a->lowest_vcn); + printf("Lowest VCN = %Li (0x%Lx)\n", l, l); + l = sle64_to_cpu(a->highest_vcn); + printf("Highest VCN = %Li (0x%Lx)\n", l, l); + printf("Mapping pairs array offset = 0x%x\n", + le16_to_cpu(a->mapping_pairs_offset)); + printf("Compression unit = 0x%x: %sCOMPRESSED\n", a->compression_unit, + a->compression_unit ? "" : "NOT "); + if (sle64_to_cpu(a->lowest_vcn)) + printf("Attribute is not the first extent. The following " + "sizes are meaningless:\n"); + l = sle64_to_cpu(a->allocated_size); + printf("Allocated size = %Li (0x%Lx)\n", l, l); + l = sle64_to_cpu(a->data_size); + printf("Data size = %Li (0x%Lx)\n", l, l); + l = sle64_to_cpu(a->initialized_size); + printf("Initialized size = %Li (0x%Lx)\n", l, l); + if (a->flags & ATTR_COMPRESSION_MASK) { + l = sle64_to_cpu(a->compressed_size); + printf("Compressed size = %Li (0x%Lx)\n", l, l); + } + i = le16_to_cpu(a->mapping_pairs_offset); + dump_mapping_pairs_array((char*)a + i, le32_to_cpu(a->length) - i); +} + +void dump_attr_record(ATTR_RECORD *a) +{ + unsigned int u; + char s[0x200]; + int i; + + printf("-- Beginning dump of attribute record. --\n"); + if (a->type == AT_END) { + printf("Attribute type = 0x%x ($END)\n", le32_to_cpu(AT_END)); + u = le32_to_cpu(a->length); + printf("Length of resident part = %u (0x%x)\n", u, u); + return; + } + u = le32_to_cpu(a->type); + for (i = 0; opt.attr_defs[i].type; i++) + if (le32_to_cpu(opt.attr_defs[i].type) >= u) + break; + if (opt.attr_defs[i].type) { +// printf("type = 0x%x\n", le32_to_cpu(opt.attr_defs[i].type)); +// { char *p = (char*)opt.attr_defs[i].name; +// printf("name = %c%c%c%c%c\n", *p, p[1], p[2], p[3], p[4]); +// } + if (ucstos(s, opt.attr_defs[i].name, sizeof(s)) == -1) { + Eprintf("Could not convert Unicode string to single " + "byte string in current locale.\n"); + strncpy(s, "Error converting Unicode string", + sizeof(s)); + } + } else + strncpy(s, "UNKNOWN_TYPE", sizeof(s)); + printf("Attribute type = 0x%x (%s)\n", u, s); + u = le32_to_cpu(a->length); + printf("Length of resident part = %u (0x%x)\n", u, u); + printf("Attribute is %sresident\n", a->non_resident ? "non-" : ""); + printf("Name length = %u unicode characters\n", a->name_length); + printf("Name offset = %u (0x%x)\n", cpu_to_le16(a->name_offset), + cpu_to_le16(a->name_offset)); + u = a->flags; + if (a->name_length) { + if (ucstos(s, (uchar_t*)((char*)a + + cpu_to_le16(a->name_offset)), + min(sizeof(s), a->name_length + 1)) == -1) { + Eprintf("Could not convert Unicode string to single " + "byte string in current locale.\n"); + strncpy(s, "Error converting Unicode string", + sizeof(s)); + + } + printf("Name = %s\n", s); + } + printf("Attribute flags = 0x%x: ", le16_to_cpu(u)); + if (!u) + printf("NONE"); + else { + int first = TRUE; + if (u & ATTR_COMPRESSION_MASK) { + if (u & ATTR_IS_COMPRESSED) { + printf("ATTR_IS_COMPRESSED"); + first = FALSE; + } + if ((u & ATTR_COMPRESSION_MASK) & ~ATTR_IS_COMPRESSED) { + if (!first) + printf(" | "); + else + first = FALSE; + printf("ATTR_UNKNOWN_COMPRESSION"); + } + } + if (u & ATTR_IS_ENCRYPTED) { + if (!first) + printf(" | "); + else + first = FALSE; + printf("ATTR_IS_ENCRYPTED"); + } + if (u & ATTR_IS_SPARSE) { + if (!first) + printf(" | "); + else + first = FALSE; + printf("ATTR_IS_SPARSE"); + } + } + printf("\n"); + printf("Attribute instance = %u\n", le16_to_cpu(a->instance)); + if (a->non_resident) { + dump_non_resident_attr(a); + } else { + dump_resident_attr(a); + } +} + +void dump_mft_record(MFT_RECORD *m) +{ + ATTR_RECORD *a; + unsigned int u; + MFT_REF r; + + printf("-- Beginning dump of mft record. --\n"); + u = le32_to_cpu(m->magic); + printf("Mft record signature (magic) = %c%c%c%c\n", u & 0xff, + u >> 8 & 0xff, u >> 16 & 0xff, u >> 24 & 0xff); + u = le16_to_cpu(m->usa_ofs); + printf("Update sequence array offset = %u (0x%x)\n", u, u); + printf("Update sequence array size = %u\n", le16_to_cpu(m->usa_count)); + printf("$LogFile sequence number (lsn) = %Lu\n", le64_to_cpu(m->lsn)); + printf("Sequence number = %u\n", le16_to_cpu(m->sequence_number)); + printf("Reference (hard link) count = %u\n", + le16_to_cpu(m->link_count)); + u = le16_to_cpu(m->attrs_offset); + printf("First attribute offset = %u (0x%x)\n", u, u); + printf("Flags = %u: ", le16_to_cpu(m->flags)); + if (m->flags & MFT_RECORD_IN_USE) + printf("MFT_RECORD_IN_USE"); + else + printf("MFT_RECORD_NOT_IN_USE"); + if (m->flags & MFT_RECORD_IS_DIRECTORY) + printf(" | MFT_RECORD_IS_DIRECTORY"); + printf("\n"); + u = le32_to_cpu(m->bytes_in_use); + printf("Bytes in use = %u (0x%x)\n", u, u); + u = le32_to_cpu(m->bytes_allocated); + printf("Bytes allocated = %u (0x%x)\n", u, u); + r = le64_to_cpu(m->base_mft_record); + printf("Base mft record reference:\n\tMft record number = %Lu\n\t" + "Sequence number = %u\n", MREF(r), MSEQNO(r)); + printf("Next attribute instance = %u\n", + le16_to_cpu(m->next_attr_instance)); + a = (ATTR_RECORD*)((char*)m + le16_to_cpu(m->attrs_offset)); + printf("-- Beginning dump of attributes within mft record. --\n"); + while ((char*)a < (char*)m + le32_to_cpu(m->bytes_in_use)) { + dump_attr_record(a); + if (a->type == AT_END) + break; + a = (ATTR_RECORD*)((char*)a + le32_to_cpu(a->length)); + }; + printf("-- End of attributes. --\n"); +} + +void format_mft_record(MFT_RECORD *m) +{ + ATTR_RECORD *a; + + memset(m, 0, vol->mft_record_size); + m->magic = magic_FILE; + /* Aligned to 2-byte boundary. */ + m->usa_ofs = cpu_to_le16((sizeof(MFT_RECORD) + 1) & ~1); + if (vol->mft_record_size >= NTFS_SECTOR_SIZE) + m->usa_count = cpu_to_le16(vol->mft_record_size / + NTFS_SECTOR_SIZE + 1); + else { + m->usa_count = cpu_to_le16(1); + Qprintf("Sector size is bigger than MFT record size. Setting " + "usa_count to 1. If Windows\nchkdsk reports this as " + "corruption, please email linux-ntfs-dev@lists.sf.net\n" + "stating that you saw this message and that the file " + "system created was corrupt.\nThank you."); + } + /* Set the update sequence number to 1. */ + *(u16*)((char*)m + ((sizeof(MFT_RECORD) + 1) & ~1)) = cpu_to_le16(1); + m->lsn = cpu_to_le64(0LL); + m->sequence_number = cpu_to_le16(1); + m->link_count = cpu_to_le16(0); + /* Aligned to 8-byte boundary. */ + m->attrs_offset = cpu_to_le16((le16_to_cpu(m->usa_ofs) + + (le16_to_cpu(m->usa_count) << 1) + 7) & ~7); + m->flags = cpu_to_le16(0); + /* + * Using attrs_offset plus eight bytes (for the termination attribute), + * aligned to 8-byte boundary. + */ + m->bytes_in_use = cpu_to_le32((le16_to_cpu(m->attrs_offset) + 8 + 7) & + ~7); + m->bytes_allocated = cpu_to_le32(vol->mft_record_size); + m->base_mft_record = cpu_to_le64((MFT_REF)0); + m->next_attr_instance = cpu_to_le16(0); + a = (ATTR_RECORD*)((char*)m + le16_to_cpu(m->attrs_offset)); + a->type = AT_END; + a->length = cpu_to_le32(0); +#if 0 + if (!opt.quiet && opt.verbose > 1) + dump_mft_record(m); +#endif +} + +/** + * make_room_for_attribute - make room for an attribute inside an mft record + * @m: mft record + * @pos: position at which to make space + * @size: byte size to make available at this position + * + * @pos points to the attribute in front of which we want to make space. + * + * Return 0 on success or -errno on error. Possible error codes are: + * + * -ENOSPC There is not enough space available to complete + * operation. The caller has to make space before calling + * this. + * -EINVAL Can only occur if mkntfs was compiled with -DEBUG. Means + * the input parameters were faulty. + */ +int make_room_for_attribute(MFT_RECORD *m, char *pos, const u32 size) +{ + u32 biu; + + if (!size) + return 0; +#ifdef DEBUG + /* + * Rigorous consistency checks. Always return -EINVAL even if more + * appropriate codes exist for simplicity of parsing the return value. + */ + if (size != ((size + 7) & ~7)) { + Eprintf("make_room_for_attribute() received non 8-byte aligned" + "size.\n"); + return -EINVAL; + } + if (!m || !pos) + return -EINVAL; + if (pos < (char*)m || pos + size < (char*)m || + pos > (char*)m + le32_to_cpu(m->bytes_allocated) || + pos + size > (char*)m + le32_to_cpu(m->bytes_allocated)) + return -EINVAL; + /* The -8 is for the attribute terminator. */ + if (pos - (char*)m > le32_to_cpu(m->bytes_in_use) - 8) + return -EINVAL; +#endif + biu = le32_to_cpu(m->bytes_in_use); + /* Do we have enough space? */ + if (biu + size > le32_to_cpu(m->bytes_allocated)) + return -ENOSPC; + /* Move everything after pos to pos + size. */ + memmove(pos + size, pos, biu - (pos - (char*)m)); + /* Update mft record. */ + m->bytes_in_use = cpu_to_le32(biu + size); + return 0; +} + +/* Return 0 on success and -errno on error. */ +int resize_resident_attribute_value(MFT_RECORD *m, ATTR_RECORD *a, + const u32 new_vsize) +{ + int new_alen, new_muse; + + /* New attribute length and mft record bytes used. */ + new_alen = (le32_to_cpu(a->length) - le32_to_cpu(a->value_length) + + new_vsize + 7) & ~7; + new_muse = le32_to_cpu(m->bytes_in_use) - le32_to_cpu(a->length) + + new_alen; + /* Check for sufficient space. */ + if (new_muse > le32_to_cpu(m->bytes_allocated) ) { + // Aarrgghh! Need to make space. Probably want generic function + // for this as we need to call it from other places, too. + return -ENOTSUP; + } + /* Move attributes behind @a to their new location. */ + memmove((char*)a + new_alen, (char*)a + le32_to_cpu(a->length), + le32_to_cpu(m->bytes_in_use) - ((char*)a - (char*)m) - + le32_to_cpu(a->length)); + /* Adjust @m to reflect change in used space. */ + m->bytes_in_use = cpu_to_le32(new_muse); + /* Adjust @a to reflect new value size. */ + a->length = cpu_to_le32(new_alen); + a->value_length = cpu_to_le32(new_vsize); + return 0; +} + +void deallocate_scattered_clusters(const run_list *rl) +{ + LCN j; + int i; + + if (!rl) + return; + /* Iterate over all runs in the run list @rl. */ + for (i = 0; rl[i].length; i++) { + /* Skip sparse runs. */ + if (rl[i].lcn == -1LL) + continue; + /* Deallocate the current run. */ + for (j = rl[i].lcn; j < rl[i].lcn + rl[i].length; j++) + ntfs_set_bit(lcn_bitmap, j, 0); + } +} + +/* + * Allocate @clusters and create a run list of the allocated clusters. + * + * Return the allocated run list. Caller has to free the run list when finished + * with it. + * + * On error return NULL and errno is set to the error code. + * + * TODO: We should be returning the size as well, but for mkntfs this is not + * necessary. + */ +run_list *allocate_scattered_clusters(s64 clusters) +{ + run_list *rl = NULL, *rlt; + VCN vcn = 0LL; + LCN lcn, end, prev_lcn = 0LL; + int rlpos = 0; + int rlsize = 0; + s64 prev_run_len = 0LL; + char bit; + + end = opt.nr_clusters; + /* Loop until all clusters are allocated. */ + while (clusters) { + /* Loop in current zone until we run out of free clusters. */ + for (lcn = opt.mft_zone_end; lcn < end; lcn++) { + bit = ntfs_get_and_set_bit(lcn_bitmap, lcn, 1); + if (bit) + continue; + /* + * Reallocate memory if necessary. Make sure we have + * enough for the terminator entry as well. + */ + if ((rlpos + 2) * sizeof(run_list) >= rlsize) { + rlsize += 4096; /* PAGE_SIZE */ + rlt = realloc(rl, rlsize); + if (!rlt) + goto err_end; + rl = rlt; + } + /* Coalesce with previous run if adjacent LCNs. */ + if (prev_lcn == lcn - prev_run_len) { + rl[rlpos - 1].length = ++prev_run_len; + vcn++; + } else { + rl[rlpos].vcn = vcn++; + rl[rlpos].lcn = prev_lcn = lcn; + rl[rlpos].length = prev_run_len = 1LL; + rlpos++; + } + /* Done? */ + if (!--clusters) { + /* Add terminator element and return. */ + rl[rlpos].vcn = vcn; + rl[rlpos].lcn = rl[rlpos].length = 0LL; + return rl; + } + + } + /* Switch to next zone, decreasing mft zone by factor 2. */ + end = opt.mft_zone_end; + opt.mft_zone_end >>= 1; + /* Have we run out of space on the volume? */ + if (opt.mft_zone_end <= 0) + goto err_end; + } + return rl; +err_end: + if (rl) { + /* Add terminator element. */ + rl[rlpos].vcn = vcn; + rl[rlpos].lcn = -1LL; + rl[rlpos].length = 0LL; + /* Deallocate all allocated clusters. */ + deallocate_scattered_clusters(rl); + /* Free the run list. */ + free(rl); + } + return NULL; +} + +/* + * Create a non-resident attribute with a predefined on disk location + * specified by the run_list @rl. The clusters specified by @rl are assumed to + * be allocated already. + * + * Return 0 on success and -errno on error. + */ +int insert_positioned_attr_in_mft_record(MFT_RECORD *m, const ATTR_TYPES type, + const char *name, u32 name_len, const IGNORE_CASE_BOOL ic, + const ATTR_FLAGS flags, const run_list *rl, + const char *val, const s64 val_len) +{ + ntfs_attr_search_ctx *ctx; + ATTR_RECORD *a; + u16 hdr_size; + int asize, mpa_size, err, i; + s64 bw = 0, inited_size; + VCN highest_vcn; + uchar_t *uname; +/* + if (base record) + lookup_attr(); + else +*/ + if (name_len) { + i = (name_len + 1) * sizeof(uchar_t); + uname = (uchar_t*)calloc(1, i); + if (!uname) + return -errno; + name_len = stoucs(uname, name, i); + if (name_len > 0xff) { + free(uname); + return -ENAMETOOLONG; + } + } else + uname = NULL; + /* Check if the attribute is already there. */ + ctx = ntfs_get_attr_search_ctx(NULL, m); + if (!ctx) { + Eprintf("Failed to allocate attribute search context.\n"); + err = -ENOMEM; + goto err_out; + } + if (ic == IGNORE_CASE) { + Eprintf("FIXME: Hit unimplemented code path #1.\n"); + err = -ENOTSUP; + goto err_out; + } + if (!ntfs_lookup_attr(type, uname, name_len, ic, 0, NULL, 0, ctx)) { + err = -EEXIST; + goto err_out; + } + if (errno != ENOENT) { + Eprintf("Corrupt inode.\n"); + err = -errno; + goto err_out; + } + a = ctx->attr; + if (flags & ATTR_COMPRESSION_MASK) { + Eprintf("Compressed attributes not supported yet.\n"); + // FIXME: Compress attribute into a temporary buffer, set + // val accordingly and save the compressed size. + err = -ENOTSUP; + goto err_out; + } + if (flags & (ATTR_IS_ENCRYPTED || ATTR_IS_SPARSE)) { + Eprintf("Encrypted/sparse attributes not supported yet.\n"); + err = -ENOTSUP; + goto err_out; + } + if (flags & ATTR_COMPRESSION_MASK) { + hdr_size = 72; + // FIXME: This compression stuff is all wrong. Never mind for + // now. (AIA) + if (val_len) + mpa_size = 0; //get_size_for_compressed_mapping_pairs(rl); + else + mpa_size = 0; + } else { + hdr_size = 64; + if (val_len) { + mpa_size = ntfs_get_size_for_mapping_pairs(vol, rl); + if (mpa_size < 0) { + err = -errno; + Eprintf("Failed to get size for mapping " + "pairs.\n"); + goto err_out; + } + } else + mpa_size = 0; + } + /* Mapping pairs array and next attribute must be 8-byte aligned. */ + asize = (((int)hdr_size + ((name_len + 7) & ~7) + mpa_size) + 7) & ~7; + /* Get the highest vcn. */ + for (i = 0, highest_vcn = 0LL; rl[i].length; i++) + highest_vcn += rl[i].length; + /* Does the value fit inside the allocated size? */ + if (highest_vcn * vol->cluster_size < val_len) { + Eprintf("BUG: Allocated size is smaller than data size!\n"); + err = -EINVAL; + goto err_out; + } + err = make_room_for_attribute(m, (char*)a, asize); + if (err == -ENOSPC) { + // FIXME: Make space! (AIA) + // can we make it non-resident? if yes, do that. + // does it fit now? yes -> do it. + // m's $DATA or $BITMAP+$INDEX_ALLOCATION resident? + // yes -> make non-resident + // does it fit now? yes -> do it. + // make all attributes non-resident + // does it fit now? yes -> do it. + // m is a base record? yes -> allocate extension record + // does the new attribute fit in there? yes -> do it. + // split up run_list into extents and place each in an extension + // record. + // FIXME: the check for needing extension records should be + // earlier on as it is very quick: asize > m->bytes_allocated? + err = -ENOTSUP; + goto err_out; + } +#ifdef DEBUG + else if (err == -EINVAL) { + fprintf(stderr, "BUG(): in insert_positioned_attribute_in_mft_" + "record(): make_room_for_attribute() returned " + "error: EINVAL!\n"); + goto err_out; + } +#endif + a->type = type; + a->length = cpu_to_le32(asize); + a->non_resident = 1; + a->name_length = name_len; + a->name_offset = cpu_to_le16(hdr_size); + a->flags = flags; + a->instance = m->next_attr_instance; + m->next_attr_instance = cpu_to_le16((le16_to_cpu(m->next_attr_instance) + + 1) & 0xffff); + a->lowest_vcn = cpu_to_le64(0); + a->highest_vcn = cpu_to_le64(highest_vcn - 1LL); + a->mapping_pairs_offset = cpu_to_le16(hdr_size + ((name_len + 7) & ~7)); + memset(a->reserved1, 0, sizeof(a->reserved1)); + // FIXME: Allocated size depends on compression. + a->allocated_size = cpu_to_le64(highest_vcn * vol->cluster_size); + a->data_size = cpu_to_le64(val_len); + if (name_len) + memcpy((char*)a + hdr_size, uname, name_len << 1); + if (flags & ATTR_COMPRESSION_MASK) { + if (flags & ATTR_COMPRESSION_MASK & ~ATTR_IS_COMPRESSED) { + Eprintf("Unknown compression format. Reverting to " + "standard compression.\n"); + a->flags &= ~ATTR_COMPRESSION_MASK; + a->flags |= ATTR_IS_COMPRESSED; + } + a->compression_unit = 4; + inited_size = val_len; + // FIXME: Set the compressed size. + a->compressed_size = cpu_to_le64(0); + // FIXME: Write out the compressed data. + // FIXME: err = build_mapping_pairs_compressed(); + err = -ENOTSUP; + } else { + a->compression_unit = 0; + bw = ntfs_rlwrite(vol->fd, rl, val, val_len, &inited_size); + if (bw != val_len) + Eprintf("Error writing non-resident attribute value." + "\n"); + err = ntfs_build_mapping_pairs(vol, (s8*)a + hdr_size + + ((name_len + 7) & ~7), mpa_size, rl); + } + a->initialized_size = cpu_to_le64(inited_size); + if (err < 0 || bw != val_len) { + // FIXME: Handle error. + // deallocate clusters + // remove attribute + if (err >= 0) + err = -EIO; + Eprintf("insert_positioned_attr_in_mft_record failed with " + "error %i.\n", err < 0 ? err : bw); + } +err_out: + if (ctx) + ntfs_put_attr_search_ctx(ctx); + if (uname) + free(uname); + return err; +} + +/* Return 0 on success and -errno on error. */ +int insert_non_resident_attr_in_mft_record(MFT_RECORD *m, const ATTR_TYPES type, + const char *name, u32 name_len, const IGNORE_CASE_BOOL ic, + const ATTR_FLAGS flags, const char *val, const s64 val_len) +{ + ntfs_attr_search_ctx *ctx; + ATTR_RECORD *a; + u16 hdr_size; + int asize, mpa_size, err, i; + run_list *rl = NULL; + s64 bw = 0; + uchar_t *uname; +/* + if (base record) + lookup_attr(); + else +*/ + if (name_len) { + i = (name_len + 1) * sizeof(uchar_t); + uname = (uchar_t*)calloc(1, i); + if (!uname) + return -errno; + name_len = stoucs(uname, name, i); + if (name_len > 0xff) { + free(uname); + return -ENAMETOOLONG; + } + } else + uname = AT_UNNAMED; + /* Check if the attribute is already there. */ + ctx = ntfs_get_attr_search_ctx(NULL, m); + if (!ctx) { + Eprintf("Failed to allocate attribute search context.\n"); + err = -ENOMEM; + goto err_out; + } + if (ic == IGNORE_CASE) { + Eprintf("FIXME: Hit unimplemented code path #2.\n"); + err = -ENOTSUP; + goto err_out; + } + if (!ntfs_lookup_attr(type, uname, name_len, ic, 0, NULL, 0, ctx)) { + err = -EEXIST; + goto err_out; + } + if (errno != ENOENT) { + Eprintf("Corrupt inode.\n"); + err = -errno; + goto err_out; + } + a = ctx->attr; + if (flags & ATTR_COMPRESSION_MASK) { + Eprintf("Compressed attributes not supported yet.\n"); + // FIXME: Compress attribute into a temporary buffer, set + // val accordingly and save the compressed size. + err = -ENOTSUP; + goto err_out; + } + if (flags & (ATTR_IS_ENCRYPTED || ATTR_IS_SPARSE)) { + Eprintf("Encrypted/sparse attributes not supported yet.\n"); + err = -ENOTSUP; + goto err_out; + } + if (val_len) { + rl = allocate_scattered_clusters((val_len + + vol->cluster_size - 1) / vol->cluster_size); + if (!rl) { + err = -errno; + Eprintf("Failed to allocate scattered clusters: %s\n", + strerror(-err)); + goto err_out; + } + } else + rl = NULL; + if (flags & ATTR_COMPRESSION_MASK) { + hdr_size = 72; + // FIXME: This compression stuff is all wrong. Never mind for + // now. (AIA) + if (val_len) + mpa_size = 0; //get_size_for_compressed_mapping_pairs(rl); + else + mpa_size = 0; + } else { + hdr_size = 64; + if (val_len) { + mpa_size = ntfs_get_size_for_mapping_pairs(vol, rl); + if (mpa_size < 0) { + err = -errno; + Eprintf("Failed to get size for mapping " + "pairs.\n"); + goto err_out; + } + } else + mpa_size = 0; + } + /* Mapping pairs array and next attribute must be 8-byte aligned. */ + asize = (((int)hdr_size + ((name_len + 7) & ~7) + mpa_size) + 7) & ~7; + err = make_room_for_attribute(m, (char*)a, asize); + if (err == -ENOSPC) { + // FIXME: Make space! (AIA) + // can we make it non-resident? if yes, do that. + // does it fit now? yes -> do it. + // m's $DATA or $BITMAP+$INDEX_ALLOCATION resident? + // yes -> make non-resident + // does it fit now? yes -> do it. + // make all attributes non-resident + // does it fit now? yes -> do it. + // m is a base record? yes -> allocate extension record + // does the new attribute fit in there? yes -> do it. + // split up run_list into extents and place each in an extension + // record. + // FIXME: the check for needing extension records should be + // earlier on as it is very quick: asize > m->bytes_allocated? + err = -ENOTSUP; + goto err_out; + } +#ifdef DEBUG + else if (err == -EINVAL) { + fprintf(stderr, "BUG(): in insert_non_resident_attribute_in_" + "mft_record(): make_room_for_attribute() " + "returned error: EINVAL!\n"); + goto err_out; + } +#endif + a->type = type; + a->length = cpu_to_le32(asize); + a->non_resident = 1; + a->name_length = name_len; + a->name_offset = cpu_to_le16(hdr_size); + a->flags = flags; + a->instance = m->next_attr_instance; + m->next_attr_instance = cpu_to_le16((le16_to_cpu(m->next_attr_instance) + + 1) & 0xffff); + a->lowest_vcn = cpu_to_le64(0); + for (i = 0; rl[i].length; i++) + ; + a->highest_vcn = cpu_to_le64(rl[i].vcn - 1); + a->mapping_pairs_offset = cpu_to_le16(hdr_size + ((name_len + 7) & ~7)); + memset(a->reserved1, 0, sizeof(a->reserved1)); + // FIXME: Allocated size depends on compression. + a->allocated_size = cpu_to_le64((val_len + (vol->cluster_size - 1)) & + ~(vol->cluster_size - 1)); + a->data_size = cpu_to_le64(val_len); + a->initialized_size = cpu_to_le64(val_len); + if (name_len) + memcpy((char*)a + hdr_size, uname, name_len << 1); + if (flags & ATTR_COMPRESSION_MASK) { + if (flags & ATTR_COMPRESSION_MASK & ~ATTR_IS_COMPRESSED) { + Eprintf("Unknown compression format. Reverting to " + "standard compression.\n"); + a->flags &= ~ATTR_COMPRESSION_MASK; + a->flags |= ATTR_IS_COMPRESSED; + } + a->compression_unit = 4; + // FIXME: Set the compressed size. + a->compressed_size = cpu_to_le64(0); + // FIXME: Write out the compressed data. + // FIXME: err = build_mapping_pairs_compressed(); + err = -ENOTSUP; + } else { + a->compression_unit = 0; + bw = ntfs_rlwrite(vol->fd, rl, val, val_len, NULL); + if (bw != val_len) + Eprintf("Error writing non-resident attribute value." + "\n"); + err = ntfs_build_mapping_pairs(vol, (s8*)a + hdr_size + + ((name_len + 7) & ~7), mpa_size, rl); + } + if (err < 0 || bw != val_len) { + // FIXME: Handle error. + // deallocate clusters + // remove attribute + if (err >= 0) + err = -EIO; + Eprintf("insert_non_resident_attr_in_mft_record failed with " + "error %i.\n", err < 0 ? err : bw); + } +err_out: + if (ctx) + ntfs_put_attr_search_ctx(ctx); + if (uname && (uname != AT_UNNAMED)) + free(uname); + if (rl) + free(rl); + return err; +} + +/* Return 0 on success and -errno on error. */ +int insert_resident_attr_in_mft_record(MFT_RECORD *m, const ATTR_TYPES type, + const char *name, u32 name_len, const IGNORE_CASE_BOOL ic, + const ATTR_FLAGS flags, const RESIDENT_ATTR_FLAGS res_flags, + const char *val, const u32 val_len) +{ + ntfs_attr_search_ctx *ctx; + ATTR_RECORD *a; + int asize, err, i; + uchar_t *uname; +/* + if (base record) + lookup_attr(); + else +*/ + if (name_len) { + i = (name_len + 1) * sizeof(uchar_t); + uname = (uchar_t*)calloc(1, i); + name_len = stoucs(uname, name, i); + if (name_len > 0xff) + return -ENAMETOOLONG; + } else + uname = AT_UNNAMED; + /* Check if the attribute is already there. */ + ctx = ntfs_get_attr_search_ctx(NULL, m); + if (!ctx) { + Eprintf("Failed to allocate attribute search context.\n"); + err = -ENOMEM; + goto err_out; + } + if (ic == IGNORE_CASE) { + Eprintf("FIXME: Hit unimplemented code path #3.\n"); + err = -ENOTSUP; + goto err_out; + } + if (!ntfs_lookup_attr(type, uname, name_len, ic, 0, val, val_len, + ctx)) { + err = -EEXIST; + goto err_out; + } + if (errno != ENOENT) { + Eprintf("Corrupt inode.\n"); + err = -errno; + goto err_out; + } + a = ctx->attr; + /* sizeof(resident attribute record header) == 24 */ + asize = ((24 + ((name_len + 7) & ~7) + val_len) + 7) & ~7; + err = make_room_for_attribute(m, (char*)a, asize); + if (err == -ENOSPC) { + // FIXME: Make space! (AIA) + // can we make it non-resident? if yes, do that. + // does it fit now? yes -> do it. + // m's $DATA or $BITMAP+$INDEX_ALLOCATION resident? + // yes -> make non-resident + // does it fit now? yes -> do it. + // make all attributes non-resident + // does it fit now? yes -> do it. + // m is a base record? yes -> allocate extension record + // does the new attribute fit in there? yes -> do it. + // split up run_list into extents and place each in an extension + // record. + // FIXME: the check for needing extension records should be + // earlier on as it is very quick: asize > m->bytes_allocated? + err = -ENOTSUP; + goto err_out; + } +#ifdef DEBUG + if (err == -EINVAL) { + fprintf(stderr, "BUG(): in insert_resident_attribute_in_mft_" + "record(): make_room_for_attribute() returned " + "error: EINVAL!\n"); + goto err_out; + } +#endif + a->type = type; + a->length = cpu_to_le32(asize); + a->non_resident = 0; + a->name_length = name_len; + a->name_offset = cpu_to_le16(24); + a->flags = cpu_to_le16(flags); + a->instance = m->next_attr_instance; + m->next_attr_instance = cpu_to_le16((le16_to_cpu(m->next_attr_instance) + + 1) & 0xffff); + a->value_length = cpu_to_le32(val_len); + a->value_offset = cpu_to_le16(24 + ((name_len + 7) & ~7)); + a->resident_flags = res_flags; + a->reservedR = 0; + if (name_len) + memcpy((char*)a + 24, uname, name_len << 1); + if (val_len) + memcpy((char*)a + le16_to_cpu(a->value_offset), val, val_len); +err_out: + if (ctx) + ntfs_put_attr_search_ctx(ctx); + if (uname && (uname != AT_UNNAMED)) + free(uname); + return err; +} + +s64 time2ntfs(s64 time) +{ + return cpu_to_le64((time + (s64)(369 * 365 + 89) * 24 * 3600) + * 10000000); +} + +/* Return 0 on success or -errno on error. */ +int add_attr_std_info(MFT_RECORD *m, const FILE_ATTR_FLAGS flags) +{ + STANDARD_INFORMATION si; + int err; + + si.creation_time = time2ntfs(time(NULL)); + si.last_data_change_time = si.creation_time; + si.last_mft_change_time = si.creation_time; + si.last_access_time = si.creation_time; + si.file_attributes = flags; /* already LE */ + if (vol->major_ver < 3) + memset(&si.reserved12, 0, sizeof(si.reserved12)); + else { + si.maximum_versions = cpu_to_le32(0); + si.version_number = cpu_to_le32(0); + si.class_id = cpu_to_le32(0); + /* FIXME: $Secure support... */ + si.security_id = cpu_to_le32(0); + /* FIXME: $Quota support... */ + si.owner_id = cpu_to_le32(0); + si.quota_charged = cpu_to_le64(0ULL); + /* FIXME: $UsnJrnl support... */ + si.usn = cpu_to_le64(0ULL); + } + /* NTFS 1.2: size of si = 48, NTFS 3.0: size of si = 72 */ + err = insert_resident_attr_in_mft_record(m, AT_STANDARD_INFORMATION, + NULL, 0, 0, 0, 0, (char*)&si, + vol->major_ver < 3 ? 48 : 72); + if (err < 0) + Eprintf("add_attr_std_info failed: %s\n", strerror(-err)); + return err; +} + +/* Return 0 on success or -errno on error. */ +int add_attr_file_name(MFT_RECORD *m, const MFT_REF parent_dir, + const s64 allocated_size, const s64 data_size, + const FILE_ATTR_FLAGS flags, const u16 packed_ea_size, + const u32 reparse_point_tag, const char *file_name, + const FILE_NAME_TYPE_FLAGS file_name_type) +{ + ntfs_attr_search_ctx *ctx; + STANDARD_INFORMATION *si; + FILE_NAME_ATTR *fn; + int i, fn_size; + + /* Check if the attribute is already there. */ + ctx = ntfs_get_attr_search_ctx(NULL, m); + if (!ctx) { + Eprintf("Failed to allocate attribute search context.\n"); + return -ENOMEM; + } + if (ntfs_lookup_attr(AT_STANDARD_INFORMATION, AT_UNNAMED, 0, 0, 0, NULL, 0, + ctx)) { + int eo = errno; + Eprintf("BUG: Standard information attribute not present in " + "file record\n"); + ntfs_put_attr_search_ctx(ctx); + return -eo; + } + si = (STANDARD_INFORMATION*)((char*)ctx->attr + + le16_to_cpu(ctx->attr->value_offset)); + i = (strlen(file_name) + 1) * sizeof(uchar_t); + fn_size = sizeof(FILE_NAME_ATTR) + i; + fn = (FILE_NAME_ATTR*)malloc(fn_size); + if (!fn) { + ntfs_put_attr_search_ctx(ctx); + return -errno; + } + fn->parent_directory = parent_dir; + + fn->creation_time = si->creation_time; + fn->last_data_change_time = si->last_data_change_time; + fn->last_mft_change_time = si->last_mft_change_time; + fn->last_access_time = si->last_access_time; + ntfs_put_attr_search_ctx(ctx); + + fn->allocated_size = cpu_to_le64(allocated_size); + fn->data_size = cpu_to_le64(data_size); + fn->file_attributes = flags; + /* These are in a union so can't have both. */ + if (packed_ea_size && reparse_point_tag) { + free(fn); + return -EINVAL; + } + if (packed_ea_size) { + fn->packed_ea_size = cpu_to_le16(packed_ea_size); + fn->reserved = cpu_to_le16(0); + } else + fn->reparse_point_tag = cpu_to_le32(reparse_point_tag); + fn->file_name_type = file_name_type; + i = stoucs(fn->file_name, file_name, i); + if (i < 1) { + free(fn); + return -EINVAL; + } + if (i > 0xff) { + free(fn); + return -ENAMETOOLONG; + } + /* No terminating null in file names. */ + fn->file_name_length = i; + fn_size = sizeof(FILE_NAME_ATTR) + i * sizeof(uchar_t); + i = insert_resident_attr_in_mft_record(m, AT_FILE_NAME, NULL, 0, 0, + 0, RESIDENT_ATTR_IS_INDEXED, (char*)fn, fn_size); + free(fn); + if (i < 0) + Eprintf("add_attr_file_name failed: %s\n", strerror(-i)); + return i; +} + +/* + * Create the security descriptor attribute adding the security descriptor @sd + * of length @sd_len to the mft record @m. + * + * Return 0 on success or -errno on error. + */ +int add_attr_sd(MFT_RECORD *m, const char *sd, const s64 sd_len) +{ + int err; + + /* Does it fit? NO: create non-resident. YES: create resident. */ + if (le32_to_cpu(m->bytes_in_use) + 24 + sd_len > + le32_to_cpu(m->bytes_allocated)) + err = insert_non_resident_attr_in_mft_record(m, + AT_SECURITY_DESCRIPTOR, NULL, 0, 0, 0, sd, + sd_len); + else + err = insert_resident_attr_in_mft_record(m, + AT_SECURITY_DESCRIPTOR, NULL, 0, 0, 0, 0, sd, + sd_len); + if (err < 0) + Eprintf("add_attr_sd failed: %s\n", strerror(-err)); + return err; +} + +/* Return 0 on success or -errno on error. */ +int add_attr_data(MFT_RECORD *m, const char *name, const u32 name_len, + const IGNORE_CASE_BOOL ic, const ATTR_FLAGS flags, + const char *val, const s64 val_len) +{ + int err; + + /* + * Does it fit? NO: create non-resident. YES: create resident. + * + * FIXME: Introduced arbitrary limit of mft record allocated size - 512. + * This is to get around the problem that if $Bitmap/$DATA becomes too + * big, but is just small enough to be resident, we would make it + * resident, and later run out of space when creating the other + * attributes and this would cause us to abort as making resident + * attributes non-resident is not supported yet. + * The proper fix is to support making resident attribute non-resident. + */ + if (le32_to_cpu(m->bytes_in_use) + 24 + val_len > + min(le32_to_cpu(m->bytes_allocated), + le32_to_cpu(m->bytes_allocated) - 512)) + err = insert_non_resident_attr_in_mft_record(m, AT_DATA, name, + name_len, ic, flags, val, val_len); + else + err = insert_resident_attr_in_mft_record(m, AT_DATA, name, + name_len, ic, flags, 0, val, val_len); + + if (err < 0) + Eprintf("add_attr_data failed: %s\n", strerror(-err)); + return err; +} + +/* + * Create a non-resident data attribute with a predefined on disk location + * specified by the run_list @rl. The clusters specified by @rl are assumed to + * be allocated already. + * + * Return 0 on success or -errno on error. + */ +int add_attr_data_positioned(MFT_RECORD *m, const char *name, + const u32 name_len, const IGNORE_CASE_BOOL ic, + const ATTR_FLAGS flags, const run_list *rl, + const char *val, const s64 val_len) +{ + int err; + + err = insert_positioned_attr_in_mft_record(m, AT_DATA, name, name_len, + ic, flags, rl, val, val_len); + if (err < 0) + Eprintf("add_attr_data_positioned failed: %s\n", + strerror(-err)); + return err; +} + +/* + * Create volume name attribute specifying the volume name @vol_name as a null + * terminated char string of length @vol_name_len (number of characters not + * including the terminating null), which is converted internally to a little + * endian uchar_t string. The name is at least 1 character long and at most + * 0xff characters long (not counting the terminating null). + * + * Return 0 on success or -errno on error. + */ +int add_attr_vol_name(MFT_RECORD *m, const char *vol_name, + const int vol_name_len) +{ + uchar_t *uname; + int i, len; + + if (vol_name_len) { + len = (vol_name_len + 1) * sizeof(uchar_t); + uname = calloc(1, len); + if (!uname) + return -errno; + i = (stoucs(uname, vol_name, len) + 1) * sizeof(uchar_t); + if (!i) { + free(uname); + return -EINVAL; + } + if (i > 0xff) { + free(uname); + return -ENAMETOOLONG; + } + } else { + uname = NULL; + len = 0; + } + i = insert_resident_attr_in_mft_record(m, AT_VOLUME_NAME, NULL, 0, 0, + 0, 0, (char*)uname, len); + if (uname) + free(uname); + if (i < 0) + Eprintf("add_attr_vol_name failed: %s\n", strerror(-i)); + return i; +} + +/* Return 0 on success or -errno on error. */ +int add_attr_vol_info(MFT_RECORD *m, const VOLUME_FLAGS flags, + const u8 major_ver, const u8 minor_ver) +{ + VOLUME_INFORMATION vi; + int err; + + memset(&vi, 0, sizeof(vi)); + vi.major_ver = major_ver; + vi.minor_ver = minor_ver; + vi.flags = flags & VOLUME_FLAGS_MASK; + err = insert_resident_attr_in_mft_record(m, AT_VOLUME_INFORMATION, NULL, + 0, 0, 0, 0, (char*)&vi, sizeof(vi)); + if (err < 0) + Eprintf("add_attr_vol_info failed: %s\n", strerror(-err)); + return err; +} + +/* Return 0 on success or -errno on error. */ +int add_attr_index_root(MFT_RECORD *m, const char *name, const u32 name_len, + const IGNORE_CASE_BOOL ic, const ATTR_TYPES indexed_attr_type, + const COLLATION_RULES collation_rule, + const u32 index_block_size) +{ + INDEX_ROOT *r; + INDEX_ENTRY_HEADER *e; + int err, val_len; + + val_len = sizeof(INDEX_ROOT) + sizeof(INDEX_ENTRY_HEADER); + r = (INDEX_ROOT*)malloc(val_len); + if (!r) + return -errno; + r->type = indexed_attr_type == AT_FILE_NAME ? AT_FILE_NAME : 0; + if (indexed_attr_type == AT_FILE_NAME && + collation_rule != COLLATION_FILE_NAME) { + free(r); + Eprintf("add_attr_index_root: indexed attribute is $FILE_NAME " + "but collation rule is not COLLATION_FILE_NAME.\n"); + return -EINVAL; + } + r->collation_rule = collation_rule; + r->index_block_size = cpu_to_le32(index_block_size); + if (index_block_size >= vol->cluster_size) { + if (index_block_size % vol->cluster_size) { + Eprintf("add_attr_index_root: index block size is not " + "a multiple of the cluster size.\n"); + free(r); + return -EINVAL; + } + r->clusters_per_index_block = index_block_size / + vol->cluster_size; + } else /* if (vol->cluster_size > index_block_size) */ { + if (index_block_size & (index_block_size - 1)) { + Eprintf("add_attr_index_root: index block size is not " + "a power of 2.\n"); + free(r); + return -EINVAL; + } + if (index_block_size < opt.sector_size) { + Eprintf("add_attr_index_root: index block size is " + "smaller than the sector size.\n"); + free(r); + return -EINVAL; + } + r->clusters_per_index_block = index_block_size / + opt.sector_size; + } + memset(&r->reserved, 0, sizeof(r->reserved)); + r->index.entries_offset = cpu_to_le32(sizeof(INDEX_HEADER)); + r->index.index_length = cpu_to_le32(sizeof(INDEX_HEADER) + + sizeof(INDEX_ENTRY_HEADER)); + r->index.allocated_size = r->index.index_length; + r->index.flags = SMALL_INDEX; + memset(&r->index.reserved, 0, sizeof(r->index.reserved)); + e = (INDEX_ENTRY_HEADER*)((char*)&r->index + + le32_to_cpu(r->index.entries_offset)); + /* + * No matter whether this is a file index or a view as this is a + * termination entry, hence no key value / data is associated with it + * at all. Thus, we just need the union to be all zero. + */ + e->indexed_file = cpu_to_le64(0LL); + e->length = cpu_to_le16(sizeof(INDEX_ENTRY_HEADER)); + e->key_length = cpu_to_le16(0); + e->flags = INDEX_ENTRY_END; + e->reserved = cpu_to_le16(0); + err = insert_resident_attr_in_mft_record(m, AT_INDEX_ROOT, name, + name_len, ic, 0, 0, (char*)r, val_len); + free(r); + if (err < 0) + Eprintf("add_attr_index_root failed: %s\n", strerror(-err)); + return err; +} + +/* Return 0 on success or -errno on error. */ +int add_attr_index_alloc(MFT_RECORD *m, const char *name, const u32 name_len, + const IGNORE_CASE_BOOL ic, const char *index_alloc_val, + const u32 index_alloc_val_len) +{ + int err; + + err = insert_non_resident_attr_in_mft_record(m, AT_INDEX_ALLOCATION, + name, name_len, ic, 0, index_alloc_val, + index_alloc_val_len); + if (err < 0) + Eprintf("add_attr_index_alloc failed: %s\n", strerror(-err)); + return err; +} + +/* Return 0 on success or -errno on error. */ +int add_attr_bitmap(MFT_RECORD *m, const char *name, const u32 name_len, + const IGNORE_CASE_BOOL ic, const char *bitmap, + const u32 bitmap_len) +{ + int err; + + /* Does it fit? NO: create non-resident. YES: create resident. */ + if (le32_to_cpu(m->bytes_in_use) + 24 + bitmap_len > + le32_to_cpu(m->bytes_allocated)) + err = insert_non_resident_attr_in_mft_record(m, AT_BITMAP, name, + name_len, ic, 0, bitmap, bitmap_len); + else + err = insert_resident_attr_in_mft_record(m, AT_BITMAP, name, + name_len, ic, 0, 0, bitmap, bitmap_len); + + if (err < 0) + Eprintf("add_attr_bitmap failed: %s\n", strerror(-err)); + return err; +} + +/* + * Create a non-resident bitmap attribute with a predefined on disk location + * specified by the run_list @rl. The clusters specified by @rl are assumed to + * be allocated already. + * + * Return 0 on success or -errno on error. + */ +int add_attr_bitmap_positioned(MFT_RECORD *m, const char *name, + const u32 name_len, const IGNORE_CASE_BOOL ic, + const run_list *rl, const char *bitmap, const u32 bitmap_len) +{ + int err; + + err = insert_positioned_attr_in_mft_record(m, AT_BITMAP, name, name_len, + ic, 0, rl, bitmap, bitmap_len); + if (err < 0) + Eprintf("add_attr_bitmap_positioned failed: %s\n", + strerror(-err)); + return err; +} + +/* + * Create bitmap and index allocation attributes, modify index root + * attribute accordingly and move all of the index entries from the index root + * into the index allocation. + * + * Return 0 on success or -errno on error. + */ +int upgrade_to_large_index(MFT_RECORD *m, const char *name, + u32 name_len, const IGNORE_CASE_BOOL ic, + INDEX_ALLOCATION **index) +{ + ntfs_attr_search_ctx *ctx; + ATTR_RECORD *a; + INDEX_ROOT *r; + INDEX_ENTRY *re; + INDEX_ALLOCATION *ia_val = NULL; + uchar_t *uname; + char bmp[8]; + char *re_start, *re_end; + int i, err, index_block_size; + + if (name_len) { + i = (name_len + 1) * sizeof(uchar_t); + uname = (uchar_t*)calloc(1, i); + if (!uname) + return -errno; + name_len = stoucs(uname, name, i); + if (name_len > 0xff) { + free(uname); + return -ENAMETOOLONG; + } + } else + uname = NULL; + /* Find the index root attribute. */ + ctx = ntfs_get_attr_search_ctx(NULL, m); + if (!ctx) { + Eprintf("Failed to allocate attribute search context.\n"); + return -ENOMEM; + } + if (ic == IGNORE_CASE) { + Eprintf("FIXME: Hit unimplemented code path #4.\n"); + err = -ENOTSUP; + goto err_out; + } + err = ntfs_lookup_attr(AT_INDEX_ROOT, uname, name_len, ic, 0, NULL, 0, + ctx); + if (uname) + free(uname); + if (err) { + err = -ENOTDIR; + goto err_out; + } + a = ctx->attr; + if (a->non_resident || a->flags) { + err = -EINVAL; + goto err_out; + } + r = (INDEX_ROOT*)((char*)a + le16_to_cpu(a->value_offset)); + re_end = (char*)r + le32_to_cpu(a->value_length); + re_start = (char*)&r->index + le32_to_cpu(r->index.entries_offset); + re = (INDEX_ENTRY*)re_start; + index_block_size = le32_to_cpu(r->index_block_size); + memset(bmp, 0, sizeof(bmp)); + ntfs_set_bit(bmp, 0ULL, 1); + /* Bitmap has to be at least 8 bytes in size. */ + err = add_attr_bitmap(m, name, name_len, ic, (char*)&bmp, sizeof(bmp)); + if (err) + goto err_out; + ia_val = calloc(1, index_block_size); + if (!ia_val) { + err = -errno; + goto err_out; + } + /* Setup header. */ + ia_val->magic = magic_INDX; + ia_val->usa_ofs = cpu_to_le16(sizeof(INDEX_ALLOCATION)); + if (index_block_size >= NTFS_SECTOR_SIZE) + ia_val->usa_count = cpu_to_le16(index_block_size / + NTFS_SECTOR_SIZE + 1); + else { + ia_val->usa_count = cpu_to_le16(1); + Qprintf("Sector size is bigger than index block size. Setting " + "usa_count to 1. If Windows\nchkdsk reports this as " + "corruption, please email linux-ntfs-dev@lists.sf.net\n" + "stating that you saw this message and that the file " + "system created was corrupt.\nThank you."); + } + /* Set USN to 1. */ + *(u16*)((char*)ia_val + le16_to_cpu(ia_val->usa_ofs)) = + cpu_to_le16(1); + ia_val->lsn = cpu_to_le64(0); + ia_val->index_block_vcn = cpu_to_le64(0); + ia_val->index.flags = LEAF_NODE; + /* Align to 8-byte boundary. */ + ia_val->index.entries_offset = cpu_to_le32((sizeof(INDEX_HEADER) + + le16_to_cpu(ia_val->usa_count) * 2 + 7) & ~7); + ia_val->index.allocated_size = cpu_to_le32(index_block_size - + (sizeof(INDEX_ALLOCATION) - sizeof(INDEX_HEADER))); + /* Find the last entry in the index root and save it in re. */ + while ((char*)re < re_end && !(re->flags & INDEX_ENTRY_END)) { + /* Next entry in index root. */ + re = (INDEX_ENTRY*)((char*)re + le16_to_cpu(re->length)); + } + /* Copy all the entries including the termination entry. */ + i = (char*)re - re_start + le16_to_cpu(re->length); + memcpy((char*)&ia_val->index + + le32_to_cpu(ia_val->index.entries_offset), re_start, i); + /* Finish setting up index allocation. */ + ia_val->index.index_length = cpu_to_le32(i + + le32_to_cpu(ia_val->index.entries_offset)); + /* Move the termination entry forward to the beginning if necessary. */ + if ((char*)re > re_start) { + memmove(re_start, (char*)re, le16_to_cpu(re->length)); + re = (INDEX_ENTRY*)re_start; + } + /* Now fixup empty index root with pointer to index allocation VCN 0. */ + r->index.flags = LARGE_INDEX; + re->flags |= INDEX_ENTRY_NODE; + if (le16_to_cpu(re->length) < sizeof(INDEX_ENTRY_HEADER) + sizeof(VCN)) + re->length = cpu_to_le16(le16_to_cpu(re->length) + sizeof(VCN)); + r->index.index_length = cpu_to_le32(le32_to_cpu(r->index.entries_offset) + + le16_to_cpu(re->length)); + r->index.allocated_size = r->index.index_length; + /* Resize index root attribute. */ + err = resize_resident_attribute_value(m, a, sizeof(INDEX_ROOT) - + sizeof(INDEX_HEADER) + + le32_to_cpu(r->index.allocated_size)); + if (err) { + // TODO: Remove the added bitmap! + // Revert index root from index allocation. + goto err_out; + } + /* Set VCN pointer to 0LL. */ + *(VCN*)((char*)re + cpu_to_le16(re->length) - sizeof(VCN)) = + cpu_to_le64(0); + err = ntfs_pre_write_mst_fixup((NTFS_RECORD*)ia_val, index_block_size); + if (err) { + err = -errno; + Eprintf("ntfs_pre_write_mst_fixup() failed in " + "upgrade_to_large_index.\n"); + goto err_out; + } + err = add_attr_index_alloc(m, name, name_len, ic, (char*)ia_val, + index_block_size); + ntfs_post_write_mst_fixup((NTFS_RECORD*)ia_val); + if (err) { + // TODO: Remove the added bitmap! + // Revert index root from index allocation. + goto err_out; + } + *index = ia_val; + return 0; +err_out: + if (ctx) + ntfs_put_attr_search_ctx(ctx); + if (ia_val) + free(ia_val); + return err; +} + +/* + * Create space of @size bytes at position @pos inside the index block @index. + * + * Return 0 on success or -errno on error. + */ +int make_room_for_index_entry_in_index_block(INDEX_BLOCK *index, + INDEX_ENTRY *pos, u32 size) +{ + u32 biu; + + if (!size) + return 0; +#ifdef DEBUG + /* + * Rigorous consistency checks. Always return -EINVAL even if more + * appropriate codes exist for simplicity of parsing the return value. + */ + if (size != ((size + 7) & ~7)) { + Eprintf("make_room_for_index_entry_in_index_block() received " + "non 8-byte aligned size.\n"); + return -EINVAL; + } + if (!index || !pos) + return -EINVAL; + if ((char*)pos < (char*)index || (char*)pos + size < (char*)index || + (char*)pos > (char*)index + sizeof(INDEX_BLOCK) - + sizeof(INDEX_HEADER) + + le32_to_cpu(index->index.allocated_size) || + (char*)pos + size > (char*)index + sizeof(INDEX_BLOCK) - + sizeof(INDEX_HEADER) + + le32_to_cpu(index->index.allocated_size)) + return -EINVAL; + /* The - sizeof(INDEX_ENTRY_HEADER) is for the index terminator. */ + if ((char*)pos - (char*)&index->index > + le32_to_cpu(index->index.index_length) + - sizeof(INDEX_ENTRY_HEADER)) + return -EINVAL; +#endif + biu = le32_to_cpu(index->index.index_length); + /* Do we have enough space? */ + if (biu + size > le32_to_cpu(index->index.allocated_size)) + return -ENOSPC; + /* Move everything after pos to pos + size. */ + memmove((char*)pos + size, (char*)pos, biu - ((char*)pos - + (char*)&index->index)); + /* Update index block. */ + index->index.index_length = cpu_to_le32(biu + size); + return 0; +} + +/* + * Insert the fully completed FILE_NAME_ATTR @file_name which is inside + * the file with mft reference @file_ref into the index (allocation) block + * @index (which belongs to @file_ref's parent directory). + * + * Return 0 on success or -errno on error. + */ +int insert_file_link_in_dir_index(INDEX_BLOCK *index, MFT_REF file_ref, + FILE_NAME_ATTR *file_name, u32 file_name_size) +{ + int err, i; + INDEX_ENTRY *ie; + char *index_end; + + /* + * Lookup dir entry @file_name in dir @index to determine correct + * insertion location. FIXME: Using a very oversimplified lookup + * method which is sufficient for mkntfs but no good whatsoever in + * real world scenario. (AIA) + */ + index_end = (char*)&index->index + + le32_to_cpu(index->index.index_length); + ie = (INDEX_ENTRY*)((char*)&index->index + + le32_to_cpu(index->index.entries_offset)); + /* + * Loop until we exceed valid memory (corruption case) or until we + * reach the last entry. + */ + while ((char*)ie < index_end && !(ie->flags & INDEX_ENTRY_END)) { +/* +#ifdef DEBUG + Dprintf("file_name_attr1->file_name_length = %i\n", + file_name->file_name_length); + if (file_name->file_name_length) { + char *__buf; + __buf = (char*)calloc(1, file_name->file_name_length + + 1); + if (!__buf) + err_exit("Failed to allocate internal buffer: " + "%s\n", strerror(errno)); + i = ucstos(__buf, (uchar_t*)&file_name->file_name, + file_name->file_name_length + 1); + if (i == -1) + Dprintf("Name contains non-displayable " + "Unicode characters.\n"); + Dprintf("file_name_attr1->file_name = %s\n", __buf); + free(__buf); + } + Dprintf("file_name_attr2->file_name_length = %i\n", + ie->key.file_name.file_name_length); + if (ie->key.file_name.file_name_length) { + char *__buf; + __buf = (char*)calloc(1, + ie->key.file_name.file_name_length + 1); + if (!__buf) + err_exit("Failed to allocate internal buffer: " + "%s\n", strerror(errno)); + i = ucstos(__buf, ie->key.file_name.file_name, + ie->key.file_name.file_name_length + 1); + if (i == -1) + Dprintf("Name contains non-displayable " + "Unicode characters.\n"); + Dprintf("file_name_attr2->file_name = %s\n", __buf); + free(__buf); + } +#endif +*/ + i = ntfs_file_compare_values(file_name, + (FILE_NAME_ATTR*)&ie->key.file_name, 1, + IGNORE_CASE, vol->upcase, vol->upcase_len); + /* + * If @file_name collates before ie->key.file_name, there is no + * matching index entry. + */ + if (i == -1) + break; + /* If file names are not equal, continue search. */ + if (i) + goto do_next; + /* File names are equal when compared ignoring case. */ + /* + * If BOTH file names are in the POSIX namespace, do a case + * sensitive comparison as well. Otherwise the names match so + * we return -EEXIST. FIXME: There are problems with this in a + * real world scenario, when one is POSIX and one isn't, but + * fine for mkntfs where we don't use POSIX namespace at all + * and hence this following code is luxury. (AIA) + */ + if (file_name->file_name_type != FILE_NAME_POSIX || + ie->key.file_name.file_name_type != FILE_NAME_POSIX) + return -EEXIST; + i = ntfs_file_compare_values(file_name, + (FILE_NAME_ATTR*)&ie->key.file_name, 1, + CASE_SENSITIVE, vol->upcase, vol->upcase_len); + if (i == -1) + break; + /* Complete match. Bugger. Can't insert. */ + if (!i) + return -EEXIST; +do_next: +#ifdef DEBUG + /* Next entry. */ + if (!ie->length) { + Dprintf("BUG: ie->length is zero, breaking out of " + "loop.\n"); + break; + } +#endif + ie = (INDEX_ENTRY*)((char*)ie + le16_to_cpu(ie->length)); + }; + i = (sizeof(INDEX_ENTRY_HEADER) + file_name_size + 7) & ~7; + err = make_room_for_index_entry_in_index_block(index, ie, i); + if (err) { + Eprintf("make_room_for_index_entry_in_index_block failed: " + "%s\n", strerror(-err)); + return err; + } + /* Create entry in place and copy file name attribute value. */ + ie->indexed_file = file_ref; + ie->length = cpu_to_le16(i); + ie->key_length = cpu_to_le16(file_name_size); + ie->flags = cpu_to_le16(0); + ie->reserved = cpu_to_le16(0); + memcpy((char*)&ie->key.file_name, (char*)file_name, file_name_size); + return 0; +} + +/* + * Create a file_name_attribute in the mft record @m_file which points to the + * parent directory with mft reference @ref_parent. + * + * Then, insert an index entry with this file_name_attribute in the index + * block @index of the index allocation attribute of the parent directory. + * + * @ref_file is the mft reference of @m_file. + * + * Return 0 on success or -errno on error. + */ +int create_hardlink(INDEX_BLOCK *index, const MFT_REF ref_parent, + MFT_RECORD *m_file, const MFT_REF ref_file, + const s64 allocated_size, const s64 data_size, + const FILE_ATTR_FLAGS flags, const u16 packed_ea_size, + const u32 reparse_point_tag, const char *file_name, + const FILE_NAME_TYPE_FLAGS file_name_type) +{ + FILE_NAME_ATTR *fn; + int i, fn_size; + + /* Create the file_name attribute. */ + i = (strlen(file_name) + 1) * sizeof(uchar_t); + fn_size = sizeof(FILE_NAME_ATTR) + i; + fn = (FILE_NAME_ATTR*)malloc(fn_size); + if (!fn) + return -errno; + fn->parent_directory = ref_parent; + // FIXME: Is this correct? Or do we have to copy the creation_time + // from the std info? + fn->creation_time = time2ntfs(time(NULL)); + fn->last_data_change_time = fn->creation_time; + fn->last_mft_change_time = fn->creation_time; + fn->last_access_time = fn->creation_time; + fn->allocated_size = cpu_to_le64(allocated_size); + fn->data_size = cpu_to_le64(data_size); + fn->file_attributes = flags; + /* These are in a union so can't have both. */ + if (packed_ea_size && reparse_point_tag) { + free(fn); + return -EINVAL; + } + if (packed_ea_size) { + fn->packed_ea_size = cpu_to_le16(packed_ea_size); + fn->reserved = cpu_to_le16(0); + } else + fn->reparse_point_tag = cpu_to_le32(reparse_point_tag); + fn->file_name_type = file_name_type; + i = stoucs(fn->file_name, file_name, i); + if (i < 1) { + free(fn); + return -EINVAL; + } + if (i > 0xff) { + free(fn); + return -ENAMETOOLONG; + } + /* No terminating null in file names. */ + fn->file_name_length = i; + fn_size = sizeof(FILE_NAME_ATTR) + i * sizeof(uchar_t); + /* Increment the link count of @m_file. */ + i = le16_to_cpu(m_file->link_count); + if (i == 0xffff) { + Eprintf("Too many hardlinks present already.\n"); + free(fn); + return -EINVAL; + } + m_file->link_count = cpu_to_le16(i + 1); + /* Add the file_name to @m_file. */ + i = insert_resident_attr_in_mft_record(m_file, AT_FILE_NAME, NULL, 0, 0, + 0, RESIDENT_ATTR_IS_INDEXED, (char*)fn, fn_size); + if (i < 0) { + Eprintf("create_hardlink failed adding file name attribute: " + "%s\n", strerror(-i)); + free(fn); + /* Undo link count increment. */ + m_file->link_count = cpu_to_le16( + le16_to_cpu(m_file->link_count) - 1); + return i; + } + /* Insert the index entry for file_name in @index. */ + i = insert_file_link_in_dir_index(index, ref_file, fn, fn_size); + if (i < 0) { + Eprintf("create_hardlink failed inserting index entry: %s\n", + strerror(-i)); + /* FIXME: Remove the file name attribute from @m_file. */ + free(fn); + /* Undo link count increment. */ + m_file->link_count = cpu_to_le16( + le16_to_cpu(m_file->link_count) - 1); + return i; + } + free(fn); + return 0; +} + +void init_options() +{ + memset(&opt, 0, sizeof(opt)); + opt.index_block_size = 4096; + opt.attr_defs = (ATTR_DEF*)&attrdef_ntfs12_array; + opt.attr_defs_len = sizeof(attrdef_ntfs12_array); + //Dprintf("Attr_defs table length = %u\n", opt.attr_defs_len); +} + +void usage(void) __attribute__ ((noreturn)); + +void usage(void) +{ + fprintf(stderr, "Copyright (c) 2001,2002 Anton Altaparmakov.\n" + "Create an NTFS volume on a user specified (block) device.\n" + "Usage: %s [-s sector-size] [-c cluster-size] " + "[-L volume-label]\n\t[-z mft-zone-multiplier] " + "[-fnqvvCFIQV] device [number-of-sectors]\n", + EXEC_NAME); + exit(1); +} + +void parse_options(int argc, char *argv[]) +{ + int c; + long l; + unsigned long u; + char *s; + +// Need to have: mft record size, index record size, ntfs version, mft size, +// logfile size, list of bad blocks, check for bad blocks, ... + if (argc && *argv) + EXEC_NAME = *argv; + fprintf(stderr, "%s v%s\n", EXEC_NAME, VERSION); + while ((c = getopt(argc, argv, "c:fnqs:vz:CFIL:QV")) != EOF) + switch (c) { + case 'n': + opt.no_action = 1; + break; + case 'c': + l = strtol(optarg, &s, 0); + if (!l || l > INT_MAX || *s) + err_exit("Invalid cluster size.\n"); + vol->cluster_size = l; + break; + case 'f': + case 'Q': + opt.quick_format = 1; + break; + case 'q': + opt.quiet = 1; + break; + case 's': + l = strtol(optarg, &s, 0); + if (!l || l > INT_MAX || *s) + err_exit("Invalid sector size.\n"); + opt.sector_size = l; + break; + case 'v': + opt.verbose++; + break; + case 'z': + l = strtol(optarg, &s, 0); + if (l < 1 || l > 4 || *s) + err_exit("Invalid MFT zone multiplier.\n"); + opt.mft_zone_multiplier = l; + break; + case 'C': + opt.enable_compression = 1; + break; + case 'F': + opt.force = 1; + break; + case 'I': + opt.disable_indexing = 1; + break; + case 'L': + vol->vol_name = optarg; + break; + case 'V': + /* Version number already printed, so just exit. */ + exit(0); + default: + usage(); + } + if (optind == argc) + usage(); + vol->dev_name = argv[optind++]; + if (optind < argc) { + u = strtoul(argv[optind++], &s, 0); + if (*s || !u || (u >= ULONG_MAX && errno == ERANGE)) + err_exit("Invalid number of sectors: %s\n", + argv[optind - 1]); + opt.nr_sectors = u; + } + if (optind < argc) + usage(); +} + +void mkntfs_exit(void) +{ + int err; + + if (index_block) + free(index_block); + if (buf) + free(buf); + if (buf2) + free(buf2); + if (lcn_bitmap) + free(lcn_bitmap); + if (mft_bitmap) + free(mft_bitmap); + if (rl) + free(rl); + if (rl_mft) + free(rl_mft); + if (rl_mft_bmp) + free(rl_mft_bmp); + if (rl_mftmirr) + free(rl_mftmirr); + if (rl_logfile) + free(rl_logfile); + if (rl_boot) + free(rl_boot); + if (rl_bad) + free(rl_bad); + if (rl_index) + free(rl_index); + if (opt.bad_blocks) + free(opt.bad_blocks); + if (opt.attr_defs != (ATTR_DEF*)attrdef_ntfs12_array) + free(opt.attr_defs); + if (vol->upcase) + free(vol->upcase); + flk.l_type = F_UNLCK; + err = fcntl(vol->fd, F_SETLK, &flk); + if (err == -1) + Eprintf("Warning: Could not unlock %s: %s\n", vol->dev_name, + strerror(errno)); + err = close(vol->fd); + if (err == -1) + Eprintf("Warning: Could not close %s: %s\n", vol->dev_name, + strerror(errno)); + if (vol) + free(vol); +} + +#define MAKE_MFT_REF(_ref, _seqno) cpu_to_le64((((u64)(_seqno)) << 48) \ + | ((u64)(_ref))) + +static inline int valid_offset(int f, long long ofs) +{ + char ch; + + if (lseek(f, ofs, SEEK_SET) >= 0 && read(f, &ch, 1) == 1) + return 1; + return 0; +} + +/* + * Returns the number of bs sized blocks in a partition. Adapted from + * e2fsutils-1.19, Copyright (C) 1995 Theodore Ts'o. + */ +long long get_device_size(int f, int bs) +{ + long long high, low; +#ifdef BLKGETSIZE + long size; + + if (ioctl(f, BLKGETSIZE, &size) >= 0) { + Dprintf("BLKGETSIZE nr 512 byte blocks = %ld (0x%ld)\n", size, + size); + return (long long)size * 512 / bs; + } +#endif +#ifdef FDGETPRM + { struct floppy_struct this_floppy; + + if (ioctl(f, FDGETPRM, &this_floppy) >= 0) { + Dprintf("FDGETPRM nr 512 byte blocks = %ld (0x%ld)\n", + this_floppy.size, this_floppy.size); + return (long long)this_floppy.size * 512 / bs; + } + } +#endif + /* + * We couldn't figure it out by using a specialized ioctl, + * so do binary search to find the size of the partition. + */ + low = 0LL; + for (high = 1024LL; valid_offset(f, high); high <<= 1) + low = high; + while (low < high - 1LL) { + const long long mid = (low + high) / 2; + + if (valid_offset(f, mid)) + low = mid; + else + high = mid; + } + lseek(f, 0LL, SEEK_SET); + return (low + 1LL) / bs; +} + +int main(int argc, char **argv) +{ + int i, j, err; + ssize_t bw; + struct stat sbuf; + long long lw, pos; + MFT_RECORD *m; + ATTR_RECORD *a; + MFT_REF root_ref; + ntfs_attr_search_ctx *ctx; + char *sd; + NTFS_BOOT_SECTOR *bs; + unsigned long mnt_flags; + + /* Initialize the random number generator with the current time. */ + srandom(time(NULL)); + /* Initialize ntfs_volume structure vol. */ + vol = (ntfs_volume*)calloc(1, sizeof(*vol)); + if (!vol) + err_exit("Could not allocate memory for internal buffer.\n"); + vol->major_ver = 1; + vol->minor_ver = 2; + vol->mft_record_size = 1024; + vol->mft_record_size_bits = 10; + /* Length is in unicode characters. */ + vol->upcase_len = 65536; + vol->upcase = (uchar_t*)malloc(vol->upcase_len * sizeof(uchar_t)); + if (!vol->upcase) + err_exit("Could not allocate memory for internal buffer.\n"); + init_upcase_table(vol->upcase, vol->upcase_len * sizeof(uchar_t)); + /* Initialize opt to zero / required values. */ + init_options(); + /* Parse command line options. */ + parse_options(argc, argv); + /* Verify we are dealing with a block device. */ + if (stat(vol->dev_name, &sbuf) == -1) { + if (errno == ENOENT) + err_exit("The device doesn't exist; did you specify " + "it correctly?\n"); + err_exit("Error getting information about %s: %s\n", + vol->dev_name, strerror(errno)); + } + if (!S_ISBLK(sbuf.st_mode)) { + Eprintf("%s is not a block device.\n", vol->dev_name); + if (!opt.force) + err_exit("Refusing to make a filesystem here!\n"); + if (!opt.nr_sectors) { + if (!sbuf.st_size && !sbuf.st_blocks) + err_exit("You must specify the number of " + "sectors.\n"); + if (opt.sector_size) { + if (sbuf.st_size) + opt.nr_sectors = sbuf.st_size / + opt.sector_size; + else + opt.nr_sectors = ((s64)sbuf.st_blocks + << 9) / opt.sector_size; + } else { + if (sbuf.st_size) + opt.nr_sectors = sbuf.st_size / 512; + else + opt.nr_sectors = sbuf.st_blocks; + opt.sector_size = 512; + } + } + fprintf(stderr, "mkntfs forced anyway.\n"); + } +#ifdef HAVE_LINUX_MAJOR_H + else if ((MAJOR(sbuf.st_rdev) == HD_MAJOR && + MINOR(sbuf.st_rdev) % 64 == 0) || + (SCSI_BLK_MAJOR(MAJOR(sbuf.st_rdev)) && + MINOR(sbuf.st_rdev) % 16 == 0)) { + err_exit("%s is entire device, not just one partition!\n", + vol->dev_name); + } +#endif + /* Make sure the file system is not mounted. */ + if (ntfs_check_if_mounted(vol->dev_name, &mnt_flags)) + Eprintf("Failed to determine whether %s is mounted: %s\n", + vol->dev_name, strerror(errno)); + else if (mnt_flags & NTFS_MF_MOUNTED) { + Eprintf("%s is mounted.\n", vol->dev_name); + if (!opt.force) + err_exit("Refusing to make a filesystem here!\n"); + fprintf(stderr, "mkntfs forced anyway. Hope /etc/mtab is " + "incorrect.\n"); + } + + /* Open the device for reading or reading and writing. */ + if (opt.no_action) { + Qprintf("Running in READ-ONLY mode!\n"); + i = O_RDONLY; + } else + i = O_RDWR; + vol->fd = open(vol->dev_name, i); + if (vol->fd == -1) + err_exit("Could not open %s: %s\n", vol->dev_name, + strerror(errno)); + /* Acquire exlusive (mandatory) write lock on the whole device. */ + memset(&flk, 0, sizeof(flk)); + if (opt.no_action) + flk.l_type = F_RDLCK; + else + flk.l_type = F_WRLCK; + flk.l_whence = SEEK_SET; + flk.l_start = flk.l_len = 0LL; + err = fcntl(vol->fd, F_SETLK, &flk); + if (err == -1) { + Eprintf("Could not lock %s for %s: %s\n", vol->dev_name, + opt.no_action ? "reading" : "writing", + strerror(errno)); + err = close(vol->fd); + if (err == -1) + Eprintf("Warning: Could not close %s: %s\n", + vol->dev_name, strerror(errno)); + exit(1); + } + /* Register our exit function which will unlock and close the device. */ + err = atexit(&mkntfs_exit); + if (err == -1) { + Eprintf("Could not set up exit() function because atexit() " + "failed. Aborting...\n"); + mkntfs_exit(); + exit(1); + } + /* If user didn't specify the sector size, determine it now. */ + if (!opt.sector_size) { +#ifdef BLKSSZGET + int _sect_size = 0; + + if (ioctl(vol->fd, BLKSSZGET, &_sect_size) >= 0) + opt.sector_size = _sect_size; + else +#endif + { + Eprintf("No sector size specified for %s and it could " + "not be obtained automatically.\n" + "Assuming sector size is 512 bytes.\n", + vol->dev_name); + opt.sector_size = 512; + } + } + /* Validate sector size. */ + if ((opt.sector_size - 1) & opt.sector_size || + opt.sector_size < 256 || opt.sector_size > 4096) + err_exit("Error: sector_size is invalid. It must be a power " + "of two, and it must be\n greater or equal 256 and " + "less than or equal 4096 bytes.\n"); + Dprintf("sector size = %i bytes\n", opt.sector_size); + /* If user didn't specify the number of sectors, determine it now. */ + if (!opt.nr_sectors) { + opt.nr_sectors = get_device_size(vol->fd, opt.sector_size); + if (opt.nr_sectors <= 0) + err_exit("get_device_size(%s) failed. Please specify " + "it manually.\n", vol->dev_name); + } + Dprintf("number of sectors = %Ld (0x%Lx)\n", opt.nr_sectors, + opt.nr_sectors); + /* Reserve the last sector for the backup boot sector. */ + opt.nr_sectors--; + /* If user didn't specify the volume size, determine it now. */ + if (!opt.volume_size) + opt.volume_size = opt.nr_sectors * opt.sector_size; + else if (opt.volume_size & (opt.sector_size - 1)) + err_exit("Error: volume_size is not a multiple of " + "sector_size.\n"); + /* Validate volume size. */ + if (opt.volume_size < 1 << 20 /* 1MiB */) + err_exit("Error: device is too small (%ikiB). Minimum NTFS " + "volume size is 1MiB.\n", opt.volume_size / 1024); + Dprintf("volume size = %LikiB\n", opt.volume_size / 1024); + /* If user didn't specify the cluster size, determine it now. */ + if (!vol->cluster_size) { + if (opt.volume_size <= 512LL << 20) /* <= 512MB */ + vol->cluster_size = 512; + else if (opt.volume_size <= 1LL << 30) /* ]512MB-1GB] */ + vol->cluster_size = 1024; + else if (opt.volume_size <= 2LL << 30) /* ]1GB-2GB] */ + vol->cluster_size = 2048; + else + vol->cluster_size = 4096; + /* For small volumes on devices with large sector sizes. */ + if (vol->cluster_size < opt.sector_size) + vol->cluster_size = opt.sector_size; + } + /* Validate cluster size. */ + if (vol->cluster_size & (vol->cluster_size - 1) || + vol->cluster_size < opt.sector_size || + vol->cluster_size > 128 * opt.sector_size || + vol->cluster_size > 65536) + err_exit("Error: cluster_size is invalid. It must be a power " + "of two, be at least\nthe same as sector_size, be " + "maximum 64kB, and the sectors per cluster value " + "has\nto fit inside eight bits. (We do not support " + "larger cluster sizes yet.)\n"); + vol->cluster_size_bits = ffs(vol->cluster_size) - 1; + Dprintf("cluster size = %i bytes\n", vol->cluster_size); + if (vol->cluster_size > 4096) { + if (opt.enable_compression) { + if (!opt.force) + err_exit("Error: cluster_size is above 4096 " + "bytes and compression is " + "requested.\nThis is not " + "possible due to limitations " + "in the compression algorithm " + "used by\nWindows.\n"); + opt.enable_compression = 0; + } + Qprintf("Warning: compression will be disabled on this volume " + "because it is not\nsupported when the cluster " + "size is above 4096 bytes. This is due to \n" + "limitations in the compression algorithm used " + "by Windows.\n"); + } + /* If user didn't specify the number of clusters, determine it now. */ + if (!opt.nr_clusters) + opt.nr_clusters = opt.volume_size / vol->cluster_size; + /* + * Check the cluster_size and nr_sectors for consistency with + * sector_size and nr_sectors. And check both of these for consistency + * with volume_size. + */ + if (opt.nr_clusters != (opt.nr_sectors * opt.sector_size) / + vol->cluster_size || + opt.volume_size / opt.sector_size != opt.nr_sectors || + opt.volume_size / vol->cluster_size != opt.nr_clusters) + err_exit("Illegal combination of volume/cluster/sector size " + "and/or cluster/sector number.\n"); + Dprintf("number of clusters = %Lu (0x%Lx)\n", opt.nr_clusters, + opt.nr_clusters); + /* Determine lcn bitmap byte size and allocate it. */ + lcn_bitmap_byte_size = (opt.nr_clusters + 7) >> 3; + /* Needs to be multiple of 8 bytes. */ + lcn_bitmap_byte_size = (lcn_bitmap_byte_size + 7) & ~7; + i = (lcn_bitmap_byte_size + vol->cluster_size - 1) & + ~(vol->cluster_size - 1); + Dprintf("lcn_bitmap_byte_size = %i, allocated = %i\n", + lcn_bitmap_byte_size, i); + lcn_bitmap = (unsigned char *)calloc(1, lcn_bitmap_byte_size); + if (!lcn_bitmap) + err_exit("Failed to allocate internal buffer: %s", + strerror(errno)); + /* + * $Bitmap can overlap the end of the volume. Any bits in this region + * must be set. This region also encompasses the backup boot sector. + */ + for (i = opt.nr_clusters; i < lcn_bitmap_byte_size << 3; i++) + ntfs_set_bit(lcn_bitmap, (u64)i, 1); + /* + * Determine mft_size: 16 mft records or 1 cluster, which ever is + * bigger, rounded to multiples of cluster size. + */ + opt.mft_size = (16 * vol->mft_record_size + vol->cluster_size - 1) + & ~(vol->cluster_size - 1); + Dprintf("MFT size = %i (0x%x) bytes\n", opt.mft_size, opt.mft_size); + /* Determine mft bitmap size and allocate it. */ + mft_bitmap_size = opt.mft_size / vol->mft_record_size; + /* Convert to bytes, at least one. */ + mft_bitmap_byte_size = (mft_bitmap_size + 7) >> 3; + /* Mft bitmap is allocated in multiples of 8 bytes. */ + mft_bitmap_byte_size = (mft_bitmap_byte_size + 7) & ~7; + Dprintf("mft_bitmap_size = %i, mft_bitmap_byte_size = %i\n", + mft_bitmap_size, mft_bitmap_byte_size); + mft_bitmap = (unsigned char *)calloc(1, mft_bitmap_byte_size); + if (!mft_bitmap) + err_exit("Failed to allocate internal buffer: %s\n", + strerror(errno)); + /* Create run list for mft bitmap. */ + rl_mft_bmp = (run_list *)malloc(2 * sizeof(run_list)); + if (!rl_mft_bmp) + err_exit("Failed to allocate internal buffer: %s\n", + strerror(errno)); + rl_mft_bmp[0].vcn = 0LL; + /* Mft bitmap is right after $Boot's data. */ + j = (8192 + vol->cluster_size - 1) / vol->cluster_size; + rl_mft_bmp[0].lcn = j; + /* + * Size is always one cluster, even though valid data size and + * initialized data size are only 8 bytes. + */ + rl_mft_bmp[1].vcn = rl_mft_bmp[0].length = 1LL; + rl_mft_bmp[1].lcn = -1LL; + rl_mft_bmp[1].length = 0LL; + /* Allocate cluster for mft bitmap. */ + ntfs_set_bit(lcn_bitmap, (s64)j, 1); + /* If user didn't specify the mft lcn, determine it now. */ + if (!opt.mft_lcn) { + /* + * We start at the higher value out of 16kiB and just after the + * mft bitmap. + */ + opt.mft_lcn = rl_mft_bmp[0].lcn + rl_mft_bmp[0].length; + if (opt.mft_lcn * vol->cluster_size < 16 * 1024) + opt.mft_lcn = (16 * 1024 + vol->cluster_size - 1) / + vol->cluster_size; + } + Dprintf("$MFT logical cluster number = 0x%x\n", opt.mft_lcn); + /* Determine MFT zone size. */ + opt.mft_zone_end = opt.nr_clusters; + switch (opt.mft_zone_multiplier) { /* % of volume size in clusters */ + case 4: + opt.mft_zone_end = opt.mft_zone_end >> 1; /* 50% */ + break; + case 3: + opt.mft_zone_end = opt.mft_zone_end * 3 >> 3; /* 37.5% */ + break; + case 2: + opt.mft_zone_end = opt.mft_zone_end >> 2; /* 25% */ + break; + /* case 1: */ + default: + opt.mft_zone_end = opt.mft_zone_end >> 3; /* 12.5% */ + break; + } + Dprintf("MFT zone size = %lukiB\n", opt.mft_zone_end / 1024); + /* + * The mft zone begins with the mft data attribute, not at the beginning + * of the device. + */ + opt.mft_zone_end += opt.mft_lcn; + /* Create run list for mft. */ + rl_mft = (run_list *)malloc(2 * sizeof(run_list)); + if (!rl_mft) + err_exit("Failed to allocate internal buffer: %s\n", + strerror(errno)); + rl_mft[0].vcn = 0LL; + rl_mft[0].lcn = opt.mft_lcn; + /* We already rounded mft size up to a cluster. */ + j = opt.mft_size / vol->cluster_size; + rl_mft[1].vcn = rl_mft[0].length = j; + rl_mft[1].lcn = -1LL; + rl_mft[1].length = 0LL; + /* Allocate clusters for mft. */ + for (i = 0; i < j; i++) + ntfs_set_bit(lcn_bitmap, opt.mft_lcn + i, 1); + /* Determine mftmirr_lcn (middle of volume). */ + opt.mftmirr_lcn = (opt.nr_sectors * opt.sector_size >> 1) + / vol->cluster_size; + Dprintf("$MFTMirr logical cluster number = 0x%x\n", opt.mftmirr_lcn); + /* Create run list for mft mirror. */ + rl_mftmirr = (run_list *)malloc(2 * sizeof(run_list)); + if (!rl_mftmirr) + err_exit("Failed to allocate internal buffer: %s\n", + strerror(errno)); + rl_mftmirr[0].vcn = 0LL; + rl_mftmirr[0].lcn = opt.mftmirr_lcn; + /* + * The mft mirror is either 4kb (the first four records) or one cluster + * in size, which ever is bigger. In either case, it contains a + * byte-for-byte identical copy of the beginning of the mft (i.e. either + * ther first four records (4kb) or the first cluster worth of records, + * whichever is bigger). + */ + j = (4 * vol->mft_record_size + vol->cluster_size - 1) / vol->cluster_size; + rl_mftmirr[1].vcn = rl_mftmirr[0].length = j; + rl_mftmirr[1].lcn = -1LL; + rl_mftmirr[1].length = 0LL; + /* Allocate clusters for mft mirror. */ + for (i = 0; i < j; i++) + ntfs_set_bit(lcn_bitmap, opt.mftmirr_lcn + i, 1); + opt.logfile_lcn = opt.mftmirr_lcn + j; + Dprintf("$LogFile logical cluster number = 0x%x\n", opt.logfile_lcn); + /* Create run list for log file. */ + rl_logfile = (run_list *)malloc(2 * sizeof(run_list)); + if (!rl_logfile) + err_exit("Failed to allocate internal buffer: %s\n", + strerror(errno)); + rl_logfile[0].vcn = 0LL; + rl_logfile[0].lcn = opt.logfile_lcn; + /* + * Determine logfile_size from volume_size (rounded up to a cluster), + * making sure it does not overflow the end of the volume. + */ + if (opt.volume_size < 2048LL * 1024) /* < 2MiB */ + opt.logfile_size = 256LL * 1024; /* -> 256kiB */ + else if (opt.volume_size < 4000000LL) /* < 4MB */ + opt.logfile_size = 512LL * 1024; /* -> 512kiB */ + else if (opt.volume_size <= 200LL * 1024 * 1024)/* < 200MiB */ + opt.logfile_size = 2048LL * 1024; /* -> 2MiB */ + else if (opt.volume_size >= 400LL << 20) /* > 400MiB */ + opt.logfile_size = 4 << 20; /* -> 4MiB */ + else + opt.logfile_size = (opt.volume_size / 100) & + ~(vol->cluster_size - 1); + j = opt.logfile_size / vol->cluster_size; + while (rl_logfile[0].lcn + j >= opt.nr_clusters) { + /* + * $Logfile would overflow volume. Need to make it smaller than + * the standard size. It's ok as we are creating a non-standard + * volume anyway if it is that small. + */ + opt.logfile_size >>= 1; + j = opt.logfile_size / vol->cluster_size; + } + opt.logfile_size = (opt.logfile_size + vol->cluster_size - 1) & + ~(vol->cluster_size - 1); + Dprintf("$LogFile (journal) size = %ikiB\n", opt.logfile_size / 1024); + /* + * FIXME: The 256kiB limit is arbitrary. Should find out what the real + * minimum requirement for Windows is so it doesn't blue screen. + */ + if (opt.logfile_size < 256 << 10) + err_exit("$LogFile would be created with invalid size. This " + "is not allowed as it would cause Windows to " + "blue screen and during boot.\n"); + rl_logfile[1].vcn = rl_logfile[0].length = j; + rl_logfile[1].lcn = -1LL; + rl_logfile[1].length = 0LL; + /* Allocate clusters for log file. */ + for (i = 0; i < j; i++) + ntfs_set_bit(lcn_bitmap, opt.logfile_lcn + i, 1); + /* Create run list for $Boot. */ + rl_boot = (run_list *)malloc(2 * sizeof(run_list)); + if (!rl_boot) + err_exit("Failed to allocate internal buffer: %s\n", + strerror(errno)); + rl_boot[0].vcn = 0LL; + rl_boot[0].lcn = 0LL; + /* + * $Boot is always 8192 (0x2000) bytes or 1 cluster, whichever is + * bigger. + */ + j = (8192 + vol->cluster_size - 1) / vol->cluster_size; + rl_boot[1].vcn = rl_boot[0].length = j; + rl_boot[1].lcn = -1LL; + rl_boot[1].length = 0LL; + /* Allocate clusters for $Boot. */ + for (i = 0; i < j; i++) + ntfs_set_bit(lcn_bitmap, 0LL + i, 1); + /* Allocate a buffer large enough to hold the mft. */ + buf = calloc(1, opt.mft_size); + if (!buf) + err_exit("Failed to allocate internal buffer: %s\n", + strerror(errno)); + /* Create run list for $BadClus, $DATA named stream $Bad. */ + rl_bad = (run_list *)malloc(2 * sizeof(run_list)); + if (!rl_bad) + err_exit("Failed to allocate internal buffer: %s\n", + strerror(errno)); + rl_bad[0].vcn = 0LL; + rl_bad[0].lcn = -1LL; + /* + * $BadClus named stream $Bad contains the whole volume as a single + * sparse run list entry. + */ + rl_bad[1].vcn = rl_bad[0].length = opt.nr_clusters; + rl_bad[1].lcn = -1LL; + rl_bad[1].length = 0LL; + + // TODO: Mark bad blocks as such. + + /* + * If not quick format, fill the device with 0s. + * FIXME: Except bad blocks! (AIA) + */ + if (!opt.quick_format) { + unsigned long position; + unsigned long mid_clust; + float progress_inc = (float)opt.nr_clusters / 100; + + Qprintf("Initialising device with zeroes: 0%%"); + fflush(stdout); + mid_clust = (opt.volume_size >> 1) / vol->cluster_size; + for (position = 0; position < opt.nr_clusters; position++) { + if (!(position % (int)(progress_inc+1))) { + Qprintf("\b\b\b\b%3.0f%%", position / + progress_inc); + fflush(stdout); + } + bw = mkntfs_write(vol->fd, buf, vol->cluster_size); + if (bw != vol->cluster_size) { + if (bw != -1 || errno != EIO) + err_exit("This should not happen.\n"); + if (!position) + err_exit("Error: Cluster zero is bad. " + "Cannot create NTFS file " + "system.\n"); + if (position == mid_clust && + (vol->major_ver < 1 || + (vol->major_ver == 1 && + vol->minor_ver < 2))) + err_exit("Error: Bad cluster found in " + "location reserved for system " + "file $Boot.\n"); + /* Add the baddie to our bad blocks list. */ + append_to_bad_blocks(position); + Qprintf("\nFound bad cluster (%ld). Adding to " + "list of bad blocks.\nInitialising " + "device with zeroes: %3.0i%%", position, + position / progress_inc); + /* Seek to next cluster. */ + lseek(vol->fd, ((off_t)position + 1) * + vol->cluster_size, SEEK_SET); + } + } + Qprintf("\b\b\b\b100%%"); + position = (opt.volume_size & (vol->cluster_size - 1)) / + opt.sector_size; + for (i = 0; i < position; i++) { + bw = mkntfs_write(vol->fd, buf, opt.sector_size); + if (bw != opt.sector_size) { + if (bw != -1 || errno != EIO) + err_exit("This should not happen.\n"); + else if (i + 1 == position && + (vol->major_ver >= 2 || + (vol->major_ver == 1 && + vol->minor_ver >= 2))) + err_exit("Error: Bad cluster found in " + "location reserved for system " + "file $Boot.\n"); + /* Seek to next sector. */ + lseek(vol->fd, opt.sector_size, SEEK_CUR); + } + } + Qprintf(" - Done.\n"); + } + Qprintf("Creating NTFS volume structures.\n"); + /* Setup an empty mft record. */ + format_mft_record((MFT_RECORD*)buf); + /* + * Copy the mft record onto all 16 records in the buffer and setup the + * sequence numbers of each system file to equal the mft record number + * of that file (only for $MFT is the sequence number 1 rather than 0). + */ + for (i = 1; i < 16; i++) { + m = (MFT_RECORD*)(buf + i * vol->mft_record_size); + memcpy(m, buf, vol->mft_record_size); + m->sequence_number = cpu_to_le16(i); + } + /* + * If a cluster contains more than the 16 system files, fill the rest + * with empty, formatted records. + */ + if (vol->cluster_size > 16 * vol->mft_record_size) { + for (i = 16; i * vol->mft_record_size < vol->cluster_size; i++) + memcpy(buf + i * vol->mft_record_size, buf, + vol->mft_record_size); + } + /* + * Create the 16 system files, adding the system information attribute + * to each as well as marking them in use in the mft bitmap. + */ + for (i = 0; i < 16; i++) { + u32 file_attrs; + + m = (MFT_RECORD*)(buf + i * vol->mft_record_size); + m->flags |= MFT_RECORD_IN_USE; + ntfs_set_bit(mft_bitmap, 0LL + i, 1); + file_attrs = FILE_ATTR_HIDDEN | FILE_ATTR_SYSTEM; + if (i == FILE_root) { + if (opt.disable_indexing) + file_attrs |= FILE_ATTR_NOT_CONTENT_INDEXED; + if (opt.enable_compression) + file_attrs |= FILE_ATTR_COMPRESSED; + } + add_attr_std_info(m, file_attrs); + // dump_mft_record(m); + } + /* The root directory mft reference. */ + root_ref = MAKE_MFT_REF(FILE_root, FILE_root); + Vprintf("Creating root directory (mft record 5)\n"); + m = (MFT_RECORD*)(buf + 5 * vol->mft_record_size); + m->flags |= MFT_RECORD_IS_DIRECTORY; + m->link_count = cpu_to_le16(le16_to_cpu(m->link_count) + 1); + err = add_attr_file_name(m, root_ref, 0LL, 0LL, + FILE_ATTR_HIDDEN | FILE_ATTR_SYSTEM | + FILE_ATTR_DUP_FILE_NAME_INDEX_PRESENT, 0, 0, + ".", FILE_NAME_WIN32_AND_DOS); + if (!err) { + init_system_file_sd(FILE_root, &sd, &i); + err = add_attr_sd(m, sd, i); + } + // FIXME: This should be IGNORE_CASE + if (!err) + err = add_attr_index_root(m, "$I30", 4, 0, AT_FILE_NAME, + COLLATION_FILE_NAME, opt.index_block_size); + // FIXME: This should be IGNORE_CASE + if (!err) + err = upgrade_to_large_index(m, "$I30", 4, 0, &index_block); + if (!err) { + ctx = ntfs_get_attr_search_ctx(NULL, m); + if (!ctx) + err_exit("Failed to allocate attribute search " + "context: %s\n", strerror(errno)); + /* There is exactly one file name so this is ok. */ + if (ntfs_lookup_attr(AT_FILE_NAME, AT_UNNAMED, 0, 0, 0, NULL, 0, + ctx)) { + ntfs_put_attr_search_ctx(ctx); + err_exit("BUG: $FILE_NAME attribute not found.\n"); + } + a = ctx->attr; + err = insert_file_link_in_dir_index(index_block, root_ref, + (FILE_NAME_ATTR*)((char*)a + + le16_to_cpu(a->value_offset)), + le32_to_cpu(a->value_length)); + ntfs_put_attr_search_ctx(ctx); + } + if (err) + err_exit("Couldn't create root directory: %s\n", + strerror(-err)); + // dump_mft_record(m); + /* Add all other attributes, on a per-file basis for clarity. */ + Vprintf("Creating $MFT (mft record 0)\n"); + m = (MFT_RECORD*)buf; + err = add_attr_data_positioned(m, NULL, 0, 0, 0, rl_mft, buf, + opt.mft_size); + if (!err) + err = create_hardlink(index_block, root_ref, m, + MAKE_MFT_REF(FILE_MFT, 1), opt.mft_size, + opt.mft_size, FILE_ATTR_HIDDEN | + FILE_ATTR_SYSTEM, 0, 0, "$MFT", + FILE_NAME_WIN32_AND_DOS); + if (!err) { + init_system_file_sd(FILE_MFT, &sd, &i); + err = add_attr_sd(m, sd, i); + } + /* mft_bitmap is not modified in mkntfs; no need to sync it later. */ + if (!err) + err = add_attr_bitmap_positioned(m, NULL, 0, 0, rl_mft_bmp, + mft_bitmap, mft_bitmap_byte_size); + if (err < 0) + err_exit("Couldn't create $MFT: %s\n", strerror(-err)); + //dump_mft_record(m); + Vprintf("Creating $MFTMirr (mft record 1)\n"); + m = (MFT_RECORD*)(buf + 1 * vol->mft_record_size); + err = add_attr_data_positioned(m, NULL, 0, 0, 0, rl_mftmirr, buf, + rl_mftmirr[0].length * vol->cluster_size); + if (!err) + err = create_hardlink(index_block, root_ref, m, + MAKE_MFT_REF(FILE_MFTMirr, FILE_MFTMirr), + rl_mftmirr[0].length * vol->cluster_size, + rl_mftmirr[0].length * vol->cluster_size, + FILE_ATTR_HIDDEN | FILE_ATTR_SYSTEM, 0, 0, + "$MFTMirr", FILE_NAME_WIN32_AND_DOS); + if (!err) { + init_system_file_sd(FILE_MFTMirr, &sd, &i); + err = add_attr_sd(m, sd, i); + } + if (err < 0) + err_exit("Couldn't create $MFTMirr: %s\n", strerror(-err)); + //dump_mft_record(m); + Vprintf("Creating $LogFile (mft record 2)\n"); + m = (MFT_RECORD*)(buf + 2 * vol->mft_record_size); + buf2 = malloc(opt.logfile_size); + if (!buf2) + err_exit("Failed to allocate internal buffer: %s\n", + strerror(errno)); + memset(buf2, -1, opt.logfile_size); + err = add_attr_data_positioned(m, NULL, 0, 0, 0, rl_logfile, buf2, + opt.logfile_size); + free(buf2); + buf2 = NULL; + if (!err) + err = create_hardlink(index_block, root_ref, m, + MAKE_MFT_REF(FILE_LogFile, FILE_LogFile), + opt.logfile_size, opt.logfile_size, + FILE_ATTR_HIDDEN | FILE_ATTR_SYSTEM, 0, 0, + "$LogFile", FILE_NAME_WIN32_AND_DOS); + if (!err) { + init_system_file_sd(FILE_LogFile, &sd, &i); + err = add_attr_sd(m, sd, i); + } + if (err < 0) + err_exit("Couldn't create $LogFile: %s\n", strerror(-err)); + //dump_mft_record(m); + Vprintf("Creating $Volume (mft record 3)\n"); + m = (MFT_RECORD*)(buf + 3 * vol->mft_record_size); + err = create_hardlink(index_block, root_ref, m, + MAKE_MFT_REF(FILE_Volume, FILE_Volume), 0LL, 0LL, + FILE_ATTR_HIDDEN | FILE_ATTR_SYSTEM, 0, 0, + "$Volume", FILE_NAME_WIN32_AND_DOS); + if (!err) { + init_system_file_sd(FILE_Volume, &sd, &i); + err = add_attr_sd(m, sd, i); + } + if (!err) + err = add_attr_data(m, NULL, 0, 0, 0, NULL, 0); + if (!err) + err = add_attr_vol_name(m, vol->vol_name, vol->vol_name ? + strlen(vol->vol_name) : 0); + if (!err) { + Qprintf("Setting the volume dirty so check disk runs on next " + "reboot into Windows.\n"); + err = add_attr_vol_info(m, VOLUME_IS_DIRTY, vol->major_ver, + vol->minor_ver); + } + if (err < 0) + err_exit("Couldn't create $Volume: %s\n", strerror(-err)); + //dump_mft_record(m); + Vprintf("Creating $AttrDef (mft record 4)\n"); + m = (MFT_RECORD*)(buf + 4 * vol->mft_record_size); + if (vol->major_ver < 3) + buf2_size = 36000; + else + buf2_size = opt.attr_defs_len; + buf2 = (char*)calloc(1, buf2_size); + if (!buf2) + err_exit("Failed to allocate internal buffer: %s\n", + strerror(errno)); + memcpy(buf2, opt.attr_defs, opt.attr_defs_len); + err = add_attr_data(m, NULL, 0, 0, 0, buf2, buf2_size); + free(buf2); + buf2 = NULL; + if (!err) + err = create_hardlink(index_block, root_ref, m, + MAKE_MFT_REF(FILE_AttrDef, FILE_AttrDef), + (buf2_size + vol->cluster_size - 1) & + ~(vol->cluster_size - 1), buf2_size, + FILE_ATTR_HIDDEN | FILE_ATTR_SYSTEM, 0, 0, + "$AttrDef", FILE_NAME_WIN32_AND_DOS); + buf2_size = 0; + if (!err) { + init_system_file_sd(FILE_AttrDef, &sd, &i); + err = add_attr_sd(m, sd, i); + } + if (err < 0) + err_exit("Couldn't create $AttrDef: %s\n", strerror(-err)); + //dump_mft_record(m); + Vprintf("Creating $Bitmap (mft record 6)\n"); + m = (MFT_RECORD*)(buf + 6 * vol->mft_record_size); + err = add_attr_data(m, NULL, 0, 0, 0, lcn_bitmap, lcn_bitmap_byte_size); + if (!err) + err = create_hardlink(index_block, root_ref, m, + MAKE_MFT_REF(FILE_Bitmap, FILE_Bitmap), + (lcn_bitmap_byte_size + vol->cluster_size - 1) & + ~(vol->cluster_size - 1), lcn_bitmap_byte_size, + FILE_ATTR_HIDDEN | FILE_ATTR_SYSTEM, 0, 0, + "$Bitmap", FILE_NAME_WIN32_AND_DOS); + if (!err) { + init_system_file_sd(FILE_Bitmap, &sd, &i); + err = add_attr_sd(m, sd, i); + } + if (err < 0) + err_exit("Couldn't create $Bitmap: %s\n", strerror(-err)); + //dump_mft_record(m); + Vprintf("Creating $Boot (mft record 7)\n"); + m = (MFT_RECORD*)(buf + 7 * vol->mft_record_size); + buf2 = calloc(1, 8192); + if (!buf2) + err_exit("Failed to allocate internal buffer: %s\n", + strerror(errno)); + memcpy(buf2, boot_array, sizeof(boot_array)); + /* + * Create the boot sector into buf2. Note, that buf2 already is zeroed + * in the boot sector section and that it has the NTFS OEM id/magic + * already inserted, so no need to worry about these things. + */ + bs = (NTFS_BOOT_SECTOR*)buf2; + bs->bpb.bytes_per_sector = cpu_to_le16(opt.sector_size); + bs->bpb.sectors_per_cluster = (u8)(vol->cluster_size / + opt.sector_size); + bs->bpb.media_type = 0xf8; /* hard disk */ + /* + * If there are problems go back to bs->unused[0-3] and set them. See + * ../include/bootsect.h for details. Other fields to also consider + * setting are: bs->bpb.sectors_per_track, .heads, and .hidden_sectors. + */ + bs->number_of_sectors = scpu_to_le64(opt.nr_sectors); + bs->mft_lcn = scpu_to_le64(opt.mft_lcn); + bs->mftmirr_lcn = scpu_to_le64(opt.mftmirr_lcn); + if (vol->mft_record_size >= vol->cluster_size) + bs->clusters_per_mft_record = vol->mft_record_size / + vol->cluster_size; + else { + bs->clusters_per_mft_record = -(ffs(vol->mft_record_size) - 1); + if ((1 << -bs->clusters_per_mft_record) != vol->mft_record_size) + err_exit("BUG: calculated clusters_per_mft_record " + "is wrong (= 0x%x)\n", + bs->clusters_per_mft_record); + } + Dprintf("Clusters per mft record = %i (0x%x)\n", + bs->clusters_per_mft_record, + bs->clusters_per_mft_record); + if (opt.index_block_size >= vol->cluster_size) + bs->clusters_per_index_record = opt.index_block_size / + vol->cluster_size; + else { + bs->clusters_per_index_record = -(ffs(opt.index_block_size) - 1); + if ((1 << -bs->clusters_per_index_record) != + opt.index_block_size) + err_exit("BUG: calculated clusters_per_index_record " + "is wrong (= 0x%x)\n", + bs->clusters_per_index_record); + } + Dprintf("Clusters per index block = %i (0x%x)\n", + bs->clusters_per_index_record, + bs->clusters_per_index_record); + /* Generate a 64-bit random number for the serial number. */ + bs->volume_serial_number = scpu_to_le64(((s64)random() << 32) | + ((s64)random() & 0xffffffff)); + /* + * Leave zero for now as NT4 leaves it zero, too. If want it later, see + * ../libntfs/bootsect.c for how to calculate it. + */ + bs->checksum = cpu_to_le32(0); + /* Make sure the bootsector is ok. */ + if (!is_boot_sector_ntfs(bs, opt.verbose > 0 ? 0 : 1)) + err_exit("FATAL: Generated boot sector is invalid!\n"); + err = add_attr_data_positioned(m, NULL, 0, 0, 0, rl_boot, buf2, 8192); + if (!err) + err = create_hardlink(index_block, root_ref, m, + MAKE_MFT_REF(FILE_Boot, FILE_Boot), + (8192 + vol->cluster_size - 1) & + ~(vol->cluster_size - 1), 8192, + FILE_ATTR_HIDDEN | FILE_ATTR_SYSTEM, 0, 0, + "$Boot", FILE_NAME_WIN32_AND_DOS); + if (!err) { + init_system_file_sd(FILE_Boot, &sd, &i); + err = add_attr_sd(m, sd, i); + } + if (err < 0) + err_exit("Couldn't create $Boot: %s\n", strerror(-err)); + Vprintf("Creating backup boot sector.\n"); + /* + * Write the first max(512, opt.sector_size) bytes from buf2 to the + * last sector. + */ + if (lseek(vol->fd, (opt.nr_sectors + 1) * opt.sector_size - i, + SEEK_SET) == (off_t)-1) + goto bb_err; + bw = mkntfs_write(vol->fd, buf2, i); + free(buf2); + buf2 = NULL; + if (bw != i) { + int _e = errno; + char *_s; + + if (bw == -1LL) + _s = strerror(_e); + else + _s = "unknown error"; + if (bw != -1LL || (bw == -1LL && _e != ENOSPC)) { + err_exit("Couldn't write backup boot sector: %s\n", _s); +bb_err: + Eprintf("Seek failed: %s\n", strerror(errno)); + } + Eprintf("Couldn't write backup boot sector. This is due to a " + "limitation in the\nLinux kernel. This is not " + "a major problem as Windows check disk will " + "create the\nbackup boot sector when it " + "is run on your next boot into Windows.\n"); + } + //dump_mft_record(m); + Vprintf("Creating $BadClus (mft record 8)\n"); + m = (MFT_RECORD*)(buf + 8 * vol->mft_record_size); + // FIXME: This should be IGNORE_CASE + /* Create a sparse named stream of size equal to the volume size. */ + err = add_attr_data_positioned(m, "$Bad", 4, 0, 0, rl_bad, NULL, + opt.nr_clusters * vol->cluster_size); + if (!err) { + err = add_attr_data(m, NULL, 0, 0, 0, NULL, 0); + } + if (!err) { + err = create_hardlink(index_block, root_ref, m, + MAKE_MFT_REF(FILE_BadClus, FILE_BadClus), + 0LL, 0LL, FILE_ATTR_HIDDEN | FILE_ATTR_SYSTEM, + 0, 0, "$BadClus", FILE_NAME_WIN32_AND_DOS); + } + if (!err) { + init_system_file_sd(FILE_BadClus, &sd, &i); + err = add_attr_sd(m, sd, i); + } + if (err < 0) + err_exit("Couldn't create $BadClus: %s\n", strerror(-err)); + //dump_mft_record(m); + Vprintf("Creating $Quota (mft record 9)\n"); + m = (MFT_RECORD*)(buf + 9 * vol->mft_record_size); + err = add_attr_data(m, NULL, 0, 0, 0, NULL, 0); + if (!err) + err = create_hardlink(index_block, root_ref, m, + MAKE_MFT_REF(9, 9), 0LL, 0LL, FILE_ATTR_HIDDEN + | FILE_ATTR_SYSTEM, 0, 0, "$Quota", + FILE_NAME_WIN32_AND_DOS); + if (!err) { + init_system_file_sd(FILE_Secure, &sd, &i); + err = add_attr_sd(m, sd, i); + } + if (err < 0) + err_exit("Couldn't create $Quota: %s\n", strerror(-err)); + //dump_mft_record(m); + Vprintf("Creating $UpCase (mft record 0xa)\n"); + m = (MFT_RECORD*)(buf + 0xa * vol->mft_record_size); + err = add_attr_data(m, NULL, 0, 0, 0, (char*)vol->upcase, + vol->upcase_len << 1); + if (!err) + err = create_hardlink(index_block, root_ref, m, + MAKE_MFT_REF(FILE_UpCase, FILE_UpCase), + ((vol->upcase_len << 1) + vol->cluster_size - 1) & + ~(vol->cluster_size - 1), vol->upcase_len << 1, + FILE_ATTR_HIDDEN | FILE_ATTR_SYSTEM, 0, 0, + "$UpCase", FILE_NAME_WIN32_AND_DOS); + if (!err) { + init_system_file_sd(FILE_UpCase, &sd, &i); + err = add_attr_sd(m, sd, i); + } + if (err < 0) + err_exit("Couldn't create $UpCase: %s\n", strerror(-err)); + //dump_mft_record(m); + /* NTFS 1.2 reserved system files (mft records 0xb-0xf) */ + for (i = 0xb; i < 0x10; i++) { + Vprintf("Creating system file (mft record 0x%x)\n", i, i); + m = (MFT_RECORD*)(buf + i * vol->mft_record_size); + err = add_attr_data(m, NULL, 0, 0, 0, NULL, 0); + if (!err) { + init_system_file_sd(i, &sd, &j); + err = add_attr_sd(m, sd, j); + } + if (err < 0) + err_exit("Couldn't create system file %i (0x%x): %s\n", + i, i, strerror(-err)); + //dump_mft_record(m); + } +// - Do not step onto bad blocks!!! +// - If any bad blocks were specified or found, modify $BadClus, allocating the +// bad clusters in $Bitmap. +// - C&w bootsector backup bootsector (backup in last sector of the +// partition). +// - If NTFS 3.0+, c&w $Secure file and $Extend directory with the +// corresponding special files in it, i.e. $ObjId, $Quota, $Reparse, and +// $UsnJrnl. And others? Or not all necessary? +// - RE: Populate $root with the system files (and $Extend directory if +// applicable). Possibly should move this as far to the top as possible and +// update during each subsequent c&w of each system file. + Vprintf("Syncing root directory index record.\n"); + m = (MFT_RECORD*)(buf + 5 * vol->mft_record_size); + i = 5 * sizeof(uchar_t); + ctx = ntfs_get_attr_search_ctx(NULL, m); + if (!ctx) + err_exit("Failed to allocate attribute search context: %s\n", + strerror(errno)); + // FIXME: This should be IGNORE_CASE! + if (ntfs_lookup_attr(AT_INDEX_ALLOCATION, I30, 4, 0, 0, + NULL, 0, ctx)) { + ntfs_put_attr_search_ctx(ctx); + err_exit("BUG: $INDEX_ALLOCATION attribute not found.\n"); + } + a = ctx->attr; + rl_index = ntfs_decompress_mapping_pairs(vol, a, NULL); + if (!rl_index) { + ntfs_put_attr_search_ctx(ctx); + err_exit("Failed to decompress run list of $INDEX_ALLOCATION " + "attribute.\n"); + } + if (sle64_to_cpu(a->initialized_size) < i) { + ntfs_put_attr_search_ctx(ctx); + err_exit("BUG: $INDEX_ALLOCATION attribute too short.\n"); + } + ntfs_put_attr_search_ctx(ctx); + i = sizeof(INDEX_BLOCK) - sizeof(INDEX_HEADER) + + le32_to_cpu(index_block->index.allocated_size); + err = ntfs_pre_write_mst_fixup((NTFS_RECORD*)index_block, i); + if (err) + err_exit("ntfs_pre_write_mst_fixup() failed while syncing " + "root directory index block.\n"); + lw = ntfs_rlwrite(vol->fd, rl_index, (char*)index_block, i, NULL); + if (lw != i) + err_exit("Error writing $INDEX_ALLOCATION.\n"); + /* No more changes to @index_block below here so no need for fixup: */ + // ntfs_post_write_mst_fixup((NTFS_RECORD*)index_block); + Vprintf("Syncing $Bitmap.\n"); + m = (MFT_RECORD*)(buf + 6 * vol->mft_record_size); + ctx = ntfs_get_attr_search_ctx(NULL, m); + if (!ctx) + err_exit("Failed to allocate attribute search context: %s\n", + strerror(errno)); + if (ntfs_lookup_attr(AT_DATA, AT_UNNAMED, 0, 0, 0, NULL, 0, ctx)) { + ntfs_put_attr_search_ctx(ctx); + err_exit("BUG: $DATA attribute not found.\n"); + } + a = ctx->attr; + if (a->non_resident) { + rl = ntfs_decompress_mapping_pairs(vol, a, NULL); + ntfs_put_attr_search_ctx(ctx); + if (!rl) + err_exit("ntfs_decompress_mapping_pairs() failed\n"); + lw = ntfs_rlwrite(vol->fd, rl, lcn_bitmap, + lcn_bitmap_byte_size, NULL); + if (lw != lcn_bitmap_byte_size) + err_exit("%s\n", lw == -1 ? strerror(errno) : + "unknown error"); + } else { + memcpy((char*)a + le16_to_cpu(a->value_offset), lcn_bitmap, + le32_to_cpu(a->value_length)); + ntfs_put_attr_search_ctx(ctx); + } + /* + * No need to sync $MFT/$BITMAP as that has never been modified since + * its creation. + */ + Vprintf("Syncing $MFT.\n"); + pos = opt.mft_lcn * vol->cluster_size; + lw = 1; + for (i = 0; i < opt.mft_size / vol->mft_record_size; i++) { + if (!opt.no_action) + lw = ntfs_mst_pwrite(vol->fd, pos, 1, + vol->mft_record_size, + buf + i * vol->mft_record_size); + if (lw != 1) + err_exit("%s\n", lw == -1 ? strerror(errno) : + "unknown error"); + pos += vol->mft_record_size; + } + Vprintf("Updating $MFTMirr.\n"); + pos = opt.mftmirr_lcn * vol->cluster_size; + lw = 1; + for (i = 0; i < rl_mftmirr[0].length * vol->cluster_size / + vol->mft_record_size; i++) { + u16 usn, *usnp; + m = (MFT_RECORD*)(buf + i * vol->mft_record_size); + /* + * Decrement the usn by one, so it becomes the same as the one + * in $MFT once it is mst protected. - This is as we need the + * $MFTMirr to have the exact same byte by byte content as + * $MFT, rather than just equivalent meaning content. + */ + usnp = (u16*)((char*)m + le16_to_cpu(m->usa_ofs)); + usn = le16_to_cpup(usnp); + if (usn-- <= 1) + usn = 0xfffe; + *usnp = cpu_to_le16(usn); + if (!opt.no_action) + lw = ntfs_mst_pwrite(vol->fd, pos, 1, + vol->mft_record_size, + buf + i * vol->mft_record_size); + if (lw != 1) + err_exit("%s\n", lw == -1 ? strerror(errno) : + "unknown error"); + pos += vol->mft_record_size; + } + Vprintf("Syncing device.\n"); + if (fdatasync(vol->fd) == -1) + err_exit("Syncing device. FAILED: %s", strerror(errno)); + Qprintf("mkntfs completed successfully. Have a nice day.\n"); + /* + * Device is unlocked and closed by the registered exit function + * mkntfs_exit(). + */ + return 0; +} + diff --git a/ntfsprogs/ntfsdump_logfile.c b/ntfsprogs/ntfsdump_logfile.c index e69de29b..e785ea96 100644 --- a/ntfsprogs/ntfsdump_logfile.c +++ b/ntfsprogs/ntfsdump_logfile.c @@ -0,0 +1,371 @@ +const char *EXEC_NAME = "NtfsDump_LogFile"; +const char *EXEC_VERSION = "1.0"; +/* + * $Id$ + * + * NtfsDump_LogFile - Part of the Linux-NTFS project. + * + * Copyright (c) 2000,2001 Anton Altaparmakov. + * + * This utility will interpret the contents of the journal ($LogFile) of an + * NTFS partition and display the results on stdout. Errors will be output to + * stderr. + * + * Anton Altaparmakov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program (in the main directory of the Linux-NTFS source + * in the file COPYING); if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * WARNING: This program might not work on architectures which do not allow + * unaligned access. For those, the program would need to start using + * get/put_unaligned macros (#include ), but not doing it yet, + * since NTFS really mostly applies to ia32 only, which does allow unaligned + * accesses. We might not actually have a problem though, since the structs are + * defined as being packed so that might be enough for gcc to insert the + * correct code. + * + * If anyone using a non-little endian and/or an aligned access only CPU tries + * this program please let me know whether it works or not! + * + * Anton Altaparmakov + */ + +#include +#include +#include +#include +#include +#include + +#include "types.h" +#include "attrib.h" +#include "mft.h" +#include "disk_io.h" +#include "logfile.h" +#include "mst.h" + +int main(int argc, char **argv) +{ + MFT_RECORD *m = NULL; + ATTR_RECORD *a; + s64 l; + unsigned char *lfd = NULL; + ntfs_volume *vol = NULL; + ntfs_attr_search_ctx *ctx = NULL; + RESTART_PAGE_HEADER *rph; + RESTART_AREA *rr; + RESTART_CLIENT *cr; + RECORD_PAGE_HEADER *rcrd_ph; + LOG_RECORD *lr; + int pass = 1; + int i, lps, client; + char zero[4096]; + + memset(zero, 0, sizeof(zero)); + printf("\n"); + if (argc != 2) { + printf("%s v%s - Interpret and display information about the " + "journal\n($LogFile) of an NTFS volume.\n\n" + /* Generic copyright / disclaimer. */ + "Copyright (c) 2000, 2001 Anton Altaparmakov.\n\n" + "%s is free software, released under the GNU " + "General Public License\nand you are welcome to " + "redistribute it under certain conditions.\n" + "%s comes with ABSOLUTELY NO WARRANTY; for details " + "read the GNU\nGeneral Public License to be found " + "in the file COPYING in the main Linux-NTFS\n" + "distribution directory.\n\n" + /* Generic part ends here. */ + "Syntax: ntfsdump_logfile partition_or_file_name\n" + " e.g. ntfsdump_logfile /dev/hda6\n\n", + EXEC_NAME, EXEC_VERSION, EXEC_NAME, EXEC_NAME); + fprintf(stderr, "Error: incorrect syntax\n"); + exit(1); + } + vol = ntfs_mount(argv[1], MS_RDONLY); + if (!vol) { + perror("ntfs_mount(MS_RDONLY) failed"); + exit(1); + } + /* Check NTFS version is ok for us. */ + printf("\nNTFS volume version is %i.%i.\n", vol->major_ver, + vol->minor_ver); + switch (vol->major_ver) { + case 1: + if (vol->minor_ver == 1 || vol->minor_ver == 2) + break; + else + goto version_error; + case 2: case 3: + if (vol->minor_ver == 0) + break; + /* Fall through on error. */ + default: +version_error: + fprintf(stderr, "Error: Unknown NTFS version.\n"); + goto error_exit; + } + /* Read in $LogFile. */ + if (ntfs_read_file_record(vol, FILE_LogFile, &m, NULL)) { + fprintf(stderr, "Error reading mft record for $LogFile.\n"); + goto error_exit; + } + if (!(m->flags & MFT_RECORD_IN_USE)) { + fprintf(stderr, "Error: $LogFile has been deleted. Run chkdsk " + "to fix this.\n"); + goto error_exit; + } + ctx = ntfs_get_attr_search_ctx(NULL, m); + if (!ctx) { + perror("Failed to allocate attribute search context"); + goto error_exit; + } + /* Find the $DATA attribute of the $LogFile. */ + if (ntfs_lookup_attr(AT_DATA, AT_UNNAMED, 0, 0, 0, NULL, 0, ctx)) { + fprintf(stderr, "Error: Attribute $DATA was not found in" \ + "$LogFile!\n"); + goto log_file_error; + } + a = ctx->attr; + /* Get length of $LogFile contents. */ + l = get_attribute_value_length(a); + if (!l) { + puts("$LogFile has zero length, no need to write to disk."); + goto log_file_error; + } + /* Allocate a buffer to hold all of the $LogFile contents. */ + lfd = (unsigned char*)malloc(l); + if (!lfd) { + puts("Not enough memory to load $LogFile."); + goto log_file_error; + } + /* Read in the $LogFile into the buffer. */ + if (l != get_attribute_value(vol, m, a, lfd)) { + puts("Amount of data read does not correspond to expected " + "length!"); + free(lfd); + goto log_file_error; + } + /* Check restart area. */ + if (!is_rstr_recordp(lfd)) { + s64 _l; + + for (_l = 0LL; _l < l; _l++) + if (lfd[_l] != (unsigned char)-1) + break; + if (_l < l) + puts("$LogFile contents are corrupt (magic RSTR " + "missing)!"); + else + puts("$LogFile is empty."); + goto log_file_error; + } + /* Do the interpretation and display now. */ + rph = (RESTART_PAGE_HEADER*)lfd; + lps = le32_to_cpu(rph->log_page_size); +pass_loc: + if (ntfs_post_read_mst_fixup((NTFS_RECORD*)rph, lps) || + is_baad_record(rph->magic)) { + puts("$LogFile incomplete multi sector transfer detected! " + "Cannot handle this yet!"); + goto log_file_error; + } + if ((pass == 2) && !memcmp(lfd, rph, lps)) { + printf("2nd restart area fully matches the 1st one. Skipping " + "display.\n"); + goto skip_rstr_pass; + } + if (le16_to_cpu(rph->major_ver != 1) || + le16_to_cpu(rph->minor_ver != 1)) { + fprintf(stderr, "$LogFile version %i.%i! Error: Unknown " + "$LogFile version!\n", + le16_to_cpu(rph->major_ver), + le16_to_cpu(rph->minor_ver)); + goto log_file_error; + } + rr = (RESTART_AREA*)((char*)rph + le16_to_cpu(rph->restart_offset)); + cr = (RESTART_CLIENT*)((char*)rr + + le16_to_cpu(rr->client_array_offset)); + /* Dump of the interpreted $LogFile restart area. */ + if (pass == 1) + printf("\n$LogFile version %i.%i.\n", + le16_to_cpu(rph->major_ver), + le16_to_cpu(rph->minor_ver)); + printf("\n%s restart area:\n", pass == 1? "1st": "2nd"); + printf("magic = RSTR\n"); + printf("ChkDskLsn = 0x%Lx\n", sle64_to_cpu(rph->chkdsk_lsn)); + printf("SystemPageSize = %u\n", le32_to_cpu(rph->system_page_size)); + printf("LogPageSize = %u\n", le32_to_cpu(rph->log_page_size)); + printf("RestartOffset = 0x%x\n", le16_to_cpu(rph->restart_offset)); + printf("\n(1st) restart record:\n"); + printf("CurrentLsn = %Lx\n", sle64_to_cpu(rr->current_lsn)); + printf("LogClients = %u\n", le16_to_cpu(rr->log_clients)); + printf("ClientFreeList = %i\n", sle16_to_cpu(rr->client_free_list)); + printf("ClientInUseList = %i\n", sle16_to_cpu(rr->client_in_use_list)); + printf("Flags = 0x%x\n", le16_to_cpu(rr->flags)); + printf("SeqNumberBits = %u (0x%x)\n", le32_to_cpu(rr->seq_number_bits), + le32_to_cpu(rr->seq_number_bits)); + printf("RestartAreaLength = 0x%x\n", + le16_to_cpu(rr->restart_area_length)); + printf("ClientArrayOffset = 0x%x\n", + le16_to_cpu(rr->client_array_offset)); + printf("FileSize = %Lu (0x%Lx)\n", le64_to_cpu(rr->file_size), + le64_to_cpu(rr->file_size)); + if (le64_to_cpu(rr->file_size) != l) + puts("$LogFile restart area indicates a log file size" + "different from the actual size!"); + printf("LastLsnDataLength = 0x%x\n", + le32_to_cpu(rr->last_lsn_data_length)); + printf("RecordLength = 0x%x\n", le16_to_cpu(rr->record_length)); + printf("LogPageDataOffset = 0x%x\n", + le16_to_cpu(rr->log_page_data_offset)); + for (client = 0; client < le16_to_cpu(rr->log_clients); client++) { + printf("\nRestart client record number %i:\n", client); + printf("OldestLsn = 0x%Lx\n", sle64_to_cpu(cr->oldest_lsn)); + printf("ClientRestartLsn = 0x%Lx\n", + sle64_to_cpu(cr->client_restart_lsn)); + printf("PrevClient = %i\n", sle16_to_cpu(cr->prev_client)); + printf("NextClient = %i\n", sle16_to_cpu(cr->next_client)); + printf("SeqNumber = 0x%Lx\n", le64_to_cpu(cr->seq_number)); + printf("ClientNameLength = 0x%x\n", + le32_to_cpu(cr->client_name_length)); + if (le32_to_cpu(cr->client_name_length)) { + // convert to ascii and print out. + // printf("ClientName = %u\n", le16_to_cpu(cr->client_name)); + } + /* Size of a restart client record is fixed at 0xa0 bytes. */ + cr = (RESTART_CLIENT*)((char*)cr + 0xa0); + } +skip_rstr_pass: + if (pass == 1) { + rph = (RESTART_PAGE_HEADER*)((char*)rph + lps); + ++pass; + goto pass_loc; + } + rcrd_ph = (RECORD_PAGE_HEADER*)rph; + /* Reuse pass for log record clienter. */ + pass = 0; + printf("\nFinished with restart area. Beginning with log area.\n"); +rcrd_pass_loc: + rcrd_ph = (RECORD_PAGE_HEADER*)((char*)rcrd_ph + lps); + if ((char*)rcrd_ph + lps > (char*)lfd + l) + goto end_of_rcrd_passes; + printf("\nLog record page number %i", pass); + if (!is_rcrd_record(rcrd_ph->magic)) { + for (i = 0; i < lps; i++) + if (((char*)rcrd_ph)[i] != (char)-1) + break; + if (i < lps) + puts(" is corrupt (magic RCRD is missing)."); + else + puts(" is empty."); + pass++; + goto rcrd_pass_loc; + } else + printf(":"); + /* Dump log record page */ + printf("\nmagic = RCRD\n"); + printf("copy.last_lsn/file_offset = 0x%Lx\n", + le64_to_cpu(rcrd_ph->copy.last_lsn)); + printf("flags = 0x%x\n", le32_to_cpu(rcrd_ph->flags)); + printf("page count = %i\n", le16_to_cpu(rcrd_ph->page_count)); + printf("page position = %i\n", le16_to_cpu(rcrd_ph->page_position)); + printf("header.next_record_offset = 0x%Lx\n", + le64_to_cpu(rcrd_ph->header.packed.next_record_offset)); + printf("header.last_end_lsn = 0x%Lx\n", + le64_to_cpu(rcrd_ph->header.packed.last_end_lsn)); + /* + * Where does the 0x40 come from? Is it just usa_offset + + * usa_client * 2 + 7 & ~7 or is it derived from somewhere? + */ + lr = (LOG_RECORD*)((char*)rcrd_ph + 0x40); + client = 0; +log_record_pass: + printf("\nLog record %i:\n", client); + printf("this lsn = 0x%Lx\n", le64_to_cpu(lr->this_lsn)); + printf("client previous lsn = 0x%Lx\n", + le64_to_cpu(lr->client_previous_lsn)); + printf("client undo next lsn = 0x%Lx\n", + le64_to_cpu(lr->client_undo_next_lsn)); + printf("client data length = 0x%x\n", + le32_to_cpu(lr->client_data_length)); + printf("client_id.seq_number = 0x%x\n", + le16_to_cpu(lr->client_id.seq_number)); + printf("client_id.client_index = 0x%x\n", + le16_to_cpu(lr->client_id.client_index)); + printf("record type = 0x%x\n", le32_to_cpu(lr->record_type)); + printf("transaction_id = 0x%x\n", le32_to_cpu(lr->transaction_id)); + printf("flags = 0x%x:", lr->flags); + if (!lr->flags) + printf(" NONE\n"); + else { + int _b = 0; + + if (lr->flags & LOG_RECORD_MULTI_PAGE) { + printf(" LOG_RECORD_MULTI_PAGE"); + _b = 1; + } + if (lr->flags & ~LOG_RECORD_MULTI_PAGE) { + if (_b) + printf(" |"); + printf(" Unknown flags"); + } + printf("\n"); + } + printf("redo_operation = 0x%x\n", le16_to_cpu(lr->redo_operation)); + printf("undo_operation = 0x%x\n", le16_to_cpu(lr->undo_operation)); + printf("redo_offset = 0x%x\n", le16_to_cpu(lr->redo_offset)); + printf("redo_length = 0x%x\n", le16_to_cpu(lr->redo_length)); + printf("undo_offset = 0x%x\n", le16_to_cpu(lr->undo_offset)); + printf("undo_length = 0x%x\n", le16_to_cpu(lr->undo_length)); + printf("target_attribute = 0x%x\n", le16_to_cpu(lr->target_attribute)); + printf("lcns_to_follow = 0x%x\n", le16_to_cpu(lr->lcns_to_follow)); + printf("record_offset = 0x%x\n", le16_to_cpu(lr->record_offset)); + printf("attribute_offset = 0x%x\n", le16_to_cpu(lr->attribute_offset)); + printf("target_vcn = 0x%Lx\n", sle64_to_cpu(lr->target_vcn)); + if (le16_to_cpu(lr->lcns_to_follow) > 0) + printf("Array of lcns:\n"); + for (i = 0; i < le16_to_cpu(lr->lcns_to_follow); i++) + printf("lcn_list[%i].lcn = 0x%Lx\n", i, + sle64_to_cpu(lr->lcn_list[i].lcn)); + client++; + lr = (LOG_RECORD*)((char*)lr + 0x70); + if (((char*)lr + 0x70 <= (char*)rcrd_ph + + le64_to_cpu(rcrd_ph->header.packed.next_record_offset))) + goto log_record_pass; + pass++; + goto rcrd_pass_loc; +end_of_rcrd_passes: +log_file_error: + printf("\n"); + /* Set return code to 0. */ + i = 0; +final_exit: + if (lfd) + free(lfd); + if (ctx) + ntfs_put_attr_search_ctx(ctx); + if (m) + free(m); + if (vol && ntfs_umount(vol, 0)) + ntfs_umount(vol, 1); + return i; +error_exit: + i = 1; + goto final_exit; +} + diff --git a/ntfsprogs/ntfsfix.8.in b/ntfsprogs/ntfsfix.8.in index e69de29b..eed93b8a 100644 --- a/ntfsprogs/ntfsfix.8.in +++ b/ntfsprogs/ntfsfix.8.in @@ -0,0 +1,56 @@ +.\" Hey, EMACS: -*- nroff -*- +.\" First parameter, NAME, should be all caps +.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection +.\" other parameters are allowed: see man(7), man(1) +.TH NTFSFIX 8 "July 2001" "Linux-NTFS version @VERSION@" +.\" Please adjust this date whenever revising the manpage. +.\" +.\" Some roff macros, for reference: +.\" .nh disable hyphenation +.\" .hy enable hyphenation +.\" .ad l left justify +.\" .ad b justify to both left and right margins +.\" .nf disable filling +.\" .fi enable filling +.\" .br insert line break +.\" .sp insert n+1 empty lines +.\" for manpage-specific macros, see man(7) +.SH NAME +ntfsfix \- tool for fixing NTFS partitions altered by the Linux kernel NTFS driver. +.SH SYNOPSIS +.B ntfsfix +.I device +.SH DESCRIPTION +This manual page documents briefly the +.B ntfsfix +command. +.PP +.\" TeX users may be more comfortable with the \fB\fP and +.\" \fI\fP escape sequences to invode bold face and italics, +.\" respectively. +\fBntfsfix\fP is a program that fixes NTFS partitions altered in any +manner with the Linux NTFS driver. \fBntfsfix\fP is \fBNOT\fP a Linux +version of chkdsk. It only tries to leave the NTFS partition in a +not-so-inconsistent state after the NTFS driver has written to it. +.sp +\fBntfsfix\fP appeared because MS chkdsk is well known for its +stupidity when fixing altered partitions. Because the main problems +are journal files, \fBntfsfix\fP aims to fix those issues. +.sp +Running ntfsfix after mounting NTFS partitions read-write is recommended +for reducing the chance of severe data loss when NT/W2K tries to remount +the affected partition(s). +.sp +In order to use \fBntfsfix\fP you must unmount the NTFS partition, and run +ntfsfix device, where device is the NTFS partition. After this, you can +safely reboot into NT/W2K. Please note that \fBntfsfix\fP is not a +chkdsk-like tool, and so is not guaranteed that it could fix all the +alterations provoked by the NTFS driver. + +.SH SEE ALSO +.BR mkntfs (8). +.br +.SH AUTHOR +This manual page was written by David Martínez Moreno +, for the Debian GNU/Linux +system (but may be used by others). diff --git a/ntfsprogs/ntfsfix.c b/ntfsprogs/ntfsfix.c index e69de29b..e852061b 100644 --- a/ntfsprogs/ntfsfix.c +++ b/ntfsprogs/ntfsfix.c @@ -0,0 +1,317 @@ +/* + * $Id$ + * + * NtfsFix - Part of the Linux-NTFS project. + * + * Copyright (c) 2000-2002 Anton Altaparmakov. + * + * This utility will attempt to fix a partition that has been damaged by the + * current Linux-NTFS driver. It should be run after dismounting a NTFS + * partition that has been mounted read-write under Linux and before rebooting + * into Windows NT/2000. NtfsFix can be run even after Windows has had mounted + * the partition, but it might be too late and irreversible damage to the data + * might have been done already. + * + * Anton Altaparmakov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program (in the main directory of the Linux-NTFS source + * in the file COPYING); if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * WARNING: This program might not work on architectures which do not allow + * unaligned access. For those, the program would need to start using + * get/put_unaligned macros (#include ), but not doing it yet, + * since NTFS really mostly applies to ia32 only, which does allow unaligned + * accesses. We might not actually have a problem though, since the structs are + * defined as being packed so that might be enough for gcc to insert the + * correct code. + * + * If anyone using a non-little endian and/or an aligned access only CPU tries + * this program please let me know whether it works or not! + * + * Anton Altaparmakov + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "types.h" +#include "attrib.h" +#include "mft.h" +#include "disk_io.h" +#include "logfile.h" + +int main(int argc, char **argv) +{ + s64 l, br; + const char *EXEC_NAME = "NtfsFix"; + const char *OK = "OK"; + const char *FAILED = "FAILED"; + unsigned char *m = NULL, *m2 = NULL; + ntfs_volume *vol; + unsigned long mnt_flags; + int i; + u16 flags; + BOOL done, force = FALSE; + + printf("\n"); + if (argc != 2 || !argv[1]) { + printf("%s v%s - Attempt to fix an NTFS partition that " + "has been damaged by the\nLinux NTFS driver. Note that " + "you should run it every time after you have used\nthe " + "Linux NTFS driver to write to an NTFS partition to " + "prevent massive data\ncorruption from happening when " + "Windows mounts the partition.\nIMPORTANT: Run this " + "only *after* unmounting the partition in Linux but " + "*before*\nrebooting into Windows NT/2000/XP or you " + "*will* suffer! - You have been warned!\n\n" + /* Generic copyright / disclaimer. */ + "Copyright (c) 2000-2002 Anton Altaparmakov.\n\n" + "%s is free software, released under the GNU " + "General Public License and you\nare welcome to " + "redistribute it under certain conditions.\n" + "%s comes with ABSOLUTELY NO WARRANTY; for details " + "read the file GNU\nGeneral Public License to be found " + "in the file COPYING in the main Linux-NTFS\n" + "distribution directory.\n\n" + /* Generic part ends here. */ + "Syntax: ntfsfix partition_or_file_name\n" + " e.g. ntfsfix /dev/hda6\n\n", EXEC_NAME, + VERSION, EXEC_NAME, EXEC_NAME); + fprintf(stderr, "Error: incorrect syntax\n"); + exit(1); + } + if (!ntfs_check_if_mounted(argv[1], &mnt_flags)) { + if ((mnt_flags & NTFS_MF_MOUNTED) && + !(mnt_flags & NTFS_MF_READONLY) && !force) { + fprintf(stderr, "Refusing to operate on read-write " + "mounted device %s.\n", argv[1]); + exit(1); + } + } else + fprintf(stderr, "Failed to determine whether %s is mounted: " + "%s\n", argv[1], strerror(errno)); + /* Attempt a full mount first. */ + printf("Mounting volume... "); + vol = ntfs_mount(argv[1], 0); + if (vol) { + puts(OK); + printf("\nProcessing of $MFT and $MFTMirr completed " + "successfully.\n\n"); + goto mount_ok; + } + puts(FAILED); + + puts("Attempting to correct errors."); + + vol = ntfs_startup_volume(argv[1], 0); + if (!vol) { + puts(FAILED); + perror("Failed to startup volume"); + fprintf(stderr, "Volume is corrupt. You should run chkdsk."); + goto error_exit; + } + + puts("Processing $MFT and $MFTMirr."); + + /* Load data from $MFT and $MFTMirr and compare the contents. */ + m = (u8*)malloc(vol->mftmirr_size << vol->mft_record_size_bits); + m2 = (u8*)malloc(vol->mftmirr_size << vol->mft_record_size_bits); + if (!m || !m2) { + perror("Failed to allocate memory"); + goto error_exit; + } + + printf("Reading $MFT... "); + l = ntfs_attr_mst_pread(vol->mft_na, 0, vol->mftmirr_size, + vol->mft_record_size, m); + if (l != vol->mftmirr_size) { + puts(FAILED); + if (l != -1) + errno = EIO; + perror("Failed to read $MFT"); + goto error_exit; + } + puts(OK); + + printf("Reading $MFTMirr... "); + l = ntfs_attr_mst_pread(vol->mftmirr_na, 0, vol->mftmirr_size, + vol->mft_record_size, m2); + if (l != vol->mftmirr_size) { + puts(FAILED); + if (l != -1) + errno = EIO; + perror("Failed to read $MFTMirr"); + goto error_exit; + } + puts(OK); + + /* + * FIXME: Need to actually check the $MFTMirr for being real. Otherwise + * we might corrupt the partition if someone is experimenting with + * software RAID and the $MFTMirr is not actually in the position we + * expect it to be... )-: + * FIXME: We should emit a warning it $MFTMirr is damaged and ask + * user whether to recreate it from $MFT or whether to abort. - The + * warning needs to include the danger of software RAID arrays. + * Maybe we should go as far as to detect whether we are running on a + * MD disk and if yes then bomb out right at the start of the program? + */ + + printf("Comparing $MFTMirr to $MFT... "); + done = FALSE; + for (i = 0; i < vol->mftmirr_size; ++i) { + const char *ESTR[12] = { "$MFT", "$MFTMirr", "$LogFile", + "$Volume", "$AttrDef", "root directory", "$Bitmap", + "$Boot", "$BadClus", "$Secure", "$UpCase", "$Extend" }; + const char *s; + + if (i < 12) + s = ESTR[i]; + else if (i < 16) + s = "system file"; + else + s = "mft record"; + + if (is_baad_recordp(m + i * vol->mft_record_size)) { + puts("FAILED"); + fprintf(stderr, "$MFT error: Incomplete multi sector " + "transfer detected in %s.\nCannot " + "handle this yet. )-:\n", s); + goto error_exit; + } + if (!is_mft_recordp(m + i * vol->mft_record_size)) { + puts("FAILED"); + fprintf(stderr, "$MFT error: Invalid mft record for " + "%s.\nCannot handle this yet. )-:\n", + s); + goto error_exit; + } + if (is_baad_recordp(m2 + i * vol->mft_record_size)) { + puts("FAILED"); + fprintf(stderr, "$MFTMirr error: Incomplete multi " + "sector transfer detected in %s.\n", s); + goto error_exit; + } + if (!is_mft_recordp(m2 + i * vol->mft_record_size)) { + puts("FAILED"); + fprintf(stderr, "$MFTMirr error: Invalid mft record " + "for %s.\n", s); + goto error_exit; + } + if (memcmp((u8*)m + i * vol->mft_record_size, (u8*)m2 + + i * vol->mft_record_size, + ntfs_get_mft_record_data_size((MFT_RECORD*)( + (u8*)m + i * vol->mft_record_size)))) { + if (!done) { + done = TRUE; + puts(FAILED); + printf("Correcting differences in " + "$MFTMirr... "); + } + br = ntfs_write_mft_record(vol, i, (MFT_RECORD*)(m + + i * vol->mft_record_size)); + if (br) { + puts(FAILED); + perror("Error correcting $MFTMirr"); + goto error_exit; + } + } + } + puts(OK); + + free(m); + free(m2); + m = m2 = NULL; + + printf("Processing of $MFT and $MFTMirr completed successfully.\n\n"); + if (ntfs_umount(vol, 0)) + ntfs_umount(vol, 1); + vol = ntfs_mount(argv[1], 0); + if (!vol) { + perror("Remount failed"); + goto error_exit; + } +mount_ok: + m = NULL; + + /* Check NTFS version is ok for us (in $Volume) */ + printf("NTFS volume version is %i.%i.\n\n", vol->major_ver, + vol->minor_ver); + if (ntfs_is_version_supported(vol)) { + fprintf(stderr, "Error: Unknown NTFS version.\n"); + goto error_exit; + } + + printf("Setting required flags on partition... "); + /* + * Set chkdsk flag, i.e. mark the partition dirty so chkdsk will run + * and fix it for us. + */ + flags = vol->flags | VOLUME_IS_DIRTY; + /* If NTFS volume version >= 2.0 then set mounted on NT4 flag. */ + if (vol->major_ver >= 2) + flags |= VOLUME_MOUNTED_ON_NT4; + if (ntfs_set_volume_flags(vol, flags)) { + puts(FAILED); + fprintf(stderr, "Error setting volume flags.\n"); + goto error_exit; + } + puts(OK); + printf("\n"); + + printf("Going to empty the journal ($LogFile)... "); + if (ntfs_reset_logfile(vol)) { + puts(FAILED); + perror("Failed to reset $LogFile"); + goto error_exit; + } + puts(OK); + printf("\n"); + + if (vol->major_ver >= 3) { + /* FIXME: If on NTFS 3.0+, check for presence of the usn journal and + disable it (if present) as Win2k might be unhappy otherwise and Bad + Things(TM) could happen depending on what applications are actually + using it for. */ + } + + /* FIXME: Should we be marking the quota out of date, too? */ + + /* That's all for now! */ + printf("NTFS partition %s was processed successfully.\n", + vol->dev_name); + /* Set return code to 0. */ + i = 0; +final_exit: + if (m) + free(m); + if (m2) + free(m2); + if (vol && ntfs_umount(vol, 0)) + ntfs_umount(vol, 1); + return i; +error_exit: + i = 1; + goto final_exit; +} + diff --git a/ntfsprogs/ntfsinfo.8.in b/ntfsprogs/ntfsinfo.8.in index e69de29b..edec6976 100644 --- a/ntfsprogs/ntfsinfo.8.in +++ b/ntfsprogs/ntfsinfo.8.in @@ -0,0 +1,25 @@ +.\" -*- nroff -*- +.\" Copyright (c) 2002 Anton Altaparmakov. All Rights Reserved. +.\" This file may be copied under the terms of the GNU Public License. +.\" +.TH NTFSINFO 8 "May 2002" "Linux-NTFS version @VERSION@" +.SH NAME +ntfsinfo \- dump a file's attributes +.SH SYNOPSIS +.B ntfsinfo +.I device +.I inode-number +.SH DESCRIPTION +.B ntfsinfo +will dump the attributes of inode +.IR inode-number . +.PP +.SH AUTHOR +.B ntfsinfo +was written by Matthew J. Fanto (fanto1mj@cmich.edu). +.SH AVAILABILITY +.B ntfsinfo +is part of the linux-ntfs package and is available from +http://linux-ntfs.sourceforge.net/. + + diff --git a/ntfsprogs/ntfsinfo.c b/ntfsprogs/ntfsinfo.c index e69de29b..45b6d1bb 100644 --- a/ntfsprogs/ntfsinfo.c +++ b/ntfsprogs/ntfsinfo.c @@ -0,0 +1,195 @@ +/*z + * $Id$ + * + * ntfsinfo - Part of the Linux-NTFS project. + * + * Copyright (c) 2002 Matthew J. Fanto + * Copyright (c) 2002 Anton Altaparmakov + * Copyright (c) 2002 Richard Russon + * + * This utility will dump a file's attributes. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program (in the main directory of the Linux-NTFS + * distribution in the file COPYING); if not, write to the Free Software + * Foundation,Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include "mft.h" +#include "attrib.h" +#include "layout.h" +#include "inode.h" + +void get_file_attribute_value(const char *dev, long int i); +void print_standard_information_attr(ntfs_attr_search_ctx * ctx); +void print_file_name_attr(ntfs_attr_search_ctx * ctx); + + +#define NTFS_TIME_OFFSET ((u64)(369*365 + 89) * 24 * 3600 * 10000000) + +int main(int argc, char **argv) +{ + const char *AUTHOR = "Matthew J. Fanto"; + const char *EXEC_NAME = "ntfsinfo"; + const char *locale; + long i; + + locale = setlocale(LC_ALL, ""); + if (!locale) { + char *locale; + + locale = setlocale(LC_ALL, NULL); + printf("Failed to set locale, using default (%s).\n", locale); + } else + printf("Using locale %s.\n", locale); + + if (argc < 3 || argc > 4) { + fprintf(stderr, "%s v%s - %s\n", EXEC_NAME, VERSION, AUTHOR); + fprintf(stderr, "Usage: ntfsinfo device inode\n"); + exit(1); + } + + else { + i = atoll(argv[2]); + get_file_attribute_value(argv[1], i); + } + + return 0; +} + +void get_file_attribute_value(const char *dev, long int i) +{ + + MFT_REF mref; + MFT_RECORD *mrec = NULL; + //ATTR_RECORD *attr = NULL; + //FILE_NAME_ATTR *file_name_attr = NULL; + //STANDARD_INFORMATION *standard_information = NULL; + //SECURITY_DESCRIPTOR_RELATIVE *security_descriptor = NULL; + ntfs_attr_search_ctx *ctx = NULL; + ntfs_volume *vol = NULL; + //char *file_name; + ntfs_inode *inode = NULL; + + vol = ntfs_mount(dev, 0); + + mref = (MFT_REF) i; + inode = ntfs_open_inode(vol, mref); + + if (ntfs_read_file_record(vol, mref, &mrec, NULL)) { + perror("Error reading file record!\n"); + exit(1); + } + + ctx = ntfs_get_attr_search_ctx(inode, mrec); + +// print_file_name_attr(ctx); + +// ctx = ntfs_get_attr_search_ctx(inode, mrec); //need to fix this + + print_standard_information_attr(ctx); +} + + +s64 ntfs2time(s64 time) +{ + s64 t; + printf("Original Time: %Li\n",time); + t = time - NTFS_TIME_OFFSET; + t = t / 10000000; + return t; + + +} + +void print_standard_information_attr(ntfs_attr_search_ctx *ctx) +{ + ATTR_RECORD *attr = NULL; + STANDARD_INFORMATION *standard_information_attr = NULL; + + if (ntfs_lookup_attr + (AT_STANDARD_INFORMATION, AT_UNNAMED, 0, 0, 0, NULL, 0, ctx)) { + perror("Error looking up $STANDARD_INFORMATION!\n"); + exit(1); + } + + attr = ctx->attr; + + standard_information_attr = + (STANDARD_INFORMATION *) ((char *) attr + + le16_to_cpu(attr->value_offset)); + + printf("Creation time: %Li\n", + ntfs2time(standard_information_attr->creation_time)); +/* printf("Last Data Change Time: %Li\n", + ntfs2time(standard_information_attr->last_data_change_time)); + printf("Last MFT Change Time: %Li\n", + ntfs2time(standard_information_attr->last_mft_change_time)); + printf("Last Access Time: %Li\n", + ntfs2time(standard_information_attr->last_access_time)); + printf("Maxium Versions: %d\n", + standard_information_attr->maximum_versions); + printf("Version Number: %d\n", + standard_information_attr->version_number); + printf("Class ID: %d\n", + standard_information_attr->class_id); + printf("Owner ID: %d\n", + standard_information_attr->owner_id); + printf("Security ID: %d\n", + standard_information_attr->security_id); + +*/ +} + +void print_file_name_attr(ntfs_attr_search_ctx *ctx) +{ + ATTR_RECORD *attr = NULL; + ntfs_attr_search_ctx *c = ctx; + FILE_NAME_ATTR *file_name_attr = NULL; + char *file_name; + + if (ntfs_lookup_attr(AT_FILE_NAME, AT_UNNAMED, 0, 0, 0, NULL, 0, ctx)) { + perror("Error looking up $FILE_NAME_ATTR!\n"); + exit(1); + } + + attr = ctx->attr; + ctx = c; + + file_name_attr = + (FILE_NAME_ATTR *) ((char *) attr + + le16_to_cpu(attr->value_offset)); + + file_name = malloc(file_name_attr->file_name_length * sizeof (char)); + + ntfs_ucstombs(file_name_attr->file_name, + file_name_attr->file_name_length, &file_name, + file_name_attr->file_name_length); + + printf("File Name: %s\n", file_name); + printf("File Name Length: %d\n", file_name_attr->file_name_length); + printf("Allocated Size: %Li\n",sle64_to_cpu(file_name_attr->allocated_size)); + printf("Data Size: %Li\n",sle64_to_cpu(file_name_attr->data_size)); +} + +/*void print_security_descriptor_attr(SECURITY_DESCRIPTOR_RELATIVE *security_descriptor) +{ + +}*/ diff --git a/ntfsprogs/ntfslabel.8.in b/ntfsprogs/ntfslabel.8.in index e69de29b..4b9a05bb 100644 --- a/ntfsprogs/ntfslabel.8.in +++ b/ntfsprogs/ntfslabel.8.in @@ -0,0 +1,54 @@ +.\" -*- nroff -*- +.\" Copyright (c) 2002 Anton Altaparmakov. All Rights Reserved. +.\" This file may be copied under the terms of the GNU Public License. +.\" Adapted from e2fsprogs-1.26/misc/e2label.8.in by Theodore Ts'o. +.\" +.TH NTFSLABEL 8 "April 2002" "Linux-NTFS version @VERSION@" +.SH NAME +ntfslabel \- display/change the label on an ntfs file system +.SH SYNOPSIS +.B ntfslabel +.I device +[ +.I new-label +] +.SH DESCRIPTION +.B ntfslabel +will display or change the file system label on the ntfs file system located on +.IR device . +.PP +If the optional argument +.I new-label +is not present, +.B ntfslabel +will simply display the current file system label. +.PP +If the optional argument +.I new-label +is present, then +.B ntfslabel +will set the file system label to be +.IR new-label . +NTFS file system labels can be at most 128 Unicode characters long; if +.I new-label +is longer than 128 Unicode characters, +.B ntfslabel +will truncate it and print a warning message. +.PP +It is also possible to set the file system label using the +.B \-L +option of +.BR mkntfs (8) +during creation of the file system. +.PP +.SH AUTHOR +.B ntfslabel +was written by Matthew J. Fanto (fanto1mj@cmich.edu). This man page was written +by Anton Altaparmakov (aia21@cantab.net). +.SH AVAILABILITY +.B ntfslabel +is part of the linux-ntfs package and is available from +http://linux-ntfs.sourceforge.net/. +.SH SEE ALSO +.BR mkntfs (8) + diff --git a/ntfsprogs/ntfslabel.c b/ntfsprogs/ntfslabel.c index e69de29b..5d07c0d0 100644 --- a/ntfsprogs/ntfslabel.c +++ b/ntfsprogs/ntfslabel.c @@ -0,0 +1,247 @@ +/* + * $Id$ + * + * ntfslabel - Part of the Linux-NTFS project. + * + * Copyright (c) 2002 Matthew J. Fanto + * Copyright (c) 2002 Anton Altaparmakov + * Copyright (c) 2002 Richard Russon + * + * This utility will display/change the label on an NTFS partition. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program (in the main directory of the Linux-NTFS + * distribution in the file COPYING); if not, write to the Free Software + * Foundation,Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "debug.h" +#include "mft.h" + +/* + * print_label - display the current label of a mounted ntfs partition. + * @dev: device to read the label from + * @mnt_flags: mount flags of the device or 0 if not mounted + * @mnt_point: mount point of the device or NULL + * + * Print the label of the device @dev to stdout. + */ +void print_label(const char *dev, const unsigned long mnt_flags, + const char *mnt_point) +{ + ntfs_volume *vol; + + if (mnt_point) { + // Try ioctl and finish if present. + // goto finished; + } + if ((mnt_flags & (NTFS_MF_MOUNTED | NTFS_MF_READONLY)) == + NTFS_MF_MOUNTED) { + fprintf(stderr, "%s is mounted read-write, results may be " + "unreliable.\n", dev); + } + vol = ntfs_mount(dev, MS_RDONLY); + if (!vol) { + fprintf(stderr, "ntfs_mount() on device %s failed: %s\n", dev, + strerror(errno)); + exit(1); + } +//finished: + printf("%s\n", vol->vol_name); + if (ntfs_umount(vol, 0)) + ntfs_umount(vol, 1); +} + +/* + * resize_resident_attribute_value - resize a resident attribute + * @m: mft record containing attribute to resize + * @a: attribute record (inside @m) which to resize + * @new_vsize: the new attribute value size to resize the attribute to + * + * Return 0 on success and -1 with errno = ENOSPC if not enough space in the + * mft record. + */ +int resize_resident_attribute_value(MFT_RECORD *m, ATTR_RECORD *a, + const u32 new_vsize) +{ + int new_alen, new_muse; + + /* New attribute length and mft record bytes used. */ + new_alen = (le16_to_cpu(a->value_offset) + new_vsize + 7) & ~7; + new_muse = le32_to_cpu(m->bytes_in_use) - le32_to_cpu(a->length) + + new_alen; + /* Check for sufficient space. */ + if (new_muse > le32_to_cpu(m->bytes_allocated)) { + errno = ENOSPC; + return -1; + } + /* Move attributes behind @a to their new location. */ + memmove((char*)a + new_alen, (char*)a + le32_to_cpu(a->length), + le32_to_cpu(m->bytes_in_use) - ((char*)a - (char*)m) - + le32_to_cpu(a->length)); + /* Adjust @m to reflect change in used space. */ + m->bytes_in_use = cpu_to_le32(new_muse); + /* Adjust @a to reflect new value size. */ + a->length = cpu_to_le32(new_alen); + a->value_length = cpu_to_le32(new_vsize); + return 0; +} + +/* + * change_label - change the current label on a device + * @dev: device to change the label on + * @mnt_flags: mount flags of the device or 0 if not mounted + * @mnt_point: mount point of the device or NULL + * @label: the new label + * + * Change the label on the device @dev to @label. + */ +void change_label(const char *dev, const unsigned long mnt_flags, + const char *mnt_point, char *label, BOOL force) +{ + ntfs_attr_search_ctx *ctx = NULL; + uchar_t *new_label = NULL; + MFT_RECORD *mrec = NULL; + ATTR_RECORD *a; + ntfs_volume *vol; + int label_len, err = 1; + + if (mnt_point) { + // Try ioctl and return if present. + // return; + } + if (mnt_flags & NTFS_MF_MOUNTED) { + /* If not the root fs or mounted read/write, refuse change. */ + if (!(mnt_flags & NTFS_MF_ISROOT) || + !(mnt_flags & NTFS_MF_READONLY)) { + if (!force) { + fprintf(stderr, "Refusing to change label on " + "read-%s mounted device %s.\n", + mnt_flags & NTFS_MF_READONLY ? + "only" : "write", dev); + return; + } + } + } + vol = ntfs_mount(dev, 0); + if (!vol) { + fprintf(stderr, "ntfs_mount() on device %s failed: %s\n", dev, + strerror(errno)); + exit(1); + } + if (ntfs_read_file_record(vol, (MFT_REF)FILE_Volume, &mrec, NULL)) { + perror("Error reading file record"); + goto err_out; + } + if (!(mrec->flags & MFT_RECORD_IN_USE)) { + fprintf(stderr, "Error: $Volume has been deleted. Run " + "chkdsk to fix this.\n"); + goto err_out; + } + ctx = ntfs_get_attr_search_ctx(NULL, mrec); + if (!ctx) { + perror("Failed to get attribute search context"); + goto err_out; + } + if (ntfs_lookup_attr(AT_VOLUME_NAME, AT_UNNAMED, 0, 0, 0, NULL, 0, ctx)) { + perror("Lookup of $VOLUME_NAME attribute failed"); + goto err_out; + } + a = ctx->attr; + if (a->non_resident) { + fprintf(stderr, "Error: Attribute $VOLUME_NAME must be " + "resident.\n"); + goto err_out; + } + label_len = ntfs_mbstoucs(label, &new_label, 0); + if (label_len == -1) { + perror("Unable to convert label string to Unicode"); + goto err_out; + } + label_len *= sizeof(uchar_t); + if (label_len > 0x100) { + fprintf(stderr, "New label is too long. Maximum %i characters " + "allowed. Truncating excess characters.\n", + 0x100 / sizeof(uchar_t)); + label_len = 0x100; + new_label[label_len / sizeof(uchar_t)] = cpu_to_le16(L'\0'); + } + if (resize_resident_attribute_value(mrec, a, label_len)) { + perror("Error resizing resident attribute"); + goto err_out; + } + memcpy((char*)a + le16_to_cpu(a->value_offset), new_label, label_len); + if (ntfs_write_mft_record(vol, (MFT_REF)FILE_Volume, mrec)) { + perror("Error writing MFT Record to disk"); + goto err_out; + } + err = 0; +err_out: + if (new_label) + free(new_label); + if (mrec) + free(mrec); + if (ntfs_umount(vol, 0)) + ntfs_umount(vol, 1); + if (err) + exit(1); +} + +int main(int argc, char **argv) +{ + const char *AUTHOR = "Matthew Fanto"; + char *EXEC_NAME = "ntfslabel"; + char *locale, *mnt_point = NULL; + unsigned long mnt_flags; + int err; + // FIXME:Implement option -F meaning force the change. + BOOL force = 0; + + locale = setlocale(LC_ALL, ""); + if (!locale) { + char *locale; + + locale = setlocale(LC_ALL, NULL); + Dprintf("Failed to set locale, using default (%s).\n", locale); + } else + Dprintf("Using locale %s.\n", locale); + if (argc && *argv) + EXEC_NAME = *argv; + if (argc < 2 || argc > 3) { + fprintf(stderr, "%s v%s - %s\n", EXEC_NAME, VERSION, AUTHOR); + fprintf(stderr, "Usage: ntfslabel device [newlabel]\n"); + exit(1); + } + err = ntfs_check_if_mounted(argv[1], &mnt_flags); + if (err) + fprintf(stderr, "Failed to determine whether %s is mounted: " + "%s\n", argv[1], strerror(errno)); + else if (mnt_flags & NTFS_MF_MOUNTED) { + // Not implemented yet. Will be used for ioctl interface to driver. + // mnt_point = ntfs_get_mount_point(argv[1]); + } + if (argc == 2) + print_label(argv[1], mnt_flags, mnt_point); + else + change_label(argv[1], mnt_flags, mnt_point, argv[2], force); + return 0; +} + diff --git a/ntfsprogs/ntfsresize.8.in b/ntfsprogs/ntfsresize.8.in index e69de29b..b2be7c28 100644 --- a/ntfsprogs/ntfsresize.8.in +++ b/ntfsprogs/ntfsresize.8.in @@ -0,0 +1,117 @@ +.\" -*- nroff -*- +.\" Copyright 2002 by Szabolcs Szakacsits All Rights Reserved. +.\" +.TH NTFSRESIZE 8 "November 2002" "Linux\-NTFS @VERSION@" +.SH NAME +ntfsresize \- resize an NTFS filesystem +.SH SYNOPSIS +.B ntfsresize +[\fB\-fhin\fR] +[\fB\-s \fIsize\fR[\fBk\fR|\fBM\fR|\fBG\fR]] +.I device +.SH DESCRIPTION +The +.B ntfsresize +program non-destructively resizes Windows NT4, 2000, XP or .NET +NTFS filesystems. At present it can be used to shrink a +defragmented NTFS filesystem located on an unmounted +.I device +(usually a disk partition). The new volume will have +.I size +bytes. +The +.I size +parameter may have one of the optional modifiers +\fBk\fR, \fBM\fR, \fBG\fR, which means the +.I size +parameter is given in kilo-, mega- or gigabytes respectively. +.B ntfsresize +conforms to the SI, ATA, IEEE standards and the disk manufacturers +by using k=10^3, M=10^6 and G=10^9. +.PP +The +.B ntfsresize +program does not manipulate the size of partitions. +If you wish to shrink an NTFS partition, first use +.B ntfsresize +to shrink the size of the filesystem. Then you may use +.BR fdisk (8) +to shrink the size of the partition by deleting the +partition and recreating it with the smaller size. +.PP +.B IMPORTANT! +When recreating the partition with +.BR fdisk (8) +make sure you create it with the same starting +disk cylinder and partition type +as before and you do not make it smaller than the new size +of the NTFS filesystem! Otherwise you may lose your entire filesystem. +Also make sure you set the bootable flag for the partition if it +existed before. Failing to do so you might not be able to boot your +computer from the disk! +.PP +Note, +.B ntfsresize +schedules 'chkdsk' to make an NTFS consistency check +when you will boot Windows. If your partition was a +system partition than Windows may intentionally reboot after +the successful consistency check. + +.SH OPTIONS +.TP +.B -f +Forces ntfsresize to proceed with the filesystem resize operation, overriding +some safety checks which +.B ntfsresize +normally enforces. You can use this +parameter multiply times if you want to overcome every single safety checks. +.TP +.B -h +Display help and exit. +.TP +.B -i +Using this option you can calculate the smallest shrunken volume size supported. +This option will not make any changes to the filesystem. +.TP +.B -n +You can use this option to make a test run before doing the real resize operation. +Volume will be opened read-only and +.B ntfsresize +displays what it would do if it were to resize the filesystem. +.TP +.B -s \fIsize\fR[\fBk\fR|\fBM\fR|\fBG\fR] +Shrink volume to \fIsize\fR[\fBk\fR|\fBM\fR|\fBG\fR] bytes. +The optional modifiers \fBk\fR, \fBM\fR, \fBG\fR mean the +.I size +parameter is given in kilo-, mega- or gigabytes respectively. +Conforming to standards, k=10^3, M=10^6 and G=10^9. +.SH BUGS +No bugs are known or has been reported so far in the current version. +If you find otherwise, please report it to +(no subscription needed). It's also strongly advised you +.B MAKE SURE YOU HAVE A BACKUP +of your important data in case of an unexpected failure. +.PP +Future work is planned to include support for volume enlargement +and resizing fragmented NTFS volumes. +Please note, Windows 2000, XP and .NET have built in NTFS defragmenter. +.SH AVAILABILITY +.B ntfsresize +is part of the linux-ntfs package and is available from +http://linux-ntfs.sf.net/ as source and pre-compiled binary. +.SH AUTHOR +.B ntfsresize +has been written by +Szabolcs Szakacsits . +.SH ACKNOWLEDGEMENT +Many thanks to Anton Altaparmakov and Richard Russon (FlatCap) +for libntfs, excellent documentation, comments, testing and fixes, +moreover to Theodore Ts'o whose +.BR resize2fs (8) +man page formed the basis of this page. +.SH SEE ALSO +.BR fdisk (8), +.BR cfdisk (8), +.BR sfdisk (8), +.BR parted (8), +.BR mkntfs (8) diff --git a/ntfsprogs/ntfsresize.c b/ntfsprogs/ntfsresize.c index e69de29b..d7d2ab65 100644 --- a/ntfsprogs/ntfsresize.c +++ b/ntfsprogs/ntfsresize.c @@ -0,0 +1,914 @@ +/** + * ntfsresize - Part of the Linux-NTFS project. + * + * Copyright (c) 2002 Szabolcs Szakacsits + * + * This utility will resize an NTFS volume. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program (in the main directory of the Linux-NTFS + * distribution in the file COPYING); if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "debug.h" +#include "types.h" +#include "support.h" +#include "endians.h" +#include "bootsect.h" +#include "disk_io.h" +#include "attrib.h" +#include "volume.h" +#include "mft.h" +#include "bitmap.h" +#include "inode.h" +#include "runlist.h" + +const char *EXEC_NAME = "ntfsresize"; + +static const char *ntfs_report_banner = +"\nReport bugs to linux-ntfs-dev@lists.sf.net. " +"Homepage: http://linux-ntfs.sf.net\n"; + +static const char *resize_warning_msg = +"WARNING: Every sanity check passed and only the DANGEROUS operations left.\n" +"Please make sure all your important data had been backed up in case of an\n" +"unexpected failure!\n"; + +static const char *resize_important_msg = +"NTFS had been successfully resized on device '%s'.\n" +"You can go on to resize the device e.g. with 'fdisk'.\n" +"IMPORTANT: When recreating the partition, make sure you\n" +" 1) create it with the same starting disk cylinder\n" +" 2) create it with the same partition type (usually 7, HPFS/NTFS)\n" +" 3) do not make it smaller than the new NTFS filesystem size\n" +" 4) set the bootable flag for the partition if it existed before\n" +"Otherwise you may lose your data or can't boot your computer from the disk!\n"; + +static const char *fragmented_volume_msg = +"The volume end is fragmented, this case is not yet supported. Defragment it\n" +"(Windows 2000, XP and .NET have built in defragmentation tool) and try again.\n"; + +struct { + int verbose; + int debug; + int ro_flag; + int force; + int info; + s64 bytes; + char *volume; +} opt; + +struct bitmap { + u8 *bm; + s64 size; +}; + +struct progress_bar { + u64 start; + u64 stop; + int resolution; + float unit; +}; + +ntfs_volume *vol = NULL; +struct bitmap lcn_bitmap; + +#define NTFS_MBYTE (1000 * 1000) + +#define ERR_PREFIX "ERROR" +#define PERR_PREFIX ERR_PREFIX "(%d): " +#define NERR_PREFIX ERR_PREFIX ": " + +#define rounded_up_division(a, b) (((a) + (b - 1)) / (b)) + + +void perr_printf(const char *fmt, ...) +{ + va_list ap; + int eo = errno; + + fprintf(stdout, PERR_PREFIX, eo); + va_start(ap, fmt); + vfprintf(stdout, fmt, ap); + va_end(ap); + printf(": %s\n", strerror(eo)); + fflush(stdout); + fflush(stderr); +} + + +int err_exit(const char *fmt, ...) +{ + va_list ap; + + fprintf(stdout, NERR_PREFIX); + va_start(ap, fmt); + vfprintf(stdout, fmt, ap); + va_end(ap); + fflush(stdout); + fflush(stderr); + exit(1); +} + + +int perr_exit(const char *fmt, ...) +{ + va_list ap; + int eo = errno; + + fprintf(stdout, PERR_PREFIX, eo); + va_start(ap, fmt); + vfprintf(stdout, fmt, ap); + va_end(ap); + printf(": %s\n", strerror(eo)); + fflush(stdout); + fflush(stderr); + exit(1); +} + + +void usage() +{ + printf("\n"); + printf ("Usage: %s [-fhin] [-s size[k|M|G]] device\n", EXEC_NAME); + printf("Shrink a defragmented NTFS volume.\n"); + printf("\n"); + Dprintf(" -d Show debug information\n"); + printf (" -f Force to progress (DANGEROUS)\n"); + printf (" -h This help text\n"); + printf (" -i Calculate the smallest shrunken size supported (read-only)\n"); + printf (" -n Make a test run without write operations (read-only)\n"); + printf (" -s size[k|M|G] Shrink volume to size[k|M|G] bytes (k=10^3, M=10^6, G=10^9)\n"); +/* printf (" -v Verbose operation\n"); */ + printf(ntfs_report_banner); + exit(1); +} + + +/* Copy-paste from e2fsprogs */ +void proceed_question(void) +{ + char buf[256]; + const char *short_yes = "yY"; + + fflush(stdout); + fflush(stderr); + printf("Are you sure you want to proceed (y/[n])? "); + buf[0] = 0; + fgets(buf, sizeof(buf), stdin); + if (strchr(short_yes, buf[0]) == 0) { + printf("OK quitting. NO CHANGES has been made to your NTFS volume.\n"); + exit(1); + } +} + + +s64 get_new_volume_size(char *s) +{ + s64 size; + char *suffix; + + size = strtoll(s, &suffix, 10); + if (size <= 0 || errno == ERANGE) + err_exit("Illegal new volume size\n"); + + if (!*suffix) + return size; + + if (strlen(suffix) > 1) + usage(); + + /* We follow the SI prefixes: + http://physics.nist.gov/cuu/Units/prefixes.html + http://physics.nist.gov/cuu/Units/binary.html + Disk partitioning tools use prefixes as, + k M G + old fdisk 2^10 2^20 10^3*2^20 + recent fdisk 10^3 10^6 10^9 + cfdisk 10^3 10^6 10^9 + sfdisk 2^10 2^20 + parted 2^10 2^20 (may change) + fdisk (DOS) 2^10 2^20 + */ + /* FIXME: check for overflow */ + switch (*suffix) { + case 'G': + size *= 1000; + case 'M': + size *= 1000; + case 'k': + size *= 1000; + break; + default: + usage(); + } + + return size; +} + + +void parse_options(int argc, char **argv) +{ + char *s; + int i; + + printf("%s v%s\n", EXEC_NAME, VERSION); + + memset(&opt, 0, sizeof(opt)); + + while ((i = getopt(argc, argv, "dfhins:")) != EOF) + switch (i) { + case 'd': + opt.debug = 1; + break; + case 'f': + opt.force++; + break; + case 'h': + usage(); + case 'i': + opt.info = 1; + break; + case 'n': + opt.ro_flag = MS_RDONLY; + break; + case 's': + opt.bytes = get_new_volume_size(optarg); + break; + case 'v': + opt.verbose++; + break; + default: + usage(); + } + if (optind == argc) + usage(); + opt.volume = argv[optind++]; + if (optind < argc) + usage(); + + stderr = stdout; + if (!opt.debug) + if (!(stderr = fopen("/dev/null", "rw"))) + perr_exit("Couldn't open /dev/null"); + + + /* If no '-s size' then estimate smallest shrunken volume size */ + if (!opt.bytes) + opt.info = 1; + + if (opt.info) { + if (opt.bytes) { + printf(NERR_PREFIX "It makes no sense to use -i and " + "-s together.\n"); + usage(); + } + opt.ro_flag = MS_RDONLY; + } +} + + +s64 nr_clusters_to_bitmap_byte_size(s64 nr_clusters) +{ + s64 bm_bsize; + + bm_bsize = rounded_up_division(nr_clusters, 8); + + /* Needs to be multiple of 8 bytes */ + bm_bsize = (bm_bsize + 7) & ~7; + Dprintf("Bitmap byte size : %lld (%lld clusters)\n", + bm_bsize, rounded_up_division(bm_bsize, vol->cluster_size)); + + return bm_bsize; +} + + +void build_lcn_usage_bitmap(ATTR_RECORD *a) +{ + run_list *rl; + int i, j; + + if (!a->non_resident) + return; + + if (!(rl = ntfs_decompress_mapping_pairs(vol, a, NULL))) + perr_exit("ntfs_decompress_mapping_pairs"); + + for (i = 0; rl[i].length; i++) { + if (rl[i].lcn == LCN_HOLE || rl[i].lcn == LCN_RL_NOT_MAPPED) + continue; + for (j = 0; j < rl[i].length; j++) { + u64 k = (u64)rl[i].lcn + j; + if (ntfs_get_and_set_bit(lcn_bitmap.bm, k, 1)) + err_exit("Cluster %lu referenced twice!\n" + "You didn't shutdown your Windows" + "properly?", k); + } + } + free(rl); +} + + +void walk_attributes(MFT_RECORD *mr) +{ + ntfs_attr_search_ctx *ctx; + + if (!(ctx = ntfs_get_attr_search_ctx(NULL, mr))) + perr_exit("ntfs_get_attr_search_ctx"); + + while (!ntfs_walk_attrs(ctx)) { + if (ctx->attr->type == AT_END) + break; + build_lcn_usage_bitmap(ctx->attr); + } + + ntfs_put_attr_search_ctx(ctx); +} + + +void get_bitmap_data(ntfs_volume *vol, struct bitmap *bm) +{ + ntfs_inode *ni; + ntfs_attr_search_ctx *ctx; + + if (!(ni = ntfs_open_inode(vol, (MFT_REF)FILE_Bitmap))) + perr_exit("ntfs_open_inode"); + + if (!(ctx = ntfs_get_attr_search_ctx(ni, NULL))) + perr_exit("ntfs_get_attr_search_ctx"); + + if (ntfs_lookup_attr(AT_DATA, AT_UNNAMED, 0, 0, 0, NULL, 0, ctx)) + perr_exit("ntfs_lookup_attr"); + + /* FIXME: get_attribute_value_length() can't handle extents */ + bm->size = get_attribute_value_length(ctx->attr); + + if (!(bm->bm = (u8 *)malloc(bm->size))) + perr_exit("get_bitmap_data"); + + if (get_attribute_value(vol, ni->mrec, ctx->attr, bm->bm) != bm->size) + perr_exit("Couldn't get $Bitmap $DATA\n"); + + ntfs_put_attr_search_ctx(ctx); + ntfs_close_inode(ni); +} + + +void compare_bitmaps(struct bitmap *a, struct bitmap *b) +{ + int i; + + if (a->size != b->size) + err_exit("$Bitmap file size doesn't match " + "calculated size ((%d != %d)\n", a->size, b->size); + + for (i = 0; i < a->size; i++) + if (a->bm[i] != b->bm[i]) + err_exit("Cluster bitmaps differ at %d (%d != %d)\n" + "You didn't shutdown your Windows properly?", + i, a->bm[i], b->bm[i]); +} + + +void progress_init(struct progress_bar *p, u64 start, u64 stop, int res) +{ + p->start = start; + p->stop = stop; + p->unit = 100.0 / (stop - start); + p->resolution = res; +} + + +void progress_update(struct progress_bar *p, u64 current) +{ + float percent = p->unit * current; + + if (current != p->stop) { + if ((current - p->start) % p->resolution) + return; + printf("%6.2f percent completed\r", percent); + } else + printf("100.00 percent completed\n"); + fflush(stdout); +} + +void walk_inodes() +{ + s32 inode = 0; + s64 last_mft_rec; + MFT_REF mref; + MFT_RECORD *mrec = NULL; + struct progress_bar progress; + + printf("Scanning volume ...\n"); + + last_mft_rec = vol->nr_mft_records - 1; + progress_init(&progress, inode, last_mft_rec, 100); + + for (; inode <= last_mft_rec; inode++) { + progress_update(&progress, inode); + + mref = (MFT_REF)inode; + if (ntfs_read_file_record(vol, mref, &mrec, NULL)) { + /* FIXME: continue only if it make sense, e.g. + MFT record not in use based on $MFT bitmap */ + if (errno == EIO) + continue; + perr_exit("Reading inode %ld failed", inode); + } + if (!(mrec->flags & MFT_RECORD_IN_USE)) + continue; + + walk_attributes(mrec); + } + if (mrec) + free(mrec); +} + + +void advise_on_resize() +{ + u64 i, old_b, new_b, g_b, old_mb, new_mb, g_mb; + int fragmanted_end; + + for (i = vol->nr_clusters - 1; i > 0; i--) + if (ntfs_get_bit(lcn_bitmap.bm, i)) + break; + + i += 2; /* first free + we reserve one for the backup boot sector */ + fragmanted_end = (i >= vol->nr_clusters) ? 1 : 0; + + if (fragmanted_end || !opt.info) { + printf(fragmented_volume_msg); + if (fragmanted_end) + exit(1); + printf("Now "); + } + + old_b = vol->nr_clusters * vol->cluster_size; + old_mb = rounded_up_division(old_b, NTFS_MBYTE); + new_b = i * vol->cluster_size; + new_mb = rounded_up_division(new_b, NTFS_MBYTE); + g_b = (vol->nr_clusters - i) * vol->cluster_size; + g_mb = g_b / NTFS_MBYTE; + + printf("You could resize at %lld bytes ", new_b); + + if ((new_mb * NTFS_MBYTE) < old_b) + printf("or %lld MB ", new_mb); + + printf("(freeing "); + + if (g_mb && (old_mb - new_mb)) + printf("%lld MB", old_mb - new_mb); + else + printf("%lld bytes", g_b); + + printf(").\n"); + exit(1); +} + + +void look_for_bad_sector(ATTR_RECORD *a) +{ + run_list *rl; + int i; + + rl = ntfs_decompress_mapping_pairs(vol, a, NULL); + if (!rl) + perr_exit("ntfs_decompress_mapping_pairs"); + + for (i = 0; rl[i].length; i++) + if (rl[i].lcn != LCN_HOLE) + err_exit("Device has bad sectors, not supported\n"); + + free(rl); +} + + +void rl_set(run_list *rl, VCN vcn, LCN lcn, s64 len) +{ + rl->vcn = vcn; + rl->lcn = lcn; + rl->length = len; +} + + +/* + * $Bitmap can overlap the end of the volume. Any bits in this region + * must be set. This region also encompasses the backup boot sector. + */ +void bitmap_file_data_fixup(s64 cluster, struct bitmap *bm) +{ + for (; cluster < bm->size << 3; cluster++) + ntfs_set_bit(bm->bm, (u64)cluster, 1); +} + + +/* + * FIXME: this function should go away and instead using a generalized + * "truncate_bitmap_unnamed_attr()" + */ +void truncate_badclust_bad_attr(ATTR_RECORD *a, s64 nr_clusters) +{ + run_list *rl_bad; + int mp_size; + char *mp; + + if (!a->non_resident) + /* FIXME: handle resident attribute value */ + perr_exit("Resident attribute in $BadClust not supported!"); + + if (!(rl_bad = (run_list *)malloc(2 * sizeof(run_list)))) + perr_exit("Couldn't get memory"); + + rl_set(rl_bad, 0LL, (LCN)LCN_HOLE, nr_clusters); + rl_set(rl_bad + 1, nr_clusters, -1LL, 0LL); + + mp_size = ntfs_get_size_for_mapping_pairs(vol, rl_bad); + + if (!(mp = (char *)calloc(1, mp_size))) + perr_exit("Couldn't get memory"); + + if (ntfs_build_mapping_pairs(vol, mp, mp_size, rl_bad)) + exit(1); + + memcpy((char *)a + a->mapping_pairs_offset, mp, mp_size); + a->highest_vcn = cpu_to_le64(nr_clusters - 1LL); + a->allocated_size = cpu_to_le64(nr_clusters * vol->cluster_size); + a->data_size = cpu_to_le64(nr_clusters * vol->cluster_size); + + free(rl_bad); + free(mp); +} + + +void truncate_bitmap_unnamed_attr(ATTR_RECORD *a, s64 nr_clusters) +{ + run_list *rl; + s64 bm_bsize, size; + s64 nr_bm_clusters; + int i, j, mp_size; + int trunc_at = -1; /* FIXME: -1 means unset */ + char *mp; + + + if (!a->non_resident) + /* FIXME: handle resident attribute value */ + perr_exit("Resident data attribute in $Bitmap not supported!"); + + bm_bsize = nr_clusters_to_bitmap_byte_size(nr_clusters); + nr_bm_clusters = rounded_up_division(bm_bsize, vol->cluster_size); + + if (!(rl = ntfs_decompress_mapping_pairs(vol, a, NULL))) + perr_exit("ntfs_decompress_mapping_pairs"); + + /* Unallocate truncated clusters in $Bitmap */ + for (i = 0; rl[i].length; i++) { + if (rl[i].vcn + rl[i].length <= nr_bm_clusters) + continue; + if (trunc_at == -1) + trunc_at = i; + if (rl[i].lcn == LCN_HOLE || rl[i].lcn == LCN_RL_NOT_MAPPED) + continue; + for (j = 0; j < rl[i].length; j++) + if (rl[i].vcn + j >= nr_bm_clusters) { + u64 k = (u64)rl[i].lcn + j; + ntfs_set_bit(lcn_bitmap.bm, k, 0); + Dprintf("Unallocate cluster: " + "%llu (%llx)\n", k, k); + } + } + + /* FIXME: realloc lcn_bitmap.bm (if it's worth the risk) */ + lcn_bitmap.size = bm_bsize; + bitmap_file_data_fixup(nr_clusters, &lcn_bitmap); + + if (trunc_at != -1) { + /* NOTE: 'i' always > 0 */ + i = nr_bm_clusters - rl[trunc_at].vcn; + rl[trunc_at].length = i; + rl_set(rl + trunc_at + 1, nr_bm_clusters, -1LL, 0LL); + + Dprintf("Runlist truncated at index %d, " + "new cluster length %d\n", trunc_at, i); + } + + if (!opt.ro_flag) { + size = ntfs_rl_pwrite(vol, rl, 0, bm_bsize, lcn_bitmap.bm); + if (bm_bsize != size) { + if (size == -1) + perr_exit("Couldn't write $Bitmap"); + printf("Couldn't write full $Bitmap file " + "(%lld from %lld)\n", size, bm_bsize); + exit(1); + } + } + + mp_size = ntfs_get_size_for_mapping_pairs(vol, rl); + + if (!(mp = (char *)calloc(1, mp_size))) + perr_exit("Couldn't get memory"); + + if (ntfs_build_mapping_pairs(vol, mp, mp_size, rl)) + exit(1); + + memcpy((char *)a + a->mapping_pairs_offset, mp, mp_size); + a->highest_vcn = cpu_to_le64(nr_bm_clusters - 1LL); + a->allocated_size = cpu_to_le64(nr_bm_clusters * vol->cluster_size); + a->data_size = cpu_to_le64(bm_bsize); + a->initialized_size = cpu_to_le64(bm_bsize); + + free(rl); + free(mp); +} + + +void lookup_data_attr(MFT_REF mref, char *aname, ntfs_attr_search_ctx **ctx) +{ + ntfs_inode *ni; + uchar_t *ustr = NULL; + int len = 0; + + if (!(ni = ntfs_open_inode(vol, mref))) + perr_exit("ntfs_open_inode"); + + if (NInoAttrList(ni)) + perr_exit("Attribute list attribute not yet supported"); + + if (!(*ctx = ntfs_get_attr_search_ctx(ni, NULL))) + perr_exit("ntfs_get_attr_search_ctx"); + + if (aname && ((len = ntfs_mbstoucs(aname, &ustr, 0)) == -1)) + perr_exit("Unable to convert string to Unicode"); + + if (!ustr || !len) { + ustr = AT_UNNAMED; + len = 0; + } + + if (ntfs_lookup_attr(AT_DATA, ustr, len, 0, 0, NULL, 0, *ctx)) + perr_exit("ntfs_lookup_attr"); + + if (ustr != AT_UNNAMED) + free(ustr); +} + + +int write_mft_record(ntfs_attr_search_ctx *ctx) +{ + if (opt.ro_flag) + return 0; + + return ntfs_write_mft_record(vol, ctx->ntfs_ino->mft_no, ctx->mrec); +} + + +void truncate_badclust_file(s64 nr_clusters) +{ + ntfs_attr_search_ctx *ctx = NULL; + + printf("Updating $BadClust file ...\n"); + + lookup_data_attr((MFT_REF)FILE_BadClus, "$Bad", &ctx); + look_for_bad_sector(ctx->attr); + /* FIXME: sanity_check_attr(ctx->attr); */ + /* FIXME: should use an "extended" truncate_bitmap_unnamed_attr() */ + truncate_badclust_bad_attr(ctx->attr, nr_clusters); + + if (write_mft_record(ctx)) + perr_exit("Couldn't update $BadClust"); + + /* FIXME: clean up API => ntfs_put_attr_search_ctx() also closes ni */ + ntfs_put_attr_search_ctx(ctx); +} + + +void truncate_bitmap_file(s64 nr_clusters) +{ + ntfs_attr_search_ctx *ctx = NULL; + + printf("Updating $Bitmap file ...\n"); + + lookup_data_attr((MFT_REF)FILE_Bitmap, NULL, &ctx); + /* FIXME: sanity_check_attr(ctx->attr); */ + truncate_bitmap_unnamed_attr(ctx->attr, nr_clusters); + + if (write_mft_record(ctx)) + perr_exit("Couldn't update $Bitmap"); + + ntfs_put_attr_search_ctx(ctx); +} + + +void setup_lcn_bitmap() +{ + /* Determine lcn bitmap byte size and allocate it. */ + lcn_bitmap.size = nr_clusters_to_bitmap_byte_size(vol->nr_clusters); + + if (!(lcn_bitmap.bm = (unsigned char *)calloc(1, lcn_bitmap.size))) + perr_exit("Failed to allocate internal buffer"); + + bitmap_file_data_fixup(vol->nr_clusters, &lcn_bitmap); +} + + +/* FIXME: should be done using ntfs_* functions */ +void update_bootsector(s64 nr_clusters) +{ + NTFS_BOOT_SECTOR bs; + + printf("Updating Boot record ...\n"); + + if (lseek(vol->fd, 0, SEEK_SET) == (off_t)-1) + perr_exit("lseek"); + + if (read(vol->fd, &bs, sizeof(NTFS_BOOT_SECTOR)) == -1) + perr_exit("read() error"); + + bs.number_of_sectors = nr_clusters * bs.bpb.sectors_per_cluster; + bs.number_of_sectors = cpu_to_le64(bs.number_of_sectors); + + if (lseek(vol->fd, 0, SEEK_SET) == (off_t)-1) + perr_exit("lseek"); + + if (!opt.ro_flag) + if (write(vol->fd, &bs, sizeof(NTFS_BOOT_SECTOR)) == -1) + perr_exit("write() error"); +} + + +void print_volume_size(char *str, ntfs_volume *v, s64 nr_clusters) +{ + s64 b; /* volume size in bytes */ + + b = nr_clusters * v->cluster_size; + printf("%s: %lld bytes (%lld MB)\n", + str, b, rounded_up_division(b, NTFS_MBYTE)); +} + + +void mount_volume() +{ + unsigned long mntflag; + + if (ntfs_check_if_mounted(opt.volume, &mntflag)) + perr_exit("Failed to check '%s' mount state", opt.volume); + + if (mntflag & NTFS_MF_MOUNTED) { + if (!(mntflag & NTFS_MF_READONLY)) + err_exit("Device %s is mounted read-write. " + "You must 'umount' it first.\n", opt.volume); + if (!opt.ro_flag) + err_exit("Device %s is mounted. " + "You must 'umount' it first.\n", opt.volume); + } + + if (!(vol = ntfs_mount(opt.volume, opt.ro_flag))) { + + int err = errno; + + perr_printf("ntfs_mount failed"); + if (errno == EINVAL) { + printf("Apparently device '%s' doesn't have a " + "valid NTFS. Maybe you selected\nthe whole " + "disk instead of a partition (e.g. /dev/hda, " + "not /dev/hda8)?\n", opt.volume); + } + exit(1); + } + + if (vol->flags & VOLUME_IS_DIRTY) + if (opt.force-- <= 0) + err_exit("Volume is dirty. Run chkdsk and " + "please try again (or see -f option).\n"); + + printf("NTFS volume version: %d.%d\n", vol->major_ver, vol->minor_ver); + if (ntfs_is_version_supported(vol)) + perr_exit("Unknown NTFS version"); + + Dprintf("Cluster size : %u\n", vol->cluster_size); + print_volume_size("Current volume size", vol, vol->nr_clusters); +} + + +void prepare_volume_fixup() +{ + if (!opt.ro_flag) { + u16 flags; + + flags = vol->flags | VOLUME_IS_DIRTY; + if (vol->major_ver >= 2) + flags |= VOLUME_MOUNTED_ON_NT4; + + printf("Schedule chkdsk NTFS consistency check at Windows boot time ...\n"); + if (ntfs_set_volume_flags(vol, flags)) + perr_exit("Failed to set $Volume dirty"); + + printf("Resetting $LogFile ... " + "(this might take a while)\n"); + if (ntfs_reset_logfile(vol)) + perr_exit("Failed to reset $LogFile"); + } +} + + +int main(int argc, char **argv) +{ + struct bitmap on_disk_lcn_bitmap; + s64 new_volume_size = 0; /* in clusters */ + int i; + + parse_options(argc, argv); + + mount_volume(); + + if (opt.bytes) { + /* Take the integer part: when shrinking we don't want + to make the volume to be bigger than requested. + Later on we will also decrease this value to save + room for the backup boot sector */ + new_volume_size = opt.bytes / vol->cluster_size; + print_volume_size("New volume size ", vol, new_volume_size); + } + + setup_lcn_bitmap(); + + walk_inodes(); + + get_bitmap_data(vol, &on_disk_lcn_bitmap); + compare_bitmaps(&on_disk_lcn_bitmap, &lcn_bitmap); + free(on_disk_lcn_bitmap.bm); + + if (opt.info) + advise_on_resize(); + + /* FIXME: check new_volume_size validity */ + + /* Backup boot sector at the end of device isn't counted in NTFS + volume size thus we have to reserve space for. We don't trust + the user does this for us: better to be on the safe side ;) */ + if (new_volume_size) + --new_volume_size; + + if (new_volume_size > vol->nr_clusters) + err_exit("Volume enlargement not yet supported\n"); + else if (new_volume_size == vol->nr_clusters) { + printf("Nothing to do: NTFS volume size is already OK.\n"); + exit(0); + } + + for (i = new_volume_size; i < vol->nr_clusters; i++) + if (ntfs_get_bit(lcn_bitmap.bm, (u64)i)) { + /* FIXME: relocate cluster */ + advise_on_resize(); + } + + if (opt.force-- <= 0 && !opt.ro_flag) { + printf(resize_warning_msg); + proceed_question(); + } + + prepare_volume_fixup(); + + truncate_badclust_file(new_volume_size); + truncate_bitmap_file(new_volume_size); + update_bootsector(new_volume_size); + + /* We don't create backup boot sector because we don't know where the + partition will be split. The scheduled chkdsk will fix it anyway */ + + if (opt.ro_flag) { + printf("The read-only test run ended successfully.\n"); + exit(0); + } + + printf("Syncing device ...\n"); + if (fsync(vol->fd) == -1) + perr_exit("fsync"); + + printf(resize_important_msg, vol->dev_name); + return 0; +} + diff --git a/ntfsprogs/ntfsundelete.8.in b/ntfsprogs/ntfsundelete.8.in index e69de29b..f28f4a05 100644 --- a/ntfsprogs/ntfsundelete.8.in +++ b/ntfsprogs/ntfsundelete.8.in @@ -0,0 +1,344 @@ +.\" Copyright (c) 2002 Richard Russon. All Rights Reserved. +.\" This file may be copied under the terms of the GNU Public License. +.\" +.TH NTFSUNDELETE 8 "June 2002" "Linux\-NTFS version @VERSION@" +.SH NAME +ntfsundelete \- recover a deleted file from an NTFS volume. +.SH SYNOPSIS +.B ntfsundelete +[ +.I options +] +.B device +.SH DESCRIPTION +.B ntfsundelete +has three modes of operation: +.IR scan , +.I undelete +and +.IR copy . +.SS Scan +.PP +The default mode, +.I scan +simply reads an NTFS Volume and looks for files that have been deleted. Then it +will print a list giving the inode number, name and size. +.SS Undelete +.PP +The +.I undelete +mode takes the inode and recovers as much of the data as possible. It save the +result to another location. Partly for safety, but mostly because NTFS write +support isn't finished. +.SS Copy +.PP +This is a wizard's option. It will save a portion of the MFT to a file. This +probably only be useful when debugging +.I ntfsundelete +.SS Notes +.B ntfsundelete +only ever +.B reads +from the NTFS Volume. +.B ntfsundelete +will never change the volume. +.SH CAVEATS +.SS Miracles +.B ntfsundelete +cannot perform the impossible. +.PP +When a file is deleted the MFT Record is marked as not in use and the bitmap +representing the disk usage is updated. If the power isn't turned off +immediately, the free space, where the file used to live, may become +overwritten. Worse, the MFT Record may be reused for another file. If this +happens it is impossible to tell where the file was on disk. +.PP +Even if all the clusters of a file are not in use, there is no guarantee that +they haven't been overwritten by some short\-lived file. +.SS Locale +In NTFS all the filenames are stored as Unicode. They will be converted into +the current locale for display by +.BR ntfsundelete . +The utility has successfully displayed some Chinese pictogram filenames and then +correctly recovered them. +.SS Extended MFT Records +In rare circumstances, a single MFT Record will not be large enough to hold the +metadata describing a file (a file would have to be in hundreds of fragments +for this to happen). In these cases one MFT record may hold the filename, but +another will hold the information about the data. +.B ntfsundelete +will not try and piece together such records. It will simply show unnamed files +with data. +.SS Compressed and Encrypted Files +.B ntfsundelete +cannot recover compressed or encrypted files. When scanning for them, it will +display as being 0% recoverable. +.SH OPTIONS +Below is a summary of all the options that +.B ntfsundelete +accepts. All options have two equivalent names. The short name is preceded by +.BR \- +and the long name is preceded by +.BR \-\- . +Any single letter options, that don't take an argument, can be combined into a +single command, e.g. +.BR \-fv +is equivalent to +.BR "\-f \-v" . +Long named options can be abbreviated to any unique prefix of their name. +.TP +.BI "\-b " num +.br +.ns +.TP +.BI "\-\-byte " num +If any clusters of the file cannot be recovered, the missing parts will be +filled with this byte. The default is zeros. +.TP +.B \-C +.br +.ns +.TP +.B \-\-case +When scanning an NTFS volume, any filename matching (using the +.B \-\-match +option) is case\-insensitive. This option makes the maching case\-sensitive. +.TP +.BI "\-c " range +.br +.ns +.TP +.BI "\-\-copy " range +This wizard's option will write a block of MFT FILE records to a file. The +default file is +.I mft +which will be created in the current directory. This option can be combined +with the +.B \-\-output +and +.B \-\-destination +options. +.TP +.BI "\-d " dir +.br +.ns +.TP +.BI "\-\-destination " dir +This option controls where to put the output file of the +.B \-\-undelete +and +.B \-\-copy +options. +.TP +.B \-f +.br +.ns +.TP +.B \-\-force +This will override some sensible defaults, such as not overwriting an existing +file. Use this option with caution. +.TP +.B \-h +.br +.ns +.TP +.B \-\-help +Show a list of options with a brief description of each one. +.TP +.BI "\-m " pattern +.br +.ns +.TP +.BI "\-\-match " pattern +Filter the output of the +.B \-\-scan +option, by only looking for matching filenames. The pattern can include the +wildcards '?', match exactly one character or '*', match zero or more +characters. By default the matching is case\-insensitive. To make the search +case sensitive, use the +.B \-\-case +option. +.TP +.BI "\-o " file +.br +.ns +.TP +.BI "\-\-output " file +Use this option to set name of output file that +.B \-\-undelete +or +.B \-\-copy +will create. +.TP +.BI "\-p " num +.br +.ns +.TP +.BI "\-\-percentage " num +Filter the output of the +.B \-\-scan +option, by only matching files with a certain amount of recoverable content. +.B Please read the caveats section for more details. +.TP +.BI \-q +.br +.ns +.TP +.BI \-\-quiet +Reduce the amount of output to a minimum. Naturally, it doesn't make sense to +combine this option with +.BR \-\-scan . +.TP +.B \-s +.br +.ns +.TP +.B \-\-scan +Search through an NTFS volume and print a list of files that could be recovered. +This is the default action of +.BR ntfsundelete . +This list can be filtered by filename, size, percentage recoverable or last +modification time, using the +.BR \-\-match , +.BR \-\-size , +.B \-\-percent +and +.B \-\-time +options, respectively. +.sp +The output of scan will be: +.sp +.nf +Inode Flags %age Date Size Filename + 6038 FN.. 93% 2002-07-17 26629 thesis.doc +.fi +.TS +lB lB +l l. +Flag Description +F/D File/Directory +N/R (Non-)Resident data stream +C/E Compressed/Encrypted data stream +! Missing attributes +.TE +.RS +.sp +.br +The percentage field shows how much of the file can potentially be recovered. +.sp +.br +.RE +.BI "\-S " range +.br +.ns +.TP +.BI "\-\-size " range +Filter the output of the +.B \-\-scan +option, by looking for a particular range of file sizes. The range may be +specified as two numbers separated by a '\-'. The sizes may be abbreviated +using the suffixes k, m, g, t, for kilobytes, megabytes, gigabytes and terabytes +respectively. +.TP +.BI "\-t " since +.br +.ns +.TP +.BI "\-\-time " since +Filter the output of the +.B \-\-scan +option. Only match files that have been altered since this time. The time must +be given as number using a suffix of d, w, m, y for days, weeks, months or years +ago. +.TP +.BI "\-u " num +.br +.ns +.TP +.BI "\-\-undelete " num +Recover the file with this inode number. This option can be combined with +.BR \-\-output , +.BR \-\-destination , +and +.BR \-\-byte . +.TP +.B \-v +.br +.ns +.TP +.B \-\-verbose +Increase the amount of output that +.B ntfsundelete +prints. +.TP +.B \-V +.br +.ns +.TP +.B \-\-version +Show the version number, copyright and license +.BR ntfsundelete . +.SH EXAMPLES +Look for deleted files on /dev/hda1. +.RS +.sp +.B ntfsundelete /dev/hda1 +.sp +.RE +Look for deleted documents on /dev/hda1. +.RS +.sp +.B ntfsundelete /dev/hda1 -s \-m '*.doc' +.sp +.RE +Look for deleted files between 5000 and 6000000 bytes, with at least 90% of the +data recoverable, on /dev/hda1. +.RS +.sp +.B ntfsundelete /dev/hda1 \-S 5k\-6m \-p 90 +.sp +.RE +Look for deleted files altered in the last two days +.RS +.sp +.B ntfsundelete /dev/hda1 \-t 2d +.sp +.RE +Undelete inode number 3689, call the file 'work.doc' and put it in the user's +home directory. +.RS +.sp +.B ntfsundelete /dev/hda1 \-u 3689 \-o work.doc \-d ~ +.sp +.RE +Save MFT Records 3689 to 3690 to a file 'debug' +.RS +.sp +.B ntfsundelete /dev/hda1 \-c 3689\-3690 \-o debug +.RE +.SH BUGS +There are some small limitations to this program, but currently no known bugs. +If you find one, please send an email to +.nh + +.hy +.SH AUTHOR +.B ntfsundelete +was written by Richard Russon (FlatCap) +.br +If you find this tool useful, make FlatCap happy and send him an email. +.SH AVAILABILITY +.B ntfsundelete +is part of the linux\-ntfs package and is available from +.br +.nh +http://linux\-ntfs.sourceforge.net/downloads.html +.hy +This manual page is available online at: +.br +.nh +http://linux\-ntfs.sourceforge.net/tools/ntfsundelete.html +.hy +.SH SEE ALSO +.BR ntfsinfo(8) +.br diff --git a/ntfsprogs/ntfsundelete.c b/ntfsprogs/ntfsundelete.c index e69de29b..f738f3bf 100644 --- a/ntfsprogs/ntfsundelete.c +++ b/ntfsprogs/ntfsundelete.c @@ -0,0 +1,2018 @@ +/** + * ntfsundelete - Part of the Linux-NTFS project. + * + * Copyright (c) 2002 Richard Russon + * + * This utility will recover deleted files from an NTFS volume. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program (in the main directory of the Linux-NTFS + * distribution in the file COPYING); if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ntfsundelete.h" +#include "bootsect.h" +#include "mft.h" +#include "attrib.h" +#include "layout.h" +#include "inode.h" +#include "disk_io.h" + +static const char *AUTHOR = "Richard Russon (FlatCap)"; +static const char *EXEC_NAME = "ntfsundelete"; +static const char *MFTFILE = "mft"; +static const char *UNNAMED = ""; +static char *NONE = ""; +static char *UNKNOWN = "unknown"; +static struct options opts; + +#define _(S) gettext(S) + +/** + * Eprintf - Print error messages + */ +void Eprintf (const char *format, ...) +{ + va_list va; + va_start (va, format); + vfprintf (stderr, format, va); + va_end (va); +} + +/** + * Iprintf - Print informative messages + */ +void Iprintf (const char *format, ...) +{ + va_list va; +#ifndef DEBUG + if (opts.quiet) + return; +#endif + va_start (va, format); + vfprintf (stdout, format, va); + va_end (va); +} + +/** + * Vprintf - Print verbose messages + */ +void Vprintf (const char *format, ...) +{ + va_list va; +#ifndef DEBUG + if (!opts.verbose) + return; +#endif + va_start (va, format); + vfprintf (stdout, format, va); + va_end (va); +} + +/** + * Dprintf - Print debug messages + */ +void Dprintf (const char *format, ...) +{ +#ifdef DEBUG + va_list va; + va_start (va, format); + vfprintf (stdout, format, va); + va_end (va); +#endif +} + + +/** + * version - Print version information about the program + * + * Print a copyright statement and a brief description of the program. + * + * Return: none + */ +void version (void) +{ + Iprintf ("%s v%s Copyright (C) 2002 %s\nRecover deleted files from an " + "NTFS Volume\n\n%s is free software, released under the GNU " + "General Public License\nand you are welcome to redistribute " + "it under certain conditions.\n%s comes with ABSOLUTELY NO " + "WARRANTY; for details read the GNU\nGeneral Public License " + "to be found in the file COPYING in the main\nLinux-NTFS " + "distribution directory.\n\n", + EXEC_NAME, VERSION, AUTHOR, EXEC_NAME, EXEC_NAME); +} + +/** + * usage - Print a list of the parameters to the program + * + * Print a list of the parameters and options for the program. + * + * Return: none + */ +void usage (void) +{ + Iprintf ("Usage: %s [options] device\n" + " -s --scan Scan for files (default)\n" + " -p num --percentage num Minimum percentage recoverable\n" + " -m pattern --match pattern Only work on files with matching names\n" + " -C --case Case sensitive matching\n" + " -S range --size range Match files of this size\n" + " -t since --time since Last referenced since this time\n" + "\n" + " -u num --undelete num Undelete inode\n" + " -o file --output file Save with this filename\n" + " -d dir --destination dir Destination directory\n" + " -b num --byte num Fill missing parts with this byte\n" + "\n" + " -c range --copy range Write a range of MFT records to a file\n" + "\n" + " -f --force Use less caution\n" + " -q --quiet Less output\n" + " -v --verbose More output\n" + " -V --version Version information\n" + " -h --help Print this help\n\n", + EXEC_NAME); + Iprintf ("Please report bugs to: linux-ntfs-dev@lists.sf.net\n\n"); +} + +/** + * transform - Convert a shell style pattern to a regex + * @pattern: String to be converted + * @regex: Resulting regular expression is put here + * + * This will transform patterns, such as "*.doc" to true regular expressions. + * The function will also place '^' and '$' around the expression to make it + * behave as the user would expect + * + * Before After + * . \. + * * .* + * ? . + * + * Notes: + * The returned string must be freed by the caller. + * If transform fails, @regex will not be changed. + * + * Return: 1, Success, the string was transformed + * 0, An error occurred + */ +int transform (const char *pattern, char **regex) +{ + char *result; + int length, i, j; + + if (!pattern || !regex) + return 0; + + length = strlen (pattern); + if (length < 1) { + Eprintf ("Pattern to transform is empty\n"); + return 0; + } + + for (i = 0; pattern[i]; i++) { + if ((pattern[i] == '*') || (pattern[i] == '.')) + length++; + } + + result = malloc (length + 3); + if (!result) { + Eprintf ("Couldn't allocate memory in transform()\n"); + return 0; + } + + result[0] = '^'; + + for (i = 0, j = 1; pattern[i]; i++, j++) { + if (pattern[i] == '*') { + result[j] = '.'; + j++; + result[j] = '*'; + } else if (pattern[i] == '.') { + result[j] = '\\'; + j++; + result[j] = '.'; + } else if (pattern[i] == '?') { + result[j] = '.'; + } else { + result[j] = pattern[i]; + } + } + + result[j] = '$'; + result[j+1] = 0; + Dprintf ("Pattern '%s' replaced with regex '%s'\n", pattern, result); + + *regex = result; + return 1; +} + +/** + * parse_time - Convert a time abbreviation to seconds + * @string: The string to be converted + * @since: The absolute time referred to + * + * Strings representing times will be converted into a time_t. The numbers will + * be regarded as seconds unless suffixed. + * + * Suffix Description + * [yY] Year + * [mM] Month + * [wW] Week + * [dD] Day + * [sS] Second + * + * Therefore, passing "1W" will return the time_t representing 1 week ago. + * + * Notes: + * Only the first character of the suffix is read. + * If parse_time fails, @since will not be changed + * + * Return: 1 Success + * 0 Error, the string was malformed + */ +int parse_time (const char *value, time_t *since) +{ + time_t result, now; + char *suffix = NULL; + + if (!value || !since) + return -1; + + Dprintf ("parsing time '%s' ago\n", value); + + result = strtoll (value, &suffix, 10); + if (result < 0 || errno == ERANGE) { + Eprintf ("Invalid time '%s'.\n", value); + return 0; + } + + if (!suffix) { + Eprintf ("Internal error, strtoll didn't return a suffix.\n"); + return 0; + } + + if (strlen (suffix) > 1) { + Eprintf ("Invalid time suffix '%s'. Use Y, M, W, D or H.\n", suffix); + return 0; + } + + switch (suffix[0]) { + case 'y': case 'Y': result *= 12; + case 'm': case 'M': result *= 4; + case 'w': case 'W': result *= 7; + case 'd': case 'D': result *= 24; + case 'h': case 'H': result *= 3600; + case 0: + break; + + default: + Eprintf ("Invalid time suffix '%s'. Use Y, M, W, D or H.\n", suffix); + return 0; + } + + now = time (NULL); + + Dprintf ("Time now = %lld, Time then = %lld.\n", (long long) now, (long long) result); + *since = now - result; + return 1; +} + +/** + * parse_size - Convert a string representing a size + * @value: String to be parsed + * @size: Parsed size + * + * Read a string and convert it to a number. Strings may be suffixed to scale + * them. Any number without a suffix is assumed to be in bytes. + * + * Suffix Description Multiple + * [tT] Terabytes 10^12 + * [gG] Gigabytes 10^9 + * [mM] Megabytes 10^6 + * [kK] Kilobytes 10^3 + * + * Notes: + * Only the first character of the suffix is read. + * The multipliers are decimal thousands, not binary: 1000, not 1024. + * If parse_size fails, @size will not be changed + * + * Return: 1 Success + * 0 Error, the string was malformed + */ +int parse_size (const char *value, long long *size) +{ + long long result; + char *suffix = NULL; + + if (!value || !size) + return 0; + + Dprintf ("Parsing size '%s'.\n", value); + + result = strtoll (value, &suffix, 10); + if (result < 0 || errno == ERANGE) { + Eprintf ("Invalid size '%s'.\n", value); + return 0; + } + + if (!suffix) { + Eprintf ("Internal error, strtoll didn't return a suffix.\n"); + return 0; + } + + + /*if (strlen (suffix) > 1) { + Eprintf ("Invalid size suffix '%s'. Use T, G, M, or K.\n", suffix); + return 0; + } Can't do this because of ranges*/ + + switch (suffix[0]) { + case 't': case 'T': result *= 1000; + case 'g': case 'G': result *= 1000; + case 'm': case 'M': result *= 1000; + case 'k': case 'K': result *= 1000; + case '-': case 0: + break; + default: + Eprintf ("Invalid size suffix '%s'. Use T, G, M, or K.\n", suffix); + return 0; + } + + Dprintf ("Parsed size = %lld.\n", result); + *size = result; + return 1; +} + +/** + * parse_range - Convert a string representing a range of numbers + * @string: The string to be parsed + * @start: The beginning of the range will be stored here + * @finish: The end of the range will be stored here + * + * Read a string of the form n-m. If the lower end is missing, zero will be + * substituted. If the upper end is missing LONG_MAX will be used. If the + * string cannot be parsed correctly, @start and @finish will not be changed. + * + * Return: 1 Success, a valid string was found + * 0 Error, the string was not a valid range + */ +int parse_range (const char *string, long long *start, long long *finish) +{ + long long a, b; + char *middle; + + if (!string || !start || !finish) + return 0; + + middle = strchr (string, '-'); + if (string == middle) { + Dprintf ("Range has no beginning, defaulting to 0.\n"); + a = 0; + } else { + if (!parse_size (string, &a)) + return 0; + } + + if (middle) { + if (middle[1] == 0) { + b = LONG_MAX; + Dprintf ("Range has no end, defaulting to %lld.\n", b); + } else { + if (!parse_size (middle+1, &b)) + return 0; + } + } else { + b = a; + } + + Dprintf ("Range '%s' = %lld - %lld\n", string, a, b); + + *start = a; + *finish = b; + return 1; +} + +/** + * parse_options - Read and validate the programs command line + * + * Read the command line, verify the syntax and parse the options. + * This function is very long, but quite simple. + * + * Return: 1 Success + * 0 Error, one or more problems + */ +int parse_options (int argc, char *argv[]) +{ + static const char *sopt = "-b:Cc:d:fhm:o:p:sS:t:u:qvV"; + static const struct option lopt[] = { + { "byte", required_argument, NULL, 'b' }, + { "case", no_argument, NULL, 'C' }, + { "copy", required_argument, NULL, 'c' }, + { "destination", required_argument, NULL, 'd' }, + { "force", no_argument, NULL, 'f' }, + { "help", no_argument, NULL, 'h' }, + { "match", required_argument, NULL, 'm' }, + { "output", required_argument, NULL, 'o' }, + { "percentage", required_argument, NULL, 'p' }, + { "scan", no_argument, NULL, 's' }, + { "size", required_argument, NULL, 'S' }, + { "time", required_argument, NULL, 't' }, + { "undelete", required_argument, NULL, 'u' }, + { "quiet", no_argument, NULL, 'q' }, + { "verbose", no_argument, NULL, 'v' }, + { "version", no_argument, NULL, 'V' }, + { NULL, 0, NULL, 0 } + }; + + char c = -1; + char *end = NULL; + int err = 0; + int ver = 0; + int help = 0; + + opterr = 0; /* We'll handle the errors, thank you. */ + + opts.mode = MODE_NONE; + opts.uinode = -1; + opts.percent = -1; + opts.fillbyte = -1; + + while ((c = getopt_long (argc, argv, sopt, lopt, NULL)) != -1) { + switch (c) { + case 1: /* A non-option argument */ + if (!opts.device) { + opts.device = argv[optind-1]; + } else { + opts.device = NULL; + err++; + } + break; + case 'b': + if (opts.fillbyte == -1) { + end = NULL; + opts.fillbyte = strtol (optarg, &end, 0); + if (end && *end) + err++; + } else { + err++; + } + break; + case 'C': + opts.match_case++; + break; + case 'c': + if (opts.mode == MODE_NONE) { + if (!parse_range (argv[optind-1], &opts.mft_begin, &opts.mft_end)) + err++; + opts.mode = MODE_COPY; + } else { + opts.mode = MODE_ERROR; + } + break; + case 'd': + if (!opts.dest) + opts.dest = argv[optind-1]; + else + err++; + break; + case 'f': + opts.force++; + break; + case 'h': + help++; + break; + case 'm': + if (!opts.match) { + if (!transform (argv[optind-1], &opts.match)) + err++; + } else { + err++; + } + break; + case 'o': + if (!opts.output) { + opts.output = argv[optind-1]; + } else { + err++; + } + break; + case 'p': + if (opts.percent == -1) { + end = NULL; + opts.percent = strtol (optarg, &end, 0); + if (end && ((*end != '%') && (*end != 0))) + err++; + } else { + err++; + } + break; + case 'q': + opts.quiet++; + break; + case 's': + if (opts.mode == MODE_NONE) + opts.mode = MODE_SCAN; + else + opts.mode = MODE_ERROR; + break; + case 'S': + if ((opts.size_begin > 0) || (opts.size_end > 0) || + !parse_range (argv[optind-1], &opts.size_begin, + &opts.size_end)) { + err++; + } + break; + case 't': + if (opts.since == 0) { + if (!parse_time (argv[optind-1], &opts.since)) + err++; + } else { + err++; + } + break; + case 'u': + if (opts.mode == MODE_NONE) { + end = NULL; + opts.mode = MODE_UNDELETE; + opts.uinode = strtol (optarg, &end, 0); + if (end && *end) + err++; + } else { + opts.mode = MODE_ERROR; + } + break; + case 'v': + opts.verbose++; + break; + case 'V': + ver++; + break; + default: + if (((optopt == 'b') || (optopt == 'c') || + (optopt == 'd') || (optopt == 'm') || + (optopt == 'o') || (optopt == 'p') || + (optopt == 'S') || (optopt == 't') || + (optopt == 'u')) && (!optarg)) { + Eprintf ("Option '%s' requires an argument.\n", argv[optind-1]); + } else { + Eprintf ("Unknown option '%s'.\n", argv[optind-1]); + } + err++; + break; + } + } + + if (help || ver) { + opts.quiet = 0; + } else { + if (opts.device == NULL) { + Eprintf ("You must specify exactly one device.\n"); + err++; + } + + if (opts.mode == MODE_NONE) { + opts.mode = MODE_SCAN; + } + + switch (opts.mode) { + case MODE_SCAN: + if (opts.output || opts.dest || (opts.fillbyte != -1)) { + Eprintf ("Scan can only be used with --percent, " + "--match, --ignore-case, --size and --time.\n"); + err++; + } + if (opts.match_case && !opts.match) { + Eprintf ("The --case option doesn't make sense without the --match option\n"); + err++; + } + break; + case MODE_UNDELETE: + if ((opts.percent != -1) || opts.match || opts.match_case || + (opts.size_begin > 0) || (opts.size_end > 0)) { + Eprintf ("Undelete can only be used with " + "--output, --destination and --byte.\n"); + err++; + } + break; + case MODE_COPY: + if ((opts.fillbyte != -1) || (opts.percent != -1) || + opts.match || opts.match_case || + (opts.size_begin > 0) || (opts.size_end > 0)) { + Eprintf ("Copy can only be used with --output and --destination.\n"); + err++; + } + break; + default: + Eprintf ("You can only select one of Scan, Undelete or Copy.\n"); + err++; + } + + if ((opts.percent < -1) || (opts.percent > 100)) { + Eprintf ("Percentage value must be in the range 0 - 100.\n"); + err++; + } + + if (opts.quiet) { + if (opts.verbose) { + Eprintf ("You may not use --quiet and --verbose at the same time.\n"); + err++; + } else if (opts.mode == MODE_SCAN) { + Eprintf ("You may not use --quiet when scanning a volume.\n"); + err++; + } + } + } + + if (ver) + version(); + if (help || err) + usage(); + + return (!err && !help && !ver); +} + + +/** + * free_file - Release the resources used by a file object + * @file: The unwanted file object + * + * This will free up the memory used by a file object and iterate through the + * object's children, freeing their resources too. + * + * Return: none + */ +void free_file (struct ufile *file) +{ + struct list_head *item, *tmp; + + if (!file) + return; + + list_for_each_safe (item, tmp, &file->name) { /* List of filenames */ + struct filename *f = list_entry (item, struct filename, list); + Dprintf ("freeing filename '%s'\n", f->name ? f->name : NONE); + if (f->name) + free (f->name); + free (f); + } + + list_for_each_safe (item, tmp, &file->data) { /* List of data streams */ + struct data *d = list_entry (item, struct data, list); + Dprintf ("freeing data stream '%s'\n", d->name ? d->name : UNNAMED); + if (d->name) + free (d->name); + if (d->run_list) + free (d->run_list); + free (d); + } + + free (file->mft); + free (file); +} + +/** + * ntfs2utc - Convert an NTFS time to Unix time + * @time: An NTFS time in 100ns units since 1601 + * + * NTFS stores times as the number of 100ns intervals since January 1st 1601 at + * 00:00 UTC. This system will not suffer from Y2K problems until ~57000AD. + * + * Return: n A Unix time (number of seconds since 1970) + */ +time_t ntfs2utc (long long time) +{ + return (time - ((long long) (369 * 365 + 89) * 24 * 3600 * 10000000)) / 10000000; +} + +/** + * find_attribute - Find an attribute of the given type + * @type: An attribute type, e.g. AT_FILE_NAME + * @ctx: A search context, created using ntfs_get_attr_search_ctx + * + * Using the search context to keep track, find the first/next occurrence of a + * given attribute type. + * + * N.B. This will return a pointer into @mft. As long as the search context + * has been created without an inode, it won't overflow the buffer. + * + * Return: Pointer Success, an attribute was found + * NULL Error, no matching attributes were found + */ +ATTR_RECORD * find_attribute (const ATTR_TYPES type, ntfs_attr_search_ctx *ctx) +{ + if (!ctx) + return NULL; + + if (ntfs_lookup_attr (type, NULL, 0, 0, 0, NULL, 0, ctx) != 0) { + Dprintf ("find_attribute didn't find an attribute of type: 0x%02x.\n", type); + return NULL; /* None / no more of that type */ + } + + Dprintf ("find_attribute found an attribute of type: 0x%02x.\n", type); + return ctx->attr; +} + +/** + * find_first_attribute - Find the first attribute of a given type + * @type: An attribute type, e.g. AT_FILE_NAME + * @mft: A buffer containing a raw MFT record + * + * Search through a raw MFT record for an attribute of a given type. + * The return value is a pointer into the MFT record that was supplied. + * + * N.B. This will return a pointer into @mft. The pointer won't stray outside + * the buffer, since we created the search context without an inode. + * + * Return: Pointer Success, an attribute was found + * NULL Error, no matching attributes were found + */ +ATTR_RECORD * find_first_attribute (const ATTR_TYPES type, MFT_RECORD *mft) +{ + ntfs_attr_search_ctx *ctx; + ATTR_RECORD *rec; + + if (!mft) + return NULL; + + ctx = ntfs_get_attr_search_ctx (NULL, mft); + if (!ctx) { + Eprintf ("Couldn't create a search context.\n"); + return NULL; + } + + rec = find_attribute (type, ctx); + ntfs_put_attr_search_ctx (ctx); + if (rec) + Dprintf ("find_first_attribute: found attr of type 0x%02x.\n", type); + else + Dprintf ("find_first_attribute: didn't find attr of type 0x%02x.\n", type); + return rec; +} + +/** + * get_filenames - Read an MFT Record's $FILENAME attributes + * @file: The file object to work with + * + * A single file may have more than one filename. This is quite common. + * Windows creates a short DOS name for each long name, e.g. LONGFI~1.XYZ, + * LongFiLeName.xyZ. + * + * The filenames that are found are put in filename objects and added to a + * linked list of filenames in the file object. For convenience, the unicode + * filename is converted into the current locale and stored in the filename + * object. + * + * One of the filenames is picked (the one with the lowest numbered namespace) + * and its locale friendly name is put in pref_name. + * + * Return: n The number of $FILENAME attributes found + * -1 Error + */ +int get_filenames (struct ufile *file) +{ + ATTR_RECORD *rec; + FILE_NAME_ATTR *attr; + ntfs_attr_search_ctx *ctx; + struct filename *name; + int count = 0; + int space = 4; + + if (!file) + return -1; + + ctx = ntfs_get_attr_search_ctx (NULL, file->mft); + if (!ctx) + return -1; + + while ((rec = find_attribute (AT_FILE_NAME, ctx))) { + /* We know this will always be resident. */ + attr = (FILE_NAME_ATTR *) ((char *) rec + le16_to_cpu (rec->value_offset)); + + name = calloc (1, sizeof (*name)); + if (!name) { + Eprintf ("Couldn't allocate memory in get_filenames().\n"); + count = -1; + break; + } + + name->uname = attr->file_name; + name->uname_len = attr->file_name_length; + name->name_space = attr->file_name_type; + name->size_alloc = sle64_to_cpu (attr->allocated_size); + name->size_data = sle64_to_cpu (attr->data_size); + name->flags = attr->file_attributes; + + name->date_c = ntfs2utc (sle64_to_cpu (attr->creation_time)); + name->date_a = ntfs2utc (sle64_to_cpu (attr->last_data_change_time)); + name->date_m = ntfs2utc (sle64_to_cpu (attr->last_mft_change_time)); + name->date_r = ntfs2utc (sle64_to_cpu (attr->last_access_time)); + + file->date = max (file->date, name->date_c); + file->date = max (file->date, name->date_a); + file->date = max (file->date, name->date_m); + file->date = max (file->date, name->date_r); + + if (ntfs_ucstombs (name->uname, name->uname_len, &name->name, + name->uname_len) < 0) { + Dprintf ("Couldn't translate filename to current locale.\n"); + } + + if (name->name_space < space) { + file->pref_name = name->name; + space = name->name_space; + } + + file->max_size = max (file->max_size, name->size_alloc); + file->max_size = max (file->max_size, name->size_data); + + list_add_tail (&name->list, &file->name); + count++; + } + + ntfs_put_attr_search_ctx (ctx); + Dprintf ("File has %d names.\n", count); + return count; +} + +/** + * get_data - Read an MFT Record's $DATA attributes + * @file: The file object to work with + * @vol: An ntfs volume obtained from ntfs_mount + * + * A file may have more than one data stream. All files will have an unnamed + * data stream which contains the file's data. Some Windows applications store + * extra information in a separate stream. + * + * The streams that are found are put in data objects and added to a linked + * list of data streams in the file object. + * + * Return: n The number of $FILENAME attributes found + * -1 Error + */ +int get_data (struct ufile *file, ntfs_volume *vol) +{ + ATTR_RECORD *rec; + ntfs_attr_search_ctx *ctx; + int count = 0; + struct data *data; + + if (!file) + return -1; + + ctx = ntfs_get_attr_search_ctx (NULL, file->mft); + if (!ctx) + return -1; + + while ((rec = find_attribute (AT_DATA, ctx))) { + data = calloc (1, sizeof (*data)); + if (!data) { + Eprintf ("Couldn't allocate memory in get_data().\n"); + count = -1; + break; + } + + data->resident = !rec->non_resident; + data->compressed = rec->flags & ATTR_IS_COMPRESSED; + data->encrypted = rec->flags & ATTR_IS_ENCRYPTED; + + if (rec->name_length) { + data->uname = (uchar_t *) ((char *) rec + le16_to_cpu (rec->name_offset)); + data->uname_len = rec->name_length; + + if (ntfs_ucstombs (data->uname, data->uname_len, &data->name, + data->uname_len) < 0) { + Eprintf ("Cannot translate name into current locale.\n"); + } + } + + if (data->resident) { + data->size_data = le32_to_cpu (rec->value_length); + data->data = ((char*) (rec)) + le16_to_cpu (rec->value_offset); + } else { + data->size_alloc = sle64_to_cpu (rec->allocated_size); + data->size_data = sle64_to_cpu (rec->data_size); + data->size_init = sle64_to_cpu (rec->initialized_size); + data->size_vcn = sle64_to_cpu (rec->highest_vcn) + 1; + } + + data->run_list = ntfs_decompress_mapping_pairs (vol, rec, NULL); + if (!data->run_list) { + Dprintf ("Couldn't decompress the data runs\n"); + } + + file->max_size = max (file->max_size, data->size_data); + file->max_size = max (file->max_size, data->size_init); + + list_add_tail (&data->list, &file->data); + count++; + } + + ntfs_put_attr_search_ctx (ctx); + Dprintf ("File has %d data streams.\n", count); + return count; +} + +/** + * read_record - Read an MFT record into memory + * @vol: An ntfs volume obtained from ntfs_mount + * @record: The record number to read + * + * Read the specified MFT record and gather as much information about it as + * possible. + * + * Return: Pointer A ufile object containing the results + * NULL Error + */ +struct ufile * read_record (ntfs_volume *vol, long long record) +{ + ATTR_RECORD *attr10, *attr20, *attr90; + struct ufile *file; + ntfs_attr *mft; + + if (!vol) + return NULL; + + file = calloc (1, sizeof (*file)); + if (!file) { + Eprintf ("Couldn't allocate memory in read_record()\n"); + return NULL; + } + + INIT_LIST_HEAD (&file->name); + INIT_LIST_HEAD (&file->data); + file->inode = record; + + file->mft = malloc (vol->mft_record_size); + if (!file->mft) { + Eprintf ("Couldn't allocate memory in read_record()\n"); + free_file (file); + return NULL; + } + + mft = ntfs_attr_open (vol->mft_ni, AT_DATA, NULL, 0); + if (!mft) { + Eprintf ("Couldn't open $MFT/$DATA: %s\n", strerror (errno)); + free_file (file); + return NULL; + } + + if (ntfs_attr_mst_pread (mft, vol->mft_record_size * record, 1, vol->mft_record_size, file->mft) < 1) { + Eprintf ("Couldn't read MFT Record %lld.\n", record); + ntfs_attr_close (mft); + free_file (file); + return NULL; + } + + ntfs_attr_close (mft); + mft = NULL; + + attr10 = find_first_attribute (AT_STANDARD_INFORMATION, file->mft); + attr20 = find_first_attribute (AT_ATTRIBUTE_LIST, file->mft); + attr90 = find_first_attribute (AT_INDEX_ROOT, file->mft); + + Dprintf ("Attributes present: %s %s %s\n", attr10?"0x10":"", attr20?"0x20":"", attr90?"0x90":""); + + if (attr10) + { + STANDARD_INFORMATION *si; + si = (STANDARD_INFORMATION *) ((char *) attr10 + le16_to_cpu (attr10->value_offset)); + file->date = max (file->date, ntfs2utc (sle64_to_cpu (si->last_data_change_time))); + } + + if (attr20 || !attr10) + file->attr_list = 1; + if (attr90) + file->directory = 1; + + if (get_filenames (file) < 0) { + Eprintf ("Couldn't get filenames.\n"); + } + if (get_data (file, vol) < 0) { + Eprintf ("Couldn't get data streams.\n"); + } + + return file; +} + + +/** + * cluster_in_use - Determine if a cluster is in use + * @vol: An ntfs volume obtained from ntfs_mount + * @lcn: The Logical Cluster Number to test + * + * The metadata file $Bitmap has one binary bit representing each cluster on + * disk. The bit will be set of each cluster that is in use. The function + * reads the relevant part of $Bitmap into a buffer and tests the bit. + * + * This function has a static buffer in which it caches a section of $Bitmap. + * If the lcn, being tested, lies outside the range, the buffer will be + * refreshed. + * + * Return: 1 Cluster is in use + * 0 Cluster is free space + * -1 Error occurred + */ +int cluster_in_use (ntfs_volume *vol, long long lcn) +{ + static unsigned char buffer[512]; + static long long bmplcn = -sizeof (buffer) - 1; /* Which bit of $Bitmap is in the buffer */ + + int byte, bit; + ntfs_attr *attr; + + if (!vol) + return -1; + + /* Does lcn lie in the section of $Bitmap we already have cached? */ + if ((lcn < bmplcn) || (lcn >= (bmplcn + (sizeof (buffer) << 3)))) { + Dprintf ("Bit lies outside cache.\n"); + attr = ntfs_attr_open (vol->lcnbmp_ni, AT_DATA, NULL, 0); + if (!attr) { + Eprintf ("Couldn't open $MFT/$BITMAP: %s\n", strerror (errno)); + return -1; + } + + /* Mark the buffer as in use, in case the read is shorter. */ + memset (buffer, 0xFF, sizeof (buffer)); + bmplcn = lcn & (~((sizeof (buffer) << 3) - 1)); + + if (ntfs_attr_pread (attr, (bmplcn>>3), sizeof (buffer), buffer) < 0) { + Eprintf ("Couldn't read $MFT/$BITMAP: %s\n", strerror (errno)); + ntfs_attr_close (attr); + return -1; + } + + Dprintf ("Reloaded bitmap buffer.\n"); + ntfs_attr_close (attr); + } + + bit = 1 << (lcn & 7); + byte = (lcn >> 3) & (sizeof (buffer) - 1); + Dprintf ("cluster = %lld, bmplcn = %lld, byte = %d, bit = %d, in use %d\n", + lcn, bmplcn, byte, bit, buffer[byte] & bit); + + return (buffer[byte] & bit); +} + +/** + * calc_percentage - Calculate how much of the file is recoverable + * @file: The file object to work with + * @vol: An ntfs volume obtained from ntfs_mount + * + * Read through all the $DATA streams and determine if each cluster in each + * stream is still free disk space. This is just measuring the potential for + * recovery. The data may have still been overwritten by a another file which + * was then deleted. + * + * Files with a resident $DATA stream will have a 100% potential. + * + * N.B. If $DATA attribute spans more than one MFT record (i.e. badly + * fragmented) then only the data in this segment will be used for the + * calculation. + * + * N.B. Currently, compressed and encrypted files cannot be recovered, so they + * will return 0%. + * + * Return: n The percentage of the file that _could_ be recovered + * -1 Error + */ +int calc_percentage (struct ufile *file, ntfs_volume *vol) +{ + run_list_element *rl = NULL; + struct list_head *pos; + struct data *data; + long long i, j; + long long start, end; + int inuse, free; + int percent = 0; + + if (!file || !vol) + return -1; + + if (file->directory) { + Dprintf ("Found a directory: not recoverable.\n"); + return 0; + } + + if (list_empty (&file->data)) { + Vprintf ("File has no data streams.\n"); + return 0; + } + + list_for_each (pos, &file->data) { + data = list_entry (pos, struct data, list); + inuse = 0; + free = 0; + + if (data->encrypted) { + Vprintf ("File is encrypted, recovery is impossible.\n"); + continue; + } + + if (data->compressed) { + Vprintf ("File is compressed, recovery not yet implemented.\n"); + continue; + } + + if (data->resident) { + Vprintf ("File is resident, therefore recoverable.\n"); + percent = 100; + data->percent = 100; + continue; + } + + rl = data->run_list; + if (!rl) { + Vprintf ("File has no run list, hence no data.\n"); + continue; + } + + if (rl[0].length <= 0) { + Vprintf ("File has an empty run list, hence no data.\n"); + continue; + } + + if (rl[0].lcn == LCN_RL_NOT_MAPPED) { /* extended mft record */ + Vprintf ("Missing segment at beginning, %lld clusters\n", rl[0].length); + inuse += rl[0].length; + rl++; + } + + for (i = 0; rl[i].length > 0; i++) { + if (rl[i].lcn == LCN_RL_NOT_MAPPED) { + Vprintf ("Missing segment at end, %lld clusters\n", rl[i].length); + inuse += rl[i].length; + continue; + } + + if (rl[i].lcn == LCN_HOLE) { + free += rl[i].length; + continue; + } + + start = rl[i].lcn; + end = rl[i].lcn + rl[i].length; + + for (j = start; j < end; j++) { + if (cluster_in_use (vol, j)) + inuse++; + else + free++; + } + } + + if ((inuse + free) == 0) { + Eprintf ("Unexpected error whilst calculating percentage for inode %lld\n", file->inode); + continue; + } + + data->percent = (free * 100) / (inuse + free); + + percent = max (percent, data->percent); + } + + Vprintf ("File is %d%% recoverable\n", percent); + return percent; +} + +/** + * dump_record - Print everything we know about an MFT record + * @file: The file to work with + * + * Output the contents of the file object. This will print everything that has + * been read from the MFT record, or implied by various means. + * + * Because of the redundant nature of NTFS, there will be some duplication of + * information, though it will have been read from different sources. + * + * N.B. If the filename is missing, or couldn't be converted to the current + * locale, "" will be displayed. + * + * Return: none + */ +void dump_record (struct ufile *file) +{ + char buffer[20]; + char *name; + struct list_head *item; + int i; + + if (!file) + return; + + Iprintf ("MFT Record %lld\n", file->inode); + Iprintf ("Type: %s\n", (file->directory) ? "Directory" : "File"); + strftime (buffer, sizeof (buffer), "%F %R", localtime (&file->date)); + Iprintf ("Date: %s\n", buffer); + + if (file->attr_list) + Iprintf ("Metadata may span more than one MFT record\n"); + + list_for_each (item, &file->name) { + struct filename *f = list_entry (item, struct filename, list); + + if (f->name) + name = f->name; + else + name = NONE; + + Iprintf ("Filename: (%d) %s\n", f->name_space, f->name); + Iprintf ("File Flags: "); + if (f->flags & FILE_ATTR_SYSTEM) Iprintf ("System "); + if (f->flags & FILE_ATTR_DIRECTORY) Iprintf ("Directory "); + if (f->flags & FILE_ATTR_SPARSE_FILE) Iprintf ("Sparse "); + if (f->flags & FILE_ATTR_REPARSE_POINT) Iprintf ("Reparse "); + if (f->flags & FILE_ATTR_COMPRESSED) Iprintf ("Compressed "); + if (f->flags & FILE_ATTR_ENCRYPTED) Iprintf ("Encrypted "); + if (!(f->flags & (FILE_ATTR_SYSTEM || FILE_ATTR_DIRECTORY || + FILE_ATTR_SPARSE_FILE || FILE_ATTR_REPARSE_POINT || + FILE_ATTR_COMPRESSED || FILE_ATTR_ENCRYPTED))) { + Iprintf (NONE); + } + Iprintf ("\n"); + Iprintf ("Size alloc: %lld\n", f->size_alloc); + Iprintf ("Size data: %lld\n", f->size_data); + + strftime (buffer, sizeof (buffer), "%F %R", localtime (&f->date_c)); + Iprintf ("Date C: %s\n", buffer); + strftime (buffer, sizeof (buffer), "%F %R", localtime (&f->date_a)); + Iprintf ("Date A: %s\n", buffer); + strftime (buffer, sizeof (buffer), "%F %R", localtime (&f->date_m)); + Iprintf ("Date M: %s\n", buffer); + strftime (buffer, sizeof (buffer), "%F %R", localtime (&f->date_r)); + Iprintf ("Date R: %s\n", buffer); + } + + Iprintf ("Data Streams:\n"); + list_for_each (item, &file->data) { + struct data *d = list_entry (item, struct data, list); + Iprintf ("Name: %s\n", (d->name) ? d->name : ""); + Iprintf ("Flags: "); + if (d->resident) Iprintf ("Resident\n"); + if (d->compressed) Iprintf ("Compressed\n"); + if (d->encrypted) Iprintf ("Encrypted\n"); + if (!d->resident && !d->compressed && !d->encrypted) + Iprintf ("None\n"); + else + Iprintf ("\n"); + + Iprintf ("Size alloc: %lld\n", d->size_alloc); + Iprintf ("Size data: %lld\n", d->size_data); + Iprintf ("Size init: %lld\n", d->size_init); + Iprintf ("Size vcn: %lld\n", d->size_vcn); + + Iprintf ("Data runs:\n"); + if ((!d->run_list) || (d->run_list[0].length <= 0)) { + Iprintf (" None\n"); + } else { + for (i = 0; d->run_list[i].length > 0; i++) { + Iprintf (" %lld @ %lld\n", d->run_list[i].length, d->run_list[i].lcn); + } + } + + Iprintf ("Amount potentially recoverable %d%%\n", d->percent); + } + + Iprintf ("________________________________________\n\n"); +} + +/** + * list_record - Print a one line summary of the file + * @file: The file to work with + * + * Print a one line description of a file. + * + * Inode Flags %age Date Size Filename + * + * The output will contain the file's inode number (MFT Record), some flags, + * the percentage of the file that is recoverable, the last modification date, + * the size and the filename. + * + * The flags are F/D = File/Directory, N/R = Data is (Non-)Resident, + * C = Compressed, E = Encrypted, ! = Metadata may span multiple records. + * + * N.B. The file size is stored in many forms in several attributes. This + * display the largest it finds. + * + * N.B. If the filename is missing, or couldn't be converted to the current + * locale, "" will be displayed. + * + * Return: none + */ +void list_record (struct ufile *file) +{ + char buffer[20]; + struct list_head *item; + char *name = NULL; + long long size = 0; + int percent = 0; + + char flagd = '.', flagr = '.', flagc = '.', flagx = '.'; + + strftime (buffer, sizeof (buffer), "%F", localtime (&file->date)); + + if (file->attr_list) + flagx = '!'; + + if (file->directory) + flagd = 'D'; + else + flagd = 'F'; + + list_for_each (item, &file->data) { + struct data *d = list_entry (item, struct data, list); + + if (!d->name) { + if (d->resident) flagr = 'R'; + else flagr = 'N'; + if (d->compressed) flagc = 'C'; /* These two are mutually exclusive */ + if (d->encrypted) flagc = 'E'; + + percent = max (percent, d->percent); + } + + size = max (size, d->size_data); + size = max (size, d->size_init); + } + + if (file->pref_name) + name = file->pref_name; + else + name = NONE; + + Iprintf ("%-8lld %c%c%c%c %3d%% %s %9lld %s\n", + file->inode, flagd, flagr, flagc, flagx, + percent, buffer, size, name); +} + +/** + * name_match - Does a file have a name matching a regex + * @re: The regular expression object + * @file: The file to be tested + * + * Iterate through the file's $FILENAME attributes and compare them against the + * regular expression, created with regcomp. + * + * Return: 1 There is a matching filename. + * 0 There is no match. + */ +int name_match (regex_t *re, struct ufile *file) +{ + struct list_head *item; + int result; + + if (!re || !file) + return 0; + + list_for_each (item, &file->name) { + struct filename *f = list_entry (item, struct filename, list); + + if (!f->name) + continue; + result = regexec (re, f->name, 0, NULL, 0); + if (result < 0) { + Eprintf ("Couldn't compare filename with regex: %s\n", strerror (errno)); + return 0; + } else if (result == REG_NOERROR) { + Dprintf ("Found a matching filename.\n"); + return 1; + } + } + + Dprintf ("Filename '%s' doesn't match regex.\n", file->pref_name); + return 0; +} + +/** + * write_data - Write out a block of data + * @fd: File descriptor to write to + * @buffer: Data to write + * @bufsize: Amount of data to write + * + * Write a block of data to a file descriptor. + * + * Return: -1 Error, something went wrong + * 0 Success, all the data was written + */ +unsigned int write_data (int fd, const char *buffer, unsigned int bufsize) +{ + ssize_t result1, result2; + + if (!buffer) { + errno = EINVAL; + return -1; + } + + result1 = write (fd, buffer, bufsize); + if ((result1 == (ssize_t) bufsize) || (result1 < 0)) + return result1; + + /* Try again with the rest of the buffer */ + buffer += result1; + bufsize -= result1; + + result2 = write (fd, buffer, bufsize); + if (result2 < 0) + return result1; + + return result1 + result2; +} + +/** + * open_file - Create a file based on the dir, name and stream supplied + * @dir: Directory in which to create the file (optional) + * @name: Filename to give the file (optional) + * @stream: Name of the stream (optional) + * + * Create a file and return the file descriptor. All the components are + * optional. If the name is missing, "unknown" will be used. If the directory + * is missing the file will be created in the current directory. If the stream + * name is present it will be appended to the filename, delimited by a colon. + * + * Return: -1 Error, failed to create the file + * n Success, this is the file descriptor + */ +int open_file (const char *dir, const char *name, const char *stream) +{ + char buf[256]; + int flags; + + if (!name) + name = UNKNOWN; + + if (dir) + if (stream) + snprintf (buf, sizeof (buf), "%s/%s:%s", dir, name, stream); + else + snprintf (buf, sizeof (buf), "%s/%s", dir, name); + else + if (stream) + snprintf (buf, sizeof (buf), "%s:%s", name, stream); + else + snprintf (buf, sizeof (buf), "%s", name); + + Vprintf ("Creating file: %s\n", buf); + + if (opts.force) + flags = O_RDWR | O_CREAT | O_TRUNC; + else + flags = O_RDWR | O_CREAT | O_EXCL; + + return open (buf, flags, S_IRUSR | S_IWUSR); +} + + +/** + * scan_disk - Search an NTFS volume for files that could be undeleted + * @vol: An ntfs volume obtained from ntfs_mount + * + * Read through all the MFT entries looking for deleted files. For each one + * determine how much of the data lies in unused disk space. + * + * The list can be filtered by name, size and date, using command line options. + * + * Return: -1 Error, something went wrong + * n Success, the number of recoverable files + */ +int scan_disk (ntfs_volume *vol) +{ + const int BUFSIZE = 8192; + char *buffer = NULL; + int results = 0; + ntfs_attr *attr; + long long size; + long long read; + long long bmpsize; + int i, j, k, b; + int percent; + struct ufile *file; + regex_t re; + + if (!vol) + return -1; + + attr = ntfs_attr_open (vol->mft_ni, AT_BITMAP, AT_UNNAMED, 0); + if (!attr) { + Eprintf ("Couldn't open $MFT/$BITMAP: %s\n", strerror (errno)); + return -1; + } + bmpsize = attr->initialized_size; + + buffer = malloc (BUFSIZE); + if (!buffer) { + Eprintf ("Couldn't allocate memory in scan_disk()\n"); + results = -1; + goto out; + } + + if (opts.match) { + int flags = REG_NOSUB; + + if (!opts.match_case) + flags |= REG_ICASE; + if (regcomp (&re, opts.match, flags)) { + Eprintf ("Couldn't create a regex.\n"); + goto out; + } + } + + Iprintf ("Inode Flags %%age Date Size Filename\n"); + Iprintf ("---------------------------------------------------------------\n"); + for (i = 0; i < bmpsize; i += BUFSIZE) { + read = min ((bmpsize - i), BUFSIZE); + size = ntfs_attr_pread (attr, i, read, buffer); + if (size < 0) + break; + + for (j = 0; j < size; j++) { + b = buffer[j]; + for (k = 0; k < 8; k++, b>>=1) { + if (((i+j)*8+k) >= vol->nr_mft_records) + goto done; + if (b & 1) + continue; + file = read_record (vol, (i+j)*8+k); + if (!file) { + Eprintf ("Couldn't read MFT Record %d.\n", (i+j)*8+k); + continue; + } + + if ((opts.since > 0) && (file->date <= opts.since)) + goto skip; + if (opts.match && !name_match (&re, file)) + goto skip; + if (opts.size_begin && (opts.size_begin > file->max_size)) + goto skip; + if (opts.size_end && (opts.size_end < file->max_size)) + goto skip; + + percent = calc_percentage (file, vol); + + if ((opts.percent == -1) || (percent >= opts.percent)) { + if (opts.verbose) + dump_record (file); + else + list_record (file); + } + + if (((opts.percent == -1) && (percent > 0)) || + ((opts.percent > 0) && (percent >= opts.percent))) { + results++; + } +skip: + free_file (file); + } + } + } +done: + Iprintf ("\nFiles with potentially recoverable content: %d\n", results); +out: + if (opts.match) + regfree (&re); + free (buffer); + if (attr) + ntfs_attr_close (attr); + return results; +} + +/** + * undelete_file - Recover a deleted file from an NTFS volume + * @vol: An ntfs volume obtained from ntfs_mount + * @inode: MFT Record number to be recovered + * + * Read an MFT Record and try an recover any data associated with it. Some of + * the clusters may be in use; these will be filled with zeros or the fill byte + * supplied in the options. + * + * Each data stream will be recovered and saved to a file. The file's name will + * be the original filename and it will be written to the current directory. + * Any named data stream will be saved as filename:streamname. + * + * The output file's name and location can be altered by using the command line + * options. + * + * N.B. We cannot tell if someone has overwritten some of the data since the + * file was deleted. + * + * Return: 0 Error, something went wrong + * 1 Success, the data was recovered + */ +int undelete_file (ntfs_volume *vol, long long inode) +{ + char *buffer = NULL; + struct ufile *file; + int i, j; + long long start, end; + run_list_element *rl; + struct list_head *item; + int fd = -1; + long long k; + int result = 0; + + if (!vol) + return 0; + + file = read_record (vol, inode); + if (!file || !file->mft) { + Eprintf ("Can't read info from mft record %lld.\n", inode); + return 0; + } + + buffer = malloc (vol->mft_record_size); + if (!buffer) + goto free; + + if (opts.verbose) { + dump_record (file); + } else { + Iprintf ("Inode Flags %%age Date Size Filename\n"); + Iprintf ("---------------------------------------------------------------\n"); + list_record (file); + Iprintf ("\n"); + } + + if (file->mft->flags & MFT_RECORD_IN_USE) { + Eprintf ("Record is in use by the mft\n"); + if (!opts.force) { + free_file (file); + return 0; + } + Vprintf ("Forced to continue.\n"); + } + + if (calc_percentage (file, vol) == 0) { + Iprintf ("File has no recoverable data.\n"); + goto free; + } + + if (list_empty (&file->data)) { + Iprintf ("File has no data. There is nothing to recover.\n"); + goto free; + } + + list_for_each (item, &file->data) { + struct data *d = list_entry (item, struct data, list); + + if (d->resident) { + fd = open_file (opts.dest, file->pref_name, d->name); + if (fd < 0) { + Eprintf ("Couldn't create file: %s\n", strerror (errno)); + goto free; + } + + Vprintf ("File has resident data.\n"); + if (write_data (fd, d->data, d->size_data) < d->size_data) { + Eprintf ("Write failed: %s\n", strerror (errno)); + close (fd); + goto free; + } + + if (close (fd) < 0) { + Eprintf ("Close failed: %s\n", strerror (errno)); + } + fd = -1; + } else { + rl = d->run_list; + if (!rl) { + Vprintf ("File has no run list, hence no data.\n"); + continue; + } + + if (rl[0].length <= 0) { + Vprintf ("File has an empty run list, hence no data.\n"); + continue; + } + + fd = open_file (opts.dest, file->pref_name, d->name); + if (fd < 0) { + Eprintf ("Couldn't create output file: %s\n", strerror (errno)); + goto free; + } + + if (rl[0].lcn == LCN_RL_NOT_MAPPED) { /* extended mft record */ + Vprintf ("Missing segment at beginning, %lld clusters.\n", rl[0].length); + memset (buffer, opts.fillbyte, sizeof (buffer)); + for (k = 0; k < rl[0].length * vol->cluster_size; k += sizeof (buffer)) { + if (write_data (fd, buffer, sizeof (buffer)) < sizeof (buffer)) { + Eprintf ("Write failed: %s\n", strerror (errno)); + close (fd); + goto free; + } + } + } + + for (i = 0; rl[i].length > 0; i++) { + + if (rl[i].lcn == LCN_RL_NOT_MAPPED) { + Vprintf ("Missing segment at end, %lld clusters.\n", rl[i].length); + memset (buffer, opts.fillbyte, sizeof (buffer)); + for (k = 0; k < rl[k].length * vol->cluster_size; k += sizeof (buffer)) { + if (write_data (fd, buffer, sizeof (buffer)) < sizeof (buffer)) { + Eprintf ("Write failed: %s\n", strerror (errno)); + close (fd); + goto free; + } + } + continue; + } + + if (rl[i].lcn == LCN_HOLE) { + Vprintf ("File has a sparse section.\n"); + memset (buffer, 0, sizeof (buffer)); + for (k = 0; k < rl[k].length * vol->cluster_size; k += sizeof (buffer)) { + if (write_data (fd, buffer, sizeof (buffer)) < sizeof (buffer)) { + Eprintf ("Write failed: %s\n", strerror (errno)); + close (fd); + goto free; + } + } + continue; + } + + start = rl[i].lcn; + end = rl[i].lcn + rl[i].length; + + for (j = start; j < end; j++) { + if (cluster_in_use (vol, j)) { + memset (buffer, opts.fillbyte, sizeof (buffer)); + if (write_data (fd, buffer, sizeof (buffer)) < sizeof (buffer)) { + Eprintf ("Write failed: %s\n", strerror (errno)); + close (fd); + goto free; + } + } else { + if (ntfs_read_clusters (vol, j, 1, buffer) < 1) { + Eprintf ("Read failed: %s\n", strerror (errno)); + close (fd); + goto free; + } + if (write_data (fd, buffer, sizeof (buffer)) < sizeof (buffer)) { + Eprintf ("Write failed: %s\n", strerror (errno)); + close (fd); + goto free; + } + } + } + } + Iprintf ("\n"); + if (close (fd) < 0) { + Eprintf ("Close failed: %s\n", strerror (errno)); + } + fd = -1; + + } + if (d->name) + Iprintf ("Undeleted '%s:%s' successfully.\n", file->pref_name, d->name); + else + Iprintf ("Undeleted '%s' successfully.\n", file->pref_name); + } + result = 1; +free: + if (buffer) + free (buffer); + free_file (file); + return result; +} + +/** + * copy_mft - Write a range of MFT Records to a file + * @vol: An ntfs volume obtained from ntfs_mount + * @mft_begin: First MFT Record to save + * @mft_end: Last MFT Record to save + * + * Read a number of MFT Records and write them to a file. + * + * Return: 0 Success, all the records were written + * 1 Error, something went wrong + */ +int copy_mft (ntfs_volume *vol, long long mft_begin, long long mft_end) +{ + ntfs_attr *mft; + char *buffer; + const char *name; + long long i; + int result = 1; + int fd; + + if (!vol) + return 1; + + if (mft_end < mft_begin) { + Eprintf ("Range to copy is backwards.\n"); + return 1; + } + + buffer = malloc (vol->mft_record_size); + if (!buffer) { + Eprintf ("Couldn't allocate memory in copy_mft()\n"); + return 1; + } + + mft = ntfs_attr_open (vol->mft_ni, AT_DATA, NULL, 0); + if (!mft) { + Eprintf ("Couldn't open $MFT/$DATA: %s\n", strerror (errno)); + goto free; + } + + name = opts.output; + if (!name) { + name = MFTFILE; + Dprintf ("No output filename, defaulting to '%s'.\n", name); + } + + fd = open_file (opts.dest, name, NULL); + if (fd < 0) { + Eprintf ("Couldn't open output file '%s': %s\n", name, strerror (errno)); + goto attr; + } + + mft_end = min (mft_end, vol->nr_mft_records - 1); + + Dprintf ("MFT records\n"); + Dprintf (" Total: %8lld\n", vol->nr_mft_records); + Dprintf (" Begin: %8lld\n", mft_begin); + Dprintf (" End: %8lld\n", mft_end); + + for (i = mft_begin; i <= mft_end; i++) { + if (ntfs_attr_pread (mft, vol->mft_record_size * i, vol->mft_record_size, buffer) < vol->mft_record_size) { + Eprintf ("Couldn't read MFT Record %d: %s.\n", i, strerror (errno)); + goto close; + } + + if (write_data (fd, buffer, vol->mft_record_size) < vol->mft_record_size) { + Eprintf ("Write failed: %s\n", strerror (errno)); + goto close; + } + } + + Vprintf ("Read %d MFT Records\n", mft_end - mft_begin + 1); + result = 0; +close: + close (fd); +attr: + ntfs_attr_close (mft); +free: + free (buffer); + return result; +} + +/** + * valid_device - Perform some safety checks on the device, before we start + * @name: Full pathname of the device/file to work with + * @force: Continue regardless of problems + * + * Check that the name refers to a device and that is isn't already mounted. + * These checks can be overridden by using the force option. + * + * Return: 1 Success, we can continue + * 0 Error, we cannot use this device + */ +int valid_device (const char *name, int force) +{ + unsigned long mnt_flags = 0; + struct stat st; + + if (stat (name, &st) == -1) { + if (errno == ENOENT) { + Eprintf ("The device %s doesn't exist\n", name); + } else { + Eprintf ("Error getting information about %s: %s\n", name, strerror (errno)); + } + return 0; + } + + if (!S_ISBLK (st.st_mode)) { + Vprintf ("%s is not a block device.\n", name); + if (!force) { + Eprintf ("Use the force option to work with files.\n"); + return 0; + } + Vprintf ("Forced to continue.\n"); + } + + /* Make sure the file system is not mounted. */ + if (ntfs_check_if_mounted (name, &mnt_flags)) { + Vprintf ("Failed to determine whether %s is mounted: %s\n", name, strerror (errno)); + if (!force) { + Eprintf ("Use the force option to ignore this error.\n"); + return 0; + } + Vprintf ("Forced to continue.\n"); + } else if (mnt_flags & NTFS_MF_MOUNTED) { + Vprintf ("The device %s, is mounted.\n", name); + if (!force) { + Eprintf ("Use the force option to work a mounted filesystem.\n"); + return 0; + } + Vprintf ("Forced to continue.\n"); + } + + Dprintf ("Device %s, will be used\n", name); + return 1; +} + +/** + * main - Begin here + * + * Start from here. + * + * Return: 0 Success, the program worked + * 1 Error, something went wrong + */ +int main (int argc, char *argv[]) +{ + const char *locale; + ntfs_volume *vol; + int result = 1; + + locale = setlocale (LC_ALL, ""); + if (!locale) { + locale = setlocale (LC_ALL, NULL); + Vprintf ("Failed to set locale, using default '%s'.\n", locale); + } else { + Vprintf ("Using locale '%s'.\n", locale); + } + + if (!parse_options (argc, argv)) + goto free; + + if (!valid_device (opts.device, opts.force)) + goto free; + + vol = ntfs_mount (opts.device, MS_RDONLY); + if (!vol) { + Eprintf ("Couldn't mount device '%s': %s\n", opts.device, strerror (errno)); + goto free; + } + + if (vol->flags & VOLUME_IS_DIRTY) { + Iprintf ("Volume is dirty.\n"); + if (!opts.force) { + Eprintf ("Run chkdsk and try again, or use the --force option.\n"); + goto umount; + } + Iprintf ("Forced to continue.\n"); + } + + switch (opts.mode) { + case MODE_SCAN: + result = !scan_disk (vol); + if (result) + Vprintf ("Failed to scan device '%s'.\n", opts.device); + break; + case MODE_UNDELETE: + result = !undelete_file (vol, opts.uinode); + if (result) + Vprintf ("Failed to undelete inode %d.\n", opts.uinode); + break; + case MODE_COPY: + result = !copy_mft (vol, opts.mft_begin, opts.mft_end); + if (result) + Vprintf ("Failed to read MFT blocks %lld-%lld.\n", + opts.mft_begin, min (vol->nr_mft_records, opts.mft_end)); + break; + default: + ; /* Cannot happen */ + } + +umount: + ntfs_umount (vol, FALSE); +free: + if (opts.match) + free (opts.match); + + return result; +} + diff --git a/ntfsprogs/ntfsundelete.h b/ntfsprogs/ntfsundelete.h index e69de29b..21a63d8b 100644 --- a/ntfsprogs/ntfsundelete.h +++ b/ntfsprogs/ntfsundelete.h @@ -0,0 +1,104 @@ +/* + * ntfsundelete - Part of the Linux-NTFS project. + * + * Copyright (c) 2002 Richard Russon + * + * This utility will recover deleted files from an NTFS volume. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program (in the main directory of the Linux-NTFS + * distribution in the file COPYING); if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _NTFSUNDELETE_H_ +#define _NTFSUNDELETE_H_ + +#include "types.h" +#include "list.h" +#include "runlist.h" + +enum optmode { + MODE_NONE = 0, + MODE_SCAN, + MODE_UNDELETE, + MODE_COPY, + MODE_ERROR +}; + +struct options { + char *device; /* Device/File to work with */ + enum optmode mode; /* Scan / Undelete / Copy */ + int percent; /* Minimum recoverability */ + int uinode; /* Undelete this inode */ + char *dest; /* Save file to this directory */ + char *output; /* With this filename */ + char fillbyte; /* Use for unrecoverable sections */ + char *match; /* Pattern for filename matching */ + int match_case; /* Case sensitive matching */ + int quiet; /* Less output */ + int verbose; /* Extra output */ + int force; /* Override common sense */ + time_t since; /* Since this time */ + long long size_begin; /* Range for file size */ + long long size_end; + long long mft_begin; /* Range for mft copy */ + long long mft_end; +}; + +struct filename { + struct list_head list; /* Previous/Next links */ + char *name; /* Filename in current locale */ + FILE_NAME_TYPE_FLAGS name_space; + uchar_t *uname; /* Filename in unicode */ + int uname_len; /* and its length */ + long long size_alloc; /* Allocated size (multiple of cluster size) */ + long long size_data; /* Actual size of data */ + FILE_ATTR_FLAGS flags; + time_t date_c; + time_t date_a; + time_t date_m; + time_t date_r; +}; + +struct data { + struct list_head list; /* Previous/Next links */ + char *name; /* Stream name in current locale */ + uchar_t *uname; /* Unicode stream name */ + int uname_len; /* and its length */ + int resident; /* Stream is resident */ + int compressed; /* Stream is compressed */ + int encrypted; /* Stream is encrypted */ + long long size_alloc; /* Allocated size (multiple of cluster size) */ + long long size_data; /* Actual size of data */ + long long size_init; /* Initialised size, may be less than data size */ + long long size_vcn; /* Highest VCN in the data runs */ + run_list_element*run_list; /* Decoded data runs */ + int percent; /* Amont potentially recoverable */ + void *data; /* If resident, a pointer to the data */ +}; + +struct ufile { + long long inode; /* MFT record number */ + time_t date; /* Last modification date/time */ + struct list_head name; /* A list of filenames */ + struct list_head data; /* A list of data streams */ + char *pref_name; /* Preferred filename */ + long long max_size; /* Largest size we find */ + int attr_list; /* MFT record may be one of many */ + int directory; /* MFT record represents a directory */ + MFT_RECORD *mft; /* Raw MFT record */ +}; + +#endif /* _NTFSUNDELETE_H_ */ + diff --git a/ntfsprogs/ntfswipe.c b/ntfsprogs/ntfswipe.c index e69de29b..120e6e52 100644 --- a/ntfsprogs/ntfswipe.c +++ b/ntfsprogs/ntfswipe.c @@ -0,0 +1,732 @@ +/** + * ntfswipe - Part of the Linux-NTFS project. + * + * Copyright (c) 2002 Richard Russon + * + * This utility will overwrite usused space on an NTFS volume. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program (in the main directory of the Linux-NTFS + * distribution in the file COPYING); if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ntfswipe.h" +#include "types.h" +#include "volume.h" + +static const char *AUTHOR = "Richard Russon (FlatCap)"; +static const char *EXEC_NAME = "ntfswipe"; +static struct options opts; + +/** + * Eprintf - Print error messages + */ +void Eprintf (const char *format, ...) +{ + va_list va; + va_start (va, format); + vfprintf (stderr, format, va); + va_end (va); +} + +/** + * Iprintf - Print informative messages + */ +void Iprintf (const char *format, ...) +{ + va_list va; +#ifndef DEBUG + if (opts.quiet) + return; +#endif + va_start (va, format); + vfprintf (stdout, format, va); + va_end (va); +} + +/** + * Vprintf - Print verbose messages + */ +void Vprintf (const char *format, ...) +{ + va_list va; +#ifndef DEBUG + if (!opts.verbose) + return; +#endif + va_start (va, format); + vfprintf (stdout, format, va); + va_end (va); +} + +/** + * Dprintf - Print debug messages + */ +#ifndef DEBUG +#define Dprintf(...) +#else +void Dprintf (const char *format, ...) +{ + va_list va; + va_start (va, format); + vfprintf (stdout, format, va); + va_end (va); +} +#endif + +/** + * wipe_unused - Wipe unused clusters + * @vol: An ntfs volume obtained from ntfs_mount + * @byte: Overwrite with this value + * + * Read $Bitmap and wipe any clusters that are marked as not in use. + * + * Return: 1 Success, the clusters were wiped + * 0 Error, something went wrong + */ +int wipe_unused (ntfs_volume *vol, int byte) +{ + if (!vol || (byte < 0)) + return 0; + + Iprintf ("wipe_unused 0x%02x\n", byte); + return 1; +} + +/** + * wipe_tails - Wipe the file tails + * @vol: An ntfs volume obtained from ntfs_mount + * @byte: Overwrite with this value + * + * Disk space is allocated in clusters. If a file isn't an exact multiple of + * the cluster size, there is some slack space at the end. Wipe this space. + * + * Return: 1 Success, the clusters were wiped + * 0 Error, something went wrong + */ +int wipe_tails (ntfs_volume *vol, int byte) +{ + if (!vol || (byte < 0)) + return 0; + + Iprintf ("wipe_tails 0x%02x\n", byte); + return 1; +} + +/** + * wipe_mft - Wipe the MFT slack space + * @vol: An ntfs volume obtained from ntfs_mount + * @byte: Overwrite with this value + * + * MFT Records are 1024 bytes long, but some of this space isn't used. Wipe any + * unused space at the end of the record and wipe any unused records. + * + * Return: 1 Success, the clusters were wiped + * 0 Error, something went wrong + */ +int wipe_mft (ntfs_volume *vol, int byte) +{ + if (!vol || (byte < 0)) + return 0; + + Iprintf ("wipe_mft 0x%02x\n", byte); + return 1; +} + +/** + * wipe_directory - Wipe the directiry indexes + * @vol: An ntfs volume obtained from ntfs_mount + * @byte: Overwrite with this value + * + * Directories are kept in sorted B+ Trees. Index blocks may not be full. Wipe + * the unused space at the ends of these blocks. + * + * Return: 1 Success, the clusters were wiped + * 0 Error, something went wrong + */ +int wipe_directory (ntfs_volume *vol, int byte) +{ + if (!vol || (byte < 0)) + return 0; + + Iprintf ("wipe_directory 0x%02x\n", byte); + return 1; +} + +/** + * wipe_logfile - Wipe the logfile (journal) + * @vol: An ntfs volume obtained from ntfs_mount + * @byte: Overwrite with this value + * + * The logfile journals the metadata to give the volume fault-tolerance. If the + * volume is in a consistant state, then this information can be erased. + * + * Return: 1 Success, the clusters were wiped + * 0 Error, something went wrong + */ +int wipe_logfile (ntfs_volume *vol, int byte) +{ + if (!vol || (byte < 0)) + return 0; + + Iprintf ("wipe_logfile 0x%02x\n", byte); + return 1; +} + +/** + * wipe_pagefile - Wipe the pagefile (swap space) + * @vol: An ntfs volume obtained from ntfs_mount + * @byte: Overwrite with this value + * + * pagefile.sys is used by Windows as extra virtual memory (swap space). + * Windows recreates the file at bootup, so it can be wiped without harm. + * + * Return: 1 Success, the clusters were wiped + * 0 Error, something went wrong + */ +int wipe_pagefile (ntfs_volume *vol, int byte) +{ + if (!vol || (byte < 0)) + return 0; + + Iprintf ("wipe_pagefile 0x%02x\n", byte); + return 1; +} + +/** + * ntfs_info - Display information about the NTFS Volume + * @vol: An ntfs volume obtained from ntfs_mount + * + * Tell the user how much could be cleaned up. List the number of free + * clusters, MFT records, etc. + * + * Return: 1 Success, displayed some info + * 0 Error, something went wrong + */ +int ntfs_info (ntfs_volume *vol) +{ + if (!vol) + return 0; + + Iprintf ("ntfs_info\n"); + return 1; +} + + +/** + * version - Print version information about the program + * + * Print a copyright statement and a brief description of the program. + * + * Return: none + */ +void version (void) +{ + Iprintf ("%s v%s Copyright (C) 2002 %s\nOverwrite the unused space on " + "an NTFS Volume\n\n%s is free software, released under the GNU " + "General Public License\nand you are welcome to redistribute " + "it under certain conditions.\n%s comes with ABSOLUTELY NO " + "WARRANTY; for details read the GNU\nGeneral Public License " + "to be found in the file COPYING in the main\nLinux-NTFS " + "distribution directory.\n\n", + EXEC_NAME, VERSION, AUTHOR, EXEC_NAME, EXEC_NAME); +} + +/** + * usage - Print a list of the parameters to the program + * + * Print a list of the parameters and options for the program. + * + * Return: none + */ +void usage (void) +{ + Iprintf ("Usage: %s [options] device\n" + " -i --info Show volume information (default)\n" + "\n" + " -d --directory Wipe directory indexes\n" + " -l --logfile Wipe the logfile (journal)\n" + " -m --mft Wipe mft space\n" + " -p --pagefile Wipe pagefile (swap space)\n" + " -t --tails Wipe file tails\n" + " -u --unused Wipe unused clusters\n" + "\n" + " -a --all Wipe all unused space\n" + "\n" + " -c num --count num Number of times to write (default = 1)\n" + " -b list --bytes list List of values to write (default = 0)\n" + "\n" + " -n --no-action Do not write to disk\n" + " -f --force Use less caution\n" + " -q --quiet Less output\n" + " -v --verbose More output\n" + " -V --version Version information\n" + " -h --help Print this help\n\n", + EXEC_NAME); + Iprintf ("Please report bugs to: linux-ntfs-dev@lists.sf.net\n\n"); +} + +/** + * parse_list - Read a comma-separated list of numbers + * @list: The comma-separated list of numbers + * @result: Store the parsed list here (must be freed by caller) + * + * Read a comma-separated list of numbers and allocate an array of ints to store + * them in. The numbers can be in decimal, octal or hex. + * + * N.B. The caller must free the memory returned in @result. + * N.B. If the function fails, @result is not changed. + * + * Return: 0 Error, invalid string + * n Success, the count of numbers parsed + */ +int parse_list (const char *list, int **result) +{ + const char *ptr; + char *end; + int i; + int count; + int *mem = NULL; + + if (!list || !result) + return 0; + + for (count = 0, ptr = list; ptr; ptr = strchr (ptr+1, ',')) + count++; + + mem = malloc ((count+1) * sizeof (int)); + if (!mem) { + Eprintf ("Couldn't allocate memory in parse_list().\n"); + return 0; + } + + memset (mem, 0xFF, (count+1) * sizeof (int)); + + for (ptr = list, i = 0; i < count; i++) { + + end = NULL; + mem[i] = strtol (ptr, &end, 0); + + if (!end || (end == ptr) || ((*end != ',') && (*end != 0))) { + Eprintf ("Invalid list '%s'\n", list); + free (mem); + return 0; + } + + if ((mem[i] < 0) || (mem[i] > 255)) { + Eprintf ("Bytes must be in range 0-255.\n"); + free (mem); + return 0; + } + + ptr = end + 1; + } + + Dprintf ("Parsing list '%s' - ", list); + for (i = 0; i <= count; i++) + Dprintf ("0x%02x ", mem[i]); + Dprintf ("\n"); + + *result = mem; + return count; +} + +/** + * parse_options - Read and validate the programs command line + * + * Read the command line, verify the syntax and parse the options. + * This function is very long, but quite simple. + * + * Return: 1 Success + * 0 Error, one or more problems + */ +int parse_options (int argc, char *argv[]) +{ + static const char *sopt = "-ab:c:dfhilmnpqtuvV"; + static const struct option lopt[] = { + { "all", no_argument, NULL, 'a' }, + { "bytes", required_argument, NULL, 'b' }, + { "count", required_argument, NULL, 'c' }, + { "directory", no_argument, NULL, 'd' }, + { "force", no_argument, NULL, 'f' }, + { "help", no_argument, NULL, 'h' }, + { "info", no_argument, NULL, 'i' }, + { "logfile", no_argument, NULL, 'l' }, + { "mft", no_argument, NULL, 'm' }, + { "no-action", no_argument, NULL, 'n' }, + { "pagefile", no_argument, NULL, 'p' }, + { "quiet", no_argument, NULL, 'q' }, + { "tails", no_argument, NULL, 't' }, + { "unused", no_argument, NULL, 'u' }, + { "verbose", no_argument, NULL, 'v' }, + { "version", no_argument, NULL, 'V' }, + { NULL, 0, NULL, 0 } + }; + + char c = -1; + char *end; + int err = 0; + int ver = 0; + int help = 0; + + opterr = 0; /* We'll handle the errors, thank you. */ + + opts.count = 1; + + while ((c = getopt_long (argc, argv, sopt, lopt, NULL)) != -1) { + switch (c) { + case 1: /* A non-option argument */ + if (!opts.device) { + opts.device = argv[optind-1]; + } else { + opts.device = NULL; + err++; + } + break; + + case 'a': + opts.directory++; + opts.logfile++; + opts.mft++; + opts.pagefile++; + opts.tails++; + opts.unused++; + break; + case 'b': + if (!opts.bytes) { + if (!parse_list (argv[optind-1], &opts.bytes)) + err++; + } else { + err++; + } + break; + case 'c': + if (opts.count == 1) { + end = NULL; + opts.count = strtol (optarg, &end, 0); + if (end && *end) + err++; + } else { + err++; + } + break; + case 'd': + opts.directory++; + break; + case 'f': + opts.force++; + break; + case 'h': + help++; + break; + case 'i': + opts.info++; + break; + case 'l': + opts.logfile++; + break; + case 'm': + opts.mft++; + break; + case 'n': + opts.noaction++; + break; + case 'p': + opts.pagefile++; + break; + case 'q': + opts.quiet++; + break; + case 't': + opts.tails++; + break; + case 'u': + opts.unused++; + break; + case 'v': + opts.verbose++; + break; + case 'V': + ver++; + break; + default: + if ((optopt == 'b') || (optopt == 'c')) { + Eprintf ("Option '%s' requires an argument.\n", argv[optind-1]); + } else { + Eprintf ("Unknown option '%s'.\n", argv[optind-1]); + } + err++; + break; + } + } + + if (help || ver) { + opts.quiet = 0; + } else { + if (opts.device == NULL) { + Eprintf ("You must specify exactly one device.\n"); + err++; + } + + if ((opts.quiet) && (opts.verbose)) { + Eprintf ("You may not use --quiet and --verbose at the same time.\n"); + err++; + } + + if (opts.info && (opts.unused || opts.tails || opts.mft || opts.directory)) { + Eprintf ("You may not use any other options with --info.\n"); + err++; + } + + if ((opts.count < 1) || (opts.count > 100)) { + Eprintf ("The iteration count must be between 1 and 100.\n"); + err++; + } + + /*if (opts.bytes && (opts.count > 0)) { + Eprintf ("You may not use both --bytes and --count.\n"); + err++; + }*/ + + /* Create a default list */ + if (!opts.bytes) { + opts.bytes = malloc (2 * sizeof (int)); + if (opts.bytes) { + opts.bytes[0] = 0; + opts.bytes[1] = -1; + } + } + + if (!opts.directory && !opts.logfile && !opts.mft && + !opts.pagefile && !opts.tails && !opts.unused) { + opts.info = 1; + } + } + + if (ver) + version(); + if (help || err) + usage(); + + return (!err && !help && !ver); +} + +/** + * valid_device - Perform some safety checks on the device, before we start + * @name: Full pathname of the device/file to work with + * @force: Continue regardless of problems + * + * Check that the name refers to a device and that is isn't already mounted. + * These checks can be overridden by using the force option. + * + * Return: 1 Success, we can continue + * 0 Error, we cannot use this device + */ +int valid_device (const char *name, int force) +{ + unsigned long mnt_flags = 0; + struct stat st; + + if (stat (name, &st) == -1) { + if (errno == ENOENT) { + Eprintf ("The device %s doesn't exist\n", name); + } else { + Eprintf ("Error getting information about %s: %s\n", name, strerror (errno)); + } + return 0; + } + + if (!S_ISBLK (st.st_mode)) { + Vprintf ("%s is not a block device.\n", name); + if (!force) { + Eprintf ("Use the force option to work with files.\n"); + return 0; + } + Vprintf ("Forced to continue.\n"); + } + + /* Make sure the file system is not mounted. */ + if (ntfs_check_if_mounted (name, &mnt_flags)) { + Vprintf ("Failed to determine whether %s is mounted: %s\n", name, strerror (errno)); + if (!force) { + Eprintf ("Use the force option to ignore this error.\n"); + return 0; + } + Vprintf ("Forced to continue.\n"); + } else if (mnt_flags & NTFS_MF_MOUNTED) { + Vprintf ("The device %s, is mounted.\n", name); + if (!force) { + Eprintf ("Use the force option to work a mounted filesystem.\n"); + return 0; + } + Vprintf ("Forced to continue.\n"); + } + + Dprintf ("Device %s, will be used\n", name); + return 1; +} + + +/** + * print_summary - Tell the use what we are about to do + * + * List the operations about to be performed. The output will be silenced by + * the --quiet option. + * + * Return: none + */ +void print_summary (void) +{ + int i; + + if (opts.noaction) + Iprintf ("%s is in 'no-action' mode, it will NOT write to disk." + "\n\n", EXEC_NAME); + + Iprintf ("%s is about to wipe:\n", EXEC_NAME); + if (opts.unused) + Iprintf ("\tunused disk space\n"); + if (opts.tails) + Iprintf ("\tfile tails\n"); + if (opts.mft) + Iprintf ("\tunused mft areas\n"); + if (opts.directory) + Iprintf ("\tunused directory index space\n"); + if (opts.logfile) + Iprintf ("\tthe logfile (journal)\n"); + if (opts.pagefile) + Iprintf ("\tthe pagefile (swap space)\n"); + + Iprintf ("\n%s will overwrite these areas with: ", EXEC_NAME); + if (opts.bytes) { + for (i = 0; opts.bytes[i] >= 0; i++) + Iprintf ("0x%02x ", opts.bytes[i]); + } + Iprintf ("\n"); + + if (opts.count > 1) + Iprintf ("%s will repeat these operations %d times.\n", EXEC_NAME, opts.count); + Iprintf ("\n"); +} + +/** + * main - Begin here + * + * Start from here. + * + * Return: 0 Success, the program worked + * 1 Error, something went wrong + */ +int main (int argc, char *argv[]) +{ + const char *locale; + ntfs_volume *vol; + int result = 1; + int flags = 0; + int i, j; + + locale = setlocale (LC_ALL, ""); + if (!locale) { + locale = setlocale (LC_ALL, NULL); + Vprintf ("Failed to set locale, using default '%s'.\n", locale); + } else { + Vprintf ("Using locale '%s'.\n", locale); + } + + if (!parse_options (argc, argv)) + return 1; + + if (!valid_device (opts.device, opts.force)) { + goto free; + } + + if (!opts.info) + print_summary(); + + if (opts.info || opts.noaction) + flags = MS_RDONLY; + vol = ntfs_mount (opts.device, flags); + if (!vol) { + Eprintf ("Couldn't mount device '%s': %s\n", opts.device, strerror (errno)); + goto free; + } + + if (vol->flags & VOLUME_IS_DIRTY) { + Iprintf ("Volume is dirty.\n"); + if (!opts.force) { + Eprintf ("Run chkdsk and try again, or use the --force option.\n"); + goto umount; + } + Iprintf ("Forced to continue.\n"); + } + + if (opts.info) { + ntfs_info (vol); + result = 0; + goto umount; + } + + /* Even if the output it quieted, you still get 5 seconds to abort. */ + if (!opts.force) { + Iprintf ("\n%s will begin in 5 seconds, press CTRL-C to abort.\n", EXEC_NAME); + sleep (5); + } + + if (!opts.bytes) { + Eprintf ("Internal error, byte list is empty\n"); + goto umount; + } + + for (i = 0; i < opts.count; i++) { + int byte; + for (j = 0; byte = opts.bytes[j], byte >= 0; j++) { + if (opts.unused && !wipe_unused (vol, byte)) + goto umount; + if (opts.tails && !wipe_tails (vol, byte)) + goto umount; + if (opts.mft && !wipe_mft (vol, byte)) + goto umount; + if (opts.directory && !wipe_directory (vol, byte)) + goto umount; + if (opts.logfile && !wipe_logfile (vol, byte)) + goto umount; + if (opts.pagefile && !wipe_pagefile (vol, byte)) + goto umount; + } + } + + result = 0; +umount: + ntfs_umount (vol, FALSE); +free: + if (opts.bytes) + free (opts.bytes); + return result; +} + + diff --git a/ntfsprogs/ntfswipe.h b/ntfsprogs/ntfswipe.h index e69de29b..f06358c2 100644 --- a/ntfsprogs/ntfswipe.h +++ b/ntfsprogs/ntfswipe.h @@ -0,0 +1,47 @@ +/* + * ntfswipe - Part of the Linux-NTFS project. + * + * Copyright (c) 2002 Richard Russon + * + * This utility will overwrite usused space on an NTFS volume. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program (in the main directory of the Linux-NTFS + * distribution in the file COPYING); if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _NTFSWIPE_H_ +#define _NTFSWIPE_H_ + +#include "types.h" + +struct options { + char *device; /* Device/File to work with */ + int info; /* Show volume info */ + int force; /* Override common sense */ + int quiet; /* Less output */ + int verbose; /* Extra output */ + int noaction; /* Do not write to disk */ + int count; /* Number of iterations */ + int *bytes; /* List of overwrite characters */ + int directory; /* Wipe directory indexes */ + int logfile; /* Wipe the logfile (journal) */ + int mft; /* Wipe mft slack space */ + int pagefile; /* Wipe pagefile (swap space) */ + int tails; /* Wipe file tails */ + int unused; /* Wipe unused clusters */ +}; + +#endif /* _NTFSWIPE_H_ */ + diff --git a/ntfsprogs/sd.c b/ntfsprogs/sd.c index e69de29b..0e0f0863 100644 --- a/ntfsprogs/sd.c +++ b/ntfsprogs/sd.c @@ -0,0 +1,200 @@ +#include "types.h" +#include "layout.h" + +/* + * NTFS 1.2 - System files security decriptors + * =========================================== + * + * Create the security descriptor for system file number @sys_file_no and + * return a pointer to the descriptor. + * + * $MFT, $MFTMirr, $LogFile, $AttrDef, $Bitmap, $Boot, $BadClus, and $UpCase + * are the same. + * + * $Volume, $Quota, and system files 0xb-0xf are the same. They are almost the + * same as the above, the only difference being that the two SIDs present in + * the DACL grant GENERIC_WRITE and GENERIC_READ equivalent priviledges while + * the above only grant GENERIC_READ equivalent priviledges. (For some reason + * the flags for GENERIC_READ/GENERIC_WRITE are not set by NT4, even though + * the permissions are equivalent, so we comply. + * + * Root directory system file (".") is different altogether. + * + * The sd is recturned in *@sd_val and has length *@sd_val_len. + * + * Do NOT free *@sd_val as it is static memory. This also means that you can + * only use *@sd_val until the next call to this function. + * + */ +void init_system_file_sd(int sys_file_no, char **sd_val, int *sd_val_len) +{ + static char sd_array[0x68]; + SECURITY_DESCRIPTOR_RELATIVE *sd; + ACL *acl; + ACCESS_ALLOWED_ACE *aa_ace; + SID *sid; + + if (sys_file_no < 0 || sys_file_no > 0xf) { + *sd_val = NULL; + *sd_val_len = 0; + return; + } + *sd_val = (char*)&sd_array; + sd = (SECURITY_DESCRIPTOR_RELATIVE*)&sd_array; + sd->revision = 1; + sd->alignment = 0; + sd->control = SE_SELF_RELATIVE | SE_DACL_PRESENT; + if (sys_file_no == FILE_root) { + *sd_val_len = 0x50; + sd->owner = cpu_to_le32(0x30); + sd->group = cpu_to_le32(0x40); + } else { + *sd_val_len = 0x68; + sd->owner = cpu_to_le32(0x48); + sd->group = cpu_to_le32(0x58); + } + sd->sacl = cpu_to_le32(0); + sd->dacl = cpu_to_le32(0x14); + /* + * Now at offset 0x14, as specified in the security descriptor, we have + * the DACL. + */ + acl = (ACL*)((char*)sd + le32_to_cpu(sd->dacl)); + acl->revision = 2; + acl->alignment1 = 0; + if (sys_file_no == FILE_root) { + acl->size = cpu_to_le16(0x1c); + acl->ace_count = cpu_to_le16(1); + } else { + acl->size = cpu_to_le16(0x34); + acl->ace_count = cpu_to_le16(2); + } + acl->alignment2 = cpu_to_le16(0); + /* + * Now at offset 0x1c, just after the DACL's ACL, we have the first + * ACE of the DACL. The type of the ACE is access allowed. + */ + aa_ace = (ACCESS_ALLOWED_ACE*)((char*)acl + sizeof(ACL)); + aa_ace->type = ACCESS_ALLOWED_ACE_TYPE; + if (sys_file_no == FILE_root) + aa_ace->flags = CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE; + else + aa_ace->flags = 0; + aa_ace->size = cpu_to_le16(0x14); + switch (sys_file_no) { + case FILE_MFT: case FILE_MFTMirr: case FILE_LogFile: + case FILE_AttrDef: case FILE_Bitmap: case FILE_Boot: + case FILE_BadClus: case FILE_UpCase: + aa_ace->mask = SYNCHRONIZE | STANDARD_RIGHTS_READ | + FILE_READ_ATTRIBUTES | FILE_READ_EA | FILE_READ_DATA; + break; + case FILE_Volume: case FILE_Secure: case 0xb ... 0xf: + aa_ace->mask = SYNCHRONIZE | STANDARD_RIGHTS_WRITE | + FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES | + FILE_WRITE_EA | FILE_READ_EA | FILE_APPEND_DATA | + FILE_WRITE_DATA | FILE_READ_DATA; + break; + case FILE_root: + aa_ace->mask = STANDARD_RIGHTS_ALL | FILE_WRITE_ATTRIBUTES | + FILE_READ_ATTRIBUTES | FILE_DELETE_CHILD | + FILE_TRAVERSE | FILE_WRITE_EA | FILE_READ_EA | + FILE_ADD_SUBDIRECTORY | FILE_ADD_FILE | + FILE_LIST_DIRECTORY; + break; + } + aa_ace->sid.revision = 1; + aa_ace->sid.sub_authority_count = 1; + aa_ace->sid.identifier_authority.value[0] = 0; + aa_ace->sid.identifier_authority.value[1] = 0; + aa_ace->sid.identifier_authority.value[2] = 0; + aa_ace->sid.identifier_authority.value[3] = 0; + aa_ace->sid.identifier_authority.value[4] = 0; + if (sys_file_no == FILE_root) { + /* SECURITY_WORLD_SID_AUTHORITY (S-1-1) */ + aa_ace->sid.identifier_authority.value[5] = 1; + aa_ace->sid.sub_authority[0] = + cpu_to_le32(SECURITY_WORLD_RID); + /* This is S-1-1-0, the WORLD_SID. */ + } else { + /* SECURITY_NT_SID_AUTHORITY (S-1-5) */ + aa_ace->sid.identifier_authority.value[5] = 5; + aa_ace->sid.sub_authority[0] = + cpu_to_le32(SECURITY_LOCAL_SYSTEM_RID); + } + /* + * Now at offset 0x30 within security descriptor, just after the first + * ACE of the DACL. All system files, except the root directory, have + * a second ACE. + */ + if (sys_file_no != FILE_root) { + /* The second ACE of the DACL. Type is access allowed. */ + aa_ace = (ACCESS_ALLOWED_ACE*)((char*)aa_ace + + le16_to_cpu(aa_ace->size)); + aa_ace->type = ACCESS_ALLOWED_ACE_TYPE; + aa_ace->flags = 0; + aa_ace->size = cpu_to_le16(0x18); + switch (sys_file_no) { + case FILE_MFT: case FILE_MFTMirr: + case FILE_LogFile: case FILE_AttrDef: + case FILE_Bitmap: case FILE_Boot: + case FILE_BadClus: case FILE_UpCase: + aa_ace->mask = SYNCHRONIZE | STANDARD_RIGHTS_READ | + FILE_READ_ATTRIBUTES | FILE_READ_EA | + FILE_READ_DATA; + break; + case FILE_Volume: case FILE_Secure: + case 0xb ... 0xf: + aa_ace->mask = SYNCHRONIZE | STANDARD_RIGHTS_READ | + FILE_WRITE_ATTRIBUTES | + FILE_READ_ATTRIBUTES | FILE_WRITE_EA | + FILE_READ_EA | FILE_APPEND_DATA | + FILE_WRITE_DATA | FILE_READ_DATA; + break; + } + aa_ace->sid.revision = 1; + aa_ace->sid.sub_authority_count = 2; + /* SECURITY_NT_SID_AUTHORITY (S-1-5) */ + aa_ace->sid.identifier_authority.value[0] = 0; + aa_ace->sid.identifier_authority.value[1] = 0; + aa_ace->sid.identifier_authority.value[2] = 0; + aa_ace->sid.identifier_authority.value[3] = 0; + aa_ace->sid.identifier_authority.value[4] = 0; + aa_ace->sid.identifier_authority.value[5] = 5; + aa_ace->sid.sub_authority[0] = + cpu_to_le32(SECURITY_BUILTIN_DOMAIN_RID); + aa_ace->sid.sub_authority[1] = + cpu_to_le32(DOMAIN_ALIAS_RID_ADMINS); + /* Now at offset 0x48 into the security descriptor. */ + } + /* As specified in the security descriptor, we now have the owner SID.*/ + sid = (SID*)((char*)sd + le32_to_cpu(sd->owner)); + sid->revision = 1; + sid->sub_authority_count = 2; + /* SECURITY_NT_SID_AUTHORITY (S-1-5) */ + sid->identifier_authority.value[0] = 0; + sid->identifier_authority.value[1] = 0; + sid->identifier_authority.value[2] = 0; + sid->identifier_authority.value[3] = 0; + sid->identifier_authority.value[4] = 0; + sid->identifier_authority.value[5] = 5; + sid->sub_authority[0] = cpu_to_le32(SECURITY_BUILTIN_DOMAIN_RID); + sid->sub_authority[1] = cpu_to_le32(DOMAIN_ALIAS_RID_ADMINS); + /* + * Now at offset 0x40 or 0x58 (root directory and the other system + * files, respectively) into the security descriptor, as specified in + * the security descriptor, we have the group SID. + */ + sid = (SID*)((char*)sd + le32_to_cpu(sd->group)); + sid->revision = 1; + sid->sub_authority_count = 2; + /* SECURITY_NT_SID_AUTHORITY (S-1-5) */ + sid->identifier_authority.value[0] = 0; + sid->identifier_authority.value[1] = 0; + sid->identifier_authority.value[2] = 0; + sid->identifier_authority.value[3] = 0; + sid->identifier_authority.value[4] = 0; + sid->identifier_authority.value[5] = 5; + sid->sub_authority[0] = cpu_to_le32(SECURITY_BUILTIN_DOMAIN_RID); + sid->sub_authority[1] = cpu_to_le32(DOMAIN_ALIAS_RID_ADMINS); +} + diff --git a/ntfsprogs/upcase.c b/ntfsprogs/upcase.c index e69de29b..a184379a 100644 --- a/ntfsprogs/upcase.c +++ b/ntfsprogs/upcase.c @@ -0,0 +1,82 @@ +/* + * $Id$ + * + * Copyright (c) 2001 Richard Russon + * Copyright (c) 2001-2002 Anton Altaparmakov + * + * Modified for mkntfs inclusion 9 June 2001 by Anton Altaparmakov. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program (in the main directory of the Linux-NTFS source + * in the file COPYING); if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include + +#include "types.h" + +void init_upcase_table(uchar_t *uc, u32 uc_len) +{ + static int uc_run_table[][3] = { /* Start, End, Add */ + {0x0061, 0x007B, -32}, {0x0451, 0x045D, -80}, {0x1F70, 0x1F72, 74}, + {0x00E0, 0x00F7, -32}, {0x045E, 0x0460, -80}, {0x1F72, 0x1F76, 86}, + {0x00F8, 0x00FF, -32}, {0x0561, 0x0587, -48}, {0x1F76, 0x1F78, 100}, + {0x0256, 0x0258, -205}, {0x1F00, 0x1F08, 8}, {0x1F78, 0x1F7A, 128}, + {0x028A, 0x028C, -217}, {0x1F10, 0x1F16, 8}, {0x1F7A, 0x1F7C, 112}, + {0x03AC, 0x03AD, -38}, {0x1F20, 0x1F28, 8}, {0x1F7C, 0x1F7E, 126}, + {0x03AD, 0x03B0, -37}, {0x1F30, 0x1F38, 8}, {0x1FB0, 0x1FB2, 8}, + {0x03B1, 0x03C2, -32}, {0x1F40, 0x1F46, 8}, {0x1FD0, 0x1FD2, 8}, + {0x03C2, 0x03C3, -31}, {0x1F51, 0x1F52, 8}, {0x1FE0, 0x1FE2, 8}, + {0x03C3, 0x03CC, -32}, {0x1F53, 0x1F54, 8}, {0x1FE5, 0x1FE6, 7}, + {0x03CC, 0x03CD, -64}, {0x1F55, 0x1F56, 8}, {0x2170, 0x2180, -16}, + {0x03CD, 0x03CF, -63}, {0x1F57, 0x1F58, 8}, {0x24D0, 0x24EA, -26}, + {0x0430, 0x0450, -32}, {0x1F60, 0x1F68, 8}, {0xFF41, 0xFF5B, -32}, + {0} + }; + static int uc_dup_table[][2] = { /* Start, End */ + {0x0100, 0x012F}, {0x01A0, 0x01A6}, {0x03E2, 0x03EF}, {0x04CB, 0x04CC}, + {0x0132, 0x0137}, {0x01B3, 0x01B7}, {0x0460, 0x0481}, {0x04D0, 0x04EB}, + {0x0139, 0x0149}, {0x01CD, 0x01DD}, {0x0490, 0x04BF}, {0x04EE, 0x04F5}, + {0x014A, 0x0178}, {0x01DE, 0x01EF}, {0x04BF, 0x04BF}, {0x04F8, 0x04F9}, + {0x0179, 0x017E}, {0x01F4, 0x01F5}, {0x04C1, 0x04C4}, {0x1E00, 0x1E95}, + {0x018B, 0x018B}, {0x01FA, 0x0218}, {0x04C7, 0x04C8}, {0x1EA0, 0x1EF9}, + {0} + }; + static int uc_byte_table[][2] = { /* Offset, Value */ + {0x00FF, 0x0178}, {0x01AD, 0x01AC}, {0x01F3, 0x01F1}, {0x0269, 0x0196}, + {0x0183, 0x0182}, {0x01B0, 0x01AF}, {0x0253, 0x0181}, {0x026F, 0x019C}, + {0x0185, 0x0184}, {0x01B9, 0x01B8}, {0x0254, 0x0186}, {0x0272, 0x019D}, + {0x0188, 0x0187}, {0x01BD, 0x01BC}, {0x0259, 0x018F}, {0x0275, 0x019F}, + {0x018C, 0x018B}, {0x01C6, 0x01C4}, {0x025B, 0x0190}, {0x0283, 0x01A9}, + {0x0192, 0x0191}, {0x01C9, 0x01C7}, {0x0260, 0x0193}, {0x0288, 0x01AE}, + {0x0199, 0x0198}, {0x01CC, 0x01CA}, {0x0263, 0x0194}, {0x0292, 0x01B7}, + {0x01A8, 0x01A7}, {0x01DD, 0x018E}, {0x0268, 0x0197}, + {0} + }; + int i, r; + + memset((char*)uc, 0, uc_len); + uc_len >>= 1; + for (i = 0; i < uc_len; i++) + uc[i] = i; + for (r = 0; uc_run_table[r][0]; r++) + for (i = uc_run_table[r][0]; i < uc_run_table[r][1]; i++) + uc[i] += uc_run_table[r][2]; + for (r = 0; uc_dup_table[r][0]; r++) + for (i = uc_dup_table[r][0]; i < uc_dup_table[r][1]; i += 2) + uc[i + 1]--; + for (r = 0; uc_byte_table[r][0]; r++) + uc[uc_byte_table[r][0]] = uc_byte_table[r][1]; +} +