244 lines
7.0 KiB
C
244 lines
7.0 KiB
C
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
|
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
|
|
╞══════════════════════════════════════════════════════════════════════════════╡
|
|
│ Copyright 2020 Justine Alexandra Roberts Tunney │
|
|
│ │
|
|
│ 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; version 2 of the License. │
|
|
│ │
|
|
│ 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, write to the Free Software │
|
|
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
|
|
│ 02110-1301 USA │
|
|
╚─────────────────────────────────────────────────────────────────────────────*/
|
|
#include "libc/calls/calls.h"
|
|
#include "libc/dce.h"
|
|
#include "libc/errno.h"
|
|
#include "libc/fmt/fmt.h"
|
|
#include "libc/log/check.h"
|
|
#include "libc/runtime/runtime.h"
|
|
#include "libc/stdio/stdio.h"
|
|
#include "libc/str/str.h"
|
|
#include "libc/sysv/consts/ex.h"
|
|
#include "libc/sysv/consts/exit.h"
|
|
#include "libc/sysv/errfuns.h"
|
|
#include "third_party/getopt/getopt.h"
|
|
|
|
#define USAGE1 \
|
|
"NAME\n\
|
|
\n\
|
|
rle - Run Length Encoder\n\
|
|
\n\
|
|
SYNOPSIS\n\
|
|
\n\
|
|
"
|
|
|
|
#define USAGE2 \
|
|
" [FLAGS] [FILE...]\n\
|
|
\n\
|
|
DESCRIPTION\n\
|
|
\n\
|
|
This is a primitive compression algorithm. Its advantage is that\n\
|
|
the concomitant rldecode() library is seventeen bytes, and works\n\
|
|
on IA-16, IA-32, and NexGen32e without needing to be recompiled.\n\
|
|
\n\
|
|
This CLI is consistent with gzip, bzip2, lzma, etc.\n\
|
|
\n\
|
|
FLAGS\n\
|
|
\n\
|
|
-1 .. -9 ignored\n\
|
|
-a ignored\n\
|
|
-c send to stdout\n\
|
|
-d decompress\n\
|
|
-f ignored\n\
|
|
-t test integrity\n\
|
|
-S SUFFIX overrides .rle extension\n\
|
|
-h shows this information\n"
|
|
|
|
FILE *fin_, *fout_;
|
|
bool decompress_, test_;
|
|
const char *suffix_, *hint_;
|
|
|
|
void StartErrorMessage(void) {
|
|
fputs("error: ", stderr);
|
|
fputs(hint_, stderr);
|
|
fputs(": ", stderr);
|
|
}
|
|
|
|
void PrintIoErrorMessage(void) {
|
|
int err;
|
|
err = errno;
|
|
StartErrorMessage();
|
|
fputs(strerror(err), stderr);
|
|
fputc('\n', stderr);
|
|
}
|
|
|
|
void PrintUsage(int rc, FILE *f) {
|
|
fputs(USAGE1, f);
|
|
fputs(program_invocation_name, f);
|
|
fputs(USAGE2, f);
|
|
exit(rc);
|
|
}
|
|
|
|
void GetOpts(int argc, char *argv[]) {
|
|
int opt;
|
|
fin_ = stdin;
|
|
suffix_ = ".rle";
|
|
while ((opt = getopt(argc, argv, "123456789S:acdfho:t")) != -1) {
|
|
switch (opt) {
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
case '8':
|
|
case '9':
|
|
case 'a':
|
|
case 'f':
|
|
break;
|
|
case 'c':
|
|
fout_ = stdout;
|
|
break;
|
|
case 'd':
|
|
decompress_ = true;
|
|
break;
|
|
case 't':
|
|
test_ = true;
|
|
break;
|
|
case 'o':
|
|
fclose_s(&fout_);
|
|
if (!(fout_ = fopen((hint_ = optarg), "w"))) {
|
|
PrintIoErrorMessage();
|
|
exit(1);
|
|
}
|
|
break;
|
|
case 'S':
|
|
suffix_ = optarg;
|
|
break;
|
|
case 'h':
|
|
case '?':
|
|
PrintUsage(EXIT_SUCCESS, stdout);
|
|
default:
|
|
PrintUsage(EX_USAGE, stderr);
|
|
}
|
|
}
|
|
}
|
|
|
|
int RunLengthEncode1(void) {
|
|
int byte1, byte2, runlength;
|
|
byte2 = -1;
|
|
runlength = 0;
|
|
if ((byte1 = fgetc(fin_)) == -1) return -1;
|
|
do {
|
|
while (++runlength < 255) {
|
|
if ((byte2 = fgetc(fin_)) != byte1) break;
|
|
}
|
|
if (fputc(runlength, fout_) == -1 || fputc(byte1, fout_) == -1) {
|
|
return -1;
|
|
}
|
|
runlength = 0;
|
|
} while ((byte1 = byte2) != -1);
|
|
return feof(fin_) ? 0 : -1;
|
|
}
|
|
|
|
int RunLengthEncode2(void) { return fputc(0, fout_) | fputc(0, fout_); }
|
|
|
|
int EmitRun(unsigned char count, unsigned char byte) {
|
|
do {
|
|
if (fputc(byte, fout_) == -1) return -1;
|
|
} while (--count);
|
|
return 0;
|
|
}
|
|
|
|
int RunLengthDecode(void) {
|
|
int byte1, byte2;
|
|
if ((byte1 = fgetc(fin_)) == -1) return einval();
|
|
if ((byte2 = fgetc(fin_)) == -1) return einval();
|
|
while (byte1) {
|
|
if (!test_ && EmitRun(byte1, byte2) == -1) return -1;
|
|
if ((byte1 = fgetc(fin_)) == -1) break;
|
|
if ((byte2 = fgetc(fin_)) == -1) return einval();
|
|
}
|
|
if (byte1 != 0 || byte2 != 0) return einval();
|
|
fgetc(fin_);
|
|
return feof(fin_) ? 0 : -1;
|
|
}
|
|
|
|
int RunLengthCode(void) {
|
|
if (test_ || decompress_) {
|
|
return RunLengthDecode();
|
|
} else {
|
|
return RunLengthEncode1();
|
|
}
|
|
}
|
|
|
|
int Run(char **paths, size_t count) {
|
|
int rc;
|
|
char *p;
|
|
size_t i, suffixlen;
|
|
const char pathbuf[PATH_MAX];
|
|
if (!count) {
|
|
hint_ = "/dev/stdin";
|
|
if (!fout_) fout_ = stdout;
|
|
rc = RunLengthCode();
|
|
rc |= fclose_s(&fin_);
|
|
} else {
|
|
rc = fclose_s(&fin_);
|
|
for (i = 0; i < count && rc != -1; ++i) {
|
|
rc = -1;
|
|
if ((fin_ = fopen((hint_ = paths[i]), "r"))) {
|
|
if (test_ || fout_) {
|
|
rc = RunLengthCode();
|
|
} else {
|
|
suffixlen = strlen(suffix_);
|
|
if (!IsTrustworthy() && strlen(paths[i]) + suffixlen + 1 > PATH_MAX) {
|
|
return eoverflow();
|
|
}
|
|
p = stpcpy(pathbuf, paths[i]);
|
|
if (!decompress_) {
|
|
strcpy(p, suffix_);
|
|
} else if (p - pathbuf > suffixlen &&
|
|
memcmp(p - suffixlen, suffix_, suffixlen) == 0) {
|
|
p[-suffixlen] = '\0';
|
|
} else {
|
|
return enotsup();
|
|
}
|
|
if ((fout_ = fopen((hint_ = pathbuf), "w"))) {
|
|
rc = RunLengthCode();
|
|
if (rc != -1 && !decompress_) {
|
|
rc = RunLengthEncode2();
|
|
}
|
|
if ((rc |= fclose_s(&fout_)) != -1) {
|
|
unlink(paths[i]);
|
|
}
|
|
}
|
|
}
|
|
rc |= fclose_s(&fin_);
|
|
}
|
|
}
|
|
}
|
|
if (rc != -1 && fout_) {
|
|
rc = RunLengthEncode2();
|
|
rc |= fclose_s(&fout_);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
int main(int argc, char *argv[]) {
|
|
GetOpts(argc, argv);
|
|
if (Run(argv + optind, argc - optind) != -1) {
|
|
return EXIT_SUCCESS;
|
|
} else {
|
|
PrintIoErrorMessage();
|
|
return EXIT_FAILURE;
|
|
}
|
|
}
|