📄 logfile.c
字号:
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); } ntfs_debug("Done."); if (wrp) *wrp = trp; else {err_out: 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. 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. * * Note that the MstProtected flag is not set on the $LogFile inode and hence * when reading pages they are not deprotected. This is because we do not know * 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, RESTART_PAGE_HEADER **rp){ 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, err; bool logfile_is_empty = true; u8 log_page_bits; ntfs_debug("Entering."); /* An empty $LogFile must have been clean before it got emptied. */ if (NVolLogFileEmpty(vol)) goto is_empty; size = i_size_read(log_vi); /* Make sure the file doesn't exceed the maximum allowed size. */ if (size > MaxLogFileSize) size = MaxLogFileSize; /* * Truncate size to a multiple of the page cache size or the default * log page size if the page cache size is between the default log page * log page size if the page cache size is between the default log page * size and twice that. */ if (PAGE_CACHE_SIZE >= DefaultLogPageSize && PAGE_CACHE_SIZE <= DefaultLogPageSize * 2) log_page_size = DefaultLogPageSize; else log_page_size = PAGE_CACHE_SIZE; log_page_mask = log_page_size - 1; /* * Use ntfs_ffs() instead of ffs() to enable the compiler to * optimize log_page_size and log_page_bits into constants. */ log_page_bits = ntfs_ffs(log_page_size) - 1; size &= ~(s64)(log_page_size - 1); /* * Ensure the log file is big enough to store at least the two restart * pages and the minimum number of log record pages. */ if (size < log_page_size * 2 || (size - log_page_size * 2) >> log_page_bits < MinLogRecordPages) { ntfs_error(vol->sb, "$LogFile is too small."); return false; } /* * Read through the file looking for a restart page. Since the restart * page header is at the beginning of a page we only need to search at * what could be the beginning of a page (for each page size) rather * than scanning the whole file byte by byte. If all potential places * contain empty and uninitialzed records, the log file can be assumed * to be empty. */ for (pos = 0; pos < size; pos <<= 1) { pgoff_t idx = pos >> PAGE_CACHE_SHIFT; if (!page || page->index != idx) { if (page) ntfs_unmap_page(page); page = ntfs_map_page(mapping, idx); if (IS_ERR(page)) { ntfs_error(vol->sb, "Error mapping $LogFile " "page (index %lu).", idx); goto err_out; } } kaddr = (u8*)page_address(page) + (pos & ~PAGE_CACHE_MASK); /* * A non-empty block means the logfile is not empty while an * empty block after a non-empty block has been encountered * means we are done. */ if (!ntfs_is_empty_recordp((le32*)kaddr)) logfile_is_empty = false; else if (!logfile_is_empty) break; /* * 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*)kaddr)) break; /* 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; } /* * 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_vi, (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; } /* * We have now found the second (modified by chkdsk) * restart page, so we can stop looking. */ break; } /* * 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 = NTFS_BLOCK_SIZE >> 1; } if (page) ntfs_unmap_page(page); if (logfile_is_empty) { NVolSetLogFileEmpty(vol);is_empty: ntfs_debug("Done. ($LogFile is empty.)"); return true; } 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_debug("Using second restart page as it is more " "recent."); ntfs_free(rstr1_ph); rstr1_ph = rstr2_ph; /* rstr1_lsn = rstr2_lsn; */ } else { ntfs_debug("Using first restart page as it is more " "recent."); ntfs_free(rstr2_ph); } rstr2_ph = NULL; } /* All consistency checks passed. */ if (rp) *rp = rstr1_ph; else ntfs_free(rstr1_ph); ntfs_debug("Done."); return true;err_out: if (rstr1_ph) ntfs_free(rstr1_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. * * At present we only look at the two restart pages and ignore the log record * pages. This is a little bit crude in that there will be a very small number * of cases where we think that a volume is dirty when in fact it is clean. * This should only affect volumes that have not been shutdown cleanly but did * not have any pending, non-check-pointed i/o, i.e. they were completely idle * at least for the five seconds preceeding the unclean shutdown. * * This function assumes that the $LogFile journal has already been consistency * checked by a call to ntfs_check_logfile() and in particular if the $LogFile * 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, const RESTART_PAGE_HEADER *rp){ ntfs_volume *vol = NTFS_SB(log_vi->i_sb); RESTART_AREA *ra; ntfs_debug("Entering."); /* An empty $LogFile must have been clean before it got emptied. */ if (NVolLogFileEmpty(vol)) { ntfs_debug("Done. ($LogFile is empty.)"); return true; } 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; } 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 * have the RESTART_VOLUME_IS_CLEAN bit set in the restart area flags, * we assume there was an unclean shutdown. */ if (ra->client_in_use_list != LOGFILE_NO_CLIENT && !(ra->flags & RESTART_VOLUME_IS_CLEAN)) { ntfs_debug("Done. $LogFile indicates a dirty shutdown."); return false; } /* $LogFile indicates a clean shutdown. */ ntfs_debug("Done. $LogFile indicates a clean shutdown."); return true;}/** * ntfs_empty_logfile - empty the contents of the $LogFile journal * @log_vi: struct inode of loaded journal $LogFile to empty * * Empty the contents of the $LogFile journal @log_vi and return 'true' on * success and 'false' on error. * * This function assumes that the $LogFile journal has already been consistency * checked by a call to ntfs_check_logfile() and that ntfs_is_logfile_clean() * has been used to ensure that the $LogFile is clean. */bool ntfs_empty_logfile(struct inode *log_vi){ VCN vcn, end_vcn; ntfs_inode *log_ni = NTFS_I(log_vi); ntfs_volume *vol = log_ni->vol; struct super_block *sb = vol->sb; runlist_element *rl; unsigned long flags; unsigned block_size, block_size_bits; int err; bool should_wait = true; ntfs_debug("Entering."); if (NVolLogFileEmpty(vol)) { ntfs_debug("Done."); return true; } /* * We cannot use ntfs_attr_set() because we may be still in the middle * of a mount operation. Thus we do the emptying by hand by first * zapping the page cache pages for the $LogFile/$DATA attribute and * then emptying each of the buffers in each of the clusters specified * by the runlist by hand. */ block_size = sb->s_blocksize; block_size_bits = sb->s_blocksize_bits; vcn = 0; read_lock_irqsave(&log_ni->size_lock, flags); end_vcn = (log_ni->initialized_size + vol->cluster_size_mask) >> vol->cluster_size_bits; read_unlock_irqrestore(&log_ni->size_lock, flags); truncate_inode_pages(log_vi->i_mapping, 0); down_write(&log_ni->runlist.lock); rl = log_ni->runlist.rl; if (unlikely(!rl || vcn < rl->vcn || !rl->length)) {map_vcn: err = ntfs_map_runlist_nolock(log_ni, vcn, NULL); if (err) { ntfs_error(sb, "Failed to map runlist fragment (error " "%d).", -err); goto err; } rl = log_ni->runlist.rl; BUG_ON(!rl || vcn < rl->vcn || !rl->length); } /* Seek to the runlist element containing @vcn. */ while (rl->length && vcn >= rl[1].vcn) rl++; do { LCN lcn; sector_t block, end_block; s64 len; /* * If this run is not mapped map it now and start again as the * runlist will have been updated. */ lcn = rl->lcn; if (unlikely(lcn == LCN_RL_NOT_MAPPED)) { vcn = rl->vcn; goto map_vcn; } /* If this run is not valid abort with an error. */ if (unlikely(!rl->length || lcn < LCN_HOLE)) goto rl_err; /* Skip holes. */ if (lcn == LCN_HOLE) continue; block = lcn << vol->cluster_size_bits >> block_size_bits; len = rl->length; if (rl[1].vcn > end_vcn) len = end_vcn - rl->vcn; end_block = (lcn + len) << vol->cluster_size_bits >> block_size_bits; /* Iterate over the blocks in the run and empty them. */ do { struct buffer_head *bh; /* Obtain the buffer, possibly not uptodate. */ bh = sb_getblk(sb, block); BUG_ON(!bh); /* Setup buffer i/o submission. */ lock_buffer(bh); bh->b_end_io = end_buffer_write_sync; get_bh(bh); /* Set the entire contents of the buffer to 0xff. */ memset(bh->b_data, -1, block_size); if (!buffer_uptodate(bh)) set_buffer_uptodate(bh); if (buffer_dirty(bh)) clear_buffer_dirty(bh); /* * Submit the buffer and wait for i/o to complete but * only for the first buffer so we do not miss really * serious i/o errors. Once the first buffer has * completed ignore errors afterwards as we can assume * that if one buffer worked all of them will work. */ submit_bh(WRITE, bh); if (should_wait) { should_wait = false; wait_on_buffer(bh); if (unlikely(!buffer_uptodate(bh))) goto io_err; } brelse(bh); } while (++block < end_block); } while ((++rl)->vcn < end_vcn); up_write(&log_ni->runlist.lock); /* * Zap the pages again just in case any got instantiated whilst we were * emptying the blocks by hand. FIXME: We may not have completed * writing to all the buffer heads yet so this may happen too early. * We really should use a kernel thread to do the emptying * asynchronously and then we can also set the volume dirty and output * an error message if emptying should fail. */ truncate_inode_pages(log_vi->i_mapping, 0); /* Set the flag so we do not have to do it again on remount. */ NVolSetLogFileEmpty(vol); ntfs_debug("Done."); return true;io_err: ntfs_error(sb, "Failed to write buffer. Unmount and run chkdsk."); goto dirty_err;rl_err: ntfs_error(sb, "Runlist is corrupt. Unmount and run chkdsk.");dirty_err: NVolSetErrors(vol); err = -EIO;err: up_write(&log_ni->runlist.lock); ntfs_error(sb, "Failed to fill $LogFile with 0xff bytes (error %d).", -err); return false;}#endif /* NTFS_RW */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -