diff options
-rw-r--r-- | difftool/compare_dir.c | 26 | ||||
-rw-r--r-- | difftool/compare_files.c | 30 | ||||
-rw-r--r-- | difftool/extract.c | 19 | ||||
-rw-r--r-- | difftool/node_compare.c | 56 | ||||
-rw-r--r-- | difftool/sqfsdiff.c | 104 | ||||
-rw-r--r-- | difftool/sqfsdiff.h | 29 | ||||
-rw-r--r-- | difftool/util.c | 7 | ||||
-rw-r--r-- | include/data_reader.h | 16 | ||||
-rw-r--r-- | lib/sqfshelper/data_reader.c | 155 |
9 files changed, 260 insertions, 182 deletions
diff --git a/difftool/compare_dir.c b/difftool/compare_dir.c index 99a0a37..477346a 100644 --- a/difftool/compare_dir.c +++ b/difftool/compare_dir.c @@ -6,16 +6,18 @@ */ #include "sqfsdiff.h" -int compare_dir_entries(sqfsdiff_t *sd, tree_node_t *old, tree_node_t *new) +int compare_dir_entries(sqfsdiff_t *sd, sqfs_tree_node_t *old, + sqfs_tree_node_t *new) { - tree_node_t *old_it = old->data.dir->children, *old_prev = NULL; - tree_node_t *new_it = new->data.dir->children, *new_prev = NULL; + sqfs_tree_node_t *old_it = old->children, *old_prev = NULL; + sqfs_tree_node_t *new_it = new->children, *new_prev = NULL; int ret, result = 0; char *path; while (old_it != NULL || new_it != NULL) { if (old_it != NULL && new_it != NULL) { - ret = strcmp(old_it->name, new_it->name); + ret = strcmp((const char *)old_it->name, + (const char *)new_it->name); } else if (old_it == NULL) { ret = 1; } else { @@ -29,8 +31,8 @@ int compare_dir_entries(sqfsdiff_t *sd, tree_node_t *old, tree_node_t *new) return -1; if ((sd->compare_flags & COMPARE_EXTRACT_FILES) && - S_ISREG(old_it->mode)) { - if (extract_files(sd, old_it->data.file, + S_ISREG(old_it->inode->base.mode)) { + if (extract_files(sd, old_it->inode, NULL, path)) { free(path); return -1; @@ -41,9 +43,9 @@ int compare_dir_entries(sqfsdiff_t *sd, tree_node_t *old, tree_node_t *new) free(path); if (old_prev == NULL) { - old->data.dir->children = old_it->next; + old->children = old_it->next; free(old_it); - old_it = old->data.dir->children; + old_it = old->children; } else { old_prev->next = old_it->next; free(old_it); @@ -56,8 +58,8 @@ int compare_dir_entries(sqfsdiff_t *sd, tree_node_t *old, tree_node_t *new) return -1; if ((sd->compare_flags & COMPARE_EXTRACT_FILES) && - S_ISREG(new_it->mode)) { - if (extract_files(sd, NULL, new_it->data.file, + S_ISREG(new_it->inode->base.mode)) { + if (extract_files(sd, NULL, new_it->inode, path)) { free(path); return -1; @@ -68,9 +70,9 @@ int compare_dir_entries(sqfsdiff_t *sd, tree_node_t *old, tree_node_t *new) free(path); if (new_prev == NULL) { - new->data.dir->children = new_it->next; + new->children = new_it->next; free(new_it); - new_it = new->data.dir->children; + new_it = new->children; } else { new_prev->next = new_it->next; free(new_it); diff --git a/difftool/compare_files.c b/difftool/compare_files.c index 6138569..591fc2c 100644 --- a/difftool/compare_files.c +++ b/difftool/compare_files.c @@ -10,12 +10,12 @@ static unsigned char old_buf[MAX_WINDOW_SIZE]; static unsigned char new_buf[MAX_WINDOW_SIZE]; static int read_blob(const char *prefix, const char *path, - data_reader_t *rd, file_info_t *fi, void *buffer, - off_t offset, size_t size) + data_reader_t *rd, const sqfs_inode_generic_t *inode, + void *buffer, uint64_t offset, size_t size) { ssize_t ret; - ret = data_reader_read(rd, fi, offset, buffer, size); + ret = data_reader_read(rd, inode, offset, buffer, size); ret = (ret < 0 || (size_t)ret < size) ? -1 : 0; if (ret) { @@ -27,20 +27,32 @@ static int read_blob(const char *prefix, const char *path, return 0; } -int compare_files(sqfsdiff_t *sd, file_info_t *old, file_info_t *new, - const char *path) +int compare_files(sqfsdiff_t *sd, const sqfs_inode_generic_t *old, + const sqfs_inode_generic_t *new, const char *path) { - uint64_t offset, diff; + uint64_t offset, diff, oldsz, newsz; int status = 0, ret; - if (old->size != new->size) + if (old->base.type == SQFS_INODE_EXT_FILE) { + oldsz = old->data.file_ext.file_size; + } else { + oldsz = old->data.file.file_size; + } + + if (new->base.type == SQFS_INODE_EXT_FILE) { + newsz = new->data.file_ext.file_size; + } else { + newsz = new->data.file.file_size; + } + + if (oldsz != newsz) goto out_different; if (sd->compare_flags & COMPARE_NO_CONTENTS) return 0; - for (offset = 0; offset < old->size; offset += diff) { - diff = old->size - offset; + for (offset = 0; offset < oldsz; offset += diff) { + diff = oldsz - offset; if (diff > MAX_WINDOW_SIZE) diff = MAX_WINDOW_SIZE; diff --git a/difftool/extract.c b/difftool/extract.c index 2e92710..45c5560 100644 --- a/difftool/extract.c +++ b/difftool/extract.c @@ -6,7 +6,7 @@ */ #include "sqfsdiff.h" -static int extract(data_reader_t *data, file_info_t *fi, +static int extract(data_reader_t *data, const sqfs_inode_generic_t *inode, const char *prefix, const char *path) { char *ptr, *temp; @@ -27,7 +27,7 @@ static int extract(data_reader_t *data, file_info_t *fi, return -1; } - if (data_reader_dump_file(data, fi, fd, true)) { + if (data_reader_dump(data, inode, fd, true)) { close(fd); return -1; } @@ -36,14 +36,19 @@ static int extract(data_reader_t *data, file_info_t *fi, return 0; } -int extract_files(sqfsdiff_t *sd, file_info_t *old, file_info_t *new, +int extract_files(sqfsdiff_t *sd, const sqfs_inode_generic_t *old, + const sqfs_inode_generic_t *new, const char *path) { - if (extract(sd->sqfs_old.data, old, "old", path)) - return -1; + if (old != NULL) { + if (extract(sd->sqfs_old.data, old, "old", path)) + return -1; + } - if (extract(sd->sqfs_new.data, new, "new", path)) - return -1; + if (new != NULL) { + if (extract(sd->sqfs_new.data, new, "new", path)) + return -1; + } return 0; } diff --git a/difftool/node_compare.c b/difftool/node_compare.c index 504c9cf..7638805 100644 --- a/difftool/node_compare.c +++ b/difftool/node_compare.c @@ -6,23 +6,24 @@ */ #include "sqfsdiff.h" -int node_compare(sqfsdiff_t *sd, tree_node_t *a, tree_node_t *b) +int node_compare(sqfsdiff_t *sd, sqfs_tree_node_t *a, sqfs_tree_node_t *b) { - char *path = node_path(a); - tree_node_t *ait, *bit; + char *path = sqfs_tree_node_get_path(a); + sqfs_tree_node_t *ait, *bit; int ret, status = 0; if (path == NULL) return -1; - if ((a->mode & S_IFMT) != (b->mode & S_IFMT)) { + if (a->inode->base.type != b->inode->base.type) { fprintf(stdout, "%s has a different type\n", path); free(path); return 1; } if (!(sd->compare_flags & COMPARE_NO_PERM)) { - if ((a->mode & ~S_IFMT) != (b->mode & ~S_IFMT)) { + if ((a->inode->base.mode & ~S_IFMT) != + (b->inode->base.mode & ~S_IFMT)) { fprintf(stdout, "%s has different permissions\n", path); status = 1; @@ -37,39 +38,53 @@ int node_compare(sqfsdiff_t *sd, tree_node_t *a, tree_node_t *b) } if (sd->compare_flags & COMPARE_TIMESTAMP) { - if (a->mod_time != b->mod_time) { + if (a->inode->base.mod_time != b->inode->base.mod_time) { fprintf(stdout, "%s has a different timestamp\n", path); status = 1; } } if (sd->compare_flags & COMPARE_INODE_NUM) { - if (a->inode_num != b->inode_num) { + if (a->inode->base.inode_number != + b->inode->base.inode_number) { fprintf(stdout, "%s has a different inode number\n", path); status = 1; } } - switch (a->mode & S_IFMT) { - case S_IFSOCK: - case S_IFIFO: + switch (a->inode->base.type) { + case SQFS_INODE_SOCKET: + case SQFS_INODE_EXT_SOCKET: + case SQFS_INODE_FIFO: + case SQFS_INODE_EXT_FIFO: break; - case S_IFBLK: - case S_IFCHR: - if (a->data.devno != b->data.devno) { + case SQFS_INODE_BDEV: + case SQFS_INODE_CDEV: + if (a->inode->data.dev.devno != b->inode->data.dev.devno) { fprintf(stdout, "%s has different device number\n", path); status = 1; } break; - case S_IFLNK: - if (strcmp(a->data.slink_target, b->data.slink_target) != 0) { + case SQFS_INODE_EXT_BDEV: + case SQFS_INODE_EXT_CDEV: + if (a->inode->data.dev_ext.devno != + b->inode->data.dev_ext.devno) { + fprintf(stdout, "%s has different device number\n", + path); + status = 1; + } + break; + case SQFS_INODE_SLINK: + case SQFS_INODE_EXT_SLINK: + if (strcmp(a->inode->slink_target, b->inode->slink_target)) { fprintf(stdout, "%s has a different link target\n", path); } break; - case S_IFDIR: + case SQFS_INODE_DIR: + case SQFS_INODE_EXT_DIR: ret = compare_dir_entries(sd, a, b); if (ret < 0) { status = -1; @@ -81,8 +96,8 @@ int node_compare(sqfsdiff_t *sd, tree_node_t *a, tree_node_t *b) free(path); path = NULL; - ait = a->data.dir->children; - bit = b->data.dir->children; + ait = a->children; + bit = b->children; while (ait != NULL && bit != NULL) { ret = node_compare(sd, ait, bit); @@ -95,8 +110,9 @@ int node_compare(sqfsdiff_t *sd, tree_node_t *a, tree_node_t *b) bit = bit->next; } break; - case S_IFREG: - ret = compare_files(sd, a->data.file, b->data.file, path); + case SQFS_INODE_FILE: + case SQFS_INODE_EXT_FILE: + ret = compare_files(sd, a->inode, b->inode, path); if (ret < 0) { status = -1; } else if (ret > 0) { diff --git a/difftool/sqfsdiff.c b/difftool/sqfsdiff.c index a661223..6d1a9af 100644 --- a/difftool/sqfsdiff.c +++ b/difftool/sqfsdiff.c @@ -6,6 +6,100 @@ */ #include "sqfsdiff.h" +static int open_sfqs(sqfs_state_t *state, const char *path) +{ + state->file = sqfs_open_file(path, SQFS_FILE_OPEN_READ_ONLY); + if (state->file == NULL) { + perror(path); + return -1; + } + + if (sqfs_super_read(&state->super, state->file)) { + fprintf(stderr, "error reading super block from %s\n", + path); + goto fail_file; + } + + if (!sqfs_compressor_exists(state->super.compression_id)) { + fprintf(stderr, "%s: unknown compressor used.\n", + path); + goto fail_file; + } + + sqfs_compressor_config_init(&state->cfg, state->super.compression_id, + state->super.block_size, + SQFS_COMP_FLAG_UNCOMPRESS); + + state->cmp = sqfs_compressor_create(&state->cfg); + if (state->cmp == NULL) { + fprintf(stderr, "%s: error creating compressor.\n", path); + goto fail_file; + } + + if (state->super.flags & SQFS_FLAG_COMPRESSOR_OPTIONS) { + if (state->cmp->read_options(state->cmp, state->file)) { + fprintf(stderr, "%s: error loading compressor " + "options.\n", path); + goto fail_cmp; + } + } + + state->idtbl = sqfs_id_table_create(); + if (state->idtbl == NULL) { + perror("error creating ID table"); + goto fail_cmp; + } + + if (sqfs_id_table_read(state->idtbl, state->file, + &state->super, state->cmp)) { + fprintf(stderr, "%s: error loading ID table\n", path); + goto fail_id; + } + + state->dr = sqfs_dir_reader_create(&state->super, state->cmp, + state->file); + if (state->dr == NULL) { + perror("creating directory reader"); + goto fail_dr; + } + + if (sqfs_dir_reader_get_full_hierarchy(state->dr, state->idtbl, + NULL, 0, &state->root)) { + fprintf(stderr, "%s: error loading file system tree\n", path); + goto fail_dr; + } + + state->data = data_reader_create(state->file, &state->super, + state->cmp); + if (state->data == NULL) { + fprintf(stderr, "%s: error loading file system tree\n", path); + goto fail_tree; + } + + return 0; +fail_tree: + sqfs_dir_tree_destroy(state->root); +fail_dr: + sqfs_dir_reader_destroy(state->dr); +fail_id: + sqfs_id_table_destroy(state->idtbl); +fail_cmp: + state->cmp->destroy(state->cmp); +fail_file: + state->file->destroy(state->file); + return -1; +} + +static void close_sfqs(sqfs_state_t *state) +{ + data_reader_destroy(state->data); + sqfs_dir_tree_destroy(state->root); + sqfs_dir_reader_destroy(state->dr); + sqfs_id_table_destroy(state->idtbl); + state->cmp->destroy(state->cmp); + state->file->destroy(state->file); +} + int main(int argc, char **argv) { int status, ret = 0; @@ -19,10 +113,10 @@ int main(int argc, char **argv) return 2; } - if (sqfs_reader_open(&sd.sqfs_old, sd.old_path)) + if (open_sfqs(&sd.sqfs_old, sd.old_path)) return 2; - if (sqfs_reader_open(&sd.sqfs_new, sd.new_path)) { + if (open_sfqs(&sd.sqfs_new, sd.new_path)) { status = 2; goto out_sqfs_old; } @@ -35,7 +129,7 @@ int main(int argc, char **argv) } } - ret = node_compare(&sd, sd.sqfs_old.fs.root, sd.sqfs_new.fs.root); + ret = node_compare(&sd, sd.sqfs_old.root, sd.sqfs_new.root); if (ret != 0) goto out; @@ -53,8 +147,8 @@ out: } else { status = 0; } - sqfs_reader_close(&sd.sqfs_new); + close_sfqs(&sd.sqfs_new); out_sqfs_old: - sqfs_reader_close(&sd.sqfs_old); + close_sfqs(&sd.sqfs_old); return status; } diff --git a/difftool/sqfsdiff.h b/difftool/sqfsdiff.h index d9a01c2..40c0fc5 100644 --- a/difftool/sqfsdiff.h +++ b/difftool/sqfsdiff.h @@ -23,11 +23,22 @@ #define MAX_WINDOW_SIZE (1024 * 1024 * 4) typedef struct { + sqfs_compressor_config_t cfg; + sqfs_compressor_t *cmp; + sqfs_super_t super; + sqfs_file_t *file; + sqfs_id_table_t *idtbl; + sqfs_dir_reader_t *dr; + sqfs_tree_node_t *root; + data_reader_t *data; +} sqfs_state_t; + +typedef struct { const char *old_path; const char *new_path; int compare_flags; - sqfs_reader_t sqfs_old; - sqfs_reader_t sqfs_new; + sqfs_state_t sqfs_old; + sqfs_state_t sqfs_new; bool compare_super; const char *extract_dir; } sqfsdiff_t; @@ -41,18 +52,20 @@ enum { COMPARE_EXTRACT_FILES = 0x20, }; -int compare_dir_entries(sqfsdiff_t *sd, tree_node_t *a, tree_node_t *b); +int compare_dir_entries(sqfsdiff_t *sd, sqfs_tree_node_t *old, + sqfs_tree_node_t *new); -char *node_path(tree_node_t *n); +char *node_path(const sqfs_tree_node_t *n); -int compare_files(sqfsdiff_t *sd, file_info_t *a, file_info_t *b, - const char *path); +int compare_files(sqfsdiff_t *sd, const sqfs_inode_generic_t *old, + const sqfs_inode_generic_t *new, const char *path); -int node_compare(sqfsdiff_t *sd, tree_node_t *a, tree_node_t *b); +int node_compare(sqfsdiff_t *sd, sqfs_tree_node_t *a, sqfs_tree_node_t *b); int compare_super_blocks(const sqfs_super_t *a, const sqfs_super_t *b); -int extract_files(sqfsdiff_t *sd, file_info_t *old, file_info_t *new, +int extract_files(sqfsdiff_t *sd, const sqfs_inode_generic_t *old, + const sqfs_inode_generic_t *new, const char *path); void process_options(sqfsdiff_t *sd, int argc, char **argv); diff --git a/difftool/util.c b/difftool/util.c index 2883a2f..5e9161a 100644 --- a/difftool/util.c +++ b/difftool/util.c @@ -6,9 +6,9 @@ */ #include "sqfsdiff.h" -char *node_path(tree_node_t *n) +char *node_path(const sqfs_tree_node_t *n) { - char *path = fstree_get_path(n); + char *path = sqfs_tree_node_get_path(n); if (path == NULL) { perror("get path"); @@ -16,8 +16,7 @@ char *node_path(tree_node_t *n) } if (canonicalize_name(path)) { - fputs("[BUG] canonicalization of fstree_get_path failed!!\n", - stderr); + fprintf(stderr, "failed to canonicalization '%s'\n", path); free(path); return NULL; } diff --git a/include/data_reader.h b/include/data_reader.h index 63bd539..77bdd82 100644 --- a/include/data_reader.h +++ b/include/data_reader.h @@ -32,26 +32,14 @@ int data_reader_dump(data_reader_t *data, const sqfs_inode_generic_t *inode, int outfd, bool allow_sparse); /* - Use a file_info_t to locate and extract all blocks of the coresponding - file and its fragment, if it has one. The entire data is dumped to the - given file descriptor. - - If allow_sparse is true, try to truncate and seek forward on outfd if a - zero block is found. If false, always write blocks of zeros to outfd. - - Returns 0 on success, prints error messages to stderr on failure. - */ -int data_reader_dump_file(data_reader_t *data, file_info_t *fi, int outfd, - bool allow_sparse); - -/* Read a chunk of data from a file. Starting from 'offset' into the uncompressed file, read 'size' bytes into 'buffer'. Returns the number of bytes read, 0 if EOF, -1 on failure. Prints an error message to stderr on failure. */ -ssize_t data_reader_read(data_reader_t *data, file_info_t *fi, +ssize_t data_reader_read(data_reader_t *data, + const sqfs_inode_generic_t *inode, uint64_t offset, void *buffer, size_t size); #endif /* DATA_READER_H */ diff --git a/lib/sqfshelper/data_reader.c b/lib/sqfshelper/data_reader.c index 0c982d7..0780da0 100644 --- a/lib/sqfshelper/data_reader.c +++ b/lib/sqfshelper/data_reader.c @@ -247,113 +247,64 @@ fail_sparse: return -1; } -int data_reader_dump_file(data_reader_t *data, file_info_t *fi, int outfd, - bool allow_sparse) -{ - uint64_t filesz = fi->size; - size_t fragsz = fi->size % data->block_size; - size_t count = fi->size / data->block_size; - off_t off = fi->startblock; - size_t i, diff; - - if (fragsz != 0 && (fi->fragment_offset >= data->block_size || - fi->fragment == 0xFFFFFFFF)) { - fragsz = 0; - ++count; - } - - if (allow_sparse && ftruncate(outfd, filesz)) - goto fail_sparse; - - for (i = 0; i < count; ++i) { - diff = filesz > data->block_size ? data->block_size : filesz; - filesz -= diff; - - if (SQFS_IS_SPARSE_BLOCK(fi->block_size[i])) { - if (allow_sparse) { - if (lseek(outfd, diff, SEEK_CUR) == (off_t)-1) - goto fail_sparse; - continue; - } - memset(data->block, 0, diff); - } else { - if (precache_data_block(data, off, fi->block_size[i])) - return -1; - off += SQFS_ON_DISK_BLOCK_SIZE(fi->block_size[i]); - } - - if (write_data("writing uncompressed block", - outfd, data->block, diff)) { - return -1; - } - } - - if (fragsz > 0) { - if (precache_fragment_block(data, fi->fragment)) - return -1; - - if (fi->fragment_offset >= data->frag_used) - goto fail_range; - - if ((fi->fragment_offset + fragsz - 1) >= data->frag_used) - goto fail_range; - - if (write_data("writing uncompressed fragment", outfd, - (char *)data->frag_block + fi->fragment_offset, - fragsz)) { - return -1; - } - } - - return 0; -fail_range: - fputs("attempted to read past fragment block limits\n", stderr); - return -1; -fail_sparse: - perror("creating sparse output file"); - return -1; -} - -ssize_t data_reader_read(data_reader_t *data, file_info_t *fi, +ssize_t data_reader_read(data_reader_t *data, + const sqfs_inode_generic_t *inode, uint64_t offset, void *buffer, size_t size) { - size_t i, diff, fragsz, count, total = 0; - off_t off; + uint32_t frag_idx, frag_off; + size_t i, diff, total = 0; + uint64_t off, filesz; char *ptr; - /* work out block count and fragment size */ - fragsz = fi->size % data->block_size; - count = fi->size / data->block_size; - - if (fragsz != 0 && (fi->fragment_offset >= data->block_size || - fi->fragment == 0xFFFFFFFF)) { - fragsz = 0; - ++count; + /* work out file location and size */ + if (inode->base.type == SQFS_INODE_EXT_FILE) { + off = inode->data.file_ext.blocks_start; + filesz = inode->data.file_ext.file_size; + frag_idx = inode->data.file_ext.fragment_idx; + frag_off = inode->data.file_ext.fragment_offset; + } else { + off = inode->data.file.blocks_start; + filesz = inode->data.file.file_size; + frag_idx = inode->data.file.fragment_index; + frag_off = inode->data.file.fragment_offset; } - /* work out block index and on-disk location */ - off = fi->startblock; + /* find location of the first block */ i = 0; - while (offset > data->block_size && i < count) { - off += SQFS_ON_DISK_BLOCK_SIZE(fi->block_size[i++]); + while (offset > data->block_size && i < inode->num_file_blocks) { + off += SQFS_ON_DISK_BLOCK_SIZE(inode->block_sizes[i++]); offset -= data->block_size; + + if (filesz >= data->block_size) { + filesz -= data->block_size; + } else { + filesz = 0; + } } /* copy data from blocks */ - while (i < count && size > 0) { + while (i < inode->num_file_blocks && size > 0 && filesz > 0) { diff = data->block_size - offset; if (size < diff) - size = diff; + diff = size; - if (SQFS_IS_SPARSE_BLOCK(fi->block_size[i])) { + if (SQFS_IS_SPARSE_BLOCK(inode->block_sizes[i])) { memset(buffer, 0, diff); } else { - if (precache_data_block(data, off, fi->block_size[i])) + if (precache_data_block(data, off, + inode->block_sizes[i])) { return -1; + } memcpy(buffer, (char *)data->block + offset, diff); - off += SQFS_ON_DISK_BLOCK_SIZE(fi->block_size[i]); + off += SQFS_ON_DISK_BLOCK_SIZE(inode->block_sizes[i]); + } + + if (filesz >= data->block_size) { + filesz -= data->block_size; + } else { + filesz = 0; } ++i; @@ -364,32 +315,30 @@ ssize_t data_reader_read(data_reader_t *data, file_info_t *fi, } /* copy from fragment */ - if (i == count && size > 0 && fragsz > 0) { - if (precache_fragment_block(data, fi->fragment)) + if (i == inode->num_file_blocks && size > 0 && filesz > 0) { + if (precache_fragment_block(data, frag_idx)) return -1; - if (fi->fragment_offset >= data->frag_used) + if (frag_off >= data->frag_used) goto fail_range; - if ((fi->fragment_offset + fragsz - 1) >= data->frag_used) + if (frag_off + filesz > data->frag_used) goto fail_range; - ptr = (char *)data->frag_block + fi->fragment_offset; - ptr += offset; + if (offset >= filesz) + return total; - if (offset >= fragsz) { - offset = 0; - size = 0; - } + if (offset + size > filesz) + size = filesz - offset; - if (offset + size > fragsz) - size = fragsz - offset; + if (size == 0) + return total; - if (size > 0) { - memcpy(buffer, ptr + offset, size); - total += size; - } + ptr = (char *)data->frag_block + frag_off + offset; + memcpy(buffer, ptr, size); + total += size; } + return total; fail_range: fputs("attempted to read past fragment block limits\n", stderr); |