mirror of
https://github.com/ovh/ovh-ttyrec.git
synced 2023-04-10 17:58:23 +03:00
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:
committed by
Stéphane Lesimple
parent
5bcd198028
commit
d0fed1131f
27
Makefile.in
27
Makefile.in
@@ -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
|
||||
|
||||
@@ -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
98
compress.c
Normal 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
21
compress.h
Normal 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
224
compress_zstd.c
Normal 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
13
compress_zstd.h
Normal 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
56
configure
vendored
@@ -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
16
io.c
@@ -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;
|
||||
}
|
||||
|
||||
24
ttyplay.c
24
ttyplay.c
@@ -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
|
||||
{
|
||||
|
||||
82
ttyrec.c
82
ttyrec.c
@@ -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" \
|
||||
|
||||
Reference in New Issue
Block a user