mirror of
				https://git.proxmox.com/git/grub2
				synced 2025-10-31 20:57:24 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			444 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			444 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* 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>
 | |
| 
 | |
| GRUB_MOD_LICENSE ("GPLv3+");
 | |
| 
 | |
| static grub_err_t
 | |
| decomp_nextvcn (struct grub_ntfs_comp *cc)
 | |
| {
 | |
|   if (cc->comp_head >= cc->comp_tail)
 | |
|     return grub_error (GRUB_ERR_BAD_FS, "compression block overflown");
 | |
|   if (grub_disk_read
 | |
|       (cc->disk,
 | |
|        (cc->comp_table[cc->comp_head].next_lcn -
 | |
| 	(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))
 | |
|     return grub_errno;
 | |
|   cc->cbuf_vcn++;
 | |
|   if ((cc->cbuf_vcn >= cc->comp_table[cc->comp_head].next_vcn))
 | |
|     cc->comp_head++;
 | |
|   cc->cbuf_ofs = 0;
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| static grub_err_t
 | |
| decomp_getch (struct grub_ntfs_comp *cc, grub_uint8_t *res)
 | |
| {
 | |
|   if (cc->cbuf_ofs >= (1U << (cc->log_spc + GRUB_NTFS_BLK_SHR)))
 | |
|     {
 | |
|       if (decomp_nextvcn (cc))
 | |
| 	return grub_errno;
 | |
|     }
 | |
|   *res = cc->cbuf[cc->cbuf_ofs++];
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| static grub_err_t
 | |
| decomp_get16 (struct grub_ntfs_comp *cc, grub_uint16_t * res)
 | |
| {
 | |
|   grub_uint8_t c1 = 0, c2 = 0;
 | |
| 
 | |
|   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
 | |
| decomp_block (struct grub_ntfs_comp *cc, grub_uint8_t *dest)
 | |
| {
 | |
|   grub_uint16_t flg, cnt;
 | |
| 
 | |
|   if (decomp_get16 (cc, &flg))
 | |
|     return grub_errno;
 | |
|   cnt = (flg & 0xFFF) + 1;
 | |
| 
 | |
|   if (dest)
 | |
|     {
 | |
|       if (flg & 0x8000)
 | |
| 	{
 | |
| 	  grub_uint8_t tag;
 | |
| 	  grub_uint32_t bits, copied;
 | |
| 
 | |
| 	  bits = copied = tag = 0;
 | |
| 	  while (cnt > 0)
 | |
| 	    {
 | |
| 	      if (copied > GRUB_NTFS_COM_LEN)
 | |
| 		return grub_error (GRUB_ERR_BAD_FS,
 | |
| 				   "compression block too large");
 | |
| 
 | |
| 	      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;
 | |
| 		  grub_uint16_t word = 0;
 | |
| 
 | |
| 		  if (decomp_get16 (cc, &word))
 | |
| 		    return grub_errno;
 | |
| 
 | |
| 		  code = word;
 | |
| 		  cnt -= 2;
 | |
| 
 | |
| 		  if (!copied)
 | |
| 		    {
 | |
| 		      grub_error (GRUB_ERR_BAD_FS, "nontext window empty");
 | |
| 		      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
 | |
| 		{
 | |
| 		  grub_uint8_t ch = 0;
 | |
| 
 | |
| 		  if (decomp_getch (cc, &ch))
 | |
| 		    return grub_errno;
 | |
| 		  dest[copied++] = ch;
 | |
| 		  cnt--;
 | |
| 		}
 | |
| 	      tag >>= 1;
 | |
| 	      bits--;
 | |
| 	    }
 | |
| 	  return 0;
 | |
| 	}
 | |
|       else
 | |
| 	{
 | |
| 	  if (cnt != GRUB_NTFS_COM_LEN)
 | |
| 	    return grub_error (GRUB_ERR_BAD_FS,
 | |
| 			       "invalid compression block size");
 | |
| 	}
 | |
|     }
 | |
| 
 | |
|   while (cnt > 0)
 | |
|     {
 | |
|       int n;
 | |
| 
 | |
|       n = (1 << (cc->log_spc + GRUB_NTFS_BLK_SHR)) - cc->cbuf_ofs;
 | |
|       if (n > cnt)
 | |
| 	n = cnt;
 | |
|       if ((dest) && (n))
 | |
| 	{
 | |
| 	  grub_memcpy (dest, &cc->cbuf[cc->cbuf_ofs], n);
 | |
| 	  dest += n;
 | |
| 	}
 | |
|       cnt -= n;
 | |
|       cc->cbuf_ofs += n;
 | |
|       if ((cnt) && (decomp_nextvcn (cc)))
 | |
| 	return grub_errno;
 | |
|     }
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| static grub_err_t
 | |
| read_block (struct grub_ntfs_rlst *ctx, grub_uint8_t *buf, grub_size_t num)
 | |
| {
 | |
|   int log_cpb = GRUB_NTFS_LOG_COM_SEC - ctx->comp.log_spc;
 | |
| 
 | |
|   while (num)
 | |
|     {
 | |
|       grub_size_t nn;
 | |
| 
 | |
|       if ((ctx->target_vcn & 0xF) == 0)
 | |
| 	{
 | |
| 
 | |
| 	  if (ctx->comp.comp_head != ctx->comp.comp_tail
 | |
| 	      && !(ctx->flags & GRUB_NTFS_RF_BLNK))
 | |
| 	    return grub_error (GRUB_ERR_BAD_FS, "invalid compression block");
 | |
| 	  ctx->comp.comp_head = ctx->comp.comp_tail = 0;
 | |
| 	  ctx->comp.cbuf_vcn = ctx->target_vcn;
 | |
| 	  ctx->comp.cbuf_ofs = (1 << (ctx->comp.log_spc + GRUB_NTFS_BLK_SHR));
 | |
| 	  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)
 | |
| 	    {
 | |
| 	      if (ctx->flags & GRUB_NTFS_RF_BLNK)
 | |
| 		break;
 | |
| 	      ctx->comp.comp_table[ctx->comp.comp_tail].next_vcn = ctx->next_vcn;
 | |
| 	      ctx->comp.comp_table[ctx->comp.comp_tail].next_lcn =
 | |
| 		ctx->curr_lcn + ctx->next_vcn - ctx->curr_vcn;
 | |
| 	      ctx->comp.comp_tail++;
 | |
| 	      if (grub_ntfs_read_run_list (ctx))
 | |
| 		return grub_errno;
 | |
| 	    }
 | |
| 	}
 | |
| 
 | |
|       nn = (16 - (unsigned) (ctx->target_vcn & 0xF)) >> log_cpb;
 | |
|       if (nn > num)
 | |
| 	nn = num;
 | |
|       num -= nn;
 | |
| 
 | |
|       if (ctx->flags & GRUB_NTFS_RF_BLNK)
 | |
| 	{
 | |
| 	  ctx->target_vcn += nn << log_cpb;
 | |
| 	  if (ctx->comp.comp_tail == 0)
 | |
| 	    {
 | |
| 	      if (buf)
 | |
| 		{
 | |
| 		  grub_memset (buf, 0, nn * GRUB_NTFS_COM_LEN);
 | |
| 		  buf += nn * GRUB_NTFS_COM_LEN;
 | |
| 		  if (grub_file_progress_hook && ctx->file)
 | |
| 		    grub_file_progress_hook (0, 0, nn * GRUB_NTFS_COM_LEN,
 | |
| 					     ctx->file);
 | |
| 		}
 | |
| 	    }
 | |
| 	  else
 | |
| 	    {
 | |
| 	      while (nn)
 | |
| 		{
 | |
| 		  if (decomp_block (&ctx->comp, buf))
 | |
| 		    return grub_errno;
 | |
| 		  if (buf)
 | |
| 		    buf += GRUB_NTFS_COM_LEN;
 | |
| 		  if (grub_file_progress_hook && ctx->file)
 | |
| 		    grub_file_progress_hook (0, 0, GRUB_NTFS_COM_LEN,
 | |
| 					     ctx->file);
 | |
| 		  nn--;
 | |
| 		}
 | |
| 	    }
 | |
| 	}
 | |
|       else
 | |
| 	{
 | |
| 	  nn <<= log_cpb;
 | |
| 	  while ((ctx->comp.comp_head < ctx->comp.comp_tail) && (nn))
 | |
| 	    {
 | |
| 	      grub_disk_addr_t tt;
 | |
| 
 | |
| 	      tt =
 | |
| 		ctx->comp.comp_table[ctx->comp.comp_head].next_vcn -
 | |
| 		ctx->target_vcn;
 | |
| 	      if (tt > nn)
 | |
| 		tt = nn;
 | |
| 	      ctx->target_vcn += tt;
 | |
| 	      if (buf)
 | |
| 		{
 | |
| 		  if (grub_disk_read
 | |
| 		      (ctx->comp.disk,
 | |
| 		       (ctx->comp.comp_table[ctx->comp.comp_head].next_lcn -
 | |
| 			(ctx->comp.comp_table[ctx->comp.comp_head].next_vcn -
 | |
| 			 ctx->target_vcn)) << ctx->comp.log_spc, 0,
 | |
| 		       tt << (ctx->comp.log_spc + GRUB_NTFS_BLK_SHR), buf))
 | |
| 		    return grub_errno;
 | |
| 		  if (grub_file_progress_hook && ctx->file)
 | |
| 		    grub_file_progress_hook (0, 0,
 | |
| 					     tt << (ctx->comp.log_spc
 | |
| 						    + GRUB_NTFS_BLK_SHR),
 | |
| 					     ctx->file);
 | |
| 		  buf += tt << (ctx->comp.log_spc + GRUB_NTFS_BLK_SHR);
 | |
| 		}
 | |
| 	      nn -= tt;
 | |
| 	      if (ctx->target_vcn >=
 | |
| 		  ctx->comp.comp_table[ctx->comp.comp_head].next_vcn)
 | |
| 		ctx->comp.comp_head++;
 | |
| 	    }
 | |
| 	  if (nn)
 | |
| 	    {
 | |
| 	      if (buf)
 | |
| 		{
 | |
| 		  if (grub_disk_read
 | |
| 		      (ctx->comp.disk,
 | |
| 		       (ctx->target_vcn - ctx->curr_vcn +
 | |
| 			ctx->curr_lcn) << ctx->comp.log_spc, 0,
 | |
| 		       nn << (ctx->comp.log_spc + GRUB_NTFS_BLK_SHR), buf))
 | |
| 		    return grub_errno;
 | |
| 		  buf += nn << (ctx->comp.log_spc + GRUB_NTFS_BLK_SHR);
 | |
| 		  if (grub_file_progress_hook && ctx->file)
 | |
| 		    grub_file_progress_hook (0, 0,
 | |
| 					     nn << (ctx->comp.log_spc
 | |
| 						    + GRUB_NTFS_BLK_SHR),
 | |
| 					     ctx->file);
 | |
| 		}
 | |
| 	      ctx->target_vcn += nn;
 | |
| 	    }
 | |
| 	}
 | |
|     }
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| static grub_err_t
 | |
| ntfscomp (grub_uint8_t *dest, grub_disk_addr_t ofs,
 | |
| 	  grub_size_t len, struct grub_ntfs_rlst *ctx)
 | |
| {
 | |
|   grub_err_t ret;
 | |
|   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);
 | |
| 	  if (grub_file_progress_hook && ctx->file)
 | |
| 	    grub_file_progress_hook (0, 0, n, ctx->file);
 | |
| 	  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;
 | |
|     }
 | |
| 
 | |
|   ctx->comp.comp_head = ctx->comp.comp_tail = 0;
 | |
|   ctx->comp.cbuf = grub_malloc (1 << (ctx->comp.log_spc + GRUB_NTFS_BLK_SHR));
 | |
|   if (!ctx->comp.cbuf)
 | |
|     return 0;
 | |
| 
 | |
|   ret = 0;
 | |
| 
 | |
|   //ctx->comp.disk->read_hook = read_hook;
 | |
|   //ctx->comp.disk->read_hook_data = read_hook_data;
 | |
| 
 | |
|   if ((vcn > ctx->target_vcn) &&
 | |
|       (read_block
 | |
|        (ctx, NULL, (vcn - ctx->target_vcn) >> (GRUB_NTFS_LOG_COM_SEC - ctx->comp.log_spc))))
 | |
|     {
 | |
|       ret = grub_errno;
 | |
|       goto quit;
 | |
|     }
 | |
| 
 | |
|   if (ofs % GRUB_NTFS_COM_LEN)
 | |
|     {
 | |
|       grub_uint32_t t, n, o;
 | |
|       void *file = ctx->file;
 | |
| 
 | |
|       ctx->file = 0;
 | |
| 
 | |
|       t = ctx->target_vcn << (ctx->comp.log_spc + GRUB_NTFS_BLK_SHR);
 | |
|       if (read_block (ctx, ctx->attr->sbuf, 1))
 | |
| 	{
 | |
| 	  ret = grub_errno;
 | |
| 	  goto quit;
 | |
| 	}
 | |
| 
 | |
|       ctx->file = file;
 | |
| 
 | |
|       ctx->attr->save_pos = t;
 | |
| 
 | |
|       o = ofs % GRUB_NTFS_COM_LEN;
 | |
|       n = GRUB_NTFS_COM_LEN - o;
 | |
|       if (n > len)
 | |
| 	n = len;
 | |
|       grub_memcpy (dest, &ctx->attr->sbuf[o], n);
 | |
|       if (grub_file_progress_hook && ctx->file)
 | |
| 	grub_file_progress_hook (0, 0, n, ctx->file);
 | |
|       if (n == len)
 | |
| 	goto quit;
 | |
|       dest += n;
 | |
|       len -= n;
 | |
|     }
 | |
| 
 | |
|   if (read_block (ctx, dest, len / GRUB_NTFS_COM_LEN))
 | |
|     {
 | |
|       ret = grub_errno;
 | |
|       goto quit;
 | |
|     }
 | |
| 
 | |
|   dest += (len / GRUB_NTFS_COM_LEN) * GRUB_NTFS_COM_LEN;
 | |
|   len = len % GRUB_NTFS_COM_LEN;
 | |
|   if (len)
 | |
|     {
 | |
|       grub_uint32_t t;
 | |
|       void *file = ctx->file;
 | |
| 
 | |
|       ctx->file = 0;
 | |
|       t = ctx->target_vcn << (ctx->comp.log_spc + GRUB_NTFS_BLK_SHR);
 | |
|       if (read_block (ctx, ctx->attr->sbuf, 1))
 | |
| 	{
 | |
| 	  ret = grub_errno;
 | |
| 	  goto quit;
 | |
| 	}
 | |
| 
 | |
|       ctx->attr->save_pos = t;
 | |
| 
 | |
|       grub_memcpy (dest, ctx->attr->sbuf, len);
 | |
|       if (grub_file_progress_hook && file)
 | |
| 	grub_file_progress_hook (0, 0, len, file);
 | |
|     }
 | |
| 
 | |
| 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;
 | |
| }
 | 
