From 74d954ac21dbad18257bbb4f39202a9f05bf07eb Mon Sep 17 00:00:00 2001 From: Maurizio Porrato Date: Tue, 17 Oct 2023 09:01:10 +0100 Subject: [PATCH] Initial commit --- .clang-format | 2 + .gitignore | 8 + Makefile | 23 +++ build-legacy.sh | 13 ++ dsk2img.c | 437 ++++++++++++++++++++++++++++++++++++++++++++++++ lzw.c | 182 ++++++++++++++++++++ lzw.h | 33 ++++ test-lzw.c | 59 +++++++ 8 files changed, 757 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 Makefile create mode 100755 build-legacy.sh create mode 100644 dsk2img.c create mode 100644 lzw.c create mode 100644 lzw.h create mode 100644 test-lzw.c diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..748f4b6 --- /dev/null +++ b/.clang-format @@ -0,0 +1,2 @@ +--- +BasedOnStyle: Webkit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4a30830 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +*~ +*% +*.o +*.obj +*.exe +dsk2img +test-lzw +.vscode/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..00b1873 --- /dev/null +++ b/Makefile @@ -0,0 +1,23 @@ +CC = clang +CFLAGS ?= -Wall -pedantic -std=c89 -O0 -g +STRIP = strip +FORMAT = clang-format -i +BIN = dsk2img +TESTS = test-lzw + +.PHONY: all strip clean format + +all: $(BIN) +tests: $(TESTS) + +strip: $(BIN) + $(STRIP) $^ + +test-lzw: lzw.o +dsk2img: lzw.o + +clean: + $(RM) $(BIN) $(TESTS) *.o *~ *% + +format: + $(FORMAT) *.c diff --git a/build-legacy.sh b/build-legacy.sh new file mode 100755 index 0000000..b8d0b12 --- /dev/null +++ b/build-legacy.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +export WATCOM=/opt/ow +PATH="$WATCOM/binl:$PATH" + +CFLAGS=(-Wall -Wextra -std=c89 -O3 -g0 -s) +CFILES=(dsk2img.c lzw.c) + +set -x + +owcc -o dsk2img.exe "${CFLAGS[@]}" -march=i86 -bdos -mcmodel=s "${CFILES[@]}" +owcc -o dsk2img-win32.exe "${CFLAGS[@]}" -march=i386 -mtune=i386 -bnt "${CFILES[@]}" +owcc -o dsk2img-os232.exe "${CFLAGS[@]}" -march=i386 -mtune=i386 -bos2v2 "${CFILES[@]}" diff --git a/dsk2img.c b/dsk2img.c new file mode 100644 index 0000000..129333a --- /dev/null +++ b/dsk2img.c @@ -0,0 +1,437 @@ +#define _POSIX_C_SOURCE 2 +#include "lzw.h" +#include +#include +#include +#include +#include +#include + +#define MAGIC_DSK_OLD 0x58aa +#define MAGIC_DSK_UNCOMPRESSED 0x59aa +#define MAGIC_DSK_COMPRESSED 0x5aaa + +#if defined(__DOS__) || defined(__WINDOWS__) || defined(__NT__) || defined(__OS2__) +#define DOSLIKE +#define DIRSEP '\\' +#else +#undef DOSLIKE +#define DIRSEP '/' +#endif + +#pragma pack(1) +struct dskheader { + uint16_t magic; /* magic identifier (0x58aa, 0x59aa or 0x5aaa) */ + uint16_t mediatype; /* disk media type as defined in the first byte of the FAT */ + uint16_t sectorsize; /* sector size in bytes */ + uint8_t clustermask; /* number of sectors per cluster minus one */ + uint8_t clustershift; /* log2(cluster size / sector size) */ + uint16_t reservedsectors; /* number of reserved sectors at the start of the disk */ + uint8_t fatcopies; /* number of copies of the FAT */ + uint16_t rootentries; /* max number of entries in the root directory */ + uint16_t firstclustersector; /* count of sectors for boot sector + FATs + root dir */ + uint16_t imageclusters; /* number of clusters in the image (empty clusters at the end are not saved) */ + uint8_t sectorsperfat; /* size of each FAT copy in sectors */ + uint16_t rootdirsector; /* count of sectors used by boot sector + FATs */ + uint32_t checksum; /* sum of all words in the image */ + uint16_t cylinders; /* number of disk cylinders (40 or 80) */ + uint16_t heads; /* number of disk heads (sides, 1 or 2) */ + uint16_t sectors; /* number of sectors per track */ + uint32_t unused; + uint16_t imagesectors; /* number of sectors in the image */ + uint16_t commentoffset; /* offset of the image comment string */ + uint16_t firstoffset; /* offset of the start of image contents */ +}; +#pragma pack() + +const struct media_type { + int sectorsize; + int cylinders; + int heads; + int sectors; + char* name; +} media_types[] = { + { 512, 80, 2, 36, "3.5\" DSED 2.88MB" }, + { 512, 80, 2, 18, "3.5\" DSHD 1.44MB" }, + { 512, 80, 2, 9, "3.5\" DSDD 720KB" }, + { 512, 80, 2, 15, "5.25\" DSHD 1.2MB" }, + { 512, 40, 1, 8, "5.25\" SSDD 160KB" }, + { 512, 40, 1, 9, "5.25\" SSDD 180KB" }, + { 512, 40, 2, 8, "5.25\" DSDD 320KB" }, + { 512, 40, 2, 9, "5.25\" DSDD 360KB" }, + { 512, 80, 1, 8, "5.25\" SSQD 320KB" }, + { 512, 80, 2, 8, "5.25\" DSQD 640KB" }, + { 512, 80, 1, 8, "3.5\" SSDD 320KB" }, + { 512, 80, 1, 9, "3.5\" SSDD 360KB" }, + { 512, 80, 2, 8, "3.5\" DSDD 640KB" }, + { 512, 80, 2, 21, "3.5\" DSHD 1.68MB DMF" }, + { 512, 80, 2, 21, "3.5\" DSHD 1.72MB DMF" }, + { -1, -1, -1, -1, "Unknown" } +}; + +char* guess_output_filename(const char* input_filename, int is_compressed) +{ + char* outname; + unsigned int ifnlen, ofnlen; + int i; + + /* NOLINTBEGIN(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) */ + ifnlen = strlen(input_filename); + ofnlen = ifnlen + 9; + outname = (char*)malloc(ofnlen); + strncpy(outname, input_filename, ofnlen); + /* strip extension */ + for (i = ifnlen; i > 0; i--) { + if (outname[i - 1] == '.') { + outname[i - 1] = '\0'; + break; + } else if (outname[i - 1] == DIRSEP) + break; + } +#ifdef DOSLIKE + if (is_compressed) /* use filename.lzw instead of filename.img.lzw on dos and win */ + strcat(outname, ".lzw"); + else + strcat(outname, ".img"); +#else + strncat(outname, ".img", ofnlen); + if (is_compressed) + strncat(outname, ".lzw", ofnlen); +#endif + /* NOLINTEND(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) */ + + return outname; +} + +void pad_file_to_size(FILE* f, unsigned long size) +{ + long r; + unsigned long current_pos; + + r = ftell(f); + if (r < 0) { + perror("ftell()"); + } + current_pos = r; + if (current_pos > size) + printf("ERROR: output file is larger than expected! (%lu vs %lu)\n", current_pos, size); + if (current_pos < size - 1) { + long pos; + + pos = ((long)size) - 1; + fseek(f, pos, SEEK_SET); + } + if (current_pos < size) + fputc(0, f); +} + +int valid_header(const struct dskheader* h) +{ + unsigned int final_sectors; + unsigned long int final_bytes; + + if (!(h->magic == MAGIC_DSK_COMPRESSED || h->magic == MAGIC_DSK_UNCOMPRESSED || h->magic == MAGIC_DSK_OLD)) + return 0; + + if (h->cylinders < 40 || h->cylinders > 80) + return 0; + if (h->heads == 0 || h->heads > 2) + return 0; + if (h->sectors < 8 || h->sectors > 36) + return 0; + /* reasonable floppy sector sizes are powers of two between 128 and 1024 */ + if (h->sectorsize < 128 || h->sectorsize > 1024 || (h->sectorsize & (h->sectorsize - 1)) != 0) + return 0; + + final_sectors = h->cylinders * h->heads * h->sectors; + final_bytes = final_sectors * h->sectorsize; + + if (final_bytes > 4000000UL) + return 0; + + if ((h->clustershift > 7) || (h->clustermask != (1UL << h->clustershift) - 1)) + return 0; + if (h->reservedsectors + h->fatcopies * h->sectorsperfat != h->rootdirsector) + return 0; + if (h->firstclustersector - 1 + (h->imageclusters << h->clustershift) != final_sectors) + return 0; + if (h->firstclustersector != h->reservedsectors + h->fatcopies * h->sectorsperfat + h->rootentries * 32 / h->sectorsize) + return 0; + if (h->imagesectors > final_sectors) + return 0; + if (h->commentoffset < sizeof(struct dskheader) || h->firstoffset <= h->commentoffset) + return 0; + + return 1; +} + +#if 0 +void dump_dsk_header(struct dskheader* h) +{ + printf("=== header =====================================\n"); + printf("magic: 0x%04x\n", h->magic); + printf("media type: 0x%04x\n", h->mediatype); + printf("sectorsize: %d\n", h->sectorsize); + printf("clustermask: %d\n", h->clustermask); + printf("clustershift: %d\n", h->clustershift); + printf("reservedsectors: %d\n", h->reservedsectors); + printf("fatcopies: %d\n", h->fatcopies); + printf("rootentries: %d\n", h->rootentries); + printf("firstclustersector: %d\n", h->firstclustersector); + printf("imageclusters: %d\n", h->imageclusters); + printf("sectorsperfat: %d\n", h->sectorsperfat); + printf("rootdirsector: %d\n", h->rootdirsector); + printf("checksum: 0x%08x\n", h->checksum); + printf("cylinders: %d\n", h->cylinders); + printf("heads: %d\n", h->heads); + printf("sectors: %d\n", h->sectors); + printf("unused: 0x%08x\n", h->unused); + printf("imagesectors: %d\n", h->imagesectors); + printf("commentoffset: %d\n", h->commentoffset); + printf("firstoffset: %d\n", h->firstoffset); + printf("================================================\n"); +} +#endif + +void dsk2img(const char* filename, int no_lzw) +{ + FILE *inf, *outf; + struct dskheader header; + char comment[600]; + char *c, *outname; + unsigned long rres; + int sres; + unsigned long int final_sectors, image_sectors, sector; + unsigned long int final_size; + long int start_offset; + uint8_t* buffer; + uint32_t checksum; + int i; + int type_geom; + char *rbuf = NULL, *wbuf = NULL; + + inf = fopen(filename, "rb"); + if (inf == NULL) + return; + + rbuf = (char*)malloc(4096); + if (rbuf) + setvbuf(inf, rbuf, _IOFBF, 4096); + else + printf("WARNING: can't allocate read buffer."); + + rres = fread(&header, sizeof(header), 1, inf); + if (rres != 1) { + printf("Short read\n"); + fclose(inf); + if (rbuf) + free(rbuf); + return; + } + + /* dump_dsk_header(&header); */ + + if (!valid_header(&header)) { + fclose(inf); + puts("Not a valid DSK file!"); + if (rbuf) + free(rbuf); + return; + } + + final_sectors = header.cylinders * header.heads * header.sectors; + final_size = final_sectors * header.sectorsize; + + printf("DSK format: "); + switch (header.magic) { + case MAGIC_DSK_OLD: + puts("Old"); + break; + case MAGIC_DSK_UNCOMPRESSED: + puts("Regular"); + break; + case MAGIC_DSK_COMPRESSED: + puts("Compressed"); + break; + default: + puts("Unknown"); + } + for (type_geom = 0; media_types[type_geom].sectors > 0; type_geom++) + if ((media_types[type_geom].cylinders == header.cylinders) && (media_types[type_geom].heads == header.heads) && (media_types[type_geom].sectors == header.sectors) && (media_types[type_geom].sectorsize == header.sectorsize)) + break; + printf("C=%d H=%d S=%d, %lu sectors of %dB (total %luB) [%s]\n", header.cylinders, header.heads, header.sectors, final_sectors, header.sectorsize, (unsigned long)final_size, media_types[type_geom].name); + + sres = fseek(inf, header.commentoffset, SEEK_SET); + if (sres != 0) { + perror("comment seek()"); + fclose(inf); + if (rbuf) + free(rbuf); + return; + } + + c = fgets(comment, sizeof(comment), inf); + if (c == NULL) { + perror("fgets()"); + fclose(inf); + if (rbuf) + free(rbuf); + return; + } + if (strlen(c) > 0) + printf("comment: %s", c); + + start_offset = header.firstoffset ? header.firstoffset : 0x200; + sres = fseek(inf, start_offset, SEEK_SET); + if (sres != 0) { + perror("start seek()"); + fclose(inf); + if (rbuf) + free(rbuf); + return; + } + + outname = guess_output_filename(filename, (header.magic == MAGIC_DSK_COMPRESSED) && (no_lzw != 0)); + + outf = fopen(outname, "wb+"); + if (outf == NULL) { + perror("fopen()"); + fclose(inf); + free(outname); + if (rbuf) + free(rbuf); + return; + } + + wbuf = (char*)malloc(4096); + if (wbuf) + setvbuf(outf, wbuf, _IOFBF, 4096); + else + printf("WARNING: can't allocate write buffer."); + + buffer = (uint8_t*)malloc(header.sectorsize); + if (buffer == NULL) { + perror("malloc()"); + fclose(outf); + fclose(inf); + free(outname); + if (rbuf) + free(rbuf); + if (wbuf) + free(wbuf); + return; + } + + /* image_sectors = (header.imageclusters << header.clustershift) + + header.firstclustersector - 1; */ + image_sectors = header.imagesectors; + checksum = 0; + if (header.magic != MAGIC_DSK_COMPRESSED) { + for (sector = 0; sector < image_sectors; sector++) { + rres = fread(buffer, header.sectorsize, 1, inf); + if (rres != 1) { + perror("fread()"); + fclose(outf); + fclose(inf); + free(buffer); + free(outname); + if (rbuf) + free(rbuf); + if (wbuf) + free(wbuf); + return; + } + for (i = 0; i < header.sectorsize / 2; i++) + checksum += buffer[i * 2] + 256 * buffer[i * 2 + 1]; + rres = fwrite(buffer, header.sectorsize, 1, outf); + if (rres != 1) { + perror("fwrite()"); + fclose(outf); + fclose(inf); + free(buffer); + free(outname); + if (rbuf) + free(rbuf); + if (wbuf) + free(wbuf); + return; + } + } + pad_file_to_size(outf, final_size); + if (checksum != header.checksum) + printf("ERROR: image checksum does not match. Expected 0x%08x but got 0x%08x\n", header.checksum, checksum); + else + puts("Done."); + } else { + if (no_lzw) { + size_t wres; + + for (sector = 0;; sector++) { + rres = fread(buffer, 1, header.sectorsize, inf); + for (i = 0; i < rres; i++) + checksum += buffer[i]; + wres = fwrite(buffer, 1, rres, outf); + if (wres != rres) { + perror("fwrite()"); + fclose(outf); + fclose(inf); + free(buffer); + free(outname); + if (rbuf) + free(rbuf); + if (wbuf) + free(wbuf); + return; + } + if (rres < header.sectorsize) + break; + } + } else { + /* TODO: checksum */ + lzw_decompress_file(inf, outf); + pad_file_to_size(outf, final_size); + } + } + + fclose(outf); + fclose(inf); + free(buffer); + free(outname); + if (rbuf) + free(rbuf); + if (wbuf) + free(wbuf); +} + +void help(void) +{ + puts("Syntax: dsk2img [-h] [-n] [FILE...]"); + puts("\t-n\tDo not attempt to decompress compressed images"); + puts("\t-h\tShow this help"); +} + +int main(int argc, char* const argv[]) +{ + int i; + int no_lzw = 0; + int c; + + while ((c = getopt(argc, argv, "hn")) != -1) + switch (c) { + case 'n': + no_lzw = 1; + break; + case '?': + case 'h': + help(); + return EXIT_SUCCESS; + } + if (optind >= argc) + help(); + else + for (i = optind; i < argc; i++) + /* TODO: handle DOS file globbing */ + dsk2img(argv[i], no_lzw); + + return EXIT_SUCCESS; +} diff --git a/lzw.c b/lzw.c new file mode 100644 index 0000000..fa4abe8 --- /dev/null +++ b/lzw.c @@ -0,0 +1,182 @@ +/* + * LZW algorithm as used in the IBM .DSK floppy disk image format. + * Uses a fixed 12bit code size and an LRU dictionary entry replacement policy. + */ + +#include "lzw.h" +#include +#include +#include +#include +#include + +void lzw_init(struct lzw_ctx* c) +{ + int i; + + c->coffset = c->doffset = c->codes = 0L; + c->input_buffer = 0; + c->dict[0].usecount = c->dict[0].length = 0; + c->dict[0].prefix = c->dict[0].lru_prev = c->dict[0].lru_next = 0; + for (i = 1; i <= 256; i++) { + c->dict[i].length = 1; + c->dict[i].prefix = 0; + c->dict[i].last = i - 1; + c->dict[i].first = i - 1; + c->dict[i].usecount = 1; /* so that this code will never end up in the lru list */ + c->dict[i].lru_prev = c->dict[i].lru_next = 0; + } + for (i = 257; i < 4096; i++) { + c->dict[i].length = 0; + c->dict[i].prefix = 0; + c->dict[i].last = 0; + c->dict[i].first = 0; + c->dict[i].usecount = 0; + c->dict[i].lru_prev = (i == 257 ? 0 : i - 1); + c->dict[i].lru_next = (i == 4095 ? 0 : i + 1); + } + c->lru_head = 257; + c->lru_tail = 4095; + c->last_emitted_code = 0; +} + +/* Remove code from the lru list */ +static void lzw_lru_unlink(struct lzw_ctx* c, uint16_t code) +{ + uint16_t next, prev; + + next = c->dict[code].lru_next; + prev = c->dict[code].lru_prev; + if (prev != 0) + c->dict[prev].lru_next = next; + else + c->lru_head = next; + if (next != 0) + c->dict[next].lru_prev = prev; + else + c->lru_tail = prev; +} + +/* Add the given code to the back of the lru list. The code must be removed from the list before calling this function. */ +static void lzw_lru_append(struct lzw_ctx* c, uint16_t code) +{ + c->dict[code].lru_next = 0; + c->dict[code].lru_prev = c->lru_tail; + c->dict[c->lru_tail].lru_next = code; + c->lru_tail = code; +} + +#if 0 +static void lzw_print_lru(struct lzw_ctx* c) +{ + printf("head=%03x tail=%03x\n", c->lru_head, c->lru_tail); + for (int i = 0; i < 4096; i++) + printf("[%03x] next=%03x prev=%03x\n", i, c->dict[i].lru_next, + c->dict[i].lru_prev); +} + +static void lzw_validate_lru(struct lzw_ctx* c) +{ + for (uint16_t code = c->lru_head; code != 0; code = c->dict[code].lru_next) { + printf("%03x", code); + if (c->dict[code].usecount != 0) + putchar('!'); + if (c->dict[code].lru_next != 0 && c->dict[c->dict[code].lru_next].lru_prev != code) + putchar('>'); + putchar(' '); + } + putchar('\n'); +} +#endif + +static int lzw_build_entry(struct lzw_ctx* c, uint16_t code) +{ + int pos; + + pos = c->lru_head; + lzw_lru_unlink(c, pos); + + if (c->dict[pos].prefix != 0) { + c->dict[c->dict[pos].prefix].usecount--; + if (c->dict[c->dict[pos].prefix].usecount == 0) + lzw_lru_append(c, c->dict[pos].prefix); + } + + c->dict[pos].prefix = c->last_emitted_code; + c->dict[pos].last = c->dict[(code == pos ? c->dict[pos].prefix : code)].first; + c->dict[pos].first = c->dict[c->dict[pos].prefix].first; + c->dict[pos].length = c->dict[c->dict[pos].prefix].length + 1; + c->dict[pos].usecount = 0; + + if (c->dict[c->dict[pos].prefix].usecount == 0) + lzw_lru_unlink(c, c->dict[pos].prefix); + c->dict[c->dict[pos].prefix].usecount++; + lzw_lru_append(c, pos); + + return pos; +} + +static void lzw_emit(struct lzw_ctx* c, uint16_t code, FILE* f) +{ + int i; + uint16_t t; + + for (i = c->dict[code].length, t = code; t != 0 && i > 0; i--, t = c->dict[t].prefix) + c->output_buffer[i - 1] = c->dict[t].last; + fwrite(c->output_buffer, c->dict[code].length, 1, f); + + c->doffset += c->dict[code].length; + c->last_emitted_code = code; +} + +static int lzw_get_next_code(struct lzw_ctx* c, FILE* f) +{ + uint16_t result; + int next_byte; + + next_byte = fgetc(f); + if (next_byte == EOF) + return -1; + c->coffset++; + + if ((c->codes & 1) == 0) { + c->input_buffer = fgetc(f); + if (c->input_buffer == EOF) + return -1; + c->coffset++; + result = (next_byte << 4) | (c->input_buffer >> 4); + } else { + result = ((c->input_buffer & 0x0f) << 8) | next_byte; + } + + c->codes++; + return result & 0x0fff; +} + +int lzw_decompress_file(FILE* infp, FILE* outfp) +{ + struct lzw_ctx* ctx; + int code; + + ctx = (struct lzw_ctx*)malloc(sizeof(struct lzw_ctx)); + if (ctx == NULL) + return -1; + + lzw_init(ctx); + + while ((code = lzw_get_next_code(ctx, infp)) != 0) { + if (ctx->last_emitted_code != 0) + lzw_build_entry(ctx, code); + lzw_emit(ctx, code, outfp); + } + + /* + printf( + "\nEnd of file after reading %ld bytes (%ld symbols) and writing %ld " + "bytes\n", + ctx.coffset, ctx.codes, ctx.doffset); + */ + free(ctx); + + return 0; +} diff --git a/lzw.h b/lzw.h new file mode 100644 index 0000000..21d5d62 --- /dev/null +++ b/lzw.h @@ -0,0 +1,33 @@ +#ifndef LZW_H_ +#define LZW_H_ + +#include +#include +#include + +struct lzw_dict_entry { + uint16_t prefix; /* Code containing the first length-1 bytes in this code */ + uint16_t length; /* Length of this code */ + uint16_t usecount; /* How many codes have this code as a prefix */ + uint16_t lru_prev; /* Previous entry in the lru list */ + uint16_t lru_next; /* Next entry in the lru list */ + uint8_t last; /* Last byte in this code */ + uint8_t first; /* First byte in this code */ +}; + +struct lzw_ctx { + struct lzw_dict_entry dict[4096]; /* Dictionary entries */ + uint8_t output_buffer[4096]; /* Temporary buffer used to collect the byte string corresponding to a code */ + size_t coffset; /* Position in the compressed stream */ + size_t doffset; /* Position in the decompressed stream */ + size_t codes; /* Number of codes processed so far */ + int input_buffer; /* Temporary buffer used in the extraction of 12bit codes from the compressed stream */ + uint16_t lru_head; /* Index of the first entry in the lru list */ + uint16_t lru_tail; /* Index of the last entry in the lru list */ + uint16_t last_emitted_code; /* Code emitted in the previous round */ +}; + +void lzw_init(struct lzw_ctx* c); +int lzw_decompress_file(FILE* infp, FILE* outfp); + +#endif diff --git a/test-lzw.c b/test-lzw.c new file mode 100644 index 0000000..daf8ad3 --- /dev/null +++ b/test-lzw.c @@ -0,0 +1,59 @@ +#include "lzw.h" +#include +#include +#include + +int decompress(char* filename) +{ + char* outfilename; + FILE *fp, *outfp; + int i; + unsigned int ifnlen, ofnlen; + + fp = fopen(filename, "rb"); + if (fp == NULL) { + perror("fopen()"); + return -1; + } + + /* NOLINTBEGIN(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) */ + ifnlen = strlen(filename); + ofnlen = ifnlen + 5; + outfilename = (char*)malloc(ofnlen); + strncpy(outfilename, filename, ofnlen); + for (i = ifnlen - 1; i > 0 && outfilename[i] != '/'; i--) + if (outfilename[i] == '.') { + outfilename[i] = '\0'; + break; + } + if (strcmp(filename, outfilename) == 0) + strncat(outfilename, ".out", ofnlen); + /* NOLINTEND(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) */ + + printf("Writing to %s\n", outfilename); + outfp = fopen(outfilename, "wb"); + if (outfp == NULL) { + perror("fopen()"); + free(outfilename); + return -1; + } + + lzw_decompress_file(fp, outfp); + free(outfilename); + fclose(outfp); + fclose(fp); + + return 0; +} + +int main(int argc, char* argv[]) +{ + int i; + + for (i = 1; i < argc; i++) { + printf("Decompressing %s\n", argv[i]); + decompress(argv[i]); + } + + return EXIT_SUCCESS; +}