feat(zstd): on-the-fly zstd (de)compression

- added generic fread/fwrite/fclose wrappers as a framework to support several (de)compression algorithms

* ttyrec & ttyplay
- add zstd support

- ttyrec: add -Z (--zstd) option to enable on-the-fly zstd compression
- ttyrec: add --zstd-try to enable on-the-fly zstd compression but silently
            fallback to no compression if not supported
- ttyrec: add -l option to fine-tune the zstd compression ratio (between 1 and 19, default 3)
- ttyrec: add --max-flush-time to specify a number of seconds after which we force zstd to flush
            its output buffers to ensure somewhat idle sessions still get flushed to disk regularly
- ttyplay: zstd decompression is automatically enabled if the file suffix is ".zst"
- ttyplay: add -Z option to force on-the-fly zstd decompression regardless of the file suffix
- ttytime: add a warning if timing a .zst file is attempted (not supported)
This commit is contained in:
Stéphane Lesimple
2019-05-27 17:15:00 +02:00
committed by Stéphane Lesimple
parent 5bcd198028
commit d0fed1131f
11 changed files with 527 additions and 44 deletions

View File

@@ -1,6 +1,7 @@
CC = %CC%
CFLAGS = -O2 -D_GNU_SOURCE -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2 -std=gnu99 %WARNINGS% -Werror
LIBS = %LIBS%
CFLAGS = -O2 -D_GNU_SOURCE -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2 -std=gnu99 -I/usr/local/include %CFLAGS%
LDFLAGS = -L/usr/local/lib
LDLIBS = %LDLIBS%
BINARIES = ttyrec ttyplay ttytime
DOCS = docs/ttyrec.1
@@ -16,23 +17,23 @@ prog: $(BINARIES)
doc: $(DOCS)
ttyrec.o: ttyrec.c configure.h
$(CC) $(CFLAGS) -c ttyrec.c
ttyrec: ttyrec.o io.o compress.o %COMPRESS_ZSTD%
$(CC) $(CFLAGS) -o $@ ttyrec.o io.o compress.o %COMPRESS_ZSTD% $(LDFLAGS) $(LDLIBS) -pthread
ttyrec: ttyrec.o io.o
$(CC) $(CFLAGS) -pthread -o ttyrec ttyrec.o io.o $(LIBS)
ttyplay: ttyplay.o io.o compress.o %COMPRESS_ZSTD%
$(CC) $(CFLAGS) -o $@ ttyplay.o io.o compress.o %COMPRESS_ZSTD% $(LDFLAGS) $(LDLIBS)
ttyplay: ttyplay.o io.o
$(CC) $(CFLAGS) -o ttyplay ttyplay.o io.o
ttytime: ttytime.o io.o
$(CC) $(CFLAGS) -o ttytime ttytime.o io.o
ttytime: ttytime.o io.o compress.o %COMPRESS_ZSTD%
$(CC) $(CFLAGS) -o $@ ttytime.o io.o compress.o %COMPRESS_ZSTD% $(LDFLAGS) $(LDLIBS)
docs/ttyrec.1: ttyrec
help2man -N --help-option=-h --version-option=-V --no-discard-stderr ./ttyrec > docs/ttyrec.1
help2man -N --help-option=-h --version-option=-V --no-discard-stderr ./ttyrec > $@
clean:
rm -f *.o $(BINARIES) $(DOCS) ttyrecord *~ Makefile configure.h
rm -f *.o $(BINARIES) $(DOCS) ttyrecord *~
distclean: clean
rm -f Makefile configure.h
style:
uncrustify -c uncrustify.cfg -l C --no-backup *.h *.c

View File

@@ -24,6 +24,7 @@ It should work under any POSIX OS that support either `openpty()` or the `grantp
- Drop-in replacement of the classic ttyrec, additional features don't break compatibility
- The code is portable and OS features that can be used are detected at compile time
- Supports on-the-fly (de)compression using the zstd algorithm
- Supports ttyrec output file rotation without interrupting the session
- Supports locking the session after a keyboard input timeout, optionally displaying a custom message
- Supports terminating the session after a keyboard input timeout
@@ -38,6 +39,8 @@ To compile the binaries and build the man pages, just run:
$ ./configure && make
You'll need `libzstd` on the build machine if you want ttyrec to be compiled with zstd support. The library will be statically linked when possible.
If you don't want to build the manpages, or you don't have the required `help2man` tool, you can run `make prog` instead.
Installation:
@@ -73,9 +76,9 @@ Execute a local script remotely with the default remote shell:
$ cat script.sh | ttyrec -- ssh remoteserver
Record a screen session:
Record a screen session, with on-the-fly compression:
$ ttyrec screen
$ ttyrec -Z screen
Usage information:

98
compress.c Normal file
View File

@@ -0,0 +1,98 @@
// vim: noai:ts=4:sw=4:expandtab:
/* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
* Copyright 2019 The ovh-ttyrec Authors. All rights reserved.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "compress.h"
#include "configure.h"
compress_mode_t compress_mode = 0;
long compress_level = -1;
#ifdef HAVE_zstd
# include "compress_zstd.h"
#endif
void set_compress_mode(compress_mode_t cm)
{
compress_mode = cm;
}
void set_compress_level(long level)
{
compress_level = level;
}
long get_compress_level(void)
{
return compress_level;
}
size_t fread_wrapper(void *ptr, size_t size, size_t nmemb, FILE *stream)
{
if (compress_mode == COMPRESS_NONE)
{
return fread(ptr, size, nmemb, stream);
}
#ifdef HAVE_zstd
else if (compress_mode == COMPRESS_ZSTD)
{
return fread_wrapper_zstd(ptr, size, nmemb, stream);
}
#endif
else
{
fprintf(stderr, "ttyrec: unsupported compression mode (%d)\r\n", compress_mode);
exit(1);
}
}
size_t fwrite_wrapper(const void *ptr, size_t size, size_t nmemb, FILE *stream)
{
if (compress_mode == COMPRESS_NONE)
{
return fwrite(ptr, size, nmemb, stream);
}
#ifdef HAVE_zstd
else if (compress_mode == COMPRESS_ZSTD)
{
return fwrite_wrapper_zstd(ptr, size, nmemb, stream);
}
#endif
else
{
fprintf(stderr, "ttyrec: unsupported compression mode (%d)\r\n", compress_mode);
exit(1);
}
}
int fclose_wrapper(FILE *fp)
{
if (compress_mode == COMPRESS_NONE)
{
return fclose(fp);
}
#ifdef HAVE_zstd
else if (compress_mode == COMPRESS_ZSTD)
{
return fclose_wrapper_zstd(fp);
}
#endif
else
{
fprintf(stderr, "ttyrec: unsupported compression mode (%d)\r\n", compress_mode);
fclose(fp);
exit(1);
}
}

21
compress.h Normal file
View File

@@ -0,0 +1,21 @@
#ifndef __TTYREC_COMPRESS_H__
#define __TTYREC_COMPRESS_H__
#include <stdlib.h>
#include <stdio.h>
size_t fwrite_wrapper(const void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fread_wrapper(void *ptr, size_t size, size_t nmemb, FILE *stream);
int fclose_wrapper(FILE *fp);
typedef enum
{
COMPRESS_NONE = 0,
COMPRESS_ZSTD = 1,
} compress_mode_t;
void set_compress_mode(compress_mode_t cm);
void set_compress_level(long level);
long get_compress_level(void);
#endif

224
compress_zstd.c Normal file
View File

@@ -0,0 +1,224 @@
// vim: noai:ts=4:sw=4:expandtab:
/* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
* Copyright 2019 The ovh-ttyrec Authors. All rights reserved.
*/
#include "compress.h"
#include "compress_zstd.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <zstd.h>
static ZSTD_CStream *cstream = NULL;
static size_t buffOutSize = 0;
static void *buffOut;
static long zstd_max_flush_seconds = ZSTD_MAX_FLUSH_SECONDS_DEFAULT;
void zstd_set_max_flush(long seconds)
{
zstd_max_flush_seconds = seconds;
}
size_t fwrite_wrapper_zstd(const void *ptr, size_t size, size_t nmemb, FILE *stream)
{
static time_t last_sync = 0;
long compress_level = get_compress_level();
if (cstream == NULL)
{
cstream = ZSTD_createCStream();
if (cstream == NULL)
{
fprintf(stderr, "ZSTD_createCStream() error\r\n");
exit(10);
}
if (compress_level < 0)
{
compress_level = 3;
}
size_t const initResult = ZSTD_initCStream(cstream, compress_level);
if (ZSTD_isError(initResult))
{
fprintf(stderr, "ZSTD_initCStream() error: %s\r\n", ZSTD_getErrorName(initResult));
exit(11);
}
if (buffOutSize == 0)
{
buffOutSize = ZSTD_CStreamOutSize();
buffOut = malloc(buffOutSize);
if (buffOut == NULL)
{
fprintf(stderr, "couldn't malloc() zstd out buffer\r\n");
exit(12);
}
}
last_sync = time(NULL);
}
size_t written = 0;
ZSTD_inBuffer input = { ptr, size * nmemb, 0 };
while (input.pos < input.size)
{
ZSTD_outBuffer output = { buffOut, buffOutSize, 0 };
size_t toRead = ZSTD_compressStream(cstream, &output, &input); /* toRead is guaranteed to be <= ZSTD_CStreamInSize() */
if (ZSTD_isError(toRead))
{
fprintf(stderr, "ZSTD_compressStream() error: %s\r\n", ZSTD_getErrorName(toRead));
exit(13);
}
size_t thisWritten = fwrite(buffOut, 1, output.pos, stream);
if (thisWritten != output.pos)
{
return thisWritten; // error or eof, pass to caller
}
written += thisWritten;
}
//fprintf(stderr, "[zstd:nbwr=%lu]", written);
// if we actually did write data to disk (instead of just compressing in memory),
// then we can reset last_sync
if (written > 0)
{
last_sync = time(NULL);
//fprintf(stderr, "[zstd:rst]");
}
// otherwise, check for last sync time. if it's > X seconds, force zstd to flush its buffers
// and write to disk. we don't want to loose data from almost-idle sessions in case of server crash
else if (last_sync + zstd_max_flush_seconds < time(NULL))
{
ZSTD_outBuffer output = { buffOut, buffOutSize, 0 };
written = ZSTD_flushStream(cstream, &output);
if (ZSTD_isError(written))
{
fprintf(stderr, "ZSTD_flushStream() error: %s\r\n", ZSTD_getErrorName(written));
exit(14);
}
//fprintf(stderr, "[zstd:tmoutflushed=%lu]", output.pos);
written = fwrite(buffOut, 1, output.pos, stream);
//fprintf(stderr, "[zstd:tmoutnbwr=%lu]", written);
last_sync = time(NULL);
}
return written;
}
int fclose_wrapper_zstd(FILE *fp)
{
if (cstream != NULL)
{
ZSTD_outBuffer output = { buffOut, buffOutSize, 0 };
size_t const remainingToFlush = ZSTD_endStream(cstream, &output); /* close frame */
if (remainingToFlush)
{
fprintf(stderr, "error: zstd not fully flushed\r\n");
}
fwrite(buffOut, 1, output.pos, fp);
//fprintf(stderr, "[closezstd:written=%lu]", output.pos);
ZSTD_freeCStream(cstream);
cstream = NULL;
}
return fclose(fp);
}
size_t fread_wrapper_zstd(void *ptr, size_t size, size_t nmemb, FILE *stream)
{
// input: compressed data read from file
// output: decompressed data from (a part of) buffin
// buffoutptr: pointing to decompressed not-yet-returned to caller data (remaining bytes is buffoutptrlen)
static ZSTD_inBuffer input = { NULL, 0, 0 };
static size_t buffOutSize;
static ZSTD_outBuffer output = { NULL, 0, 0 };
static char *buffOutPtr = NULL;
static size_t buffOutPtrLen = 0; // number of valid not-yet-returned bytes after ptr
static ZSTD_DStream *dstream = NULL;
// static because ZSTD_initDStream return the first recommended input size, we'll use ot for first fread()
static size_t toRead;
size_t remainingBytesToReturn = size * nmemb;
char *returnData = (char *)ptr;
// init dstream if needed (first call only)
if (dstream == NULL)
{
dstream = ZSTD_createDStream();
toRead = ZSTD_initDStream(dstream);
input.src = malloc(ZSTD_DStreamInSize());
buffOutSize = ZSTD_DStreamOutSize();
output.dst = malloc(buffOutSize);
}
// do we have remaining decompressed data from a previous call, ready to be returned?
GOTDATA:
if (buffOutPtrLen > 0)
{
if (buffOutPtrLen >= remainingBytesToReturn)
{
// easy: we already have all the wanted data in the previous runs buffer
// so we'll just consume data from it and return
memcpy(returnData, buffOutPtr, remainingBytesToReturn);
buffOutPtrLen -= remainingBytesToReturn;
buffOutPtr += remainingBytesToReturn;
return nmemb;
}
else
{
// we have SOME data in the previous runs buffer, use it
memcpy(returnData, buffOutPtr, buffOutPtrLen);
returnData += buffOutPtrLen;
remainingBytesToReturn -= buffOutPtrLen;
buffOutPtrLen = 0;
buffOutPtr = NULL;
}
}
// if we're here, we don't have any data left in buffOutPtr, and the caller wants more data
// but maybe we still have not-yet-decompressed data from a previously read compressed chunk?
DECOMPRESS:
if (input.pos < input.size)
{
output.pos = 0;
output.size = buffOutSize;
toRead = ZSTD_decompressStream(dstream, &output, &input); /* toRead : size of next compressed block */
if (ZSTD_isError(toRead))
{
fprintf(stderr, "ZSTD_decompressStream() error: %s\r\n", ZSTD_getErrorName(toRead));
exit(16);
}
buffOutPtr = output.dst; // aka buffOut
buffOutPtrLen = output.pos;
if (buffOutPtrLen == 0)
{
// ok this is an empty frame (or beggining of zst stream), read again
goto DECOMPRESS;
}
goto GOTDATA;
}
// nope we don't, alright, decompress a new chunk then
else
{
size_t read = fread((void *)input.src, 1, toRead, stream);
if (read == 0)
{
// eof or error, return it
return 0;
}
input.size = read;
input.pos = 0;
goto DECOMPRESS;
}
}

13
compress_zstd.h Normal file
View File

@@ -0,0 +1,13 @@
#ifndef __TTYREC_COMPRESS_ZSTD_H__
#define __TTYREC_COMPRESS_ZSTD_H__
#include <stdio.h>
#define ZSTD_MAX_FLUSH_SECONDS_DEFAULT 15
size_t fread_wrapper_zstd(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite_wrapper_zstd(const void *ptr, size_t size, size_t nmemb, FILE *stream);
int fclose_wrapper_zstd(FILE *fp);
void zstd_set_max_flush(long seconds);
#endif

56
configure vendored
View File

@@ -10,14 +10,52 @@ which $CC >/dev/null 2>&1 || CC=clang
which $CC >/dev/null 2>&1 || CC=cc
echo "$CC"
LIBS=''
LDLIBS=''
DEFINES_STR='uses:'
WARNINGS=''
CFLAGS=''
COMPRESS_ZSTD=''
srcfile=$(mktemp)
mv $srcfile $srcfile.c
trap "rm -f $srcfile.c" INT HUP EXIT
printf "%b" "Checking if compiler can create executables... "
cat >$srcfile.c <<EOF
int main(void) { return 0; }
EOF
if $CC $srcfile.c -o /dev/null >/dev/null 2>&1; then
echo "yes"
else
echo "no"
echo
echo "Please ensure you have a working C compiler and relaunch configure."
exit 1
fi
printf "%b" "Looking for libzstd... "
cat >$srcfile.c <<EOF
#include <zstd.h>
int main(void) { ZSTD_CStream *c = ZSTD_createCStream(); ZSTD_initCStream(c, 3); ZSTD_freeCStream(c); return 0; }
EOF
if [ "$NO_ZSTD" != 1 ] && $CC $srcfile.c -L/usr/local/lib -I/usr/local/include -lzstd -o /dev/null >/dev/null 2>&1; then
echo "yes"
echo '#define HAVE_zstd' >>"$curdir/configure.h"
COMPRESS_ZSTD='compress_zstd.c'
printf "%b" "Checking whether we can link zstd statically... "
libzstda=$($CC -print-file-name=libzstd.a 2>/dev/null)
if [ -n "$libzstda" ] && [ -f "$libzstda" ] && [ "$NO_STATIC_ZSTD" != 1 ]; then
echo "yes"
DEFINES_STR="$DEFINES_STR zstd[static]"
LDLIBS="$LDLIBS $libzstda"
else
echo "no"
DEFINES_STR="$DEFINES_STR zstd"
LDLIBS="$LDLIBS -lzstd"
fi
else
echo "no"
fi
printf "%b" "Looking for isastream()... "
cat >$srcfile.c <<EOF
#include <stropts.h>
@@ -92,7 +130,7 @@ int main(void) { return openpty(0, 0, 0, 0, 0); }
EOF
if $CC $srcfile.c -lutil -o /dev/null >/dev/null 2>&1; then
echo "yes (pty.h)"
LIBS="$LIBS -lutil"
LDLIBS="$LDLIBS -lutil"
echo '#define HAVE_openpty' >>"$curdir/configure.h"
echo '#define HAVE_openpty_pty_h' >>"$curdir/configure.h"
DEFINES_STR="$DEFINES_STR openpty[pty.h]"
@@ -103,7 +141,7 @@ int main(void) { return openpty(0, 0, 0, 0, 0); }
EOF
if $CC $srcfile.c -lutil -o /dev/null >/dev/null 2>&1; then
echo "yes (util.h)"
LIBS="$LIBS -lutil"
LDLIBS="$LDLIBS -lutil"
echo '#define HAVE_openpty' >>"$curdir/configure.h"
echo '#define HAVE_openpty_util_h' >>"$curdir/configure.h"
DEFINES_STR="$DEFINES_STR openpty[util.h]"
@@ -114,7 +152,7 @@ int main(void) { return openpty(0, 0, 0, 0, 0); }
EOF
if $CC $srcfile.c -lutil -o /dev/null >/dev/null 2>&1; then
echo "yes (libutil.h)"
LIBS="$LIBS -lutil"
LDLIBS="$LDLIBS -lutil"
echo '#define HAVE_openpty' >>"$curdir/configure.h"
echo '#define HAVE_openpty_libutil_h' >>"$curdir/configure.h"
DEFINES_STR="$DEFINES_STR openpty[libutil.h]"
@@ -127,21 +165,23 @@ fi
echo "Checking for supported compiler options..."
for w in -Wall -Wextra -pedantic -Wno-unused-result -Wbad-function-cast -Wmissing-declarations \
-Wmissing-prototypes -Wnested-externs -Wold-style-definition -Wstrict-prototypes \
-Wpointer-sign -Wmissing-parameter-type -Wold-style-declaration
-Wpointer-sign -Wmissing-parameter-type -Wold-style-declaration -Werror -Wl,--as-needed
do
[ "$w" = "-Wl,--as-needed" ] && [ "$CC" != "gcc" ] && continue
echo 'int main(void) { return 0; }' >$srcfile.c
if [ $($CC $srcfile.c $w -o /dev/null 2>&1 | wc -l) = 0 ]; then
echo "... OK $w"
WARNINGS="$WARNINGS $w"
CFLAGS="$CFLAGS $w"
else
echo "... unsupported $w"
fi
done
sed "s/%CC%/$CC/g;s/%LIBS%/$LIBS/g;s/%WARNINGS%/$WARNINGS/" $(dirname $0)/Makefile.in > $(dirname $0)/Makefile
sed "s:%CC%:$CC:g;s:%LDLIBS%:$LDLIBS:g;s:%CFLAGS%:$CFLAGS:;s:%COMPRESS_ZSTD%:$COMPRESS_ZSTD:" $(dirname $0)/Makefile.in > $(dirname $0)/Makefile
cat >>"$curdir/configure.h" <<EOF
#define DEFINES_STR "$DEFINES_STR"
#define COMPILER_NAME "$CC"
#endif
EOF

16
io.c
View File

@@ -44,7 +44,9 @@
#include <string.h>
#include <unistd.h>
#include "io.h"
#include "ttyrec.h"
#include "compress.h"
#define SWAP_ENDIAN(val) \
((unsigned int)( \
@@ -53,16 +55,6 @@
(((unsigned int)(val) & (unsigned int)0x00ff0000U) >> 8) | \
(((unsigned int)(val) & (unsigned int)0xff000000U) >> 24)))
int read_header(FILE *fp, Header *h);
int write_header(FILE *fp, Header *h);
void set_progname(const char *name);
FILE *efopen(const char *path, const char *mode);
int edup(int oldfd);
int edup2(int oldfd, int newfd);
FILE *efdopen(int fd, const char *mode);
static int is_little_endian(void)
{
static int retval = -1;
@@ -106,7 +98,7 @@ int read_header(FILE *fp, Header *h)
{
int buf[3];
if (fread(buf, sizeof(int), 3, fp) == 0)
if (fread_wrapper(buf, sizeof(int), 3, fp) == 0)
{
return 0;
}
@@ -127,7 +119,7 @@ int write_header(FILE *fp, Header *h)
buf[1] = convert_to_little_endian(h->tv.tv_usec);
buf[2] = convert_to_little_endian(h->len);
if (fwrite(buf, sizeof(int), 3, fp) == 0)
if (fwrite_wrapper(buf, sizeof(int), 3, fp) == 0)
{
return 0;
}

View File

@@ -49,6 +49,8 @@
#include "ttyrec.h"
#include "io.h"
#include "compress.h"
#include "configure.h"
typedef double (*WaitFunc) (struct timeval prev,
struct timeval cur,
@@ -192,7 +194,7 @@ int ttyread(FILE *fp, Header *h, char **buf)
perror("malloc");
}
if (fread(*buf, 1, h->len, fp) == 0)
if (fread_wrapper(*buf, 1, h->len, fp) == 0)
{
perror("fread");
}
@@ -291,6 +293,10 @@ void usage(void)
printf(" -s SPEED Set speed to SPEED [1.0]\n");
printf(" -n No wait mode\n");
printf(" -p Peek another person's ttyrecord\n");
#ifdef HAVE_zstd
printf(" -Z Enable on-the-fly zstd decompression\n");
printf("\nThe -Z flag is implied if the file suffix is \".zst\"\n");
#endif
exit(EXIT_FAILURE);
}
@@ -320,7 +326,11 @@ int main(int argc, char **argv)
set_progname(argv[0]);
while (1)
{
#ifdef HAVE_zstd
int ch = getopt(argc, argv, "s:npZ");
#else
int ch = getopt(argc, argv, "s:np");
#endif
if (ch == EOF)
{
break;
@@ -344,6 +354,12 @@ int main(int argc, char **argv)
process = ttypeek;
break;
#ifdef HAVE_zstd
case 'Z':
set_compress_mode(COMPRESS_ZSTD);
break;
#endif
default:
usage();
}
@@ -352,6 +368,12 @@ int main(int argc, char **argv)
if (optind < argc)
{
input = efopen(argv[optind], "r");
#ifdef HAVE_zstd
if (strstr(argv[optind], ".zst") == argv[optind] + strlen(argv[optind]) - 4)
{
set_compress_mode(COMPRESS_ZSTD);
}
#endif
}
else
{

View File

@@ -84,6 +84,7 @@
#include "configure.h"
#include "ttyrec.h"
#include "io.h"
#include "compress.h"
#ifdef HAVE_openpty
# if defined(HAVE_openpty_pty_h)
@@ -95,6 +96,13 @@
# endif
#endif
// for ZSTD_versionNumber()
// and zstd_set_max_flush()
#ifdef HAVE_zstd
# include <zstd.h>
# include "compress_zstd.h"
#endif
#if defined(__linux__)
# define LINUX_OS
# define OS_STR "Linux"
@@ -215,6 +223,8 @@ static int parent_stdin_isatty = 0;
static char line[] = "/dev/ptyXX";
#endif
static long opt_compress_level = 0;
static int opt_zstd = 0;
static int opt_want_tty = 1; // never=0, auto=1, force=2
static int opt_append = 0;
static int opt_debug = 0;
@@ -276,6 +286,61 @@ int main(int argc, char **argv)
switch ((char)ch)
{
// long option without short-option counterpart
case 0:
if (strcmp(long_options[option_index].name, "zstd-try") == 0)
{
#ifdef HAVE_zstd
opt_zstd++;
set_compress_mode(COMPRESS_ZSTD);
#endif
}
else if (strcmp(long_options[option_index].name, "max-flush-time") == 0)
{
#ifdef HAVE_zstd
errno = 0;
long max_flush_seconds = strtol(optarg, NULL, 10);
if ((errno != 0) || (max_flush_seconds <= 0))
{
help();
fprintf(stderr, "Invalid value passed to --%s (%s), expected a strictly positive integer\r\n", long_options[option_index].name, optarg);
exit(EXIT_FAILURE);
}
zstd_set_max_flush(max_flush_seconds);
#endif
}
else
{
fprintf(stderr, "Unknown long option %s\r\n", long_options[option_index].name);
fail();
}
break;
// on-the-fly zstd compression
case 'Z':
#ifdef HAVE_zstd
opt_zstd++;
set_compress_mode(COMPRESS_ZSTD);
#else
fprintf(stderr, "zstd support has not been enabled at compile time.\r\n");
fail();
#endif
break;
// compression level of compression algorithm
case 'l':
errno = 0;
opt_compress_level = strtol(optarg, NULL, 10);
if ((errno != 0) || (opt_compress_level <= 0))
{
help();
fprintf(stderr, "Invalid value passed to -%c (%s), expected a strictly positive integer\r\n", (char)ch, optarg);
exit(EXIT_FAILURE);
}
printdbg("level %c=%ld\r\n", ch, opt_compress_level);
set_compress_level(opt_compress_level);
break;
// debug ttyrec
case 'v':
opt_debug++;
@@ -399,7 +464,10 @@ int main(int argc, char **argv)
printf("%s (%s)\r\n", DEFINES_STR, OS_STR);
#endif
#ifdef __VERSION__
printf("compiler version %s\r\n", __VERSION__);
printf("compiler version %s (%s)\r\n", __VERSION__, COMPILER_NAME);
#endif
#ifdef HAVE_zstd
printf("libzstd version %u (%d.%d.%d)\r\n", ZSTD_versionNumber(), ZSTD_VERSION_MAJOR, ZSTD_VERSION_MINOR, ZSTD_VERSION_RELEASE);
#endif
exit(0);
@@ -699,7 +767,7 @@ void doinput(void)
int cc;
char ibuf[BUFSIZ];
(void)fclose(fscript);
(void)fclose_wrapper(fscript);
#ifdef HAVE_openpty
if (openpty_used)
{
@@ -875,7 +943,7 @@ void swing_output_file(int signal)
{
set_ttyrec_file_name(&newname);
fclose(fscript);
fclose_wrapper(fscript);
if ((fscript = fopen(newname, "w")) == NULL)
{
@@ -1240,7 +1308,7 @@ void dooutput(void)
}
}
(void)write_header(fscript, &h);
(void)fwrite(obuf, 1, cc, fscript);
(void)fwrite_wrapper(obuf, 1, cc, fscript);
bytes_out += cc;
last_activity = time(NULL);
}
@@ -1270,7 +1338,7 @@ void dooutput(void)
// called by subchild
void doshell(const char *command, char **params)
{
(void)fclose(fscript);
(void)fclose_wrapper(fscript);
if (use_tty)
{
getslave();
@@ -1352,7 +1420,7 @@ void done(int status)
printdbg("child: done, cleaning up and exiting with %d (child=%d subchild=%d)\r\n", WEXITSTATUS(status), child, subchild);
// if we were locked, unlock before exiting to avoid leaving the real terminal of our user stuck in altscreen
unlock_session(SIGUSR2);
(void)fclose(fscript);
(void)fclose_wrapper(fscript);
(void)close(master);
}
else
@@ -1786,7 +1854,7 @@ void help(void)
" -a, --append open the ttyrec output file in append mode instead of write-clobber mode\n" \
" -Z, --zstd enable on-the-fly compression of output file using zstd,\n" \
" the resulting file will have a '.ttyrec.zst' extension\n" \
" --try-zstd enable on-the-fly zstd compression, silently fallback to no compression if not available\n" \
" --zstd-try enable on-the-fly zstd compression, silently fallback to no compression if not available\n" \
" --max-flush-time S specify the maximum number of seconds after which we'll force zstd to flush its output buffers\n" \
" to ensure that even somewhat quiet sessions gets regularly written out to disk, default is %d\n" \
" -l, --level LEVEL set compression level, must be between 1 and 19 for zstd, default is 3\n" \

View File

@@ -39,10 +39,11 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "io.h"
#include "ttyrec.h"
#include "io.h"
int calc_time(const char *filename);