From 24de91b8faa3b760a73a5cfa0910b242f65a9b44 Mon Sep 17 00:00:00 2001 From: szaka Date: Sat, 24 Sep 2005 15:59:53 +0000 Subject: [PATCH] - Support journals which have been modified by chkdsk. (Anton, Szaka) - Support journals ($LogFile) with only one restart page as well as journals with two different restart pages. (Anton, Szaka) --- ChangeLog | 3 + include/ntfs/logfile.h | 6 +- libntfs/logfile.c | 267 +++++++++++++++++++++++------------------ libntfs/volume.c | 7 +- 4 files changed, 158 insertions(+), 125 deletions(-) diff --git a/ChangeLog b/ChangeLog index b024320d..788fb6b4 100644 --- a/ChangeLog +++ b/ChangeLog @@ -42,6 +42,9 @@ xx/xx/2005 - 1.12.0-WIP libntfs/volume.c. Needed to compile on DOS with djgpp. (Christophe) - Switch ntfsmount to use getopt_long. (Yuval, minor fixes by Yura) - ntfsmount: Fix 'df' output. (Yuval) + - Support journals which have been modified by chkdsk. (Anton, Szaka) + - Support journals ($LogFile) with only one restart page as well as + journals with two different restart pages. (Anton, Szaka) 08/08/2005 - 1.11.2 - ntfsdecrypt now works and lots of fixes and improvements. diff --git a/include/ntfs/logfile.h b/include/ntfs/logfile.h index 0fde8d13..4e361e2f 100644 --- a/include/ntfs/logfile.h +++ b/include/ntfs/logfile.h @@ -1,7 +1,7 @@ /* * logfile.h - Exports for $LogFile handling. Part of the Linux-NTFS project. * - * Copyright (c) 2000-2004 Anton Altaparmakov + * Copyright (c) 2000-2005 Anton Altaparmakov * * This program/include file is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as published @@ -376,8 +376,8 @@ typedef struct { } __attribute__((__packed__)) lcn_list[0]; } __attribute__ ((__packed__)) LOG_RECORD; -extern BOOL ntfs_check_logfile(ntfs_attr *log_na); -extern BOOL ntfs_is_logfile_clean(ntfs_attr *log_na); +extern BOOL ntfs_check_logfile(ntfs_attr *log_na, RESTART_PAGE_HEADER **rp); +extern BOOL ntfs_is_logfile_clean(ntfs_attr *log_na, RESTART_PAGE_HEADER *rp); extern int ntfs_empty_logfile(ntfs_attr *na); #endif /* defined _NTFS_LOGFILE_H */ diff --git a/libntfs/logfile.c b/libntfs/logfile.c index 0c2a9112..94e156ad 100644 --- a/libntfs/logfile.c +++ b/libntfs/logfile.c @@ -112,7 +112,7 @@ static BOOL ntfs_check_restart_page_header(RESTART_PAGE_HEADER *rp, s64 pos) */ if (!ntfs_is_chkd_record(rp->magic) && sle64_to_cpu(rp->chkdsk_lsn)) { ntfs_error(vi->i_sb, "$LogFile restart page is not modified " - "chkdsk but a chkdsk LSN is specified."); + "by chkdsk but a chkdsk LSN is specified."); return FALSE; } ntfs_debug("Done."); @@ -301,10 +301,12 @@ err_out: * @log_na: opened ntfs attribute for journal $LogFile * @rp: restart page to check * @pos: position in @log_na at which the restart page resides - * @wrp: copy of the multi sector transfer deprotected restart page + * @wrp: [OUT] copy of the multi sector transfer deprotected restart page + * @lsn: [OUT] set to the current logfile lsn on success * - * Check the restart page @rp for consistency and return TRUE if it is - * consistent and FALSE otherwise. + * Check the restart page @rp for consistency and return 0 if it is consistent + * and errno otherwise. The restart page may have been modified by chkdsk in + * which case its magic is CHKD instead of RSTR. * * This function only needs NTFS_BLOCK_SIZE bytes in @rp, i.e. it does not * require the full restart page. @@ -312,24 +314,33 @@ err_out: * If @wrp is not NULL, on success, *@wrp will point to a buffer containing a * copy of the complete multi sector transfer deprotected page. On failure, * *@wrp is undefined. + * + * Simillarly, if @lsn is not NULL, on succes *@lsn will be set to the current + * logfile lsn according to this restart page. On failure, *@lsn is undefined. + * + * The following error codes are defined: + * EINVAL - The restart page is inconsistent. + * ENOMEM - Not enough memory to load the restart page. + * EIO - Failed to reading from $LogFile. */ -static BOOL ntfs_check_and_load_restart_page(ntfs_attr *log_na, - RESTART_PAGE_HEADER *rp, s64 pos, RESTART_PAGE_HEADER **wrp) +static int ntfs_check_and_load_restart_page(ntfs_attr *log_na, + RESTART_PAGE_HEADER *rp, s64 pos, RESTART_PAGE_HEADER **wrp, + LSN *lsn) { RESTART_AREA *ra; RESTART_PAGE_HEADER *trp; - BOOL ret; + int err; ntfs_debug("Entering."); /* Check the restart page header for consistency. */ if (!ntfs_check_restart_page_header(rp, pos)) { /* Error output already done inside the function. */ - return FALSE; + return EINVAL; } /* Check the restart area for consistency. */ if (!ntfs_check_restart_area(rp)) { /* Error output already done inside the function. */ - return FALSE; + return EINVAL; } ra = (RESTART_AREA*)((u8*)rp + le16_to_cpu(rp->restart_area_offset)); /* @@ -340,7 +351,7 @@ static BOOL ntfs_check_and_load_restart_page(ntfs_attr *log_na, if (!trp) { ntfs_error(vi->i_sb, "Failed to allocate memory for $LogFile " "restart page buffer."); - return FALSE; + return ENOMEM; } /* * Read the whole of the restart page into the buffer. If it fits @@ -353,36 +364,67 @@ static BOOL ntfs_check_and_load_restart_page(ntfs_attr *log_na, if (ntfs_attr_pread(log_na, pos, le32_to_cpu( rp->system_page_size), trp) != le32_to_cpu(rp->system_page_size)) { + err = errno; ntfs_error(, "Failed to read whole restart page into " "the buffer."); + if (err != ENOMEM) + err = EIO; goto err_out; } /* Perform the multi sector transfer deprotection on the buffer. */ if (ntfs_mst_post_read_fixup((NTFS_RECORD*)trp, le32_to_cpu(rp->system_page_size))) { - ntfs_error(vi->i_sb, "Multi sector transfer error detected in " - "$LogFile restart page."); - goto err_out; + /* + * A multi sector tranfer error was detected. We only need to + * abort if the restart page contents exceed the multi sector + * transfer fixup of the first sector. + */ + if (le16_to_cpu(rp->restart_area_offset) + + le16_to_cpu(ra->restart_area_length) > + NTFS_BLOCK_SIZE - sizeof(u16)) { + ntfs_error(vi->i_sb, "Multi sector transfer error " + "detected in $LogFile restart page."); + err = EINVAL; + goto err_out; + } + } + /* + * If the restart page is modified by chkdsk or there are no active + * logfile clients, the logfile is consistent. Otherwise, need to + * check the log client records for consistency, too. + */ + err = 0; + if (ntfs_is_rstr_record(rp->magic) && + ra->client_in_use_list != LOGFILE_NO_CLIENT) { + if (!ntfs_check_log_client_array(trp)) { + err = EINVAL; + goto err_out; + } + } + if (lsn) { + if (ntfs_is_rstr_record(rp->magic)) + *lsn = sle64_to_cpu(ra->current_lsn); + else /* if (ntfs_is_chkd_record(rp->magic)) */ + *lsn = sle64_to_cpu(rp->chkdsk_lsn); } - /* Check the log client records for consistency. */ - ret = ntfs_check_log_client_array(trp); - if (ret && wrp) - *wrp = trp; - else - free(trp); ntfs_debug("Done."); - return ret; + if (wrp) + *wrp = trp; + else { err_out: - free(trp); - return FALSE; + free(trp); + } + return err; } /** * ntfs_check_logfile - check in the journal if the volume is consistent * @log_na: ntfs attribute of loaded journal $LogFile to check + * @rp: [OUT] on success this is a copy of the current restart page * * Check the $LogFile journal for consistency and return TRUE if it is - * consistent and FALSE if not. + * consistent and FALSE if not. On success, the current restart page is + * returned in *@rp. Caller must call ntfs_free(*@rp) when finished with it. * * At present we only check the two restart pages and ignore the log record * pages. @@ -392,17 +434,16 @@ err_out: * if the $LogFile was created on a system with a different page size to ours * yet and mst deprotection would fail if our page size is smaller. */ -BOOL ntfs_check_logfile(ntfs_attr *log_na) +BOOL ntfs_check_logfile(ntfs_attr *log_na, RESTART_PAGE_HEADER **rp) { - s64 size, pos, rstr1_pos, rstr2_pos; + s64 size, pos; + LSN rstr1_lsn, rstr2_lsn; ntfs_volume *vol = log_na->ni->vol; - u8 *buf = NULL; + u8 *kaddr = NULL; RESTART_PAGE_HEADER *rstr1_ph = NULL; RESTART_PAGE_HEADER *rstr2_ph = NULL; - int log_page_size, log_page_mask, ofs; + int log_page_size, log_page_mask, err; BOOL logfile_is_empty = TRUE; - BOOL rstr1_found = FALSE; - BOOL rstr2_found = FALSE; u8 log_page_bits; ntfs_debug("Entering."); @@ -432,8 +473,8 @@ BOOL ntfs_check_logfile(ntfs_attr *log_na) return FALSE; } /* Allocate memory for restart page. */ - buf = malloc(NTFS_BLOCK_SIZE); - if (!buf) { + kaddr = malloc(NTFS_BLOCK_SIZE); + if (!kaddr) { ntfs_error(, "Not enough memory."); return FALSE; } @@ -449,7 +490,7 @@ BOOL ntfs_check_logfile(ntfs_attr *log_na) /* * Read first NTFS_BLOCK_SIZE bytes of potential restart page. */ - if (ntfs_attr_pread(log_na, pos, NTFS_BLOCK_SIZE, buf) != + if (ntfs_attr_pread(log_na, pos, NTFS_BLOCK_SIZE, kaddr) != NTFS_BLOCK_SIZE) { ntfs_error(, "Failed to read first NTFS_BLOCK_SIZE " "bytes of potential restart page."); @@ -461,7 +502,7 @@ BOOL ntfs_check_logfile(ntfs_attr *log_na) * empty block after a non-empty block has been encountered * means we are done. */ - if (!ntfs_is_empty_recordp((le32*)buf)) + if (!ntfs_is_empty_recordp((le32*)kaddr)) logfile_is_empty = FALSE; else if (!logfile_is_empty) break; @@ -469,59 +510,53 @@ BOOL ntfs_check_logfile(ntfs_attr *log_na) * A log record page means there cannot be a restart page after * this so no need to continue searching. */ - if (ntfs_is_rcrd_recordp((le32*)buf)) + if (ntfs_is_rcrd_recordp((le32*)kaddr)) break; - /* - * A modified by chkdsk restart page means we cannot handle - * this log file. - */ - if (ntfs_is_chkd_recordp((le32*)buf)) { - ntfs_error(vol->sb, "$LogFile has been modified by " - "chkdsk. Mount this volume in " - "Windows."); - goto err_out; - } - /* If not a restart page, continue. */ - if (!ntfs_is_rstr_recordp((le32*)buf)) { - /* Skip to the minimum page size for the next one. */ + /* If not a (modified by chkdsk) restart page, continue. */ + if (!ntfs_is_rstr_recordp((le32*)kaddr) && + !ntfs_is_chkd_recordp((le32*)kaddr)) { if (!pos) pos = NTFS_BLOCK_SIZE >> 1; continue; } - /* We now know we have a restart page. */ - if (!pos) { - rstr1_found = TRUE; - rstr1_pos = pos; - } else { - if (rstr2_found) { - ntfs_error(vol->sb, "Found more than two " - "restart pages in $LogFile."); - goto err_out; + /* + * Check the (modified by chkdsk) restart page for consistency + * and get a copy of the complete multi sector transfer + * deprotected restart page. + */ + err = ntfs_check_and_load_restart_page(log_na, + (RESTART_PAGE_HEADER*)kaddr, pos, + !rstr1_ph ? &rstr1_ph : &rstr2_ph, + !rstr1_ph ? &rstr1_lsn : &rstr2_lsn); + if (!err) { + /* + * If we have now found the first (modified by chkdsk) + * restart page, continue looking for the second one. + */ + if (!pos) { + pos = NTFS_BLOCK_SIZE >> 1; + continue; } - rstr2_found = TRUE; - rstr2_pos = pos; + /* + * We have now found the second (modified by chkdsk) + * restart page, so we can stop looking. + */ + break; } /* - * Check the restart page for consistency and get a copy of the - * complete multi sector transfer deprotected restart page. - */ - if (!ntfs_check_and_load_restart_page(log_na, - (RESTART_PAGE_HEADER*)buf, pos, - !pos ? &rstr1_ph : &rstr2_ph)) { - /* Error output already done inside the function. */ - goto err_out; - } - /* - * We have a valid restart page. The next one must be after - * a whole system page size as specified by the valid restart - * page. + * Error output already done inside the function. Note, we do + * not abort if the restart page was invalid as we might still + * find a valid one further in the file. */ + if (err != EINVAL) + goto err_out; + /* Continue looking. */ if (!pos) - pos = le32_to_cpu(rstr1_ph->system_page_size) >> 1; + pos = NTFS_BLOCK_SIZE >> 1; } - if (buf) { - free(buf); - buf = NULL; + if (kaddr) { + free(kaddr); + kaddr = NULL; } if (logfile_is_empty) { NVolSetLogFileEmpty(vol); @@ -529,31 +564,37 @@ is_empty: ntfs_debug("Done. ($LogFile is empty.)"); return TRUE; } - if (!rstr1_found || !rstr2_found) { - ntfs_error(vol->sb, "Did not find two restart pages in " - "$LogFile."); - goto err_out; + if (!rstr1_ph) { + if (rstr2_ph) + ntfs_error(vol->sb, "BUG: rstr2_ph isn't NULL!"); + ntfs_error(vol->sb, "Did not find any restart pages in " + "$LogFile and it was not empty."); + return FALSE; } - /* - * The two restart areas must be identical except for the update - * sequence number. - */ - ofs = le16_to_cpu(rstr1_ph->usa_ofs); - if (memcmp(rstr1_ph, rstr2_ph, ofs) || (ofs += sizeof(u16), - memcmp((u8*)rstr1_ph + ofs, (u8*)rstr2_ph + ofs, - le32_to_cpu(rstr1_ph->system_page_size) - ofs))) { - ntfs_error(vol->sb, "The two restart pages in $LogFile do not " - "match."); - goto err_out; + /* If both restart pages were found, use the more recent one. */ + if (rstr2_ph) { + /* + * If the second restart area is more recent, switch to it. + * Otherwise just throw it away. + */ + if (rstr2_lsn > rstr1_lsn) { + free(rstr1_ph); + rstr1_ph = rstr2_ph; + /* rstr1_lsn = rstr2_lsn; */ + } else + free(rstr2_ph); + rstr2_ph = NULL; } - free(rstr1_ph); - free(rstr2_ph); /* All consistency checks passed. */ + if (rp) + *rp = rstr1_ph; + else + free(rstr1_ph); ntfs_debug("Done."); return TRUE; err_out: - if (buf) - free(buf); + if (kaddr) + free(kaddr); if (rstr1_ph) free(rstr1_ph); if (rstr2_ph) @@ -564,6 +605,7 @@ err_out: /** * ntfs_is_logfile_clean - check in the journal if the volume is clean * @log_na: ntfs attribute of loaded journal $LogFile to check + * @rp: copy of the current restart page * * Analyze the $LogFile journal and return TRUE if it indicates the volume was * shutdown cleanly and FALSE if not. @@ -580,9 +622,8 @@ err_out: * is empty this function requires that NVolLogFileEmpty() is true otherwise an * empty volume will be reported as dirty. */ -BOOL ntfs_is_logfile_clean(ntfs_attr *log_na) +BOOL ntfs_is_logfile_clean(ntfs_attr *log_na, RESTART_PAGE_HEADER *rp) { - RESTART_PAGE_HEADER *rp; RESTART_AREA *ra; ntfs_debug("Entering."); @@ -591,29 +632,19 @@ BOOL ntfs_is_logfile_clean(ntfs_attr *log_na) ntfs_debug("Done. ($LogFile is empty.)"); return TRUE; } - /* Allocate memory. */ - rp = malloc(NTFS_BLOCK_SIZE); if (!rp) { - ntfs_error(, "Not enough memory."); + ntfs_error(, "Restart page header is NULL."); return FALSE; } - /* - * Read the first restart page. It will be possibly incomplete and - * will not be multi sector transfer deprotected but we only need the - * first NTFS_BLOCK_SIZE bytes so it does not matter. - */ - if (ntfs_attr_pread(log_na, 0, NTFS_BLOCK_SIZE, rp) != - NTFS_BLOCK_SIZE) { - ntfs_error(, "Failed to read first restart page."); - goto err_out; - } - if (!ntfs_is_rstr_record(rp->magic)) { - ntfs_error(vol->sb, "No restart page found at offset zero in " - "$LogFile. This is probably a bug in that " - "the $LogFile should have been consistency " - "checked before calling this function."); - goto err_out; + if (!ntfs_is_rstr_record(rp->magic) && + !ntfs_is_chkd_record(rp->magic)) { + ntfs_error(vol->sb, "Restart page buffer is invalid. This is " + "probably a bug in that the $LogFile should " + "have been consistency checked before calling " + "this function."); + return FALSE; } + ra = (RESTART_AREA*)((u8*)rp + le16_to_cpu(rp->restart_area_offset)); /* * If the $LogFile has active clients, i.e. it is open, and we do not @@ -623,15 +654,11 @@ BOOL ntfs_is_logfile_clean(ntfs_attr *log_na) if (ra->client_in_use_list != LOGFILE_NO_CLIENT && !(ra->flags & RESTART_VOLUME_IS_CLEAN)) { ntfs_debug("Done. $LogFile indicates a dirty shutdown."); - goto err_out; + return FALSE; } - free(rp); /* $LogFile indicates a clean shutdown. */ ntfs_debug("Done. $LogFile indicates a clean shutdown."); return TRUE; -err_out: - free(rp); - return FALSE; } /** diff --git a/libntfs/volume.c b/libntfs/volume.c index 2142ce44..f5ddff7a 100644 --- a/libntfs/volume.c +++ b/libntfs/volume.c @@ -1,7 +1,7 @@ /* * volume.c - NTFS volume handling code. Part of the Linux-NTFS project. * - * Copyright (c) 2000-2004 Anton Altaparmakov + * Copyright (c) 2000-2005 Anton Altaparmakov * Copyright (c) 2002-2005 Szabolcs Szakacsits * * This program/include file is free software; you can redistribute it and/or @@ -566,6 +566,7 @@ static int ntfs_volume_check_logfile(ntfs_volume *vol) { ntfs_inode *ni; ntfs_attr *na = NULL; + RESTART_PAGE_HEADER *rp = NULL; int err = 0; if ((ni = ntfs_inode_open(vol, FILE_LogFile)) == NULL) { @@ -578,8 +579,10 @@ static int ntfs_volume_check_logfile(ntfs_volume *vol) err = EIO; goto exit; } - if (!ntfs_check_logfile(na) || !ntfs_is_logfile_clean(na)) + if (!ntfs_check_logfile(na, &rp) || !ntfs_is_logfile_clean(na, rp)) err = EOPNOTSUPP; + if (rp) + free(rp); exit: if (na) ntfs_attr_close(na);