diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /fs/ntfs/logfile.c |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'fs/ntfs/logfile.c')
-rw-r--r-- | fs/ntfs/logfile.c | 705 |
1 files changed, 705 insertions, 0 deletions
diff --git a/fs/ntfs/logfile.c b/fs/ntfs/logfile.c new file mode 100644 index 000000000000..5e280abafab3 --- /dev/null +++ b/fs/ntfs/logfile.c | |||
@@ -0,0 +1,705 @@ | |||
1 | /* | ||
2 | * logfile.c - NTFS kernel journal handling. Part of the Linux-NTFS project. | ||
3 | * | ||
4 | * Copyright (c) 2002-2004 Anton Altaparmakov | ||
5 | * | ||
6 | * This program/include file is free software; you can redistribute it and/or | ||
7 | * modify it under the terms of the GNU General Public License as published | ||
8 | * by the Free Software Foundation; either version 2 of the License, or | ||
9 | * (at your option) any later version. | ||
10 | * | ||
11 | * This program/include file is distributed in the hope that it will be | ||
12 | * useful, but WITHOUT ANY WARRANTY; without even the implied warranty | ||
13 | * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU General Public License | ||
17 | * along with this program (in the main directory of the Linux-NTFS | ||
18 | * distribution in the file COPYING); if not, write to the Free Software | ||
19 | * Foundation,Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
20 | */ | ||
21 | |||
22 | #ifdef NTFS_RW | ||
23 | |||
24 | #include <linux/types.h> | ||
25 | #include <linux/fs.h> | ||
26 | #include <linux/highmem.h> | ||
27 | #include <linux/buffer_head.h> | ||
28 | #include <linux/bitops.h> | ||
29 | |||
30 | #include "attrib.h" | ||
31 | #include "aops.h" | ||
32 | #include "debug.h" | ||
33 | #include "logfile.h" | ||
34 | #include "malloc.h" | ||
35 | #include "volume.h" | ||
36 | #include "ntfs.h" | ||
37 | |||
38 | /** | ||
39 | * ntfs_check_restart_page_header - check the page header for consistency | ||
40 | * @vi: $LogFile inode to which the restart page header belongs | ||
41 | * @rp: restart page header to check | ||
42 | * @pos: position in @vi at which the restart page header resides | ||
43 | * | ||
44 | * Check the restart page header @rp for consistency and return TRUE if it is | ||
45 | * consistent and FALSE otherwise. | ||
46 | * | ||
47 | * This function only needs NTFS_BLOCK_SIZE bytes in @rp, i.e. it does not | ||
48 | * require the full restart page. | ||
49 | */ | ||
50 | static BOOL ntfs_check_restart_page_header(struct inode *vi, | ||
51 | RESTART_PAGE_HEADER *rp, s64 pos) | ||
52 | { | ||
53 | u32 logfile_system_page_size, logfile_log_page_size; | ||
54 | u16 usa_count, usa_ofs, usa_end, ra_ofs; | ||
55 | |||
56 | ntfs_debug("Entering."); | ||
57 | /* | ||
58 | * If the system or log page sizes are smaller than the ntfs block size | ||
59 | * or either is not a power of 2 we cannot handle this log file. | ||
60 | */ | ||
61 | logfile_system_page_size = le32_to_cpu(rp->system_page_size); | ||
62 | logfile_log_page_size = le32_to_cpu(rp->log_page_size); | ||
63 | if (logfile_system_page_size < NTFS_BLOCK_SIZE || | ||
64 | logfile_log_page_size < NTFS_BLOCK_SIZE || | ||
65 | logfile_system_page_size & | ||
66 | (logfile_system_page_size - 1) || | ||
67 | logfile_log_page_size & (logfile_log_page_size - 1)) { | ||
68 | ntfs_error(vi->i_sb, "$LogFile uses unsupported page size."); | ||
69 | return FALSE; | ||
70 | } | ||
71 | /* | ||
72 | * We must be either at !pos (1st restart page) or at pos = system page | ||
73 | * size (2nd restart page). | ||
74 | */ | ||
75 | if (pos && pos != logfile_system_page_size) { | ||
76 | ntfs_error(vi->i_sb, "Found restart area in incorrect " | ||
77 | "position in $LogFile."); | ||
78 | return FALSE; | ||
79 | } | ||
80 | /* We only know how to handle version 1.1. */ | ||
81 | if (sle16_to_cpu(rp->major_ver) != 1 || | ||
82 | sle16_to_cpu(rp->minor_ver) != 1) { | ||
83 | ntfs_error(vi->i_sb, "$LogFile version %i.%i is not " | ||
84 | "supported. (This driver supports version " | ||
85 | "1.1 only.)", (int)sle16_to_cpu(rp->major_ver), | ||
86 | (int)sle16_to_cpu(rp->minor_ver)); | ||
87 | return FALSE; | ||
88 | } | ||
89 | /* Verify the size of the update sequence array. */ | ||
90 | usa_count = 1 + (logfile_system_page_size >> NTFS_BLOCK_SIZE_BITS); | ||
91 | if (usa_count != le16_to_cpu(rp->usa_count)) { | ||
92 | ntfs_error(vi->i_sb, "$LogFile restart page specifies " | ||
93 | "inconsistent update sequence array count."); | ||
94 | return FALSE; | ||
95 | } | ||
96 | /* Verify the position of the update sequence array. */ | ||
97 | usa_ofs = le16_to_cpu(rp->usa_ofs); | ||
98 | usa_end = usa_ofs + usa_count * sizeof(u16); | ||
99 | if (usa_ofs < sizeof(RESTART_PAGE_HEADER) || | ||
100 | usa_end > NTFS_BLOCK_SIZE - sizeof(u16)) { | ||
101 | ntfs_error(vi->i_sb, "$LogFile restart page specifies " | ||
102 | "inconsistent update sequence array offset."); | ||
103 | return FALSE; | ||
104 | } | ||
105 | /* | ||
106 | * Verify the position of the restart area. It must be: | ||
107 | * - aligned to 8-byte boundary, | ||
108 | * - after the update sequence array, and | ||
109 | * - within the system page size. | ||
110 | */ | ||
111 | ra_ofs = le16_to_cpu(rp->restart_area_offset); | ||
112 | if (ra_ofs & 7 || ra_ofs < usa_end || | ||
113 | ra_ofs > logfile_system_page_size) { | ||
114 | ntfs_error(vi->i_sb, "$LogFile restart page specifies " | ||
115 | "inconsistent restart area offset."); | ||
116 | return FALSE; | ||
117 | } | ||
118 | /* | ||
119 | * Only restart pages modified by chkdsk are allowed to have chkdsk_lsn | ||
120 | * set. | ||
121 | */ | ||
122 | if (!ntfs_is_chkd_record(rp->magic) && sle64_to_cpu(rp->chkdsk_lsn)) { | ||
123 | ntfs_error(vi->i_sb, "$LogFile restart page is not modified " | ||
124 | "chkdsk but a chkdsk LSN is specified."); | ||
125 | return FALSE; | ||
126 | } | ||
127 | ntfs_debug("Done."); | ||
128 | return TRUE; | ||
129 | } | ||
130 | |||
131 | /** | ||
132 | * ntfs_check_restart_area - check the restart area for consistency | ||
133 | * @vi: $LogFile inode to which the restart page belongs | ||
134 | * @rp: restart page whose restart area to check | ||
135 | * | ||
136 | * Check the restart area of the restart page @rp for consistency and return | ||
137 | * TRUE if it is consistent and FALSE otherwise. | ||
138 | * | ||
139 | * This function assumes that the restart page header has already been | ||
140 | * consistency checked. | ||
141 | * | ||
142 | * This function only needs NTFS_BLOCK_SIZE bytes in @rp, i.e. it does not | ||
143 | * require the full restart page. | ||
144 | */ | ||
145 | static BOOL ntfs_check_restart_area(struct inode *vi, RESTART_PAGE_HEADER *rp) | ||
146 | { | ||
147 | u64 file_size; | ||
148 | RESTART_AREA *ra; | ||
149 | u16 ra_ofs, ra_len, ca_ofs; | ||
150 | u8 fs_bits; | ||
151 | |||
152 | ntfs_debug("Entering."); | ||
153 | ra_ofs = le16_to_cpu(rp->restart_area_offset); | ||
154 | ra = (RESTART_AREA*)((u8*)rp + ra_ofs); | ||
155 | /* | ||
156 | * Everything before ra->file_size must be before the first word | ||
157 | * protected by an update sequence number. This ensures that it is | ||
158 | * safe to access ra->client_array_offset. | ||
159 | */ | ||
160 | if (ra_ofs + offsetof(RESTART_AREA, file_size) > | ||
161 | NTFS_BLOCK_SIZE - sizeof(u16)) { | ||
162 | ntfs_error(vi->i_sb, "$LogFile restart area specifies " | ||
163 | "inconsistent file offset."); | ||
164 | return FALSE; | ||
165 | } | ||
166 | /* | ||
167 | * Now that we can access ra->client_array_offset, make sure everything | ||
168 | * up to the log client array is before the first word protected by an | ||
169 | * update sequence number. This ensures we can access all of the | ||
170 | * restart area elements safely. Also, the client array offset must be | ||
171 | * aligned to an 8-byte boundary. | ||
172 | */ | ||
173 | ca_ofs = le16_to_cpu(ra->client_array_offset); | ||
174 | if (((ca_ofs + 7) & ~7) != ca_ofs || | ||
175 | ra_ofs + ca_ofs > NTFS_BLOCK_SIZE - sizeof(u16)) { | ||
176 | ntfs_error(vi->i_sb, "$LogFile restart area specifies " | ||
177 | "inconsistent client array offset."); | ||
178 | return FALSE; | ||
179 | } | ||
180 | /* | ||
181 | * The restart area must end within the system page size both when | ||
182 | * calculated manually and as specified by ra->restart_area_length. | ||
183 | * Also, the calculated length must not exceed the specified length. | ||
184 | */ | ||
185 | ra_len = ca_ofs + le16_to_cpu(ra->log_clients) * | ||
186 | sizeof(LOG_CLIENT_RECORD); | ||
187 | if (ra_ofs + ra_len > le32_to_cpu(rp->system_page_size) || | ||
188 | ra_ofs + le16_to_cpu(ra->restart_area_length) > | ||
189 | le32_to_cpu(rp->system_page_size) || | ||
190 | ra_len > le16_to_cpu(ra->restart_area_length)) { | ||
191 | ntfs_error(vi->i_sb, "$LogFile restart area is out of bounds " | ||
192 | "of the system page size specified by the " | ||
193 | "restart page header and/or the specified " | ||
194 | "restart area length is inconsistent."); | ||
195 | return FALSE; | ||
196 | } | ||
197 | /* | ||
198 | * The ra->client_free_list and ra->client_in_use_list must be either | ||
199 | * LOGFILE_NO_CLIENT or less than ra->log_clients or they are | ||
200 | * overflowing the client array. | ||
201 | */ | ||
202 | if ((ra->client_free_list != LOGFILE_NO_CLIENT && | ||
203 | le16_to_cpu(ra->client_free_list) >= | ||
204 | le16_to_cpu(ra->log_clients)) || | ||
205 | (ra->client_in_use_list != LOGFILE_NO_CLIENT && | ||
206 | le16_to_cpu(ra->client_in_use_list) >= | ||
207 | le16_to_cpu(ra->log_clients))) { | ||
208 | ntfs_error(vi->i_sb, "$LogFile restart area specifies " | ||
209 | "overflowing client free and/or in use lists."); | ||
210 | return FALSE; | ||
211 | } | ||
212 | /* | ||
213 | * Check ra->seq_number_bits against ra->file_size for consistency. | ||
214 | * We cannot just use ffs() because the file size is not a power of 2. | ||
215 | */ | ||
216 | file_size = (u64)sle64_to_cpu(ra->file_size); | ||
217 | fs_bits = 0; | ||
218 | while (file_size) { | ||
219 | file_size >>= 1; | ||
220 | fs_bits++; | ||
221 | } | ||
222 | if (le32_to_cpu(ra->seq_number_bits) != 67 - fs_bits) { | ||
223 | ntfs_error(vi->i_sb, "$LogFile restart area specifies " | ||
224 | "inconsistent sequence number bits."); | ||
225 | return FALSE; | ||
226 | } | ||
227 | /* The log record header length must be a multiple of 8. */ | ||
228 | if (((le16_to_cpu(ra->log_record_header_length) + 7) & ~7) != | ||
229 | le16_to_cpu(ra->log_record_header_length)) { | ||
230 | ntfs_error(vi->i_sb, "$LogFile restart area specifies " | ||
231 | "inconsistent log record header length."); | ||
232 | return FALSE; | ||
233 | } | ||
234 | /* Dito for the log page data offset. */ | ||
235 | if (((le16_to_cpu(ra->log_page_data_offset) + 7) & ~7) != | ||
236 | le16_to_cpu(ra->log_page_data_offset)) { | ||
237 | ntfs_error(vi->i_sb, "$LogFile restart area specifies " | ||
238 | "inconsistent log page data offset."); | ||
239 | return FALSE; | ||
240 | } | ||
241 | ntfs_debug("Done."); | ||
242 | return TRUE; | ||
243 | } | ||
244 | |||
245 | /** | ||
246 | * ntfs_check_log_client_array - check the log client array for consistency | ||
247 | * @vi: $LogFile inode to which the restart page belongs | ||
248 | * @rp: restart page whose log client array to check | ||
249 | * | ||
250 | * Check the log client array of the restart page @rp for consistency and | ||
251 | * return TRUE if it is consistent and FALSE otherwise. | ||
252 | * | ||
253 | * This function assumes that the restart page header and the restart area have | ||
254 | * already been consistency checked. | ||
255 | * | ||
256 | * Unlike ntfs_check_restart_page_header() and ntfs_check_restart_area(), this | ||
257 | * function needs @rp->system_page_size bytes in @rp, i.e. it requires the full | ||
258 | * restart page and the page must be multi sector transfer deprotected. | ||
259 | */ | ||
260 | static BOOL ntfs_check_log_client_array(struct inode *vi, | ||
261 | RESTART_PAGE_HEADER *rp) | ||
262 | { | ||
263 | RESTART_AREA *ra; | ||
264 | LOG_CLIENT_RECORD *ca, *cr; | ||
265 | u16 nr_clients, idx; | ||
266 | BOOL in_free_list, idx_is_first; | ||
267 | |||
268 | ntfs_debug("Entering."); | ||
269 | ra = (RESTART_AREA*)((u8*)rp + le16_to_cpu(rp->restart_area_offset)); | ||
270 | ca = (LOG_CLIENT_RECORD*)((u8*)ra + | ||
271 | le16_to_cpu(ra->client_array_offset)); | ||
272 | /* | ||
273 | * Check the ra->client_free_list first and then check the | ||
274 | * ra->client_in_use_list. Check each of the log client records in | ||
275 | * each of the lists and check that the array does not overflow the | ||
276 | * ra->log_clients value. Also keep track of the number of records | ||
277 | * visited as there cannot be more than ra->log_clients records and | ||
278 | * that way we detect eventual loops in within a list. | ||
279 | */ | ||
280 | nr_clients = le16_to_cpu(ra->log_clients); | ||
281 | idx = le16_to_cpu(ra->client_free_list); | ||
282 | in_free_list = TRUE; | ||
283 | check_list: | ||
284 | for (idx_is_first = TRUE; idx != LOGFILE_NO_CLIENT_CPU; nr_clients--, | ||
285 | idx = le16_to_cpu(cr->next_client)) { | ||
286 | if (!nr_clients || idx >= le16_to_cpu(ra->log_clients)) | ||
287 | goto err_out; | ||
288 | /* Set @cr to the current log client record. */ | ||
289 | cr = ca + idx; | ||
290 | /* The first log client record must not have a prev_client. */ | ||
291 | if (idx_is_first) { | ||
292 | if (cr->prev_client != LOGFILE_NO_CLIENT) | ||
293 | goto err_out; | ||
294 | idx_is_first = FALSE; | ||
295 | } | ||
296 | } | ||
297 | /* Switch to and check the in use list if we just did the free list. */ | ||
298 | if (in_free_list) { | ||
299 | in_free_list = FALSE; | ||
300 | idx = le16_to_cpu(ra->client_in_use_list); | ||
301 | goto check_list; | ||
302 | } | ||
303 | ntfs_debug("Done."); | ||
304 | return TRUE; | ||
305 | err_out: | ||
306 | ntfs_error(vi->i_sb, "$LogFile log client array is corrupt."); | ||
307 | return FALSE; | ||
308 | } | ||
309 | |||
310 | /** | ||
311 | * ntfs_check_and_load_restart_page - check the restart page for consistency | ||
312 | * @vi: $LogFile inode to which the restart page belongs | ||
313 | * @rp: restart page to check | ||
314 | * @pos: position in @vi at which the restart page resides | ||
315 | * @wrp: copy of the multi sector transfer deprotected restart page | ||
316 | * | ||
317 | * Check the restart page @rp for consistency and return TRUE if it is | ||
318 | * consistent and FALSE otherwise. | ||
319 | * | ||
320 | * This function only needs NTFS_BLOCK_SIZE bytes in @rp, i.e. it does not | ||
321 | * require the full restart page. | ||
322 | * | ||
323 | * If @wrp is not NULL, on success, *@wrp will point to a buffer containing a | ||
324 | * copy of the complete multi sector transfer deprotected page. On failure, | ||
325 | * *@wrp is undefined. | ||
326 | */ | ||
327 | static BOOL ntfs_check_and_load_restart_page(struct inode *vi, | ||
328 | RESTART_PAGE_HEADER *rp, s64 pos, RESTART_PAGE_HEADER **wrp) | ||
329 | { | ||
330 | RESTART_AREA *ra; | ||
331 | RESTART_PAGE_HEADER *trp; | ||
332 | int size; | ||
333 | BOOL ret; | ||
334 | |||
335 | ntfs_debug("Entering."); | ||
336 | /* Check the restart page header for consistency. */ | ||
337 | if (!ntfs_check_restart_page_header(vi, rp, pos)) { | ||
338 | /* Error output already done inside the function. */ | ||
339 | return FALSE; | ||
340 | } | ||
341 | /* Check the restart area for consistency. */ | ||
342 | if (!ntfs_check_restart_area(vi, rp)) { | ||
343 | /* Error output already done inside the function. */ | ||
344 | return FALSE; | ||
345 | } | ||
346 | ra = (RESTART_AREA*)((u8*)rp + le16_to_cpu(rp->restart_area_offset)); | ||
347 | /* | ||
348 | * Allocate a buffer to store the whole restart page so we can multi | ||
349 | * sector transfer deprotect it. | ||
350 | */ | ||
351 | trp = ntfs_malloc_nofs(le32_to_cpu(rp->system_page_size)); | ||
352 | if (!trp) { | ||
353 | ntfs_error(vi->i_sb, "Failed to allocate memory for $LogFile " | ||
354 | "restart page buffer."); | ||
355 | return FALSE; | ||
356 | } | ||
357 | /* | ||
358 | * Read the whole of the restart page into the buffer. If it fits | ||
359 | * completely inside @rp, just copy it from there. Otherwise map all | ||
360 | * the required pages and copy the data from them. | ||
361 | */ | ||
362 | size = PAGE_CACHE_SIZE - (pos & ~PAGE_CACHE_MASK); | ||
363 | if (size >= le32_to_cpu(rp->system_page_size)) { | ||
364 | memcpy(trp, rp, le32_to_cpu(rp->system_page_size)); | ||
365 | } else { | ||
366 | pgoff_t idx; | ||
367 | struct page *page; | ||
368 | int have_read, to_read; | ||
369 | |||
370 | /* First copy what we already have in @rp. */ | ||
371 | memcpy(trp, rp, size); | ||
372 | /* Copy the remaining data one page at a time. */ | ||
373 | have_read = size; | ||
374 | to_read = le32_to_cpu(rp->system_page_size) - size; | ||
375 | idx = (pos + size) >> PAGE_CACHE_SHIFT; | ||
376 | BUG_ON((pos + size) & ~PAGE_CACHE_MASK); | ||
377 | do { | ||
378 | page = ntfs_map_page(vi->i_mapping, idx); | ||
379 | if (IS_ERR(page)) { | ||
380 | ntfs_error(vi->i_sb, "Error mapping $LogFile " | ||
381 | "page (index %lu).", idx); | ||
382 | goto err_out; | ||
383 | } | ||
384 | size = min_t(int, to_read, PAGE_CACHE_SIZE); | ||
385 | memcpy((u8*)trp + have_read, page_address(page), size); | ||
386 | ntfs_unmap_page(page); | ||
387 | have_read += size; | ||
388 | to_read -= size; | ||
389 | idx++; | ||
390 | } while (to_read > 0); | ||
391 | } | ||
392 | /* Perform the multi sector transfer deprotection on the buffer. */ | ||
393 | if (post_read_mst_fixup((NTFS_RECORD*)trp, | ||
394 | le32_to_cpu(rp->system_page_size))) { | ||
395 | ntfs_error(vi->i_sb, "Multi sector transfer error detected in " | ||
396 | "$LogFile restart page."); | ||
397 | goto err_out; | ||
398 | } | ||
399 | /* Check the log client records for consistency. */ | ||
400 | ret = ntfs_check_log_client_array(vi, trp); | ||
401 | if (ret && wrp) | ||
402 | *wrp = trp; | ||
403 | else | ||
404 | ntfs_free(trp); | ||
405 | ntfs_debug("Done."); | ||
406 | return ret; | ||
407 | err_out: | ||
408 | ntfs_free(trp); | ||
409 | return FALSE; | ||
410 | } | ||
411 | |||
412 | /** | ||
413 | * ntfs_ckeck_logfile - check in the journal if the volume is consistent | ||
414 | * @log_vi: struct inode of loaded journal $LogFile to check | ||
415 | * | ||
416 | * Check the $LogFile journal for consistency and return TRUE if it is | ||
417 | * consistent and FALSE if not. | ||
418 | * | ||
419 | * At present we only check the two restart pages and ignore the log record | ||
420 | * pages. | ||
421 | * | ||
422 | * Note that the MstProtected flag is not set on the $LogFile inode and hence | ||
423 | * when reading pages they are not deprotected. This is because we do not know | ||
424 | * if the $LogFile was created on a system with a different page size to ours | ||
425 | * yet and mst deprotection would fail if our page size is smaller. | ||
426 | */ | ||
427 | BOOL ntfs_check_logfile(struct inode *log_vi) | ||
428 | { | ||
429 | s64 size, pos, rstr1_pos, rstr2_pos; | ||
430 | ntfs_volume *vol = NTFS_SB(log_vi->i_sb); | ||
431 | struct address_space *mapping = log_vi->i_mapping; | ||
432 | struct page *page = NULL; | ||
433 | u8 *kaddr = NULL; | ||
434 | RESTART_PAGE_HEADER *rstr1_ph = NULL; | ||
435 | RESTART_PAGE_HEADER *rstr2_ph = NULL; | ||
436 | int log_page_size, log_page_mask, ofs; | ||
437 | BOOL logfile_is_empty = TRUE; | ||
438 | BOOL rstr1_found = FALSE; | ||
439 | BOOL rstr2_found = FALSE; | ||
440 | u8 log_page_bits; | ||
441 | |||
442 | ntfs_debug("Entering."); | ||
443 | /* An empty $LogFile must have been clean before it got emptied. */ | ||
444 | if (NVolLogFileEmpty(vol)) | ||
445 | goto is_empty; | ||
446 | size = log_vi->i_size; | ||
447 | /* Make sure the file doesn't exceed the maximum allowed size. */ | ||
448 | if (size > MaxLogFileSize) | ||
449 | size = MaxLogFileSize; | ||
450 | /* | ||
451 | * Truncate size to a multiple of the page cache size or the default | ||
452 | * log page size if the page cache size is between the default log page | ||
453 | * log page size if the page cache size is between the default log page | ||
454 | * size and twice that. | ||
455 | */ | ||
456 | if (PAGE_CACHE_SIZE >= DefaultLogPageSize && PAGE_CACHE_SIZE <= | ||
457 | DefaultLogPageSize * 2) | ||
458 | log_page_size = DefaultLogPageSize; | ||
459 | else | ||
460 | log_page_size = PAGE_CACHE_SIZE; | ||
461 | log_page_mask = log_page_size - 1; | ||
462 | /* | ||
463 | * Use generic_ffs() instead of ffs() to enable the compiler to | ||
464 | * optimize log_page_size and log_page_bits into constants. | ||
465 | */ | ||
466 | log_page_bits = generic_ffs(log_page_size) - 1; | ||
467 | size &= ~(log_page_size - 1); | ||
468 | /* | ||
469 | * Ensure the log file is big enough to store at least the two restart | ||
470 | * pages and the minimum number of log record pages. | ||
471 | */ | ||
472 | if (size < log_page_size * 2 || (size - log_page_size * 2) >> | ||
473 | log_page_bits < MinLogRecordPages) { | ||
474 | ntfs_error(vol->sb, "$LogFile is too small."); | ||
475 | return FALSE; | ||
476 | } | ||
477 | /* | ||
478 | * Read through the file looking for a restart page. Since the restart | ||
479 | * page header is at the beginning of a page we only need to search at | ||
480 | * what could be the beginning of a page (for each page size) rather | ||
481 | * than scanning the whole file byte by byte. If all potential places | ||
482 | * contain empty and uninitialzed records, the log file can be assumed | ||
483 | * to be empty. | ||
484 | */ | ||
485 | for (pos = 0; pos < size; pos <<= 1) { | ||
486 | pgoff_t idx = pos >> PAGE_CACHE_SHIFT; | ||
487 | if (!page || page->index != idx) { | ||
488 | if (page) | ||
489 | ntfs_unmap_page(page); | ||
490 | page = ntfs_map_page(mapping, idx); | ||
491 | if (IS_ERR(page)) { | ||
492 | ntfs_error(vol->sb, "Error mapping $LogFile " | ||
493 | "page (index %lu).", idx); | ||
494 | return FALSE; | ||
495 | } | ||
496 | } | ||
497 | kaddr = (u8*)page_address(page) + (pos & ~PAGE_CACHE_MASK); | ||
498 | /* | ||
499 | * A non-empty block means the logfile is not empty while an | ||
500 | * empty block after a non-empty block has been encountered | ||
501 | * means we are done. | ||
502 | */ | ||
503 | if (!ntfs_is_empty_recordp((le32*)kaddr)) | ||
504 | logfile_is_empty = FALSE; | ||
505 | else if (!logfile_is_empty) | ||
506 | break; | ||
507 | /* | ||
508 | * A log record page means there cannot be a restart page after | ||
509 | * this so no need to continue searching. | ||
510 | */ | ||
511 | if (ntfs_is_rcrd_recordp((le32*)kaddr)) | ||
512 | break; | ||
513 | /* | ||
514 | * A modified by chkdsk restart page means we cannot handle | ||
515 | * this log file. | ||
516 | */ | ||
517 | if (ntfs_is_chkd_recordp((le32*)kaddr)) { | ||
518 | ntfs_error(vol->sb, "$LogFile has been modified by " | ||
519 | "chkdsk. Mount this volume in " | ||
520 | "Windows."); | ||
521 | goto err_out; | ||
522 | } | ||
523 | /* If not a restart page, continue. */ | ||
524 | if (!ntfs_is_rstr_recordp((le32*)kaddr)) { | ||
525 | /* Skip to the minimum page size for the next one. */ | ||
526 | if (!pos) | ||
527 | pos = NTFS_BLOCK_SIZE >> 1; | ||
528 | continue; | ||
529 | } | ||
530 | /* We now know we have a restart page. */ | ||
531 | if (!pos) { | ||
532 | rstr1_found = TRUE; | ||
533 | rstr1_pos = pos; | ||
534 | } else { | ||
535 | if (rstr2_found) { | ||
536 | ntfs_error(vol->sb, "Found more than two " | ||
537 | "restart pages in $LogFile."); | ||
538 | goto err_out; | ||
539 | } | ||
540 | rstr2_found = TRUE; | ||
541 | rstr2_pos = pos; | ||
542 | } | ||
543 | /* | ||
544 | * Check the restart page for consistency and get a copy of the | ||
545 | * complete multi sector transfer deprotected restart page. | ||
546 | */ | ||
547 | if (!ntfs_check_and_load_restart_page(log_vi, | ||
548 | (RESTART_PAGE_HEADER*)kaddr, pos, | ||
549 | !pos ? &rstr1_ph : &rstr2_ph)) { | ||
550 | /* Error output already done inside the function. */ | ||
551 | goto err_out; | ||
552 | } | ||
553 | /* | ||
554 | * We have a valid restart page. The next one must be after | ||
555 | * a whole system page size as specified by the valid restart | ||
556 | * page. | ||
557 | */ | ||
558 | if (!pos) | ||
559 | pos = le32_to_cpu(rstr1_ph->system_page_size) >> 1; | ||
560 | } | ||
561 | if (page) { | ||
562 | ntfs_unmap_page(page); | ||
563 | page = NULL; | ||
564 | } | ||
565 | if (logfile_is_empty) { | ||
566 | NVolSetLogFileEmpty(vol); | ||
567 | is_empty: | ||
568 | ntfs_debug("Done. ($LogFile is empty.)"); | ||
569 | return TRUE; | ||
570 | } | ||
571 | if (!rstr1_found || !rstr2_found) { | ||
572 | ntfs_error(vol->sb, "Did not find two restart pages in " | ||
573 | "$LogFile."); | ||
574 | goto err_out; | ||
575 | } | ||
576 | /* | ||
577 | * The two restart areas must be identical except for the update | ||
578 | * sequence number. | ||
579 | */ | ||
580 | ofs = le16_to_cpu(rstr1_ph->usa_ofs); | ||
581 | if (memcmp(rstr1_ph, rstr2_ph, ofs) || (ofs += sizeof(u16), | ||
582 | memcmp((u8*)rstr1_ph + ofs, (u8*)rstr2_ph + ofs, | ||
583 | le32_to_cpu(rstr1_ph->system_page_size) - ofs))) { | ||
584 | ntfs_error(vol->sb, "The two restart pages in $LogFile do not " | ||
585 | "match."); | ||
586 | goto err_out; | ||
587 | } | ||
588 | ntfs_free(rstr1_ph); | ||
589 | ntfs_free(rstr2_ph); | ||
590 | /* All consistency checks passed. */ | ||
591 | ntfs_debug("Done."); | ||
592 | return TRUE; | ||
593 | err_out: | ||
594 | if (page) | ||
595 | ntfs_unmap_page(page); | ||
596 | if (rstr1_ph) | ||
597 | ntfs_free(rstr1_ph); | ||
598 | if (rstr2_ph) | ||
599 | ntfs_free(rstr2_ph); | ||
600 | return FALSE; | ||
601 | } | ||
602 | |||
603 | /** | ||
604 | * ntfs_is_logfile_clean - check in the journal if the volume is clean | ||
605 | * @log_vi: struct inode of loaded journal $LogFile to check | ||
606 | * | ||
607 | * Analyze the $LogFile journal and return TRUE if it indicates the volume was | ||
608 | * shutdown cleanly and FALSE if not. | ||
609 | * | ||
610 | * At present we only look at the two restart pages and ignore the log record | ||
611 | * pages. This is a little bit crude in that there will be a very small number | ||
612 | * of cases where we think that a volume is dirty when in fact it is clean. | ||
613 | * This should only affect volumes that have not been shutdown cleanly but did | ||
614 | * not have any pending, non-check-pointed i/o, i.e. they were completely idle | ||
615 | * at least for the five seconds preceeding the unclean shutdown. | ||
616 | * | ||
617 | * This function assumes that the $LogFile journal has already been consistency | ||
618 | * checked by a call to ntfs_check_logfile() and in particular if the $LogFile | ||
619 | * is empty this function requires that NVolLogFileEmpty() is true otherwise an | ||
620 | * empty volume will be reported as dirty. | ||
621 | */ | ||
622 | BOOL ntfs_is_logfile_clean(struct inode *log_vi) | ||
623 | { | ||
624 | ntfs_volume *vol = NTFS_SB(log_vi->i_sb); | ||
625 | struct page *page; | ||
626 | RESTART_PAGE_HEADER *rp; | ||
627 | RESTART_AREA *ra; | ||
628 | |||
629 | ntfs_debug("Entering."); | ||
630 | /* An empty $LogFile must have been clean before it got emptied. */ | ||
631 | if (NVolLogFileEmpty(vol)) { | ||
632 | ntfs_debug("Done. ($LogFile is empty.)"); | ||
633 | return TRUE; | ||
634 | } | ||
635 | /* | ||
636 | * Read the first restart page. It will be possibly incomplete and | ||
637 | * will not be multi sector transfer deprotected but we only need the | ||
638 | * first NTFS_BLOCK_SIZE bytes so it does not matter. | ||
639 | */ | ||
640 | page = ntfs_map_page(log_vi->i_mapping, 0); | ||
641 | if (IS_ERR(page)) { | ||
642 | ntfs_error(vol->sb, "Error mapping $LogFile page (index 0)."); | ||
643 | return FALSE; | ||
644 | } | ||
645 | rp = (RESTART_PAGE_HEADER*)page_address(page); | ||
646 | if (!ntfs_is_rstr_record(rp->magic)) { | ||
647 | ntfs_error(vol->sb, "No restart page found at offset zero in " | ||
648 | "$LogFile. This is probably a bug in that " | ||
649 | "the $LogFile should have been consistency " | ||
650 | "checked before calling this function."); | ||
651 | goto err_out; | ||
652 | } | ||
653 | ra = (RESTART_AREA*)((u8*)rp + le16_to_cpu(rp->restart_area_offset)); | ||
654 | /* | ||
655 | * If the $LogFile has active clients, i.e. it is open, and we do not | ||
656 | * have the RESTART_VOLUME_IS_CLEAN bit set in the restart area flags, | ||
657 | * we assume there was an unclean shutdown. | ||
658 | */ | ||
659 | if (ra->client_in_use_list != LOGFILE_NO_CLIENT && | ||
660 | !(ra->flags & RESTART_VOLUME_IS_CLEAN)) { | ||
661 | ntfs_debug("Done. $LogFile indicates a dirty shutdown."); | ||
662 | goto err_out; | ||
663 | } | ||
664 | ntfs_unmap_page(page); | ||
665 | /* $LogFile indicates a clean shutdown. */ | ||
666 | ntfs_debug("Done. $LogFile indicates a clean shutdown."); | ||
667 | return TRUE; | ||
668 | err_out: | ||
669 | ntfs_unmap_page(page); | ||
670 | return FALSE; | ||
671 | } | ||
672 | |||
673 | /** | ||
674 | * ntfs_empty_logfile - empty the contents of the $LogFile journal | ||
675 | * @log_vi: struct inode of loaded journal $LogFile to empty | ||
676 | * | ||
677 | * Empty the contents of the $LogFile journal @log_vi and return TRUE on | ||
678 | * success and FALSE on error. | ||
679 | * | ||
680 | * This function assumes that the $LogFile journal has already been consistency | ||
681 | * checked by a call to ntfs_check_logfile() and that ntfs_is_logfile_clean() | ||
682 | * has been used to ensure that the $LogFile is clean. | ||
683 | */ | ||
684 | BOOL ntfs_empty_logfile(struct inode *log_vi) | ||
685 | { | ||
686 | ntfs_volume *vol = NTFS_SB(log_vi->i_sb); | ||
687 | |||
688 | ntfs_debug("Entering."); | ||
689 | if (!NVolLogFileEmpty(vol)) { | ||
690 | int err; | ||
691 | |||
692 | err = ntfs_attr_set(NTFS_I(log_vi), 0, log_vi->i_size, 0xff); | ||
693 | if (unlikely(err)) { | ||
694 | ntfs_error(vol->sb, "Failed to fill $LogFile with " | ||
695 | "0xff bytes (error code %i).", err); | ||
696 | return FALSE; | ||
697 | } | ||
698 | /* Set the flag so we do not have to do it again on remount. */ | ||
699 | NVolSetLogFileEmpty(vol); | ||
700 | } | ||
701 | ntfs_debug("Done."); | ||
702 | return TRUE; | ||
703 | } | ||
704 | |||
705 | #endif /* NTFS_RW */ | ||