/* Two copies of these will exist at the beginning of the log file */ struct RESTART_AREA {
__le64 current_lsn; // 0x00: Current logical end of log file.
__le16 log_clients; // 0x08: Maximum number of clients.
__le16 client_idx[2]; // 0x0A: Free/use index into the client record arrays.
__le16 flags; // 0x0E: See RESTART_SINGLE_PAGE_IO.
__le32 seq_num_bits; // 0x10: The number of bits in sequence number.
__le16 ra_len; // 0x14:
__le16 client_off; // 0x16:
__le64 l_size; // 0x18: Usable log file size.
__le32 last_lsn_data_len; // 0x20:
__le16 rec_hdr_len; // 0x24: Log page data offset.
__le16 data_off; // 0x26: Log page data length.
__le32 open_log_count; // 0x28:
__le32 align[5]; // 0x2C: struct CLIENT_REC clients[]; // 0x40:
};
/* * One entry exists in the Dirty Pages Table for each page which is dirty at * the time the Restart Area is written.
*/ struct DIR_PAGE_ENTRY {
__le32 next; // 0x00: RESTART_ENTRY_ALLOCATED if allocated
__le32 target_attr; // 0x04: Index into the Open attribute Table
__le32 transfer_len; // 0x08:
__le32 lcns_follow; // 0x0C:
__le64 vcn; // 0x10: Vcn of dirty page
__le64 oldest_lsn; // 0x18:
__le64 page_lcns[]; // 0x20:
};
struct LFS_RECORD {
__le16 next_record_off; // 0x00: Offset of the free space in the page,
u8 align[6]; // 0x02:
__le64 last_end_lsn; // 0x08: lsn for the last log record which ends on the page,
};
static_assert(sizeof(struct LFS_RECORD) == 0x10);
struct RECORD_PAGE_HDR { struct NTFS_RECORD_HEADER rhdr; // 'RCRD'
__le32 rflags; // 0x10: See LOG_PAGE_LOG_RECORD_END
__le16 page_count; // 0x14:
__le16 page_pos; // 0x16: struct LFS_RECORD record_hdr; // 0x18:
__le16 fixups[10]; // 0x28:
__le32 file_off; // 0x3c: Used when major version >= 2
};
// clang-format on
// Page contains the end of a log record. #define LOG_PAGE_LOG_RECORD_END cpu_to_le32(0x00000001)
/* * Array for log records which require a target attribute. * A true indicates that the corresponding restart operation * requires a target attribute.
*/ staticconst u8 AttributeRequired[] = {
0xFC, 0xFB, 0xFF, 0x10, 0x06,
};
staticinlinebool can_skip_action(enum NTFS_LOG_OPERATION op)
{ switch (op) { case Noop: case DeleteDirtyClusters: case HotFix: case EndTopLevelAction: case PrepareTransaction: case CommitTransaction: case ForgetTransaction: case CompensationLogRecord: case OpenNonresidentAttribute: case OpenAttributeTableDump: case AttributeNamesDump: case DirtyPageTableDump: case TransactionTableDump: returntrue; default: returnfalse;
}
}
struct lcb { struct LFS_RECORD_HDR *lrh; // Log record header of the current lsn. struct LOG_REC_HDR *log_rec;
u32 ctx_mode; // lcb_ctx_undo_next/lcb_ctx_prev/lcb_ctx_next struct CLIENT_ID client; bool alloc; // If true the we should deallocate 'log_rec'.
};
ra = Add2Ptr(rhdr, ro);
cl = le16_to_cpu(ra->log_clients);
if (cl > 1) returnfalse;
off = le16_to_cpu(ra->client_off);
if (!IS_ALIGNED(off, 8) || ro + off > SECTOR_SIZE - sizeof(short)) returnfalse;
off += cl * sizeof(struct CLIENT_REC);
if (off > sys_page) returnfalse;
/* * Check the restart length field and whether the entire * restart area is contained that length.
*/ if (le16_to_cpu(rhdr->ra_off) + le16_to_cpu(ra->ra_len) > sys_page ||
off > le16_to_cpu(ra->ra_len)) { returnfalse;
}
/* * As a final check make sure that the use list and the free list * are either empty or point to a valid client.
*/
fl = le16_to_cpu(ra->client_idx[0]);
ul = le16_to_cpu(ra->client_idx[1]); if ((fl != LFS_NO_CLIENT && fl >= cl) ||
(ul != LFS_NO_CLIENT && ul >= cl)) returnfalse;
/* Make sure the sequence number bits match the log file size. */
l_size = le64_to_cpu(ra->l_size);
if (seq_bits != le32_to_cpu(ra->seq_num_bits)) returnfalse;
/* The log page data offset and record header length must be quad-aligned. */ if (!IS_ALIGNED(le16_to_cpu(ra->data_off), 8) ||
!IS_ALIGNED(le16_to_cpu(ra->rec_hdr_len), 8)) returnfalse;
/* Find the start of the client array. */
ca = Add2Ptr(ra, le16_to_cpu(ra->client_off));
/* * Start with the free list. * Check that all the clients are valid and that there isn't a cycle. * Do the in-use list on the second pass.
*/ for (i = 0; i < 2; i++) {
u16 client_idx = le16_to_cpu(ra->client_idx[i]); bool first_client = true;
u16 clients = le16_to_cpu(ra->log_clients);
while (client_idx != LFS_NO_CLIENT) { conststruct CLIENT_REC *cr;
if (!clients ||
client_idx >= le16_to_cpu(ra->log_clients)) returnfalse;
clients -= 1;
cr = ca + client_idx;
client_idx = le16_to_cpu(cr->next_client);
if (first_client) {
first_client = false; if (cr->prev_client != LFS_NO_CLIENT_LE) returnfalse;
}
}
}
returntrue;
}
/* * remove_client * * Remove a client record from a client record list an restart area.
*/ staticinlinevoid remove_client(struct CLIENT_REC *ca, conststruct CLIENT_REC *cr, __le16 *head)
{ if (cr->prev_client == LFS_NO_CLIENT_LE)
*head = cr->next_client; else
ca[le16_to_cpu(cr->prev_client)].next_client = cr->next_client;
if (cr->next_client != LFS_NO_CLIENT_LE)
ca[le16_to_cpu(cr->next_client)].prev_client = cr->prev_client;
}
/* * add_client - Add a client record to the start of a list.
*/ staticinlinevoid add_client(struct CLIENT_REC *ca, u16 index, __le16 *head)
{ struct CLIENT_REC *cr = ca + index;
if (*head != LFS_NO_CLIENT_LE)
ca[le16_to_cpu(*head)].prev_client = cpu_to_le16(index);
*head = cpu_to_le16(index);
}
/* * Enumerate restart table. * * @t - table to enumerate. * @c - current enumerated element. * * enumeration starts with @c == NULL * returns next element or NULL
*/ staticinlinevoid *enum_rstbl(struct RESTART_TABLE *t, void *c)
{
__le32 *e;
u32 bprt;
u16 rsize;
if (!t) return NULL;
rsize = le16_to_cpu(t->size);
if (!c) { /* start enumeration. */ if (!t->total) return NULL;
e = Add2Ptr(t, sizeof(struct RESTART_TABLE));
} else {
e = Add2Ptr(c, rsize);
}
/* Loop until we hit the first one allocated, or the end of the list. */ for (bprt = bytes_per_rt(t); PtrOffset(t, e) < bprt;
e = Add2Ptr(e, rsize)) { if (*e == RESTART_ENTRY_ALLOCATED_LE) return e;
} return NULL;
}
/* * find_dp - Search for a @vcn in Dirty Page Table.
*/ staticinlinestruct DIR_PAGE_ENTRY *find_dp(struct RESTART_TABLE *dptbl,
u32 target_attr, u64 vcn)
{
__le32 ta = cpu_to_le32(target_attr); struct DIR_PAGE_ENTRY *dp = NULL;
while ((dp = enum_rstbl(dptbl, dp))) {
u64 dp_vcn = le64_to_cpu(dp->vcn);
/* * Verify each entry is either allocated or points * to a valid offset the table.
*/ for (i = 0; i < ne; i++) {
off = le32_to_cpu(*(__le32 *)Add2Ptr(
rt, i * rsize + sizeof(struct RESTART_TABLE)));
/* * Walk through the list headed by the first entry to make * sure none of the entries are currently being used.
*/ for (off = ff; off;) { if (off == RESTART_ENTRY_ALLOCATED) returnfalse;
off = le32_to_cpu(*(__le32 *)Add2Ptr(rt, off));
if (off > ts - sizeof(__le32)) returnfalse;
}
returntrue;
}
/* * free_rsttbl_idx - Free a previously allocated index a Restart Table.
*/ staticinlinevoid free_rsttbl_idx(struct RESTART_TABLE *rt, u32 off)
{
__le32 *e;
u32 lf = le32_to_cpu(rt->last_free);
__le32 off_le = cpu_to_le32(off);
e = Add2Ptr(rt, off);
if (off < le32_to_cpu(rt->free_goal)) {
*e = rt->first_free;
rt->first_free = off_le; if (!lf)
rt->last_free = off_le;
} else { if (lf)
*(__le32 *)Add2Ptr(rt, lf) = off_le; else
rt->first_free = off_le;
/* * alloc_rsttbl_idx * * Allocate an index from within a previously initialized Restart Table.
*/ staticinlinevoid *alloc_rsttbl_idx(struct RESTART_TABLE **tbl)
{
u32 off;
__le32 *e; struct RESTART_TABLE *t = *tbl;
if (!t->first_free) {
*tbl = t = extend_rsttbl(t, 16, ~0u); if (!t) return NULL;
}
off = le32_to_cpu(t->first_free);
/* Dequeue this entry and zero it. */
e = Add2Ptr(t, off);
t->first_free = *e;
memset(e, 0, le16_to_cpu(t->size));
*e = RESTART_ENTRY_ALLOCATED_LE;
/* If list is going empty, then we fix the last_free as well. */ if (!t->first_free)
t->last_free = 0;
le16_add_cpu(&t->total, 1);
return Add2Ptr(t, off);
}
/* * alloc_rsttbl_from_idx * * Allocate a specific index from within a previously initialized Restart Table.
*/ staticinlinevoid *alloc_rsttbl_from_idx(struct RESTART_TABLE **tbl, u32 vbo)
{
u32 off;
__le32 *e; struct RESTART_TABLE *rt = *tbl;
u32 bytes = bytes_per_rt(rt);
u16 esize = le16_to_cpu(rt->size);
/* If the entry is not the table, we will have to extend the table. */ if (vbo >= bytes) { /* * Extend the size by computing the number of entries between * the existing size and the desired index and adding 1 to that.
*/
u32 bytes2idx = vbo - bytes;
/* * There should always be an integral number of entries * being added. Now extend the table.
*/
*tbl = rt = extend_rsttbl(rt, bytes2idx / esize + 1, bytes); if (!rt) return NULL;
}
/* See if the entry is already allocated, and just return if it is. */
e = Add2Ptr(rt, vbo);
if (*e == RESTART_ENTRY_ALLOCATED_LE) return e;
/* * Walk through the table, looking for the entry we're * interested and the previous entry.
*/
off = le32_to_cpu(rt->first_free);
e = Add2Ptr(rt, off);
if (off == vbo) { /* this is a match */
rt->first_free = *e; goto skip_looking;
}
/* * Need to walk through the list looking for the predecessor * of our entry.
*/ for (;;) { /* Remember the entry just found */
u32 last_off = off;
__le32 *last_e = e;
/* Should never run of entries. */
/* Lookup up the next entry the list. */
off = le32_to_cpu(*last_e);
e = Add2Ptr(rt, off);
/* If this is our match we are done. */ if (off == vbo) {
*last_e = *e;
/* * If this was the last entry, we update that * table as well.
*/ if (le32_to_cpu(rt->last_free) == off)
rt->last_free = cpu_to_le32(last_off); break;
}
}
skip_looking: /* If the list is now empty, we fix the last_free as well. */ if (!rt->first_free)
rt->last_free = 0;
/* Zero this entry. */
memset(e, 0, esize);
*e = RESTART_ENTRY_ALLOCATED_LE;
struct RESTART_AREA *ra; /* In-memory image of the next restart area. */
u32 ra_size; /* The usable size of the restart area. */
/* * If true, then the in-memory restart area is to be written * to the first position on the disk.
*/ bool init_ra; bool set_dirty; /* True if we need to set dirty flag. */
/* Compute the offset in the log file of the next log page. */ staticinline u32 next_page_off(struct ntfs_log *log, u32 off)
{
off = (off & ~log->sys_page_mask) + log->page_size; return off >= log->l_size ? log->first_page : off;
}
if (usa_error)
*usa_error = bBAAD; /* Check that the update sequence array for this page is valid */ /* If we don't allow errors, raise an error status */ elseif (bBAAD)
err = -EINVAL;
/* * log_read_rst * * It walks through 512 blocks of the file looking for a valid * restart page header. It will stop the first time we find a * valid page header.
*/ staticint log_read_rst(struct ntfs_log *log, bool first, struct restart_info *info)
{
u32 skip;
u64 vbo; struct RESTART_HDR *r_page = NULL;
/* Determine which restart area we are looking for. */ if (first) {
vbo = 0;
skip = 512;
} else {
vbo = 512;
skip = 0;
}
/* Read a page header at the current offset. */ if (read_log_page(log, vbo, (struct RECORD_PAGE_HDR **)&r_page,
&usa_error)) { /* Ignore any errors. */ continue;
}
/* Exit if the signature is a log record page. */ if (r_page->rhdr.sign == NTFS_RCRD_SIGNATURE) {
info->initialized = true; break;
}
if (!bchk && !brst) { if (r_page->rhdr.sign != NTFS_FFFF_SIGNATURE) { /* * Remember if the signature does not * indicate uninitialized file.
*/
info->initialized = true;
} continue;
}
/* Let's check the restart area if this is a valid page. */ if (!is_rst_page_hdr_valid(vbo, r_page)) goto check_result;
ra = Add2Ptr(r_page, le16_to_cpu(r_page->ra_off));
if (!is_rst_area_valid(r_page)) goto check_result;
/* * We have a valid restart page header and restart area. * If chkdsk was run or we have no clients then we have * no more checking to do.
*/ if (bchk || ra->client_idx[1] == LFS_NO_CLIENT_LE) {
info->valid_page = true; goto check_result;
}
if (is_client_area_valid(r_page, usa_error)) {
info->valid_page = true;
ra = Add2Ptr(r_page, le16_to_cpu(r_page->ra_off));
}
check_result: /* * If chkdsk was run then update the caller's * values and return.
*/ if (r_page->rhdr.sign == NTFS_CHKD_SIGNATURE) {
info->chkdsk_was_run = true;
info->last_lsn = le64_to_cpu(r_page->rhdr.lsn);
info->restart = true;
info->r_page = r_page; return 0;
}
/* * If we have a valid page then copy the values * we need from it.
*/ if (info->valid_page) {
info->last_lsn = le64_to_cpu(ra->current_lsn);
info->restart = true;
info->r_page = r_page; return 0;
}
}
/* * The total available log file space is the number of * log file pages times the space available on each page.
*/
log->total_avail_pages = log->l_size - log->first_page;
log->total_avail = log->total_avail_pages >> log->page_bits;
/* * We assume that we can't use the end of the page less than * the file record size. * Then we won't need to reserve more than the caller asks for.
*/
log->max_current_avail = log->total_avail * log->reserved;
log->total_avail = log->total_avail * log->data_size;
log->current_avail = log->max_current_avail;
}
/* * log_create_ra - Fill a restart area from the values stored in @log.
*/ staticstruct RESTART_AREA *log_create_ra(struct ntfs_log *log)
{ struct CLIENT_REC *cr; struct RESTART_AREA *ra = kzalloc(log->restart_size, GFP_NOFS);
/* Add the length of the header. */
data_len += log->record_header_len;
/* * If this lsn is contained this log page we are done. * Otherwise we need to walk through several log pages.
*/ if (data_len > tail) {
data_len -= tail;
tail = log->data_size;
page_off = log->data_off - 1;
for (;;) {
final_log_off = next_page_off(log, final_log_off);
/* * We are done if the remaining bytes * fit on this page.
*/ if (data_len <= tail) break;
data_len -= tail;
}
}
/* * We add the remaining bytes to our starting position on this page * and then add that value to the file offset of this log page.
*/ return final_log_off + data_len + page_off;
}
/* Remember if we wrapped. */ if (end <= vbo)
seq += 1;
/* Log page header for this page. */
err = read_log_page(log, hdr_off, &page, NULL); if (err) return err;
/* * If the lsn we were given was not the last lsn on this page, * then the starting offset for the next lsn is on a quad word * boundary following the last file offset for the current lsn. * Otherwise the file offset is the start of the data on the next page.
*/ if (this_lsn == le64_to_cpu(page->rhdr.lsn)) { /* If we wrapped, we need to increment the sequence number. */
hdr_off = next_page_off(log, hdr_off); if (hdr_off == log->first_page)
seq += 1;
/* Compute the lsn based on the file offset and the sequence count. */
*lsn = vbo_to_lsn(log, vbo, seq);
/* * If this lsn is within the legal range for the file, we return true. * Otherwise false indicates that there are no more lsn's.
*/ if (!is_lsn_in_file(log, *lsn))
*lsn = 0;
kfree(page);
return 0;
}
/* * current_log_avail - Calculate the number of bytes available for log records.
*/ static u32 current_log_avail(struct ntfs_log *log)
{
u32 oldest_off, next_free_off, free_bytes;
if (log->l_flags & NTFSLOG_NO_LAST_LSN) { /* The entire file is available. */ return log->max_current_avail;
}
/* * If there is a last lsn the restart area then we know that we will * have to compute the free range. * If there is no oldest lsn then start at the first page of the file.
*/
oldest_off = (log->l_flags & NTFSLOG_NO_OLDEST_LSN) ?
log->first_page :
(log->oldest_lsn_off & ~log->sys_page_mask);
/* * We will use the next log page offset to compute the next free page. * If we are going to reuse this page go to the next page. * If we are at the first page then use the end of the file.
*/
next_free_off = (log->l_flags & NTFSLOG_REUSE_TAIL) ?
log->next_page + log->page_size :
log->next_page == log->first_page ? log->l_size :
log->next_page;
/* If the two offsets are the same then there is no available space. */ if (oldest_off == next_free_off) return 0; /* * If the free offset follows the oldest offset then subtract * this range from the total available pages.
*/
free_bytes =
oldest_off < next_free_off ?
log->total_avail_pages - (next_free_off - oldest_off) :
oldest_off - next_free_off;
if (rhdr->sign == NTFS_FFFF_SIGNATURE || !rhdr->sign) returnfalse;
/* * If the last lsn on the page occurs was written after the page * that caused the original error then we have a fatal error.
*/
lsn_seq = lsn >> log->file_data_bits;
/* * If the sequence number for the lsn the page is equal or greater * than lsn we expect, then this is a subsequent write.
*/ return lsn_seq >= seq ||
(lsn_seq == seq - 1 && log->first_page == vbo &&
vbo != (lsn_to_vbo(log, lsn) & ~log->page_mask));
}
/* * last_log_lsn * * Walks through the log pages for a file, searching for the * last log page written to the file.
*/ staticint last_log_lsn(struct ntfs_log *log)
{ int err; bool usa_error = false; bool replace_page = false; bool reuse_page = log->l_flags & NTFSLOG_REUSE_TAIL; bool wrapped_file, wrapped;
/* * If we are at the expected first page of a transfer check to see * if either tail copy is at this offset. * If this page is the last page of a transfer, check if we wrote * a subsequent tail copy.
*/ if (page_cnt == page_pos || page_cnt == page_pos + 1) { /* * Check if the offset matches either the first or second * tail copy. It is possible it will match both.
*/ if (curpage_off == final_off)
tail_page = first_tail;
/* * If we already matched on the first page then * check the ending lsn's.
*/ if (curpage_off == second_off) { if (!tail_page ||
(second_tail &&
le64_to_cpu(second_tail->record_hdr.last_end_lsn) >
le64_to_cpu(first_tail->record_hdr
.last_end_lsn))) {
tail_page = second_tail;
}
}
}
use_tail_page: if (tail_page) { /* We have a candidate for a tail copy. */
lsn_cur = le64_to_cpu(tail_page->record_hdr.last_end_lsn);
if (last_ok_lsn < lsn_cur) { /* * If the sequence number is not expected, * then don't use the tail copy.
*/ if (expected_seq != (lsn_cur >> log->file_data_bits))
tail_page = NULL;
} elseif (last_ok_lsn > lsn_cur) { /* * If the last lsn is greater than the one on * this page then forget this tail.
*/
tail_page = NULL;
}
}
/* *If we have an error on the current page, * we will break of this loop.
*/ if (err || usa_error) goto check_tail;
/* * Done if the last lsn on this page doesn't match the previous known * last lsn or the sequence number is not expected.
*/
lsn_cur = le64_to_cpu(page->rhdr.lsn); if (last_ok_lsn != lsn_cur &&
expected_seq != (lsn_cur >> log->file_data_bits)) { goto check_tail;
}
/* * Check that the page position and page count values are correct. * If this is the first page of a transfer the position must be 1 * and the count will be unknown.
*/ if (page_cnt == page_pos) { if (page->page_pos != cpu_to_le16(1) &&
(!reuse_page || page->page_pos != page->page_count)) { /* * If the current page is the first page we are * looking at and we are reusing this page then * it can be either the first or last page of a * transfer. Otherwise it can only be the first.
*/ goto check_tail;
}
} elseif (le16_to_cpu(page->page_count) != page_cnt ||
le16_to_cpu(page->page_pos) != page_pos + 1) { /* * The page position better be 1 more than the last page * position and the page count better match.
*/ goto check_tail;
}
/* * We have a valid page the file and may have a valid page * the tail copy area. * If the tail page was written after the page the file then * break of the loop.
*/ if (tail_page &&
le64_to_cpu(tail_page->record_hdr.last_end_lsn) > lsn_cur) { /* Remember if we will replace the page. */
replace_page = true; goto check_tail;
}
tail_page = NULL;
if (is_log_record_end(page)) { /* * Since we have read this page we know the sequence number * is the same as our expected value.
*/
log->seq_num = expected_seq;
log->last_lsn = le64_to_cpu(page->record_hdr.last_end_lsn);
log->ra->current_lsn = page->record_hdr.last_end_lsn;
log->l_flags &= ~NTFSLOG_NO_LAST_LSN;
/* * If there is room on this page for another header then * remember we want to reuse the page.
*/ if (log->record_header_len <=
log->page_size -
le16_to_cpu(page->record_hdr.next_record_off)) {
log->l_flags |= NTFSLOG_REUSE_TAIL;
log->next_page = curpage_off;
} else {
log->l_flags &= ~NTFSLOG_REUSE_TAIL;
log->next_page = nextpage_off;
}
/* Remember if we wrapped the log file. */ if (wrapped_file)
log->l_flags |= NTFSLOG_WRAPPED;
}
/* * Remember the last page count and position. * Also remember the last known lsn.
*/
page_cnt = le16_to_cpu(page->page_count);
page_pos = le16_to_cpu(page->page_pos);
last_ok_lsn = le64_to_cpu(page->rhdr.lsn);
next_page_1:
if (wrapped) {
expected_seq += 1;
wrapped_file = 1;
}
/* Remember that the partial IO will start at the next page. */
second_off = nextpage_off;
/* * If the next page is the first page of the file then update * the sequence number for log records which begon the next page.
*/ if (wrapped)
expected_seq += 1;
/* * If we have a tail copy or are performing single page I/O we can * immediately look at the next page.
*/ if (replace_page || (log->ra->flags & RESTART_SINGLE_PAGE_IO)) {
page_cnt = 2;
page_pos = 1; goto check_valid;
}
if (page_pos != page_cnt) goto check_valid; /* * If the next page causes us to wrap to the beginning of the log * file then we know which page to check next.
*/ if (wrapped) {
page_cnt = 2;
page_pos = 1; goto check_valid;
}
cur_pos = 2;
next_test_page:
kfree(tst_page);
tst_page = NULL;
/* Walk through the file, reading log pages. */
err = read_log_page(log, nextpage_off, &tst_page, &usa_error);
/* * If we get a USA error then assume that we correctly found * the end of the original transfer.
*/ if (usa_error) goto file_is_valid;
/* * If we were able to read the page, we examine it to see if it * is the same or different Io block.
*/ if (err) goto next_test_page_1;
/* * read_log_rec_buf - Copy a log record from the file to a buffer. * * The log record may span several log pages and may even wrap the file.
*/ staticint read_log_rec_buf(struct ntfs_log *log, conststruct LFS_RECORD_HDR *rh, void *buffer)
{ int err; struct RECORD_PAGE_HDR *ph = NULL;
u64 lsn = le64_to_cpu(rh->this_lsn);
u32 vbo = lsn_to_vbo(log, lsn) & ~log->page_mask;
u32 off = lsn_to_page_off(log, lsn) + log->record_header_len;
u32 data_len = le32_to_cpu(rh->client_data_len);
/* * While there are more bytes to transfer, * we continue to attempt to perform the read.
*/ for (;;) { bool usa_error;
u32 tail = log->page_size - off;
if (tail >= data_len)
tail = data_len;
data_len -= tail;
err = read_log_page(log, vbo, &ph, &usa_error); if (err) goto out;
/* * The last lsn on this page better be greater or equal * to the lsn we are copying.
*/ if (lsn > le64_to_cpu(ph->rhdr.lsn)) {
err = -EINVAL; goto out;
}
memcpy(buffer, Add2Ptr(ph, off), tail);
/* If there are no more bytes to transfer, we exit the loop. */ if (!data_len) { if (!is_log_record_end(ph) ||
lsn > le64_to_cpu(ph->record_hdr.last_end_lsn)) {
err = -EINVAL; goto out;
} break;
}
/* Read the record header for this lsn. */ if (!rh) {
err = read_log_page(log, lsn_to_vbo(log, lsn),
(struct RECORD_PAGE_HDR **)&rh, NULL);
lcb->lrh = rh; if (err) return err;
}
/* * If the lsn the log record doesn't match the desired * lsn then the disk is corrupt.
*/ if (lsn != le64_to_cpu(rh->this_lsn)) return -EINVAL;
len = le32_to_cpu(rh->client_data_len);
/* * Check that the length field isn't greater than the total * available space the log file.
*/
rec_len = len + log->record_header_len; if (rec_len >= log->total_avail) return -EINVAL;
/* * If the entire log record is on this log page, * put a pointer to the log record the context block.
*/ if (rh->flags & LOG_RECORD_MULTI_PAGE) { void *lr = kmalloc(len, GFP_NOFS);
if (!lr) return -ENOMEM;
lcb->log_rec = lr;
lcb->alloc = true;
/* Copy the data into the buffer returned. */
err = read_log_rec_buf(log, rh, lr); if (err) return err;
} else { /* If beyond the end of the current page -> an error. */
u32 page_off = lsn_to_page_off(log, lsn);
if (page_off + len + log->record_header_len > log->page_size) return -EINVAL;
/* Find the log record indicated by the given lsn. */
err = find_log_rec(log, lsn, lcb); if (err) goto out;
*lcb_ = lcb; return 0;
out:
lcb_put(lcb);
*lcb_ = NULL; return err;
}
/* * find_client_next_lsn * * Attempt to find the next lsn to return to a client based on the context mode.
*/ staticint find_client_next_lsn(struct ntfs_log *log, struct lcb *lcb, u64 *lsn)
{ int err;
u64 next_lsn; struct LFS_RECORD_HDR *hdr;
hdr = lcb->lrh;
*lsn = 0;
if (lcb_ctx_next != lcb->ctx_mode) goto check_undo_next;
/* Loop as long as another lsn can be found. */ for (;;) {
u64 current_lsn;
err = next_log_lsn(log, hdr, ¤t_lsn); if (err) goto out;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.