diff options
Diffstat (limited to 'grub-core/fs/ntfscomp.c')
-rw-r--r-- | grub-core/fs/ntfscomp.c | 443 |
1 files changed, 443 insertions, 0 deletions
diff --git a/grub-core/fs/ntfscomp.c b/grub-core/fs/ntfscomp.c new file mode 100644 index 0000000..3cd97d3 --- /dev/null +++ b/grub-core/fs/ntfscomp.c @@ -0,0 +1,443 @@ +/* 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; +} |