1 /** 2 * mst.c - Multi sector fixup handling code. Originated from the Linux-NTFS project. 3 * 4 * Copyright (c) 2000-2004 Anton Altaparmakov 5 * Copyright (c) 2006-2009 Szabolcs Szakacsits 6 * 7 * This program/include file is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU General Public License as published 9 * by the Free Software Foundation; either version 2 of the License, or 10 * (at your option) any later version. 11 * 12 * This program/include file is distributed in the hope that it will be 13 * useful, but WITHOUT ANY WARRANTY; without even the implied warranty 14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU General Public License for more details. 16 * 17 * You should have received a copy of the GNU General Public License 18 * along with this program (in the main directory of the NTFS-3G 19 * distribution in the file COPYING); if not, write to the Free Software 20 * Foundation,Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 21 */ 22 23 #ifdef HAVE_CONFIG_H 24 #include "config.h" 25 #endif 26 27 #ifdef HAVE_ERRNO_H 28 #include <errno.h> 29 #endif 30 31 #include "mst.h" 32 #include "logging.h" 33 34 /* 35 * Basic validation of a NTFS multi-sector record. The record size must be a 36 * multiple of the logical sector size; and the update sequence array must be 37 * properly aligned, of the expected length, and must end before the last le16 38 * in the first logical sector. 39 */ 40 static BOOL 41 is_valid_record(u32 size, u16 usa_ofs, u16 usa_count) 42 { 43 return size % NTFS_BLOCK_SIZE == 0 && 44 usa_ofs % 2 == 0 && 45 usa_count == 1 + (size / NTFS_BLOCK_SIZE) && 46 usa_ofs + ((u32)usa_count * 2) <= NTFS_BLOCK_SIZE - 2; 47 } 48 49 /** 50 * ntfs_mst_post_read_fixup - deprotect multi sector transfer protected data 51 * @b: pointer to the data to deprotect 52 * @size: size in bytes of @b 53 * 54 * Perform the necessary post read multi sector transfer fixups and detect the 55 * presence of incomplete multi sector transfers. - In that case, overwrite the 56 * magic of the ntfs record header being processed with "BAAD" (in memory only!) 57 * and abort processing. 58 * 59 * Return 0 on success and -1 on error, with errno set to the error code. The 60 * following error codes are defined: 61 * EINVAL Invalid arguments or invalid NTFS record in buffer @b. 62 * EIO Multi sector transfer error was detected. Magic of the NTFS 63 * record in @b will have been set to "BAAD". 64 */ 65 int ntfs_mst_post_read_fixup_warn(NTFS_RECORD *b, const u32 size, 66 BOOL warn) 67 { 68 u16 usa_ofs, usa_count, usn; 69 u16 *usa_pos, *data_pos; 70 71 ntfs_log_trace("Entering\n"); 72 73 /* Setup the variables. */ 74 usa_ofs = le16_to_cpu(b->usa_ofs); 75 usa_count = le16_to_cpu(b->usa_count); 76 77 if (!is_valid_record(size, usa_ofs, usa_count)) { 78 errno = EINVAL; 79 if (warn) { 80 ntfs_log_perror("%s: magic: 0x%08lx size: %ld " 81 " usa_ofs: %d usa_count: %u", 82 __FUNCTION__, 83 (long)le32_to_cpu(*(le32 *)b), 84 (long)size, (int)usa_ofs, 85 (unsigned int)usa_count); 86 } 87 return -1; 88 } 89 /* Position of usn in update sequence array. */ 90 usa_pos = (u16*)b + usa_ofs/sizeof(u16); 91 /* 92 * The update sequence number which has to be equal to each of the 93 * u16 values before they are fixed up. Note no need to care for 94 * endianness since we are comparing and moving data for on disk 95 * structures which means the data is consistent. - If it is 96 * consistency the wrong endianness it doesn't make any difference. 97 */ 98 usn = *usa_pos; 99 /* 100 * Position in protected data of first u16 that needs fixing up. 101 */ 102 data_pos = (u16*)b + NTFS_BLOCK_SIZE/sizeof(u16) - 1; 103 /* 104 * Check for incomplete multi sector transfer(s). 105 */ 106 while (--usa_count) { 107 if (*data_pos != usn) { 108 /* 109 * Incomplete multi sector transfer detected! )-: 110 * Set the magic to "BAAD" and return failure. 111 * Note that magic_BAAD is already converted to le32. 112 */ 113 errno = EIO; 114 ntfs_log_perror("Incomplete multi-sector transfer: " 115 "magic: 0x%08x size: %d usa_ofs: %d usa_count:" 116 " %d data: %d usn: %d", le32_to_cpu(*(le32 *)b), size, 117 usa_ofs, usa_count, *data_pos, usn); 118 b->magic = magic_BAAD; 119 return -1; 120 } 121 data_pos += NTFS_BLOCK_SIZE/sizeof(u16); 122 } 123 /* Re-setup the variables. */ 124 usa_count = le16_to_cpu(b->usa_count); 125 data_pos = (u16*)b + NTFS_BLOCK_SIZE/sizeof(u16) - 1; 126 /* Fixup all sectors. */ 127 while (--usa_count) { 128 /* 129 * Increment position in usa and restore original data from 130 * the usa into the data buffer. 131 */ 132 *data_pos = *(++usa_pos); 133 /* Increment position in data as well. */ 134 data_pos += NTFS_BLOCK_SIZE/sizeof(u16); 135 } 136 return 0; 137 } 138 139 /* 140 * Deprotect multi sector transfer protected data 141 * with a warning if an error is found. 142 */ 143 144 int ntfs_mst_post_read_fixup(NTFS_RECORD *b, const u32 size) 145 { 146 return (ntfs_mst_post_read_fixup_warn(b,size,TRUE)); 147 } 148 149 /** 150 * ntfs_mst_pre_write_fixup - apply multi sector transfer protection 151 * @b: pointer to the data to protect 152 * @size: size in bytes of @b 153 * 154 * Perform the necessary pre write multi sector transfer fixup on the data 155 * pointer to by @b of @size. 156 * 157 * Return 0 if fixups applied successfully or -1 if no fixups were performed 158 * due to errors. In that case errno i set to the error code (EINVAL). 159 * 160 * NOTE: We consider the absence / invalidity of an update sequence array to 161 * mean error. This means that you have to create a valid update sequence 162 * array header in the ntfs record before calling this function, otherwise it 163 * will fail (the header needs to contain the position of the update sequence 164 * array together with the number of elements in the array). You also need to 165 * initialise the update sequence number before calling this function 166 * otherwise a random word will be used (whatever was in the record at that 167 * position at that time). 168 */ 169 int ntfs_mst_pre_write_fixup(NTFS_RECORD *b, const u32 size) 170 { 171 u16 usa_ofs, usa_count, usn; 172 le16 le_usn; 173 le16 *usa_pos, *data_pos; 174 175 ntfs_log_trace("Entering\n"); 176 177 /* Sanity check + only fixup if it makes sense. */ 178 if (!b || ntfs_is_baad_record(b->magic) || 179 ntfs_is_hole_record(b->magic)) { 180 errno = EINVAL; 181 ntfs_log_perror("%s: bad argument", __FUNCTION__); 182 return -1; 183 } 184 /* Setup the variables. */ 185 usa_ofs = le16_to_cpu(b->usa_ofs); 186 usa_count = le16_to_cpu(b->usa_count); 187 188 if (!is_valid_record(size, usa_ofs, usa_count)) { 189 errno = EINVAL; 190 ntfs_log_perror("%s", __FUNCTION__); 191 return -1; 192 } 193 /* Position of usn in update sequence array. */ 194 usa_pos = (le16*)((u8*)b + usa_ofs); 195 /* 196 * Cyclically increment the update sequence number 197 * (skipping 0 and -1, i.e. 0xffff). 198 */ 199 usn = le16_to_cpup(usa_pos) + 1; 200 if (usn == 0xffff || !usn) 201 usn = 1; 202 le_usn = cpu_to_le16(usn); 203 *usa_pos = le_usn; 204 /* Position in data of first le16 that needs fixing up. */ 205 data_pos = (le16*)b + NTFS_BLOCK_SIZE/sizeof(le16) - 1; 206 /* Fixup all sectors. */ 207 while (--usa_count) { 208 /* 209 * Increment the position in the usa and save the 210 * original data from the data buffer into the usa. 211 */ 212 *(++usa_pos) = *data_pos; 213 /* Apply fixup to data. */ 214 *data_pos = le_usn; 215 /* Increment position in data as well. */ 216 data_pos += NTFS_BLOCK_SIZE/sizeof(le16); 217 } 218 return 0; 219 } 220 221 /** 222 * ntfs_mst_post_write_fixup - deprotect multi sector transfer protected data 223 * @b: pointer to the data to deprotect 224 * 225 * Perform the necessary post write multi sector transfer fixup, not checking 226 * for any errors, because we assume we have just used 227 * ntfs_mst_pre_write_fixup(), thus the data will be fine or we would never 228 * have gotten here. 229 */ 230 void ntfs_mst_post_write_fixup(NTFS_RECORD *b) 231 { 232 u16 *usa_pos, *data_pos; 233 234 u16 usa_ofs = le16_to_cpu(b->usa_ofs); 235 u16 usa_count = le16_to_cpu(b->usa_count); 236 237 ntfs_log_trace("Entering\n"); 238 239 /* Position of usn in update sequence array. */ 240 usa_pos = (u16*)b + usa_ofs/sizeof(u16); 241 242 /* Position in protected data of first u16 that needs fixing up. */ 243 data_pos = (u16*)b + NTFS_BLOCK_SIZE/sizeof(u16) - 1; 244 245 /* Fixup all sectors. */ 246 while (--usa_count) { 247 /* 248 * Increment position in usa and restore original data from 249 * the usa into the data buffer. 250 */ 251 *data_pos = *(++usa_pos); 252 253 /* Increment position in data as well. */ 254 data_pos += NTFS_BLOCK_SIZE/sizeof(u16); 255 } 256 } 257 258