ntfs.c 28.9 KB
Newer Older
1 2 3
/* ntfs.c - NTFS filesystem */
/*
 *  GRUB  --  GRand Unified Bootloader
4
 *  Copyright (C) 2007,2008,2009 Free Software Foundation, Inc.
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

20 21
#define grub_fshelp_node grub_ntfs_file 

22 23 24 25 26 27
#include <grub/file.h>
#include <grub/mm.h>
#include <grub/misc.h>
#include <grub/disk.h>
#include <grub/dl.h>
#include <grub/fshelp.h>
28
#include <grub/ntfs.h>
29
#include <grub/charset.h>
30

31 32
GRUB_MOD_LICENSE ("GPLv3+");

33 34
static grub_dl_t my_mod;

35 36
#define grub_fshelp_node grub_ntfs_file 

37 38 39 40 41
static inline grub_uint16_t
u16at (void *ptr, grub_size_t ofs)
{
  return grub_le_to_cpu16 (grub_get_unaligned16 ((char *) ptr + ofs));
}
42

43 44 45 46 47
static inline grub_uint32_t
u32at (void *ptr, grub_size_t ofs)
{
  return grub_le_to_cpu32 (grub_get_unaligned32 ((char *) ptr + ofs));
}
48

49 50 51 52 53
static inline grub_uint64_t
u64at (void *ptr, grub_size_t ofs)
{
  return grub_le_to_cpu64 (grub_get_unaligned64 ((char *) ptr + ofs));
}
54 55

grub_ntfscomp_func_t grub_ntfscomp_func;
56

57
static grub_err_t
58
fixup (grub_uint8_t *buf, grub_size_t len, const grub_uint8_t *magic)
59
{
60 61
  grub_uint16_t ss;
  grub_uint8_t *pu;
62
  grub_uint16_t us;
63

64 65
  COMPILE_TIME_ASSERT ((1 << GRUB_NTFS_BLK_SHR) == GRUB_DISK_SECTOR_SIZE);

66 67 68 69
  if (grub_memcmp (buf, magic, 4))
    return grub_error (GRUB_ERR_BAD_FS, "%s label not found", magic);

  ss = u16at (buf, 6) - 1;
70 71
  if (ss != len)
    return grub_error (GRUB_ERR_BAD_FS, "size not match");
72 73 74 75 76
  pu = buf + u16at (buf, 4);
  us = u16at (pu, 0);
  buf -= 2;
  while (ss > 0)
    {
77
      buf += GRUB_DISK_SECTOR_SIZE;
78 79
      pu += 2;
      if (u16at (buf, 0) != us)
80
	return grub_error (GRUB_ERR_BAD_FS, "fixup signature not match");
81 82
      buf[0] = pu[0];
      buf[1] = pu[1];
83 84 85 86 87 88
      ss--;
    }

  return 0;
}

89
static grub_err_t read_mft (struct grub_ntfs_data *data, grub_uint8_t *buf,
90
			    grub_uint64_t mftno);
91
static grub_err_t read_attr (struct grub_ntfs_attr *at, grub_uint8_t *dest,
92
			     grub_disk_addr_t ofs, grub_size_t len,
93
			     int cached,
94 95
			     grub_disk_read_hook_t read_hook,
			     void *read_hook_data);
96

97 98
static grub_err_t read_data (struct grub_ntfs_attr *at, grub_uint8_t *pa,
			     grub_uint8_t *dest,
99
			     grub_disk_addr_t ofs, grub_size_t len,
100
			     int cached,
101 102
			     grub_disk_read_hook_t read_hook,
			     void *read_hook_data);
103 104 105 106 107

static void
init_attr (struct grub_ntfs_attr *at, struct grub_ntfs_file *mft)
{
  at->mft = mft;
108
  at->flags = (mft == &mft->data->mmft) ? GRUB_NTFS_AF_MMFT : 0;
109 110 111 112 113 114 115 116 117 118 119 120
  at->attr_nxt = mft->buf + u16at (mft->buf, 0x14);
  at->attr_end = at->emft_buf = at->edat_buf = at->sbuf = NULL;
}

static void
free_attr (struct grub_ntfs_attr *at)
{
  grub_free (at->emft_buf);
  grub_free (at->edat_buf);
  grub_free (at->sbuf);
}

121 122
static grub_uint8_t *
find_attr (struct grub_ntfs_attr *at, grub_uint8_t attr)
123
{
124
  if (at->flags & GRUB_NTFS_AF_ALST)
125 126 127 128 129 130
    {
    retry:
      while (at->attr_nxt < at->attr_end)
	{
	  at->attr_cur = at->attr_nxt;
	  at->attr_nxt += u16at (at->attr_cur, 4);
131
	  if ((*at->attr_cur == attr) || (attr == 0))
132
	    {
133
	      grub_uint8_t *new_pos;
134

135
	      if (at->flags & GRUB_NTFS_AF_MMFT)
136 137
		{
		  if ((grub_disk_read
138
		       (at->mft->data->disk, u32at (at->attr_cur, 0x10), 0,
139 140 141
			512, at->emft_buf))
		      ||
		      (grub_disk_read
142
		       (at->mft->data->disk, u32at (at->attr_cur, 0x14), 0,
143 144 145
			512, at->emft_buf + 512)))
		    return NULL;

146 147
		  if (fixup (at->emft_buf, at->mft->data->mft_size,
			     (const grub_uint8_t *) "FILE"))
148 149 150 151 152 153 154 155 156 157
		    return NULL;
		}
	      else
		{
		  if (read_mft (at->mft->data, at->emft_buf,
				u32at (at->attr_cur, 0x10)))
		    return NULL;
		}

	      new_pos = &at->emft_buf[u16at (at->emft_buf, 0x14)];
158
	      while (*new_pos != 0xFF)
159
		{
160
		  if ((*new_pos == *at->attr_cur)
161 162 163 164 165 166 167
		      && (u16at (new_pos, 0xE) == u16at (at->attr_cur, 0x18)))
		    {
		      return new_pos;
		    }
		  new_pos += u16at (new_pos, 4);
		}
	      grub_error (GRUB_ERR_BAD_FS,
168
			  "can\'t find 0x%X in attribute list",
169 170 171 172 173 174 175
			  (unsigned char) *at->attr_cur);
	      return NULL;
	    }
	}
      return NULL;
    }
  at->attr_cur = at->attr_nxt;
176
  while (*at->attr_cur != 0xFF)
177 178
    {
      at->attr_nxt += u16at (at->attr_cur, 4);
179
      if (*at->attr_cur == GRUB_NTFS_AT_ATTRIBUTE_LIST)
180
	at->attr_end = at->attr_cur;
181
      if ((*at->attr_cur == attr) || (attr == 0))
182 183 184 185 186
	return at->attr_cur;
      at->attr_cur = at->attr_nxt;
    }
  if (at->attr_end)
    {
187
      grub_uint8_t *pa;
188

189
      at->emft_buf = grub_malloc (at->mft->data->mft_size << GRUB_NTFS_BLK_SHR);
190 191 192 193 194 195
      if (at->emft_buf == NULL)
	return NULL;

      pa = at->attr_end;
      if (pa[8])
	{
196
          grub_uint32_t n;
197 198 199

          n = ((u32at (pa, 0x30) + GRUB_DISK_SECTOR_SIZE - 1)
               & (~(GRUB_DISK_SECTOR_SIZE - 1)));
200
	  at->attr_cur = at->attr_end;
201
	  at->edat_buf = grub_malloc (n);
202 203
	  if (!at->edat_buf)
	    return NULL;
204
	  if (read_data (at, pa, at->edat_buf, 0, n, 0, 0, 0))
205 206
	    {
	      grub_error (GRUB_ERR_BAD_FS,
207
			  "fail to read non-resident attribute list");
208 209 210 211 212 213 214 215 216 217
	      return NULL;
	    }
	  at->attr_nxt = at->edat_buf;
	  at->attr_end = at->edat_buf + u32at (pa, 0x30);
	}
      else
	{
	  at->attr_nxt = at->attr_end + u16at (pa, 0x14);
	  at->attr_end = at->attr_end + u32at (pa, 4);
	}
218
      at->flags |= GRUB_NTFS_AF_ALST;
219 220
      while (at->attr_nxt < at->attr_end)
	{
221
	  if ((*at->attr_nxt == attr) || (attr == 0))
222 223 224 225 226 227
	    break;
	  at->attr_nxt += u16at (at->attr_nxt, 4);
	}
      if (at->attr_nxt >= at->attr_end)
	return NULL;

228
      if ((at->flags & GRUB_NTFS_AF_MMFT) && (attr == GRUB_NTFS_AT_DATA))
229
	{
230
	  at->flags |= GRUB_NTFS_AF_GPOS;
231 232
	  at->attr_cur = at->attr_nxt;
	  pa = at->attr_cur;
233 234 235 236 237
	  grub_set_unaligned32 ((char *) pa + 0x10,
				grub_cpu_to_le32 (at->mft->data->mft_start));
	  grub_set_unaligned32 ((char *) pa + 0x14,
				grub_cpu_to_le32 (at->mft->data->mft_start
						  + 1));
238 239 240
	  pa = at->attr_nxt + u16at (pa, 4);
	  while (pa < at->attr_end)
	    {
241
	      if (*pa != attr)
242 243 244
		break;
	      if (read_attr
		  (at, pa + 0x10,
245
		   u32at (pa, 0x10) * (at->mft->data->mft_size << GRUB_NTFS_BLK_SHR),
246
		   at->mft->data->mft_size << GRUB_NTFS_BLK_SHR, 0, 0, 0))
247 248 249 250
		return NULL;
	      pa += u16at (pa, 4);
	    }
	  at->attr_nxt = at->attr_cur;
251
	  at->flags &= ~GRUB_NTFS_AF_GPOS;
252 253 254 255 256 257
	}
      goto retry;
    }
  return NULL;
}

258
static grub_uint8_t *
259
locate_attr (struct grub_ntfs_attr *at, struct grub_ntfs_file *mft,
260
	     grub_uint8_t attr)
261
{
262
  grub_uint8_t *pa;
263 264

  init_attr (at, mft);
265 266
  pa = find_attr (at, attr);
  if (pa == NULL)
267
    return NULL;
268
  if ((at->flags & GRUB_NTFS_AF_ALST) == 0)
269 270 271
    {
      while (1)
	{
272 273
	  pa = find_attr (at, attr);
	  if (pa == NULL)
274
	    break;
275
	  if (at->flags & GRUB_NTFS_AF_ALST)
276 277 278 279 280 281 282 283 284 285
	    return pa;
	}
      grub_errno = GRUB_ERR_NONE;
      free_attr (at);
      init_attr (at, mft);
      pa = find_attr (at, attr);
    }
  return pa;
}

286 287
static grub_disk_addr_t
read_run_data (const grub_uint8_t *run, int nn, int sig)
288
{
289
  grub_uint64_t r = 0;
290

291 292
  if (sig && nn && (run[nn - 1] & 0x80))
    r = -1;
293

294
  grub_memcpy (&r, run, nn);
295

296
  return grub_le_to_cpu64 (r);
297 298
}

299 300
grub_err_t
grub_ntfs_read_run_list (struct grub_ntfs_rlst * ctx)
301
{
302
  grub_uint8_t c1, c2;
303
  grub_disk_addr_t val;
304
  grub_uint8_t *run;
305 306 307

  run = ctx->cur_run;
retry:
308 309 310
  c1 = ((*run) & 0x7);
  c2 = ((*run) >> 4) & 0x7;
  run++;
311 312
  if (!c1)
    {
313
      if ((ctx->attr) && (ctx->attr->flags & GRUB_NTFS_AF_ALST))
314
	{
315
	  grub_disk_read_hook_t save_hook;
316 317 318

	  save_hook = ctx->comp.disk->read_hook;
	  ctx->comp.disk->read_hook = 0;
319
	  run = find_attr (ctx->attr, *ctx->attr->attr_cur);
320 321 322 323 324 325 326 327 328 329 330 331
	  ctx->comp.disk->read_hook = save_hook;
	  if (run)
	    {
	      if (run[8] == 0)
		return grub_error (GRUB_ERR_BAD_FS,
				   "$DATA should be non-resident");

	      run += u16at (run, 0x20);
	      ctx->curr_lcn = 0;
	      goto retry;
	    }
	}
332
      return grub_error (GRUB_ERR_BAD_FS, "run list overflown");
333 334
    }
  ctx->curr_vcn = ctx->next_vcn;
335 336 337 338
  ctx->next_vcn += read_run_data (run, c1, 0);	/* length of current VCN */
  run += c1;
  val = read_run_data (run, c2, 1);	/* offset to previous LCN */
  run += c2;
339 340
  ctx->curr_lcn += val;
  if (val == 0)
341
    ctx->flags |= GRUB_NTFS_RF_BLNK;
342
  else
343
    ctx->flags &= ~GRUB_NTFS_RF_BLNK;
344 345 346 347
  ctx->cur_run = run;
  return 0;
}

348 349
static grub_disk_addr_t
grub_ntfs_read_block (grub_fshelp_node_t node, grub_disk_addr_t block)
350 351 352 353
{
  struct grub_ntfs_rlst *ctx;

  ctx = (struct grub_ntfs_rlst *) node;
354
  if (block >= ctx->next_vcn)
355
    {
356
      if (grub_ntfs_read_run_list (ctx))
357 358 359 360
	return -1;
      return ctx->curr_lcn;
    }
  else
361
    return (ctx->flags & GRUB_NTFS_RF_BLNK) ? 0 : (block -
362 363 364 365
					 ctx->curr_vcn + ctx->curr_lcn);
}

static grub_err_t
366
read_data (struct grub_ntfs_attr *at, grub_uint8_t *pa, grub_uint8_t *dest,
367
	   grub_disk_addr_t ofs, grub_size_t len, int cached,
368
	   grub_disk_read_hook_t read_hook, void *read_hook_data)
369 370 371 372 373 374 375 376 377
{
  struct grub_ntfs_rlst cc, *ctx;

  if (len == 0)
    return 0;

  grub_memset (&cc, 0, sizeof (cc));
  ctx = &cc;
  ctx->attr = at;
378
  ctx->comp.log_spc = at->mft->data->log_spc;
379 380
  ctx->comp.disk = at->mft->data->disk;

381 382 383
  if (read_hook == grub_file_progress_hook)
    ctx->file = read_hook_data;

384 385 386
  if (pa[8] == 0)
    {
      if (ofs + len > u32at (pa, 0x10))
387
	return grub_error (GRUB_ERR_BAD_FS, "read out of range");
388 389 390 391 392 393
      grub_memcpy (dest, pa + u32at (pa, 0x14) + ofs, len);
      return 0;
    }

  ctx->cur_run = pa + u16at (pa, 0x20);

394 395 396 397 398
  ctx->next_vcn = u32at (pa, 0x10);
  ctx->curr_lcn = 0;

  if ((pa[0xC] & GRUB_NTFS_FLAG_COMPRESSED)
      && !(at->flags & GRUB_NTFS_AF_GPOS))
399 400
    {
      if (!cached)
401
	return grub_error (GRUB_ERR_BAD_FS, "attribute can\'t be compressed");
402

403 404 405
      return (grub_ntfscomp_func) ? grub_ntfscomp_func (dest, ofs, len, ctx)
	: grub_error (GRUB_ERR_BAD_FS, N_("module `%s' isn't loaded"),
		      "ntfscomp");
406 407
    }

408
  ctx->target_vcn = ofs >> (GRUB_NTFS_BLK_SHR + ctx->comp.log_spc);
409 410
  while (ctx->next_vcn <= ctx->target_vcn)
    {
411
      if (grub_ntfs_read_run_list (ctx))
412 413 414
	return grub_errno;
    }

415
  if (at->flags & GRUB_NTFS_AF_GPOS)
416
    {
417
      grub_disk_addr_t st0, st1;
418
      grub_uint64_t m;
419

420
      m = (ofs >> GRUB_NTFS_BLK_SHR) & ((1 << ctx->comp.log_spc) - 1);
421 422

      st0 =
423
	((ctx->target_vcn - ctx->curr_vcn + ctx->curr_lcn) << ctx->comp.log_spc) + m;
424 425
      st1 = st0 + 1;
      if (st1 ==
426
	  (ctx->next_vcn - ctx->curr_vcn + ctx->curr_lcn) << ctx->comp.log_spc)
427
	{
428
	  if (grub_ntfs_read_run_list (ctx))
429
	    return grub_errno;
430
	  st1 = ctx->curr_lcn << ctx->comp.log_spc;
431
	}
432 433
      grub_set_unaligned32 (dest, grub_cpu_to_le32 (st0));
      grub_set_unaligned32 (dest + 4, grub_cpu_to_le32 (st1));
434 435 436
      return 0;
    }

437 438 439 440 441 442
  grub_fshelp_read_file (ctx->comp.disk, (grub_fshelp_node_t) ctx,
			 read_hook, read_hook_data, ofs, len,
			 (char *) dest,
			 grub_ntfs_read_block, ofs + len,
			 ctx->comp.log_spc, 0);
  return grub_errno;
443 444 445
}

static grub_err_t
446
read_attr (struct grub_ntfs_attr *at, grub_uint8_t *dest, grub_disk_addr_t ofs,
447
	   grub_size_t len, int cached,
448
	   grub_disk_read_hook_t read_hook, void *read_hook_data)
449
{
450 451 452
  grub_uint8_t *save_cur;
  grub_uint8_t attr;
  grub_uint8_t *pp;
453 454 455 456
  grub_err_t ret;

  save_cur = at->attr_cur;
  at->attr_nxt = at->attr_cur;
457
  attr = *at->attr_nxt;
458
  if (at->flags & GRUB_NTFS_AF_ALST)
459
    {
460
      grub_uint8_t *pa;
461
      grub_disk_addr_t vcn;
462

463 464
      /* If compression is possible make sure that we include possible
	 compressed block size.  */
465
      if (GRUB_NTFS_LOG_COM_SEC >= at->mft->data->log_spc)
466
	vcn = ((ofs >> GRUB_NTFS_COM_LOG_LEN)
467
	       << (GRUB_NTFS_LOG_COM_SEC - at->mft->data->log_spc)) & ~0xFULL;
468
      else
469
	vcn = ofs >> (at->mft->data->log_spc + GRUB_NTFS_BLK_SHR);
470 471 472
      pa = at->attr_nxt + u16at (at->attr_nxt, 4);
      while (pa < at->attr_end)
	{
473
	  if (*pa != attr)
474 475 476 477 478 479 480 481 482
	    break;
	  if (u32at (pa, 8) > vcn)
	    break;
	  at->attr_nxt = pa;
	  pa += u16at (pa, 4);
	}
    }
  pp = find_attr (at, attr);
  if (pp)
483 484
    ret = read_data (at, pp, dest, ofs, len, cached,
		     read_hook, read_hook_data);
485 486 487
  else
    ret =
      (grub_errno) ? grub_errno : grub_error (GRUB_ERR_BAD_FS,
488
					      "attribute not found");
489 490 491 492 493
  at->attr_cur = save_cur;
  return ret;
}

static grub_err_t
494
read_mft (struct grub_ntfs_data *data, grub_uint8_t *buf, grub_uint64_t mftno)
495 496
{
  if (read_attr
497
      (&data->mmft.attr, buf, mftno * ((grub_disk_addr_t) data->mft_size << GRUB_NTFS_BLK_SHR),
498
       data->mft_size << GRUB_NTFS_BLK_SHR, 0, 0, 0))
499
    return grub_error (GRUB_ERR_BAD_FS, "read MFT 0x%llx fails", (unsigned long long) mftno);
500
  return fixup (buf, data->mft_size, (const grub_uint8_t *) "FILE");
501 502 503
}

static grub_err_t
504
init_file (struct grub_ntfs_file *mft, grub_uint64_t mftno)
505 506 507 508 509
{
  unsigned short flag;

  mft->inode_read = 1;

510
  mft->buf = grub_malloc (mft->data->mft_size << GRUB_NTFS_BLK_SHR);
511 512 513 514 515 516 517 518
  if (mft->buf == NULL)
    return grub_errno;

  if (read_mft (mft->data, mft->buf, mftno))
    return grub_errno;

  flag = u16at (mft->buf, 0x16);
  if ((flag & 1) == 0)
519 520
    return grub_error (GRUB_ERR_BAD_FS, "MFT 0x%llx is not in use",
		       (unsigned long long) mftno);
521 522 523

  if ((flag & 2) == 0)
    {
524
      grub_uint8_t *pa;
525

526
      pa = locate_attr (&mft->attr, mft, GRUB_NTFS_AT_DATA);
527
      if (pa == NULL)
528 529
	return grub_error (GRUB_ERR_BAD_FS, "no $DATA in MFT 0x%llx",
			   (unsigned long long) mftno);
530 531 532 533

      if (!pa[8])
	mft->size = u32at (pa, 0x10);
      else
534
	mft->size = u64at (pa, 0x30);
535

536
      if ((mft->attr.flags & GRUB_NTFS_AF_ALST) == 0)
537 538 539 540 541 542 543 544 545 546 547 548 549 550 551
	mft->attr.attr_end = 0;	/*  Don't jump to attribute list */
    }
  else
    init_attr (&mft->attr, mft);

  return 0;
}

static void
free_file (struct grub_ntfs_file *mft)
{
  free_attr (&mft->attr);
  grub_free (mft->buf);
}

552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573
static char *
get_utf8 (grub_uint8_t *in, grub_size_t len)
{
  grub_uint8_t *buf;
  grub_uint16_t *tmp;
  grub_size_t i;

  buf = grub_malloc (len * GRUB_MAX_UTF8_PER_UTF16 + 1);
  tmp = grub_malloc (len * sizeof (tmp[0]));
  if (!buf || !tmp)
    {
      grub_free (buf);
      grub_free (tmp);
      return NULL;
    }
  for (i = 0; i < len; i++)
    tmp[i] = grub_le_to_cpu16 (grub_get_unaligned16 (in + 2 * i));
  *grub_utf16_to_utf8 (buf, tmp, len) = '\0';
  grub_free (tmp);
  return (char *) buf;
}

574
static int
575
list_file (struct grub_ntfs_file *diro, grub_uint8_t *pos,
576
	   grub_fshelp_iterate_dir_hook_t hook, void *hook_data)
577
{
578
  grub_uint8_t *np;
579 580 581 582
  int ns;

  while (1)
    {
583 584
      grub_uint8_t namespace;
      char *ustr;
585

586 587 588
      if (pos[0xC] & 2)		/* end signature */
	break;

589
      np = pos + 0x50;
590
      ns = *(np++);
591 592 593 594 595 596 597
      namespace = *(np++);

      /*
       *  Ignore files in DOS namespace, as they will reappear as Win32
       *  names.
       */
      if ((ns) && (namespace != 2))
598 599 600
	{
	  enum grub_fshelp_filetype type;
	  struct grub_ntfs_file *fdiro;
601
	  grub_uint32_t attr;
602

603 604 605 606 607 608 609
	  attr = u32at (pos, 0x48);
	  if (attr & GRUB_NTFS_ATTR_REPARSE)
	    type = GRUB_FSHELP_SYMLINK;
	  else if (attr & GRUB_NTFS_ATTR_DIRECTORY)
	    type = GRUB_FSHELP_DIR;
	  else
	    type = GRUB_FSHELP_REG;
610

611
	  fdiro = grub_zalloc (sizeof (struct grub_ntfs_file));
612 613 614 615
	  if (!fdiro)
	    return 0;

	  fdiro->data = diro->data;
616
	  fdiro->ino = u64at (pos, 0) & 0xffffffffffffULL;
617
	  fdiro->mtime = u64at (pos, 0x20);
618

619
	  ustr = get_utf8 (np, ns);
620
	  if (ustr == NULL)
621 622 623 624
	    {
	      grub_free (fdiro);
	      return 0;
	    }
625 626 627
          if (namespace)
            type |= GRUB_FSHELP_CASE_INSENSITIVE;

628
	  if (hook (ustr, type, fdiro, hook_data))
629 630 631 632 633 634 635 636 637 638 639 640
	    {
	      grub_free (ustr);
	      return 1;
	    }

	  grub_free (ustr);
	}
      pos += u16at (pos, 8);
    }
  return 0;
}

641 642 643 644 645 646 647 648
struct symlink_descriptor
{
  grub_uint32_t type;
  grub_uint32_t total_len;
  grub_uint16_t off1;
  grub_uint16_t len1;
  grub_uint16_t off2;
  grub_uint16_t len2;
649
} GRUB_PACKED;
650 651 652 653 654 655 656

static char *
grub_ntfs_read_symlink (grub_fshelp_node_t node)
{
  struct grub_ntfs_file *mft;
  struct symlink_descriptor symdesc;
  grub_err_t err;
657
  grub_uint8_t *buf16;
658 659
  char *buf, *end;
  grub_size_t len;
660
  grub_uint8_t *pa;
661 662 663 664 665 666 667 668 669 670 671 672 673 674
  grub_size_t off;

  mft = (struct grub_ntfs_file *) node;

  mft->buf = grub_malloc (mft->data->mft_size << GRUB_NTFS_BLK_SHR);
  if (mft->buf == NULL)
    return NULL;

  if (read_mft (mft->data, mft->buf, mft->ino))
    return NULL;

  pa = locate_attr (&mft->attr, mft, GRUB_NTFS_AT_SYMLINK);
  if (pa == NULL)
    {
675 676
      grub_error (GRUB_ERR_BAD_FS, "no $SYMLINK in MFT 0x%llx",
		  (unsigned long long) mft->ino);
677 678 679
      return NULL;
    }

680
  err = read_attr (&mft->attr, (grub_uint8_t *) &symdesc, 0,
681
		   sizeof (struct symlink_descriptor), 1, 0, 0);
682 683 684 685 686 687
  if (err)
    return NULL;

  switch (grub_cpu_to_le32 (symdesc.type))
    {
    case 0xa000000c:
688 689
      off = (sizeof (struct symlink_descriptor) + 4
	     + grub_cpu_to_le32 (symdesc.off1));
690 691 692
      len = grub_cpu_to_le32 (symdesc.len1);
      break;
    case 0xa0000003:
693 694
      off = (sizeof (struct symlink_descriptor)
	     + grub_cpu_to_le32 (symdesc.off1));
695 696 697 698 699 700 701 702 703 704 705 706
      len = grub_cpu_to_le32 (symdesc.len1);
      break;
    default:
      grub_error (GRUB_ERR_BAD_FS, "symlink type invalid (%x)",
		  grub_cpu_to_le32 (symdesc.type));
      return NULL;
    }

  buf16 = grub_malloc (len);
  if (!buf16)
    return NULL;

707
  err = read_attr (&mft->attr, buf16, off, len, 1, 0, 0);
708 709 710
  if (err)
    return NULL;

711
  buf = get_utf8 (buf16, len / 2);
712 713 714 715 716
  if (!buf)
    {
      grub_free (buf16);
      return NULL;
    }
717
  grub_free (buf16);
718

719 720 721
  for (end = buf; *end; end++)
    if (*end == '\\')
      *end = '/';
722 723 724 725 726 727 728 729 730 731 732

  /* Split the sequence to avoid GCC thinking that this is a trigraph.  */
  if (grub_memcmp (buf, "/?" "?/", 4) == 0 && buf[5] == ':' && buf[6] == '/'
      && grub_isalpha (buf[4]))
    {
      grub_memmove (buf, buf + 6, end - buf + 1 - 6);
      end -= 6; 
    }
  return buf;
}

733 734
static int
grub_ntfs_iterate_dir (grub_fshelp_node_t dir,
735
		       grub_fshelp_iterate_dir_hook_t hook, void *hook_data)
736
{
737
  grub_uint8_t *bitmap;
738
  struct grub_ntfs_attr attr, *at;
739
  grub_uint8_t *cur_pos, *indx, *bmp;
740 741
  int ret = 0;
  grub_size_t bitmap_len;
742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758
  struct grub_ntfs_file *mft;

  mft = (struct grub_ntfs_file *) dir;

  if (!mft->inode_read)
    {
      if (init_file (mft, mft->ino))
	return 0;
    }

  indx = NULL;
  bmp = NULL;

  at = &attr;
  init_attr (at, mft);
  while (1)
    {
759 760
      cur_pos = find_attr (at, GRUB_NTFS_AT_INDEX_ROOT);
      if (cur_pos == NULL)
761
	{
762
	  grub_error (GRUB_ERR_BAD_FS, "no $INDEX_ROOT");
763 764 765 766 767 768 769 770 771 772 773 774 775 776 777
	  goto done;
	}

      /* Resident, Namelen=4, Offset=0x18, Flags=0x00, Name="$I30" */
      if ((u32at (cur_pos, 8) != 0x180400) ||
	  (u32at (cur_pos, 0x18) != 0x490024) ||
	  (u32at (cur_pos, 0x1C) != 0x300033))
	continue;
      cur_pos += u16at (cur_pos, 0x14);
      if (*cur_pos != 0x30)	/* Not filename index */
	continue;
      break;
    }

  cur_pos += 0x10;		/* Skip index root */
778
  ret = list_file (mft, cur_pos + u16at (cur_pos, 0), hook, hook_data);
779 780 781 782 783 784 785
  if (ret)
    goto done;

  bitmap = NULL;
  bitmap_len = 0;
  free_attr (at);
  init_attr (at, mft);
786
  while ((cur_pos = find_attr (at, GRUB_NTFS_AT_BITMAP)) != NULL)
787 788 789
    {
      int ofs;

790
      ofs = cur_pos[0xA];
791 792 793 794 795
      /* Namelen=4, Name="$I30" */
      if ((cur_pos[9] == 4) &&
	  (u32at (cur_pos, ofs) == 0x490024) &&
	  (u32at (cur_pos, ofs + 4) == 0x300033))
	{
796 797 798
          int is_resident = (cur_pos[8] == 0);

          bitmap_len = ((is_resident) ? u32at (cur_pos, 0x10) :
799
                        u32at (cur_pos, 0x28));
800

801 802 803 804 805
          bmp = grub_malloc (bitmap_len);
          if (bmp == NULL)
            goto done;

	  if (is_resident)
806
	    {
807
              grub_memcpy (bmp, cur_pos + u16at (cur_pos, 0x14),
808
                           bitmap_len);
809
	    }
810 811
          else
            {
812
              if (read_data (at, cur_pos, bmp, 0, bitmap_len, 0, 0, 0))
813 814
                {
                  grub_error (GRUB_ERR_BAD_FS,
815
                              "fails to read non-resident $BITMAP");
816 817 818 819 820
                  goto done;
                }
              bitmap_len = u32at (cur_pos, 0x30);
            }

821
          bitmap = bmp;
822 823 824 825 826
	  break;
	}
    }

  free_attr (at);
827
  cur_pos = locate_attr (at, mft, GRUB_NTFS_AT_INDEX_ALLOCATION);
828 829 830 831 832 833 834
  while (cur_pos != NULL)
    {
      /* Non-resident, Namelen=4, Offset=0x40, Flags=0, Name="$I30" */
      if ((u32at (cur_pos, 8) == 0x400401) &&
	  (u32at (cur_pos, 0x40) == 0x490024) &&
	  (u32at (cur_pos, 0x44) == 0x300033))
	break;
835
      cur_pos = find_attr (at, GRUB_NTFS_AT_INDEX_ALLOCATION);
836 837 838 839 840 841 842 843 844 845
    }

  if ((!cur_pos) && (bitmap))
    {
      grub_error (GRUB_ERR_BAD_FS, "$BITMAP without $INDEX_ALLOCATION");
      goto done;
    }

  if (bitmap)
    {
846 847
      grub_disk_addr_t i;
      grub_uint8_t v;
848

849
      indx = grub_malloc (mft->data->idx_size << GRUB_NTFS_BLK_SHR);
850 851 852 853
      if (indx == NULL)
	goto done;

      v = 1;
854
      for (i = 0; i < (grub_disk_addr_t)bitmap_len * 8; i++)
855 856 857 858
	{
	  if (*bitmap & v)
	    {
	      if ((read_attr
859
		   (at, indx, i * (mft->data->idx_size << GRUB_NTFS_BLK_SHR),
860
		    (mft->data->idx_size << GRUB_NTFS_BLK_SHR), 0, 0, 0))
861 862
		  || (fixup (indx, mft->data->idx_size,
			     (const grub_uint8_t *) "INDX")))
863
		goto done;
864 865
	      ret = list_file (mft, &indx[0x18 + u16at (indx, 0x18)],
			       hook, hook_data);
866 867 868 869
	      if (ret)
		goto done;
	    }
	  v <<= 1;
870
	  if (!v)
871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890
	    {
	      v = 1;
	      bitmap++;
	    }
	}
    }

done:
  free_attr (at);
  grub_free (indx);
  grub_free (bmp);

  return ret;
}

static struct grub_ntfs_data *
grub_ntfs_mount (grub_disk_t disk)
{
  struct grub_ntfs_bpb bpb;
  struct grub_ntfs_data *data = 0;
891
  grub_uint32_t spc;
892 893 894 895

  if (!disk)
    goto fail;

896
  data = (struct grub_ntfs_data *) grub_zalloc (sizeof (*data));
897 898 899 900 901 902
  if (!data)
    goto fail;

  data->disk = disk;

  /* Read the BPB.  */
903
  if (grub_disk_read (disk, 0, 0, sizeof (bpb), &bpb))
904 905
    goto fail;

906 907 908 909 910
  if (grub_memcmp ((char *) &bpb.oem_name, "NTFS", 4) != 0
      || bpb.sectors_per_cluster == 0
      || (bpb.sectors_per_cluster & (bpb.sectors_per_cluster - 1)) != 0
      || bpb.bytes_per_sector == 0
      || (bpb.bytes_per_sector & (bpb.bytes_per_sector - 1)) != 0)
911 912
    goto fail;

913 914 915
  spc = (((grub_uint32_t) bpb.sectors_per_cluster
	  * (grub_uint32_t) grub_le_to_cpu16 (bpb.bytes_per_sector))
	 >> GRUB_NTFS_BLK_SHR);
916
  if (spc == 0)
917
    goto fail;
918

919 920
  for (data->log_spc = 0; (1U << data->log_spc) < spc; data->log_spc++);

921
  if (bpb.clusters_per_mft > 0)
922
    data->mft_size = ((grub_disk_addr_t) bpb.clusters_per_mft) << data->log_spc;
923 924
  else if (-bpb.clusters_per_mft < GRUB_NTFS_BLK_SHR || -bpb.clusters_per_mft >= 31)
    goto fail;
925
  else
926
    data->mft_size = 1ULL << (-bpb.clusters_per_mft - GRUB_NTFS_BLK_SHR);
927 928

  if (bpb.clusters_per_index > 0)
929 930
    data->idx_size = (((grub_disk_addr_t) bpb.clusters_per_index)
		      << data->log_spc);
931 932
  else if (-bpb.clusters_per_index < GRUB_NTFS_BLK_SHR || -bpb.clusters_per_index >= 31)
    goto fail;
933
  else
934
    data->idx_size = 1ULL << (-bpb.clusters_per_index - GRUB_NTFS_BLK_SHR);
935

936
  data->mft_start = grub_le_to_cpu64 (bpb.mft_lcn) << data->log_spc;
937

938
  if ((data->mft_size > GRUB_NTFS_MAX_MFT) || (data->idx_size > GRUB_NTFS_MAX_IDX))
939 940 941 942 943
    goto fail;

  data->mmft.data = data;
  data->cmft.data = data;

944
  data->mmft.buf = grub_malloc (data->mft_size << GRUB_NTFS_BLK_SHR);
945 946 947 948
  if (!data->mmft.buf)
    goto fail;

  if (grub_disk_read
949
      (disk, data->mft_start, 0, data->mft_size << GRUB_NTFS_BLK_SHR, data->mmft.buf))
950 951
    goto fail;

952 953
  data->uuid = grub_le_to_cpu64 (bpb.num_serial);

954
  if (fixup (data->mmft.buf, data->mft_size, (const grub_uint8_t *) "FILE"))
955 956
    goto fail;

957
  if (!locate_attr (&data->mmft.attr, &data->mmft, GRUB_NTFS_AT_DATA))
958 959
    goto fail;

960
  if (init_file (&data->cmft, GRUB_NTFS_FILE_ROOT))
961 962 963 964 965 966 967 968 969 970 971
    goto fail;

  return data;

fail:
  grub_error (GRUB_ERR_BAD_FS, "not an ntfs filesystem");

  if (data)
    {
      free_file (&data->mmft);
      free_file (&data->cmft);
972
      grub_free (data);
973 974 975 976
    }
  return 0;
}

977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001
/* Context for grub_ntfs_dir.  */
struct grub_ntfs_dir_ctx
{
  grub_fs_dir_hook_t hook;
  void *hook_data;
};

/* Helper for grub_ntfs_dir.  */
static int
grub_ntfs_dir_iter (const char *filename, enum grub_fshelp_filetype filetype,
		    grub_fshelp_node_t node, void *data)
{
  struct grub_ntfs_dir_ctx *ctx = data;
  struct grub_dirhook_info info;

  grub_memset (&info, 0, sizeof (info));
  info.dir = ((filetype & GRUB_FSHELP_TYPE_MASK) == GRUB_FSHELP_DIR);
  info.mtimeset = 1;
  info.mtime = grub_divmod64 (node->mtime, 10000000, 0) 
    - 86400ULL * 365 * (1970 - 1601)
    - 86400ULL * ((1970 - 1601) / 4) + 86400ULL * ((1970 - 1601) / 100);
  grub_free (node);
  return ctx->hook (filename, &info, ctx->hook_data);
}

1002 1003
static grub_err_t
grub_ntfs_dir (grub_device_t device, const char *path,
1004
	       grub_fs_dir_hook_t hook, void *hook_data)
1005
{
1006
  struct grub_ntfs_dir_ctx ctx = { hook, hook_data };
1007 1008 1009 1010 1011 1012 1013 1014 1015 1016
  struct grub_ntfs_data *data = 0;
  struct grub_fshelp_node *fdiro = 0;

  grub_dl_ref (my_mod);

  data = grub_ntfs_mount (device->disk);
  if (!data)
    goto fail;

  grub_fshelp_find_file (path, &data->cmft, &fdiro, grub_ntfs_iterate_dir,
1017
			 grub_ntfs_read_symlink, GRUB_FSHELP_DIR);
1018 1019 1020 1021

  if (grub_errno)
    goto fail;

1022
  grub_ntfs_iterate_dir (fdiro, grub_ntfs_dir_iter, &ctx);
1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054

fail:
  if ((fdiro) && (fdiro != &data->cmft))
    {
      free_file (fdiro);
      grub_free (fdiro);
    }
  if (data)
    {
      free_file (&data->mmft);
      free_file (&data->cmft);
      grub_free (data);
    }

  grub_dl_unref (my_mod);

  return grub_errno;
}

static grub_err_t
grub_ntfs_open (grub_file_t file, const char *name)
{
  struct grub_ntfs_data *data = 0;
  struct grub_fshelp_node *mft = 0;

  grub_dl_ref (my_mod);

  data = grub_ntfs_mount (file->device->disk);
  if (!data)
    goto fail;

  grub_fshelp_find_file (name, &data->cmft, &mft, grub_ntfs_iterate_dir,
1055
			 grub_ntfs_read_symlink, GRUB_FSHELP_REG);
1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099

  if (grub_errno)
    goto fail;

  if (mft != &data->cmft)
    {
      free_file (&data->cmft);
      grub_memcpy (&data->cmft, mft, sizeof (*mft));
      grub_free (mft);
      if (!data->cmft.inode_read)
	{
	  if (init_file (&data->cmft, data->cmft.ino))
	    goto fail;
	}
    }

  file->size = data->cmft.size;
  file->data = data;
  file->offset = 0;

  return 0;

fail:
  if (data)
    {
      free_file (&data->mmft);
      free_file (&data->cmft);
      grub_free (data);
    }

  grub_dl_unref (my_mod);

  return grub_errno;
}

static grub_ssize_t
grub_ntfs_read (grub_file_t file, char *buf, grub_size_t len)
{
  struct grub_ntfs_file *mft;

  mft = &((struct grub_ntfs_data *) file->data)->cmft;
  if (file->read_hook)
    mft->attr.save_pos = 1;

1100
  read_attr (&mft->attr, (grub_uint8_t *) buf, file->offset, len, 1,
1101
	     file->read_hook, file->read_hook_data);
1102
  return (grub_errno) ? -1 : (grub_ssize_t) len;
1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128
}

static grub_err_t
grub_ntfs_close (grub_file_t file)
{
  struct grub_ntfs_data *data;

  data = file->data;

  if (data)
    {
      free_file (&data->mmft);
      free_file (&data->cmft);
      grub_free (data);
    }

  grub_dl_unref (my_mod);

  return grub_errno;
}

static grub_err_t
grub_ntfs_label (grub_device_t device, char **label)
{
  struct grub_ntfs_data *data = 0;
  struct grub_fshelp_node *mft = 0;
1129
  grub_uint8_t *pa;
1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146

  grub_dl_ref (my_mod);

  *label = 0;

  data = grub_ntfs_mount (device->disk);
  if (!data)
    goto fail;

  grub_fshelp_find_file ("/$Volume", &data->cmft, &mft, grub_ntfs_iterate_dir,
			 0, GRUB_FSHELP_REG);

  if (grub_errno)
    goto fail;

  if (!mft->inode_read)
    {
1147
      mft->buf = grub_malloc (mft->data->mft_size << GRUB_NTFS_BLK_SHR);
1148 1149 1150 1151 1152 1153 1154 1155
      if (mft->buf == NULL)
	goto fail;

      if (read_mft (mft->data, mft->buf, mft->ino))
	goto fail;
    }

  init_attr (&mft->attr, mft);
1156
  pa = find_attr (&mft->attr, GRUB_NTFS_AT_VOLUME_NAME);
1157 1158 1159 1160 1161 1162
  if ((pa) && (pa[8] == 0) && (u32at (pa, 0x10)))
    {
      int len;

      len = u32at (pa, 0x10) / 2;
      pa += u16at (pa, 0x14);
1163
      *label = get_utf8 (pa, len);
1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183
    }

fail:
  if ((mft) && (mft != &data->cmft))
    {
      free_file (mft);
      grub_free (mft);
    }
  if (data)
    {
      free_file (&data->mmft);
      free_file (&data->cmft);
      grub_free (data);
    }

  grub_dl_unref (my_mod);

  return grub_errno;
}

1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194
static grub_err_t
grub_ntfs_uuid (grub_device_t device, char **uuid)
{
  struct grub_ntfs_data *data;
  grub_disk_t disk = device->disk;

  grub_dl_ref (my_mod);

  data = grub_ntfs_mount (disk);
  if (data)
    {
1195
      char *ptr;
1196
      *uuid = grub_xasprintf ("%016llx", (unsigned long long) data->uuid);
1197 1198 1199
      if (*uuid)
	for (ptr = *uuid; *ptr; ptr++)
	  *ptr = grub_toupper (*ptr);
1200 1201 1202
      free_file (&data->mmft);
      free_file (&data->cmft);
      grub_free (data);
1203 1204 1205 1206 1207 1208 1209 1210 1211
    }
  else
    *uuid = NULL;

  grub_dl_unref (my_mod);

  return grub_errno;
}

1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222
static struct grub_fs grub_ntfs_fs =
  {
    .name = "ntfs",
    .dir = grub_ntfs_dir,
    .open = grub_ntfs_open,
    .read = grub_ntfs_read,
    .close = grub_ntfs_close,
    .label = grub_ntfs_label,
    .uuid = grub_ntfs_uuid,
#ifdef GRUB_UTIL
    .reserved_first_sector = 1,
1223
    .blocklist_install = 1,
1224 1225
#endif
    .next = 0
1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237
};

GRUB_MOD_INIT (ntfs)
{
  grub_fs_register (&grub_ntfs_fs);
  my_mod = mod;
}

GRUB_MOD_FINI (ntfs)
{
  grub_fs_unregister (&grub_ntfs_fs);
}