| /* 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; |
| } |