diff options
Diffstat (limited to 'fs/ntfs/logfile.c')
-rw-r--r-- | fs/ntfs/logfile.c | 251 |
1 files changed, 136 insertions, 115 deletions
diff --git a/fs/ntfs/logfile.c b/fs/ntfs/logfile.c index 8edb8e20fb08..0173e95500d9 100644 --- a/fs/ntfs/logfile.c +++ b/fs/ntfs/logfile.c @@ -121,7 +121,7 @@ static BOOL ntfs_check_restart_page_header(struct inode *vi, */ 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."); @@ -312,10 +312,12 @@ err_out: * @vi: $LogFile inode to which the restart page belongs * @rp: restart page to check * @pos: position in @vi 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. @@ -323,25 +325,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(struct inode *vi, - RESTART_PAGE_HEADER *rp, s64 pos, RESTART_PAGE_HEADER **wrp) +static int ntfs_check_and_load_restart_page(struct inode *vi, + RESTART_PAGE_HEADER *rp, s64 pos, RESTART_PAGE_HEADER **wrp, + LSN *lsn) { RESTART_AREA *ra; RESTART_PAGE_HEADER *trp; - int size; - BOOL ret; + int size, err; ntfs_debug("Entering."); /* Check the restart page header for consistency. */ if (!ntfs_check_restart_page_header(vi, rp, pos)) { /* Error output already done inside the function. */ - return FALSE; + return -EINVAL; } /* Check the restart area for consistency. */ if (!ntfs_check_restart_area(vi, rp)) { /* Error output already done inside the function. */ - return FALSE; + return -EINVAL; } ra = (RESTART_AREA*)((u8*)rp + le16_to_cpu(rp->restart_area_offset)); /* @@ -352,7 +362,7 @@ static BOOL ntfs_check_and_load_restart_page(struct inode *vi, 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 @@ -379,6 +389,9 @@ static BOOL ntfs_check_and_load_restart_page(struct inode *vi, if (IS_ERR(page)) { ntfs_error(vi->i_sb, "Error mapping $LogFile " "page (index %lu).", idx); + err = PTR_ERR(page); + if (err != -EIO && err != -ENOMEM) + err = -EIO; goto err_out; } size = min_t(int, to_read, PAGE_CACHE_SIZE); @@ -392,29 +405,57 @@ static BOOL ntfs_check_and_load_restart_page(struct inode *vi, /* Perform the multi sector transfer deprotection on the buffer. */ if (post_read_mst_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(vi, 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(vi, trp); - if (ret && wrp) - *wrp = trp; - else - ntfs_free(trp); ntfs_debug("Done."); - return ret; + if (wrp) + *wrp = trp; + else { err_out: - ntfs_free(trp); - return FALSE; + ntfs_free(trp); + } + return err; } /** * ntfs_check_logfile - check the journal for consistency * @log_vi: struct inode 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. @@ -424,19 +465,18 @@ 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(struct inode *log_vi) +BOOL ntfs_check_logfile(struct inode *log_vi, RESTART_PAGE_HEADER **rp) { - s64 size, pos, rstr1_pos, rstr2_pos; + s64 size, pos; + LSN rstr1_lsn, rstr2_lsn; ntfs_volume *vol = NTFS_SB(log_vi->i_sb); struct address_space *mapping = log_vi->i_mapping; struct page *page = 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."); @@ -491,7 +531,7 @@ BOOL ntfs_check_logfile(struct inode *log_vi) if (IS_ERR(page)) { ntfs_error(vol->sb, "Error mapping $LogFile " "page (index %lu).", idx); - return FALSE; + goto err_out; } } kaddr = (u8*)page_address(page) + (pos & ~PAGE_CACHE_MASK); @@ -510,99 +550,95 @@ BOOL ntfs_check_logfile(struct inode *log_vi) */ 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*)kaddr)) { - 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*)kaddr)) { - /* 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; - } - rstr2_found = TRUE; - rstr2_pos = pos; - } /* - * Check the restart page for consistency and get a copy of the - * complete multi sector transfer deprotected restart page. + * Check the (modified by chkdsk) 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_vi, + err = ntfs_check_and_load_restart_page(log_vi, (RESTART_PAGE_HEADER*)kaddr, pos, - !pos ? &rstr1_ph : &rstr2_ph)) { - /* Error output already done inside the function. */ - goto err_out; + !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; + } + /* + * We have now found the second (modified by chkdsk) + * restart page, so we can stop looking. + */ + break; } /* - * 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) { + ntfs_unmap_page(page); + goto err_out; + } + /* Continue looking. */ if (!pos) - pos = le32_to_cpu(rstr1_ph->system_page_size) >> 1; + pos = NTFS_BLOCK_SIZE >> 1; } - if (page) { + if (page) ntfs_unmap_page(page); - page = NULL; - } if (logfile_is_empty) { NVolSetLogFileEmpty(vol); 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) { + BUG_ON(rstr2_ph); + ntfs_error(vol->sb, "Did not find any restart pages in " + "$LogFile and it was not empty."); + return FALSE; + } + /* 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) { + ntfs_free(rstr1_ph); + rstr1_ph = rstr2_ph; + /* rstr1_lsn = rstr2_lsn; */ + } else + ntfs_free(rstr2_ph); + rstr2_ph = NULL; } - /* - * 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; - } - ntfs_free(rstr1_ph); - ntfs_free(rstr2_ph); /* All consistency checks passed. */ + if (rp) + *rp = rstr1_ph; + else + ntfs_free(rstr1_ph); ntfs_debug("Done."); return TRUE; err_out: - if (page) - ntfs_unmap_page(page); if (rstr1_ph) ntfs_free(rstr1_ph); - if (rstr2_ph) - ntfs_free(rstr2_ph); return FALSE; } /** * ntfs_is_logfile_clean - check in the journal if the volume is clean * @log_vi: struct inode 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. @@ -619,11 +655,9 @@ 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(struct inode *log_vi) +BOOL ntfs_is_logfile_clean(struct inode *log_vi, const RESTART_PAGE_HEADER *rp) { ntfs_volume *vol = NTFS_SB(log_vi->i_sb); - struct page *page; - RESTART_PAGE_HEADER *rp; RESTART_AREA *ra; ntfs_debug("Entering."); @@ -632,24 +666,15 @@ BOOL ntfs_is_logfile_clean(struct inode *log_vi) ntfs_debug("Done. ($LogFile is empty.)"); return TRUE; } - /* - * 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. - */ - page = ntfs_map_page(log_vi->i_mapping, 0); - if (IS_ERR(page)) { - ntfs_error(vol->sb, "Error mapping $LogFile page (index 0)."); + BUG_ON(!rp); + 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; } - rp = (RESTART_PAGE_HEADER*)page_address(page); - 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; - } 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 @@ -659,15 +684,11 @@ BOOL ntfs_is_logfile_clean(struct inode *log_vi) 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; } - ntfs_unmap_page(page); /* $LogFile indicates a clean shutdown. */ ntfs_debug("Done. $LogFile indicates a clean shutdown."); return TRUE; -err_out: - ntfs_unmap_page(page); - return FALSE; } /** |