ntfscomp.c 10.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
/* ntfscomp.c - compression support for the NTFS filesystem */
/*
 *  Copyright (C) 2007 Free Software Foundation, Inc.
 *
 *  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/>.
 */

#include <grub/file.h>
#include <grub/mm.h>
#include <grub/misc.h>
#include <grub/disk.h>
#include <grub/dl.h>
#include <grub/ntfs.h>

26 27
GRUB_MOD_LICENSE ("GPLv3+");

28 29 30 31
static grub_err_t
decomp_nextvcn (struct grub_ntfs_comp *cc)
{
  if (cc->comp_head >= cc->comp_tail)
32
    return grub_error (GRUB_ERR_BAD_FS, "compression block overflown");
33 34
  if (grub_disk_read
      (cc->disk,
35
       (cc->comp_table[cc->comp_head].next_lcn -
36 37 38
	(cc->comp_table[cc->comp_head].next_vcn - cc->cbuf_vcn)) << cc->log_spc,
       0,
       1 << (cc->log_spc + GRUB_NTFS_BLK_SHR), cc->cbuf))
39 40
    return grub_errno;
  cc->cbuf_vcn++;
41
  if ((cc->cbuf_vcn >= cc->comp_table[cc->comp_head].next_vcn))
42 43 44 45 46 47
    cc->comp_head++;
  cc->cbuf_ofs = 0;
  return 0;
}

static grub_err_t
48
decomp_getch (struct grub_ntfs_comp *cc, grub_uint8_t *res)
49
{
50
  if (cc->cbuf_ofs >= (1U << (cc->log_spc + GRUB_NTFS_BLK_SHR)))
51 52 53 54
    {
      if (decomp_nextvcn (cc))
	return grub_errno;
    }
55
  *res = cc->cbuf[cc->cbuf_ofs++];
56 57 58 59 60 61
  return 0;
}

static grub_err_t
decomp_get16 (struct grub_ntfs_comp *cc, grub_uint16_t * res)
{
62
  grub_uint8_t c1 = 0, c2 = 0;
63 64 65 66 67 68 69 70 71

  if ((decomp_getch (cc, &c1)) || (decomp_getch (cc, &c2)))
    return grub_errno;
  *res = ((grub_uint16_t) c2) * 256 + ((grub_uint16_t) c1);
  return 0;
}

/* Decompress a block (4096 bytes) */
static grub_err_t
72
decomp_block (struct grub_ntfs_comp *cc, grub_uint8_t *dest)
73 74 75 76 77 78 79 80 81 82 83
{
  grub_uint16_t flg, cnt;

  if (decomp_get16 (cc, &flg))
    return grub_errno;
  cnt = (flg & 0xFFF) + 1;

  if (dest)
    {
      if (flg & 0x8000)
	{
84
	  grub_uint8_t tag;
85 86 87 88 89
	  grub_uint32_t bits, copied;

	  bits = copied = tag = 0;
	  while (cnt > 0)
	    {
90
	      if (copied > GRUB_NTFS_COM_LEN)
91
		return grub_error (GRUB_ERR_BAD_FS,
92
				   "compression block too large");
93 94 95 96 97 98 99 100 101 102 103 104 105 106

	      if (!bits)
		{
		  if (decomp_getch (cc, &tag))
		    return grub_errno;

		  bits = 8;
		  cnt--;
		  if (cnt <= 0)
		    break;
		}
	      if (tag & 1)
		{
		  grub_uint32_t i, len, delta, code, lmask, dshift;
107
		  grub_uint16_t word = 0;
108 109 110 111 112 113 114 115 116

		  if (decomp_get16 (cc, &word))
		    return grub_errno;

		  code = word;
		  cnt -= 2;

		  if (!copied)
		    {
117
		      grub_error (GRUB_ERR_BAD_FS, "nontext window empty");
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
		      return 0;
		    }

		  for (i = copied - 1, lmask = 0xFFF, dshift = 12; i >= 0x10;
		       i >>= 1)
		    {
		      lmask >>= 1;
		      dshift--;
		    }

		  delta = code >> dshift;
		  len = (code & lmask) + 3;

		  for (i = 0; i < len; i++)
		    {
		      dest[copied] = dest[copied - delta - 1];
		      copied++;
		    }
		}
	      else
		{
139
		  grub_uint8_t ch = 0;
140 141 142 143 144 145 146 147 148 149 150 151 152

		  if (decomp_getch (cc, &ch))
		    return grub_errno;
		  dest[copied++] = ch;
		  cnt--;
		}
	      tag >>= 1;
	      bits--;
	    }
	  return 0;
	}
      else
	{
153
	  if (cnt != GRUB_NTFS_COM_LEN)
154
	    return grub_error (GRUB_ERR_BAD_FS,
155
			       "invalid compression block size");
156 157 158 159 160 161 162
	}
    }

  while (cnt > 0)
    {
      int n;

163
      n = (1 << (cc->log_spc + GRUB_NTFS_BLK_SHR)) - cc->cbuf_ofs;
164 165 166 167
      if (n > cnt)
	n = cnt;
      if ((dest) && (n))
	{
168
	  grub_memcpy (dest, &cc->cbuf[cc->cbuf_ofs], n);
169 170 171 172 173 174 175 176 177 178 179
	  dest += n;
	}
      cnt -= n;
      cc->cbuf_ofs += n;
      if ((cnt) && (decomp_nextvcn (cc)))
	return grub_errno;
    }
  return 0;
}

static grub_err_t
180
read_block (struct grub_ntfs_rlst *ctx, grub_uint8_t *buf, grub_size_t num)
181
{
182
  int log_cpb = GRUB_NTFS_LOG_COM_SEC - ctx->comp.log_spc;
183 184 185

  while (num)
    {
186
      grub_size_t nn;
187 188 189 190

      if ((ctx->target_vcn & 0xF) == 0)
	{

191 192
	  if (ctx->comp.comp_head != ctx->comp.comp_tail
	      && !(ctx->flags & GRUB_NTFS_RF_BLNK))
193
	    return grub_error (GRUB_ERR_BAD_FS, "invalid compression block");
194 195
	  ctx->comp.comp_head = ctx->comp.comp_tail = 0;
	  ctx->comp.cbuf_vcn = ctx->target_vcn;
196
	  ctx->comp.cbuf_ofs = (1 << (ctx->comp.log_spc + GRUB_NTFS_BLK_SHR));
197 198 199 200 201 202 203
	  if (ctx->target_vcn >= ctx->next_vcn)
	    {
	      if (grub_ntfs_read_run_list (ctx))
		return grub_errno;
	    }
	  while (ctx->target_vcn + 16 > ctx->next_vcn)
	    {
204
	      if (ctx->flags & GRUB_NTFS_RF_BLNK)
205
		break;
206 207
	      ctx->comp.comp_table[ctx->comp.comp_tail].next_vcn = ctx->next_vcn;
	      ctx->comp.comp_table[ctx->comp.comp_tail].next_lcn =
208 209 210 211 212 213 214
		ctx->curr_lcn + ctx->next_vcn - ctx->curr_vcn;
	      ctx->comp.comp_tail++;
	      if (grub_ntfs_read_run_list (ctx))
		return grub_errno;
	    }
	}

215
      nn = (16 - (unsigned) (ctx->target_vcn & 0xF)) >> log_cpb;
216 217 218 219
      if (nn > num)
	nn = num;
      num -= nn;

220
      if (ctx->flags & GRUB_NTFS_RF_BLNK)
221
	{
222
	  ctx->target_vcn += nn << log_cpb;
223 224 225 226
	  if (ctx->comp.comp_tail == 0)
	    {
	      if (buf)
		{
227 228
		  grub_memset (buf, 0, nn * GRUB_NTFS_COM_LEN);
		  buf += nn * GRUB_NTFS_COM_LEN;
229 230 231
		  if (grub_file_progress_hook && ctx->file)
		    grub_file_progress_hook (0, 0, nn * GRUB_NTFS_COM_LEN,
					     ctx->file);
232 233 234 235 236 237 238 239 240
		}
	    }
	  else
	    {
	      while (nn)
		{
		  if (decomp_block (&ctx->comp, buf))
		    return grub_errno;
		  if (buf)
241
		    buf += GRUB_NTFS_COM_LEN;
242 243 244
		  if (grub_file_progress_hook && ctx->file)
		    grub_file_progress_hook (0, 0, GRUB_NTFS_COM_LEN,
					     ctx->file);
245 246 247 248 249 250
		  nn--;
		}
	    }
	}
      else
	{
251
	  nn <<= log_cpb;
252 253
	  while ((ctx->comp.comp_head < ctx->comp.comp_tail) && (nn))
	    {
254
	      grub_disk_addr_t tt;
255 256

	      tt =
257
		ctx->comp.comp_table[ctx->comp.comp_head].next_vcn -
258 259 260 261 262 263 264 265
		ctx->target_vcn;
	      if (tt > nn)
		tt = nn;
	      ctx->target_vcn += tt;
	      if (buf)
		{
		  if (grub_disk_read
		      (ctx->comp.disk,
266 267
		       (ctx->comp.comp_table[ctx->comp.comp_head].next_lcn -
			(ctx->comp.comp_table[ctx->comp.comp_head].next_vcn -
268 269
			 ctx->target_vcn)) << ctx->comp.log_spc, 0,
		       tt << (ctx->comp.log_spc + GRUB_NTFS_BLK_SHR), buf))
270
		    return grub_errno;
271 272 273 274 275
		  if (grub_file_progress_hook && ctx->file)
		    grub_file_progress_hook (0, 0,
					     tt << (ctx->comp.log_spc
						    + GRUB_NTFS_BLK_SHR),
					     ctx->file);
276
		  buf += tt << (ctx->comp.log_spc + GRUB_NTFS_BLK_SHR);
277 278 279
		}
	      nn -= tt;
	      if (ctx->target_vcn >=
280
		  ctx->comp.comp_table[ctx->comp.comp_head].next_vcn)
281 282 283 284 285 286 287 288 289
		ctx->comp.comp_head++;
	    }
	  if (nn)
	    {
	      if (buf)
		{
		  if (grub_disk_read
		      (ctx->comp.disk,
		       (ctx->target_vcn - ctx->curr_vcn +
290 291
			ctx->curr_lcn) << ctx->comp.log_spc, 0,
		       nn << (ctx->comp.log_spc + GRUB_NTFS_BLK_SHR), buf))
292
		    return grub_errno;
293
		  buf += nn << (ctx->comp.log_spc + GRUB_NTFS_BLK_SHR);
294 295 296 297 298
		  if (grub_file_progress_hook && ctx->file)
		    grub_file_progress_hook (0, 0,
					     nn << (ctx->comp.log_spc
						    + GRUB_NTFS_BLK_SHR),
					     ctx->file);
299 300 301 302 303 304 305 306 307
		}
	      ctx->target_vcn += nn;
	    }
	}
    }
  return 0;
}

static grub_err_t
308 309
ntfscomp (grub_uint8_t *dest, grub_disk_addr_t ofs,
	  grub_size_t len, struct grub_ntfs_rlst *ctx)
310 311
{
  grub_err_t ret;
312 313 314 315 316 317 318 319 320 321 322 323 324
  grub_disk_addr_t vcn;

  if (ctx->attr->sbuf)
    {
      if ((ofs & (~(GRUB_NTFS_COM_LEN - 1))) == ctx->attr->save_pos)
	{
	  grub_disk_addr_t n;

	  n = GRUB_NTFS_COM_LEN - (ofs - ctx->attr->save_pos);
	  if (n > len)
	    n = len;

	  grub_memcpy (dest, ctx->attr->sbuf + ofs - ctx->attr->save_pos, n);
325 326
	  if (grub_file_progress_hook && ctx->file)
	    grub_file_progress_hook (0, 0, n, ctx->file);
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349
	  if (n == len)
	    return 0;

	  dest += n;
	  len -= n;
	  ofs += n;
	}
    }
  else
    {
      ctx->attr->sbuf = grub_malloc (GRUB_NTFS_COM_LEN);
      if (ctx->attr->sbuf == NULL)
	return grub_errno;
      ctx->attr->save_pos = 1;
    }

  vcn = ctx->target_vcn = (ofs >> GRUB_NTFS_COM_LOG_LEN) * (GRUB_NTFS_COM_SEC >> ctx->comp.log_spc);
  ctx->target_vcn &= ~0xFULL;
  while (ctx->next_vcn <= ctx->target_vcn)
    {
      if (grub_ntfs_read_run_list (ctx))
	return grub_errno;
    }
350 351

  ctx->comp.comp_head = ctx->comp.comp_tail = 0;
352
  ctx->comp.cbuf = grub_malloc (1 << (ctx->comp.log_spc + GRUB_NTFS_BLK_SHR));
353 354 355 356 357 358
  if (!ctx->comp.cbuf)
    return 0;

  ret = 0;

  //ctx->comp.disk->read_hook = read_hook;
359
  //ctx->comp.disk->read_hook_data = read_hook_data;
360 361 362

  if ((vcn > ctx->target_vcn) &&
      (read_block
363
       (ctx, NULL, (vcn - ctx->target_vcn) >> (GRUB_NTFS_LOG_COM_SEC - ctx->comp.log_spc))))
364 365 366 367 368
    {
      ret = grub_errno;
      goto quit;
    }

369
  if (ofs % GRUB_NTFS_COM_LEN)
370 371
    {
      grub_uint32_t t, n, o;
372 373 374
      void *file = ctx->file;

      ctx->file = 0;
375

376
      t = ctx->target_vcn << (ctx->comp.log_spc + GRUB_NTFS_BLK_SHR);
377
      if (read_block (ctx, ctx->attr->sbuf, 1))
378 379 380 381 382
	{
	  ret = grub_errno;
	  goto quit;
	}

383 384
      ctx->file = file;

385
      ctx->attr->save_pos = t;
386

387 388
      o = ofs % GRUB_NTFS_COM_LEN;
      n = GRUB_NTFS_COM_LEN - o;
389 390
      if (n > len)
	n = len;
391
      grub_memcpy (dest, &ctx->attr->sbuf[o], n);
392 393
      if (grub_file_progress_hook && ctx->file)
	grub_file_progress_hook (0, 0, n, ctx->file);
394 395 396 397 398 399
      if (n == len)
	goto quit;
      dest += n;
      len -= n;
    }

400
  if (read_block (ctx, dest, len / GRUB_NTFS_COM_LEN))
401 402 403 404 405
    {
      ret = grub_errno;
      goto quit;
    }

406 407
  dest += (len / GRUB_NTFS_COM_LEN) * GRUB_NTFS_COM_LEN;
  len = len % GRUB_NTFS_COM_LEN;
408 409 410
  if (len)
    {
      grub_uint32_t t;
411
      void *file = ctx->file;
412

413
      ctx->file = 0;
414
      t = ctx->target_vcn << (ctx->comp.log_spc + GRUB_NTFS_BLK_SHR);
415
      if (read_block (ctx, ctx->attr->sbuf, 1))
416 417 418 419 420
	{
	  ret = grub_errno;
	  goto quit;
	}

421
      ctx->attr->save_pos = t;
422

423
      grub_memcpy (dest, ctx->attr->sbuf, len);
424 425
      if (grub_file_progress_hook && file)
	grub_file_progress_hook (0, 0, len, file);
426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443
    }

quit:
  //ctx->comp.disk->read_hook = 0;
  if (ctx->comp.cbuf)
    grub_free (ctx->comp.cbuf);
  return ret;
}

GRUB_MOD_INIT (ntfscomp)
{
  grub_ntfscomp_func = ntfscomp;
}

GRUB_MOD_FINI (ntfscomp)
{
  grub_ntfscomp_func = NULL;
}