pax_global_header00006660000000000000000000000064145020367050014514gustar00rootroot0000000000000052 comment=36e8dde2bc280a79e7aab0c744d5a5ba4311c344 ovh-ttyrec-1.1.7.1/000077500000000000000000000000001450203670500137655ustar00rootroot00000000000000ovh-ttyrec-1.1.7.1/.github/000077500000000000000000000000001450203670500153255ustar00rootroot00000000000000ovh-ttyrec-1.1.7.1/.github/workflows/000077500000000000000000000000001450203670500173625ustar00rootroot00000000000000ovh-ttyrec-1.1.7.1/.github/workflows/check.yml000066400000000000000000000037141450203670500211670ustar00rootroot00000000000000name: basic checks on: [pull_request, push] jobs: check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: verify debian changelog run: | VERSION=$(awk '/static const char version/ { gsub(/[\";]/, "", $6); print $6; exit }' ttyrec.c) if ! head -n1 debian/changelog | grep -F "ovh-ttyrec ($VERSION) master"; then echo "inconsistency between version ($VERSION) and debian changelog:" head -n1 debian/changelog exit 1 fi - name: install prerequisites run: sudo apt-get install -y uncrustify make git debhelper - name: check style run: | ./configure make style git diff if ! git diff --quiet; then echo "Please make style." exit 1 fi - name: compile with zstd autodetection (none) run: | sudo apt-get remove --purge -y libzstd-dev make clean && ./configure && make -j$(nproc) && ./ttyrec -V if ./ttyrec -V | grep -qF 'zstd'; then exit 1 fi - name: compile with zstd autodetection (static) run: | sudo apt-get install -y libzstd-dev make clean && ./configure && make -j$(nproc) && ./ttyrec -V ./ttyrec -V | grep -qF 'zstd[static]' - name: compile with shared libzstd run: | make clean && NO_STATIC_ZSTD=1 ./configure && make -j$(nproc) && ./ttyrec -V if ./ttyrec -V | grep -qF 'zstd[static]'; then exit 1 fi ./ttyrec -V | grep -qF 'zstd' - name: compile without zstd support run: | make clean && NO_ZSTD=1 ./configure && make -j$(nproc) && ./ttyrec -V if ./ttyrec -V | grep -qF 'zstd'; then exit 1 fi - name: build and test debian package run: | make clean && ./configure && make -j$(nproc) && ./ttyrec -V make deb ls -l .. sudo dpkg -i ../ovh-ttyrec_*.deb dpkg -L ovh-ttyrec ovh-ttyrec-1.1.7.1/.github/workflows/release.yml000066400000000000000000000201721450203670500215270ustar00rootroot00000000000000name: publish release assets on: release: types: [published] jobs: checks: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: persist-credentials: false - name: verify changelogs run: | VERSION=$(awk '/static const char version/ { gsub(/[\";]/, "", $6); print $6; exit }' ttyrec.c) if ! head -n1 debian/changelog | grep -F "ovh-ttyrec ($VERSION) master"; then echo "inconsistency between version ($VERSION) and debian changelog:" head -n1 debian/changelog exit 1 fi if ! grep -F " $VERSION" ovh-ttyrec.spec; then echo "inconsistency between version ($VERSION) and rpm changelog:" exit 1 fi - name: install prerequisites run: sudo apt-get update && sudo apt-get install -y uncrustify make git gcc - name: check style run: | ./configure make style git diff if ! git diff --quiet; then echo "Please make style." exit 1 fi - name: compile with zstd autodetection (none) run: | sudo apt-get remove --purge -y libzstd-dev make clean && ./configure && make -j$(nproc) && ./ttyrec -V if ./ttyrec -V | grep -qF 'zstd'; then exit 1 fi - name: compile with zstd autodetection (static) run: | sudo apt-get install -y libzstd-dev make clean && ./configure && make -j$(nproc) && ./ttyrec -V ./ttyrec -V | grep -qF 'zstd[static]' - name: compile with shared libzstd run: | make clean && NO_STATIC_ZSTD=1 ./configure && make -j$(nproc) && ./ttyrec -V if ./ttyrec -V | grep -qF 'zstd[static]'; then exit 1 fi ./ttyrec -V | grep -qF 'zstd' - name: compile without zstd support run: | make clean && NO_ZSTD=1 ./configure && make -j$(nproc) && ./ttyrec -V if ./ttyrec -V | grep -qF 'zstd'; then exit 1 fi - name: compile fully static version run: | make clean && STATIC=1 ./configure && make -j$(nproc) && ./ttyrec -V ./ttyrec -V | grep -qF 'zstd[static]' if ldd ttyrec; then exit 1 fi multiarch: needs: checks runs-on: ubuntu-latest strategy: matrix: cross: - arm32v5 - arm32v7 - arm64v8 - ppc64le - mips64le - s390x - i386 - amd64 steps: - uses: actions/checkout@v4 with: persist-credentials: false - name: prepare qemu run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes - name: install prerequisites run: sudo apt-get update && sudo apt-get install -y rsync zip - name: create source zip file for rpm build run: | curdir=$PWD tempfolder=$(mktemp -d) cd $tempfolder mkdir ovh-ttyrec rsync -va --exclude=.git $curdir/ ovh-ttyrec/ mkdir -p ~/rpmbuild/SOURCES zip -9r ~/rpmbuild/SOURCES/master.zip ovh-ttyrec - name: build for ${{ matrix.cross }} run: > mkdir /tmp/pkg && docker run --rm -e DEBIAN_FRONTEND=noninteractive -v $PWD:/pkg/code -v /tmp/pkg:/pkg -v $HOME:/root ${{ matrix.cross }}/debian:10 /bin/bash -c ' set -exo pipefail; apt-get update; apt-get install -y make gcc libzstd-dev dpkg-dev debhelper unzip rpm binutils; cd /pkg/code; dpkg-buildpackage -b -rfakeroot -us -uc; if [ "${{ matrix.cross }}" = i386 ]; then target="--target=i386"; else target=""; fi set +o pipefail; rpm --showrc | head; set -o pipefail; if [ $(rpm -E "%{_arch}") != "%{_arch}" ]; then rpmbuild -bb $target ovh-ttyrec.spec; else arch=$(rpm --showrc | grep "^build arch" | awk "{print \$4}"); rpmbuild --define "_arch $arch" -bb $target ovh-ttyrec.spec; fi; mv ~/rpmbuild/RPMS/*/*.rpm /pkg; STATIC=1 ./configure && make clean && make -j$(nproc) && ./ttyrec -V; ./ttyrec -V | grep -qF "zstd[static]"; if ldd ttyrec; then exit 1; fi; version=$(./ttyrec -V | head -n1 | cut -d" " -f2 | grep -Eo "[0-9][A-Za-z0-9._-]+"); mkdir ovh-ttyrec-$version; strip ttyrec ttyplay ttytime; install ttyrec ttyplay ttytime ovh-ttyrec-$version; cp -va docs ovh-ttyrec-$version; staticname=ovh-ttyrec-${version}_$(dpkg --print-architecture)-linux-static-binary.tar.gz; tar cvzf /pkg/$staticname ovh-ttyrec-$version; ' - name: get release vars id: getvars run: | rpmpath=$(find /tmp/pkg -mindepth 1 -maxdepth 1 -type f -name "*.rpm") rpmname=$(basename "$rpmpath") echo "RPM package name is $rpmname ($rpmpath)" echo "::set-output name=rpmname::$rpmname" echo "::set-output name=rpmpath::$rpmpath" debpath=$(find /tmp/pkg -mindepth 1 -maxdepth 1 -type f -name "*.deb" ! -name "*dbgsym*") debname=$(basename "$debpath") echo "Debian package name is $debname ($debpath)" echo "::set-output name=debname::$debname" echo "::set-output name=debpath::$debpath" staticpath=$(find /tmp/pkg -mindepth 1 -maxdepth 1 -type f -name "*-linux-static-binary.tar.gz" | head -n1) staticname=$(basename "$staticpath") echo "Static tar.gz archive name is $staticname ($staticpath)" echo "::set-output name=staticname::$staticname" echo "::set-output name=staticpath::$staticpath" - name: upload rpm package uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ github.event.release.upload_url }} asset_path: ${{ steps.getvars.outputs.rpmpath }} asset_name: ${{ steps.getvars.outputs.rpmname }} asset_content_type: application/x-rpm - name: upload debian package uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ github.event.release.upload_url }} asset_path: ${{ steps.getvars.outputs.debpath }} asset_name: ${{ steps.getvars.outputs.debname }} asset_content_type: application/x-debian-package - name: upload static binary uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ github.event.release.upload_url }} asset_path: ${{ steps.getvars.outputs.staticpath }} asset_name: ${{ steps.getvars.outputs.staticname }} asset_content_type: application/gzip freebsd: needs: checks runs-on: macos-latest name: FreeBSD steps: - uses: actions/checkout@v4 with: persist-credentials: false - name: Build for FreeBSD id: build uses: vmactions/freebsd-vm@v0 with: usesh: true sync: sshfs run: | set -ex freebsd-version pkg install -y gmake zstd STATIC=1 ./configure gmake ./ttyrec -V ./ttyrec -V | grep -qF "zstd[static]" file ttyrec | grep -qF "statically" version=$(./ttyrec -V | head -n1 | cut -d" " -f2 | grep -Eo "[0-9][A-Za-z0-9._-]+") mkdir ovh-ttyrec-$version strip ttyrec ttyplay ttytime install ttyrec ttyplay ttytime ovh-ttyrec-$version cp -va docs ovh-ttyrec-$version staticname=ovh-ttyrec-${version}_$(uname -m)-freebsd-static-binary.tar.gz tar cvzf $staticname ovh-ttyrec-$version echo "Static tar.gz archive name is $staticname" echo "::set-output name=staticname::$staticname" - name: upload static binary uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ github.event.release.upload_url }} asset_path: ${{ steps.build.outputs.staticname }} asset_name: ${{ steps.build.outputs.staticname }} asset_content_type: application/gzip ovh-ttyrec-1.1.7.1/.travis.yml000066400000000000000000000033711450203670500161020ustar00rootroot00000000000000language: c compiler: - clang - gcc matrix: include: - os: osx osx_image: xcode10.2 - os: osx osx_image: xcode9.4 - os: osx osx_image: xcode8.3 - os: osx osx_image: xcode7.3 - os: osx osx_image: xcode6.4 - os: linux addons: apt: sources: - ubuntu-toolchain-r-test packages: - gcc-4.4 env: - MATRIX_EVAL="CC=gcc-4.4" - os: linux addons: apt: sources: - ubuntu-toolchain-r-test packages: - gcc-4.9 env: - MATRIX_EVAL="CC=gcc-4.9" # works on Precise and Trusty - os: linux addons: apt: sources: - ubuntu-toolchain-r-test packages: - gcc-5 env: - MATRIX_EVAL="CC=gcc-5" # works on Precise and Trusty - os: linux addons: apt: sources: - ubuntu-toolchain-r-test packages: - gcc-6 env: - MATRIX_EVAL="CC=gcc-6" # works on Precise and Trusty - os: linux addons: apt: sources: - ubuntu-toolchain-r-test packages: - gcc-7 env: - MATRIX_EVAL="CC=gcc-7" # works on Precise and Trusty - os: linux addons: apt: sources: - ubuntu-toolchain-r-test packages: - gcc-7 env: - MATRIX_EVAL="CC=gcc-8" # works on Precise and Trusty - os: linux addons: apt: sources: - ubuntu-toolchain-r-test packages: - gcc-7 env: - MATRIX_EVAL="CC=gcc-9" before_install: - eval "${MATRIX_EVAL}" ovh-ttyrec-1.1.7.1/AUTHORS000066400000000000000000000007131450203670500150360ustar00rootroot00000000000000# This is the official list of ovh-ttyrec authors for copyright purposes. # This file is distinct from the CONTRIBUTORS files # and it lists the copyright holders only. # Names should be added to this file as one of # Organization's name # Individual's name # Individual's name # See CONTRIBUTORS for the meaning of multiple email addresses. # Please keep the list sorted. OVH SAS ovh-ttyrec-1.1.7.1/CONTRIBUTING.md000066400000000000000000000057601450203670500162260ustar00rootroot00000000000000# Contributing to ovh-ttyrec This project accepts contributions. In order to contribute, you should pay attention to a few things: 1. your code must follow the coding style rules 2. your code must be unit-tested 3. your code must be documented 4. your work must be signed (see below) 5. you may contribute through GitHub Pull Requests # Coding and documentation Style Please follow the coding style you'll find in the main ttyrec.c file. Ensure you `make style` before comitting. # Submitting Modifications The contributions should be submitted through Github Pull Requests and follow the DCO which is defined below. Note that we want to keep compatibility with ttyrec classic, and as ovh-ttyrec needs to be extremely stable (as it's used in critical environment), deep modifications will probably get rejected. For this project, stability will always be preferred over fancy. # Licensing for new files ovh-ttyrec is licensed under a Modified 3-Clause BSD license. Anything contributed to ovh-ttyrec must be released under this license. When introducing a new file into the project, please make sure it has a copyright header making clear under which license it's being released. # Developer Certificate of Origin (DCO) To improve tracking of contributions to this project we will use a process modeled on the modified DCO 1.1 and use a "sign-off" procedure on patches that are being emailed around or contributed in any other way. The sign-off is a simple line at the end of the explanation for the patch, which certifies that you wrote it or otherwise have the right to pass it on as an open-source patch. The rules are pretty simple: if you can certify the below: By making a contribution to this project, I certify that: (a) The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or (b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source License and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or (c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it. (d) The contribution is made free of any other party's intellectual property claims or rights. (e) I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved. then you just add a line saying Signed-off-by: Random J Developer using your real name (sorry, no pseudonyms or anonymous contributions.) ovh-ttyrec-1.1.7.1/CONTRIBUTORS000066400000000000000000000010141450203670500156410ustar00rootroot00000000000000# This is the official list of people who can contribute # (and typically have contributed) code to the repository. # # Names should be added to this file only after verifying that # the individual or the individual's organization has agreed to # the appropriate CONTRIBUTING.md file. # # Names should be added to this file like so: # Individual's name # Individual's name # # Please keep the list sorted. # Stéphane Lesimple ovh-ttyrec-1.1.7.1/LICENSE000066400000000000000000000027231450203670500147760ustar00rootroot00000000000000Copyright 2001-2019, OVH SAS. All rights reserved. Modified 3-Clause BSD Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of OVH SAS nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY OVH SAS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL OVH SAS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ovh-ttyrec-1.1.7.1/MAINTAINERS000066400000000000000000000006301450203670500154610ustar00rootroot00000000000000# This is the official list of the project maintainers. # This is mostly useful for contributors that want to push # significant pull requests or for project management issues. # # # Names should be added to this file like so: # Individual's name # Individual's name # # Please keep the list sorted. # Stéphane Lesimple ovh-ttyrec-1.1.7.1/Makefile.in000066400000000000000000000031101450203670500160250ustar00rootroot00000000000000CC ?= %CC% CFLAGS += -O2 -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2 -I/usr/local/include %CFLAGS% $(RPM_OPT_FLAGS) LDFLAGS += -L/usr/local/lib LDLIBS += %LDLIBS% %PTHREAD% BINARIES = ttyrec ttyplay ttytime include config.mk PREFIX ?= /usr/local BINDIR ?= $(PREFIX)/bin UNAME_S := $(shell uname -s) ifeq ($(UNAME_S),FreeBSD) MANDIR ?= $(PREFIX)/man else MANDIR ?= $(PREFIX)/share/man endif .PHONY: all deb rpm clean distclean style dist install test all: $(BINARIES) deb: dpkg-buildpackage -b -rfakeroot -us -uc rpm: clean o=$$PWD && t=$$(mktemp -d) && cd $$t && mkdir ovh-ttyrec && rsync -a --exclude=.git $$o/ ovh-ttyrec/ && zip -9r ~/rpmbuild/SOURCES/master.zip ovh-ttyrec rpmbuild -bb ovh-ttyrec.spec ls -lh ~/rpmbuild/RPMS/*/ovh-ttyrec*.rpm ttyrec: ttyrec.o io.o compress.o %COMPRESS_ZSTD% $(CC) $(CFLAGS) -o $@ ttyrec.o io.o compress.o %COMPRESS_ZSTD% $(LDFLAGS) $(LDLIBS) ttyplay: ttyplay.o io.o compress.o %COMPRESS_ZSTD% $(CC) $(CFLAGS) -o $@ ttyplay.o io.o compress.o %COMPRESS_ZSTD% $(LDFLAGS) $(LDLIBS) ttytime: ttytime.o io.o compress.o %COMPRESS_ZSTD% $(CC) $(CFLAGS) -o $@ ttytime.o io.o compress.o %COMPRESS_ZSTD% $(LDFLAGS) $(LDLIBS) clean: rm -f *.o $(BINARIES) ttyrecord *~ distclean: clean rm -f Makefile configure.h style: uncrustify -c uncrustify.cfg -l C --no-backup *.h *.c dist: tar cvzf ttyrec.tar.gz *.c *.h docs/ debian/ configure Makefile.in uncrustify.cfg install: install -d $(DESTDIR)$(BINDIR) install $(BINARIES) $(DESTDIR)$(BINDIR)/ install -d $(DESTDIR)$(MANDIR)/man1 install -m 0644 docs/* $(DESTDIR)$(MANDIR)/man1/ test: all ./ttyrec -V ovh-ttyrec-1.1.7.1/README.md000066400000000000000000000101601450203670500152420ustar00rootroot00000000000000ovh-ttyrec ========== `ttyrec` is a terminal (tty) recorder, it comes with `ttyplay`, which is a tty player. The original ttyrec is Copyright (c) 2000 Satoru Takabayashi. The original ttyrec is based on the `script` program, Copyright (c) 1980 Regents of the University of California. ovh-ttyrec is based (and compatible with) the original ttyrec, and can be used as a drop-in replacement. It is licensed under the 3-clause BSD license (see LICENSE file). Efforts have been made to ensure the code is portable. It is known to work under at least: - Linux (all versions and distros) - BSD (known to work under at least FreeBSD, NetBSD, OpenBSD, DragonFlyBSD) - Darwin (macOS aka OS X aka Mac OS X) - Haiku (community OS compatible with BeOS) - OpenSolaris (known to work under at least OmniOS CE) It should work under any POSIX OS that support either `openpty()` or the `grantpt()`/`unlockpt() `mechanisms. ## features - 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 - Supports manually locking or terminating the session via "cheatcodes" (specific keystrokes) - Supports a no-tty mode, relying on pipes instead of pseudottys, while still recording stdout/stderr - Automatically detects whether to use pseudottys or pipes, also overridable from command-line - Supports reporting the number of bytes that were output to the terminal on session exit ## compilation 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 explicitly don't want libzstd, define `NO_ZSTD=1` before running configure. If you want it but dynamically linked, define `NO_STATIC_ZSTD=1`. Installation: $ make install Note that installation is not needed to test the binaries: you can just call `./ttyrec` from the build folder. ## build a .deb package If you want to build a .deb (Debian/Ubuntu) package, just run: $ ./configure && make deb ## build a .rpm package If you want to build a .rpm (RHEL/CentOS) package, just run: $ ./configure && make rpm ## usage The simplest usage is just calling the binary, it'll execute the users' shell and record the session until exit: $ ttyrec To replay this session: $ ttyplay ./ttyrecord Run some shell commands: $ ttyrec -f cmds.ttyrec -- sh -c 'for i in a b c; do echo $i; done' Connect to a remote machine interactively, lock the session after 1 minute of inactivity, and kill it after 5 minutes of inactivity: $ ttyrec -t 60 -k 300 -- ssh remoteserver Execute a local script remotely with the default remote shell: $ cat script.sh | ttyrec -- ssh remoteserver Record a screen session, with on-the-fly compression: $ ttyrec -Z screen Usage information: $ ttyrec -h ## version scheme We follow the version format `A.B.C.D`. The following rules apply: - A is incremented when the file format of ttyrec changes, as long as A=1, the format is compatible with the original ttyrec (and original ttyplay) - B is incremented for a breaking change in the way ttyrec can be called (a command-line option was removed for example), which means in that case, other programs or scripts using ttyrec should be checked for compatibility - C is incremented for any non-hotfix change that stays backwards compatible (a new feature that can be enabled with a new command-line option for example) - D is incremented for a quickfix/hotfix, or a change in the build system, docs, etc. When a digit is incremented, all the "lower" ones go back to zero, i.e. if we are at version 4.7.1.5, and we implement a breaking change, the version number becomes 4.8.0.0. ovh-ttyrec-1.1.7.1/compress.c000066400000000000000000000024301450203670500157630ustar00rootroot00000000000000// 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 #include #include #include #include "compress.h" #include "configure.h" #ifdef HAVE_zstd # include "compress_zstd.h" #endif size_t (*fread_wrapper)(void *ptr, size_t size, size_t nmemb, FILE *stream) = fread; size_t (*fwrite_wrapper)(const void *ptr, size_t size, size_t nmemb, FILE *stream) = fwrite; int (*fclose_wrapper)(FILE *fp) = fclose; static long compress_level = -1; int set_compress_mode(compress_mode_t cm) { switch (cm) { case COMPRESS_NONE: fread_wrapper = fread; fwrite_wrapper = fwrite; fclose_wrapper = fclose; break; #ifdef HAVE_zstd case COMPRESS_ZSTD: fread_wrapper = fread_wrapper_zstd; fwrite_wrapper = fwrite_wrapper_zstd; fclose_wrapper = fclose_wrapper_zstd; break; #endif default: fprintf(stderr, "ttyrec: unsupported compression mode\r\n"); return 1; } return 0; } void set_compress_level(long level) { compress_level = level; } long get_compress_level(void) { return compress_level; } ovh-ttyrec-1.1.7.1/compress.h000066400000000000000000000010101450203670500157610ustar00rootroot00000000000000#ifndef __TTYREC_COMPRESS_H__ #define __TTYREC_COMPRESS_H__ #include #include extern size_t (*fwrite_wrapper)(const void *ptr, size_t size, size_t nmemb, FILE *stream); extern size_t (*fread_wrapper)(void *ptr, size_t size, size_t nmemb, FILE *stream); extern int (*fclose_wrapper)(FILE *fp); typedef enum { COMPRESS_NONE = 0, COMPRESS_ZSTD = 1, } compress_mode_t; int set_compress_mode(compress_mode_t cm); void set_compress_level(long level); long get_compress_level(void); #endif ovh-ttyrec-1.1.7.1/compress_zstd.c000066400000000000000000000170171450203670500170360ustar00rootroot00000000000000// 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 #include #include #include #include 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 lose 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) input.src // 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 beginning of zst stream), read again goto DECOMPRESS; } goto GOTDATA; } // nope we don't, alright, decompress a new chunk then else { if (toRead == 0) { // the current stream is over, but maybe we have additional streams // concatenated back-to-back in the file, such as when --append is used? ZSTD_freeDStream(dstream); dstream = ZSTD_createDStream(); toRead = ZSTD_initDStream(dstream); } 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; } } ovh-ttyrec-1.1.7.1/compress_zstd.h000066400000000000000000000006001450203670500170310ustar00rootroot00000000000000#ifndef __TTYREC_COMPRESS_ZSTD_H__ #define __TTYREC_COMPRESS_ZSTD_H__ #include #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 ovh-ttyrec-1.1.7.1/configure000077500000000000000000000211471450203670500157010ustar00rootroot00000000000000#! /bin/sh curdir="$(dirname "$0")" # parse options, they can be specified by the build system (for .deb or .rpm), such as: # deb: # --build=x86_64-linux-gnu --prefix=/usr --includedir=\${prefix}/include --mandir=\${prefix}/share/man --infodir=\${prefix}/share/info --sysconfdir=/etc --localstatedir=/var --disable-silent-rules --libdir=\${prefix}/lib/x86_64-linux-gnu --libexecdir=\${prefix}/lib/x86_64-linux-gnu --disable-maintainer-mode --disable-dependency-tracking # rpm: # --host=x86_64-pc-linux-gnu --build=x86_64-pc-linux-gnu --program-prefix= --disable-dependency-tracking --prefix=/usr --exec-prefix=/usr --bindir=/usr/bin --sbindir=/usr/sbin --sysconfdir=/etc --datadir=/usr/share --includedir=/usr/include --libdir=/usr/lib64 --libexecdir=/usr/lib/x86_64-linux-gnu --localstatedir=/var --sharedstatedir=/usr/com --mandir=/usr/share/man --infodir=/usr/share/info MKCONF="$curdir/config.mk" rm -f "$MKCONF" touch "$MKCONF" while [ -n "$1" ]; do value=$(echo "$1" | cut -d= -f2- | sed -e "s/\${prefix}/\$(PREFIX)/g") case "$1" in --prefix=*) echo "PREFIX ?= $value" >> "$MKCONF"; echo "Will use PREFIX=$value";; --mandir=*) echo "MANDIR ?= $value" >> "$MKCONF"; echo "Will use MANDIR=$value";; --bindir=*) echo "BINDIR ?= $value" >> "$MKCONF"; echo "Will use BINDIR=$value";; esac shift done echo '#ifndef CONFIGURE_H' >"$curdir/configure.h" echo '#define CONFIGURE_H' >>"$curdir/configure.h" printf "%b" "Looking for compiler... " [ -z "$CC" ] && CC=gcc command -v $CC >/dev/null 2>&1 || CC=clang command -v $CC >/dev/null 2>&1 || CC=cc echo "$CC" LDLIBS='' DEFINES_STR='uses:' CFLAGS='-std=c99' PTHREAD='' COMPRESS_ZSTD='' if [ "$STATIC" = 1 ]; then CFLAGS="$CFLAGS -static" fi os=$(uname -s) if [ "$os" = Linux ]; then CFLAGS="$CFLAGS -D_XOPEN_SOURCE=600 -D_XOPEN_SOURCE_EXTENDED -D_GNU_SOURCE -pipe" elif [ "$os" = NetBSD ]; then true elif [ "$os" = FreeBSD ]; then true elif [ "$os" = OpenBSD ]; then true elif [ "$os" = DragonFly ]; then true elif [ "$os" = Darwin ]; then true elif [ "$os" = SunOS ]; then CFLAGS="$CFLAGS -D__EXTENSIONS__" elif [ "$os" = Haiku ]; then true fi srcfile=$(mktemp) mv "$srcfile" "$srcfile.c" # shellcheck disable=SC2064 trap "rm -f $srcfile.c" INT HUP EXIT printf "%b" "Checking if compiler can create executables... " cat >"$srcfile.c" </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" "Checking how to get pthread support... " cat >"$srcfile.c" </dev/null 2>&1; then echo "-pthread" PTHREAD='-pthread' else echo "-lpthread" PTHREAD='-lpthread' fi printf "%b" "Looking for libzstd... " cat >"$srcfile.c" < 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.o' printf "%b" "Checking whether we can link zstd statically... " for dir in $($CC -print-search-dirs | awk '/^libraries:/ {$1=""; print}' | tr : "\n") /usr/local/lib do test -f "$dir/libzstd.a" && libzstda="$dir/libzstd.a" done if [ -n "$libzstda" ] && [ -f "$libzstda" ] && [ "$NO_STATIC_ZSTD" != 1 ]; then echo "yes ($libzstda)" 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" < int main(void) { return isastream(0); } EOF if $CC "$srcfile.c" -o /dev/null >/dev/null 2>&1; then echo "yes" echo '#define HAVE_isastream' >>"$curdir/configure.h" DEFINES_STR="$DEFINES_STR isastream" else echo "no" fi printf "%b" "Looking for cfmakeraw()... " cat >"$srcfile.c" < #include int main(void) { cfmakeraw(0); return 0; } EOF if $CC "$srcfile.c" -o /dev/null >/dev/null 2>&1; then echo "yes" echo '#define HAVE_cfmakeraw' >>"$curdir/configure.h" DEFINES_STR="$DEFINES_STR cfmakeraw" else echo "no" fi printf "%b" "Looking for getpt()... " cat >"$srcfile.c" < int main(void) { return getpt(); } EOF if $CC "$srcfile.c" -o /dev/null >/dev/null 2>&1; then echo "yes" echo '#define HAVE_getpt' >>"$curdir/configure.h" DEFINES_STR="$DEFINES_STR getpt" else echo "no" fi printf "%b" "Looking for posix_openpt()... " cat >"$srcfile.c" < #include int main(void) { return posix_openpt(0); } EOF if $CC "$srcfile.c" -o /dev/null >/dev/null 2>&1; then echo "yes" echo '#define HAVE_posix_openpt' >>"$curdir/configure.h" DEFINES_STR="$DEFINES_STR posix_openpt" else echo "no" fi printf "%b" "Looking for grantpt()... " cat >"$srcfile.c" < int main(void) { (void)ptsname(0); return grantpt(0) + unlockpt(0); } EOF if $CC "$srcfile.c" -o /dev/null >/dev/null 2>&1; then echo "yes" echo '#define HAVE_grantpt' >>"$curdir/configure.h" DEFINES_STR="$DEFINES_STR grantpt" else echo "no" fi printf "%b" "Looking for openpty()... " cat >"$srcfile.c" < 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, libutil)" 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]" else cat >"$srcfile.c" < 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, libutil)" 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]" else cat >"$srcfile.c" < 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, libutil)" 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]" else cat >"$srcfile.c" < int main(void) { return openpty(0, 0, 0, 0, 0); } EOF if $CC "$srcfile.c" -lbsd -o /dev/null >/dev/null 2>&1; then echo "yes (pty.h, libbsd)" echo '#define HAVE_openpty' >>"$curdir/configure.h" echo '#define HAVE_openpty_pty_h' >>"$curdir/configure.h" DEFINES_STR="$DEFINES_STR openpty[pty.h/libbsd]" LDLIBS="$LDLIBS -lbsd" else echo "no" fi fi fi 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 -Wl,--as-needed \ -Wno-unused-command-line-argument do 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" if echo "$w" | grep -q -- '-Wl,'; then LDFLAGS="$LDFLAGS $w" else CFLAGS="$CFLAGS $w" fi else echo "... unsupported $w" fi done cat "$(dirname "$0")"/Makefile.in > "$(dirname "$0")"/Makefile.tmp for i in CC LDLIBS CFLAGS COMPRESS_ZSTD PTHREAD do replace=$(eval printf "%b" "\"\$$i\"") sed "s:%$i%:$replace:g" "$(dirname "$0")"/Makefile.tmp > "$(dirname "$0")"/Makefile cat "$(dirname "$0")"/Makefile > "$(dirname "$0")"/Makefile.tmp done rm -f "$(dirname "$0")"/Makefile.tmp cat >>"$curdir/configure.h" < Thu, 09 May 2019 12:56:27 +0200 ovh-ttyrec-1.1.7.1/debian/README.source000066400000000000000000000001471450203670500173700ustar00rootroot00000000000000ovh-ttyrec for Debian --------------------- See the included README.md file for more information. ovh-ttyrec-1.1.7.1/debian/changelog000066400000000000000000000073351450203670500170710ustar00rootroot00000000000000ovh-ttyrec (1.1.7.1) master; urgency=medium * fix: ttyplay: playing zstd-compressed files created in append mode halted after first stream -- Stéphane Lesimple (deb packages) Mon, 18 Sep 2023 10:12:21 +0200 ovh-ttyrec (1.1.7.0) master; urgency=medium * feat: add --stealth-stdout and --stealth-stderr -- Stéphane Lesimple (deb packages) Fri, 15 Sep 2023 15:39:33 +0200 ovh-ttyrec (1.1.6.7) master; urgency=medium * fix: rare interlocking on exit * enh: default install prefix is now /usr/local * fix: FreeBSD compilation * chore: autobuild for FreeBSD -- Stéphane Lesimple (deb packages) Mon, 29 Mar 2021 17:48:37 +0200 ovh-ttyrec (1.1.6.6) master; urgency=medium * chore: display machine triplet in -V -- Stéphane Lesimple (deb packages) Mon, 09 Nov 2020 10:23:41 +0100 ovh-ttyrec (1.1.6.5) master; urgency=medium * fix: race condition when running w/o pty -- Stéphane Lesimple (deb packages) Tue, 15 Sep 2020 10:59:22 +0200 ovh-ttyrec (1.1.6.4) master; urgency=medium * fix: -k was not working correctly when used without -t -- Stéphane Lesimple (deb packages) Thu, 05 Mar 2020 16:29:12 +0100 ovh-ttyrec (1.1.6.3) master; urgency=medium * fix: race condition on exit when a sighandler gets called while we're in libc's exit(), fixes #7 -- Stéphane Lesimple (deb packages) Thu, 10 Oct 2019 14:43:17 +0200 ovh-ttyrec (1.1.6.2) master; urgency=medium * fix: race condition on exit where ttyrec could get stuck -- Stéphane Lesimple (deb packages) Fri, 30 Aug 2019 12:39:23 +0200 ovh-ttyrec (1.1.6.1) master; urgency=medium * enh: with -f, auto-append .zst if -Z or --zstd was specified * fix: allow usage of -f even if -F if specified -- Stéphane Lesimple (deb packages) Fri, 14 Jun 2019 12:41:54 +0200 ovh-ttyrec (1.1.6.0) master; urgency=medium * feat: added generic fread/fwrite/fclose wrappers as a framework to support several (de)compression algorithms * feat: add zstd support to ttyrec and ttyplay ttyrec: add -Z option to enable on-the-fly compression if available ttyrec: add --zstd to force on-the-fly zstd compression 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) * feat: implement long-options parsing for ttyrec * feat: add --name-format (-F) to specify a custom file name compatible with strftime() * feat: add --warn-before-lock and --warn-before-kill options * fix: abort if doinput() can't write to master * chore: nicify termios debug output * chore: get rid of help2man requirement * chore: portability fixes, tested under Linux, FreeBSD, NetBSD, OpenBSD, DragonFlyBSD, Darwin, Haiku, OmniOS -- Stéphane Lesimple (deb packages) Tue, 04 Jun 2019 10:17:11 +0200 ovh-ttyrec (1.1.5.0) master; urgency=medium * First public release * Add -c option to enable cheatcodes, as they're now disabled by default -- Stéphane Lesimple (deb packages) Thu, 09 May 2019 12:55:21 +0200 ovh-ttyrec-1.1.7.1/debian/compat000066400000000000000000000000021450203670500164050ustar00rootroot000000000000009 ovh-ttyrec-1.1.7.1/debian/control000066400000000000000000000023731450203670500166170ustar00rootroot00000000000000Source: ovh-ttyrec Section: ovh Priority: extra Maintainer: Stéphane Lesimple Build-Depends: debhelper (>= 7.0.50~) Standards-Version: 3.8.4 Package: ovh-ttyrec Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends} Conflicts: ttyrec Description: Extended (but compatible) fork of ttyrec ttyrec is a terminal (tty) recorder, it comes with ttyplay, which is a tty player. Some features of ovh-ttyrec follow: - 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 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 - Supports manually locking or terminating the session via "cheatcodes" (specific keystrokes) - Supports a no-tty mode, relying on pipes instead of pseudottys, while still recording stdout/stderr - Automatically detects whether to use pseudottys or pipes, also overridable from command-line - Supports reporting the number of bytes that were output to the terminal on session exit ovh-ttyrec-1.1.7.1/debian/copyright000066400000000000000000000005741450203670500171500ustar00rootroot00000000000000This work was packaged for Debian by: Stéphane Lesimple on Fri, 13 Jul 2012 15:16:31 +0200 Upstream Author(s): Stéphane Lesimple Copyright: Copyright 2001-2019, OVH SAS. License: See included LICENSE file. The Debian packaging is: Under the same terms as the included LICENSE file. ovh-ttyrec-1.1.7.1/debian/links000066400000000000000000000000421450203670500162460ustar00rootroot00000000000000usr/bin/ttyrec usr/bin/ovh-ttyrec ovh-ttyrec-1.1.7.1/debian/manpages000066400000000000000000000000541450203670500167240ustar00rootroot00000000000000docs/ttyplay.1 docs/ttyrec.1 docs/ttytime.1 ovh-ttyrec-1.1.7.1/debian/rules000077500000000000000000000006721450203670500162740ustar00rootroot00000000000000#!/usr/bin/make -f # -*- makefile -*- # Sample debian/rules that uses debhelper. # This file was originally written by Joey Hess and Craig Small. # As a special exception, when this file is copied by dh-make into a # dh-make output file, you may use that output file without restriction. # This special exception was added by Craig Small in version 0.37 of dh-make. # Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 %: dh $@ ovh-ttyrec-1.1.7.1/docs/000077500000000000000000000000001450203670500147155ustar00rootroot00000000000000ovh-ttyrec-1.1.7.1/docs/ttyplay.1000066400000000000000000000021501450203670500165030ustar00rootroot00000000000000.\" .\" This manual page is written by NAKANO Takeo .\" .TH TTYPLAY 1 .SH NAME ttyplay \- player of the tty session recorded by ttyrec .SH SYNOPSIS .br .B ttyplay .I [\-s SPEED] [\-n] [\-p] file .br .SH DESCRIPTION .B Ttyplay plays the tty session in .IR file , which was recorded previously by .BR ttyrec (1). .PP When .B \-p option is given, .B ttyplay output the .I file as it grows. It means that you can see the "live" shell session running by another user. .PP If you hit any key during playback, it will go right to the next character typed. This is handy when examining sessions where a user spends a lot of time at a prompt. .PP Additionally, there are some special keys defined: .TP .BI + " or " f double the speed of playback. .TP .BI \- " or " s halve the speed of playback. .TP .BI 1 set playback to speed 1.0 again. .SH OPTIONS .TP .BI \-s " SPEED" multiple the playing speed by .I SPEED (default is 1). .TP .B \-n no wait mode. Ignore the timing information in .IR file . .TP .B \-p peek another person's tty session. .SH "SEE ALSO" .BR script (1), .BR ttyrec (1), .BR ttytime (1) ovh-ttyrec-1.1.7.1/docs/ttyrec.1000066400000000000000000000076501450203670500163210ustar00rootroot00000000000000.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4. .TH TTYREC "1" "June 2019" "ttyrec v1.1.5.0" "User Commands" .SH NAME ttyrec \- manual page for ttyrec v1.1.5.0 .SH SYNOPSIS .B ttyrec [\fI\,options\/\fR] \fI\,-- \/\fR[\fI\,command options\/\fR] .TP legacy compatibility mode: .B ttyrec \fB\-e\fR [options] [ttyrec file name] .SH OPTIONS .TP \fB\-z\fR, \fB\-\-uuid\fR UUID specify an UUID (can be any string) that will appear in the ttyrec output file names, and kept with SIGUSR1 rotations (default: own PID) .TP \fB\-f\fR, \fB\-\-output\fR FILE full path of the first ttyrec file to write to (autogenerated if omitted) .TP \fB\-d\fR, \fB\-\-dir\fR FOLDER folder where to write the ttyrec files (taken from \fB\-f\fR if omitted, defaulting to working directory if both \fB\-f\fR and \fB\-d\fR are omitted) .TP \fB\-F\fR, \fB\-\-name\-format\fR FMT custom strftime\-compatible format string to qualify the full path of the output files, including the SIGUSR1 rotated ones .TP \fB\-a\fR, \fB\-\-append\fR open the ttyrec output file in append mode instead of write\-clobber mode .TP \fB\-Z\fR enable on\-the\-fly compression if available, silently fallback to no compression if not .TP \fB\-\-zstd\fR force on\-the\-fly compression of output file using zstd, the resulting file will have a '.ttyrec.zst' extension .TP \fB\-\-max\-flush\-time\fR S specify the maximum number of seconds after which we'll force zstd to flush its output buffers to ensure that even somewhat quiet sessions gets regularly written out to disk, default is 15 .TP \fB\-l\fR, \fB\-\-level\fR LEVEL set compression level, must be between 1 and 19 for zstd, default is 3 .TP \fB\-n\fR, \fB\-\-count\-bytes\fR count the number of bytes out and print it on termination (experimental) .TP \fB\-t\fR, \fB\-\-lock\-timeout\fR S lock session on input timeout after S seconds .TP \fB\-\-warn\-before\-lock\fR S warn S seconds before locking (see \fB\-\-lock\-timeout\fR) .TP \fB\-k\fR, \fB\-\-kill\-timeout\fR S kill session on input timeout after S seconds .TP \fB\-\-warn\-before\-kill\fR S warn S seconds before killing (see \fB\-\-kill\-timeout\fR) .TP \fB\-C\fR, \fB\-\-no\-cheatcodes\fR disable cheat\-codes (see below), this is the default .TP \fB\-c\fR, \fB\-\-cheatcodes\fR enable cheat\-codes (see below) .TP \fB\-p\fR, \fB\-\-no\-openpty\fR don't use openpty() even when it's available .TP \fB\-T\fR, \fB\-\-term\fR MODE MODE can be either 'never' (never allocate a pseudotty, even if stdin is a tty, and use pipes to handle stdout/stderr instead), 'always' (always allocate a pseudotty, even if stdin is not a tty) or 'auto' (default, allocate a pseudotty if stdin is a tty, uses pipes otherwise) .TP \fB\-v\fR, \fB\-\-verbose\fR verbose (debug) mode, use twice for more verbosity .TP \fB\-V\fR, \fB\-\-version\fR show version information .TP \fB\-e\fR, \fB\-\-shell\-cmd\fR CMD enables legacy compatibility mode and specifies the command to be run under the user's $SHELL \fB\-c\fR .SH EXAMPLES .TP Run some shell commands in legacy mode: .B ttyrec \-e 'for i in a b c; do echo $i; done' outfile.ttyrec .TP Run some shell commands in normal mode: .B ttyrec \-f /tmp/normal.ttyrec \-\- sh \-c 'for i in a b c; do echo $i; done' .TP Connect to a remote machine interactively: .B ttyrec \-t 60 \-k 300 \-\- ssh remoteserver .TP Execute a local script remotely with the default remote shell: .B ttyrec \-\- ssh remoteserver < script.sh .TP Record a screen session: .B ttyrec screen .SH FOOTNOTES .SS "Handled signals:" .TP SIGUSR1 close current ttyrec file and reopen a new one (log rotation) .TP SIGURG lock session .TP SIGUSR2 unlock session .SS "Cheat-codes (magic keystrokes combinations):" .TP ^L^L^L^L^L^L^L^L lock your session (that's 8 CTRL+L's) .TP ^K^I^L^L^K^I^L^L kill your session .SS "Remark about session lock and session kill:" .IP If we don't have a tty, we can't lock, so \-t will be ignored, whereas \-k will be applied without warning, as there's no tty to output a warning to. ovh-ttyrec-1.1.7.1/docs/ttytime.1000066400000000000000000000006601450203670500165000ustar00rootroot00000000000000.\" .\" This manual page is written by NAKANO Takeo .\" .TH TTYTIME 1 .SH NAME ttytime \- print the time of the recorded session data by ttyrec(1) .SH SYNOPSIS .br .B ttytime .I file... .SH DESCRIPTION .B Ttytime tells you the time of recorded data in seconds. For example: .sp .RS .nf % ttytime *.tty 173 foo.tty 1832 bar.tty .fi .RE .SH "SEE ALSO" .BR script (1), .BR ttyrec (1), .BR ttyplay (1) ovh-ttyrec-1.1.7.1/io.c000066400000000000000000000114101450203670500145350ustar00rootroot00000000000000/* Use of this source code is governed by a BSD-style * license that can be found in the LICENSE file. * Copyright 2001-2019 The ovh-ttyrec Authors. All rights reserved. * * This work is based on the original ttyrec, whose license text * can be found below unmodified. * * Copyright (c) 2000 Satoru Takabayashi * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include "io.h" #include "ttyrec.h" #include "compress.h" #define SWAP_ENDIAN(val) \ ((unsigned int)( \ (((unsigned int)(val) & (unsigned int)0x000000ffU) << 24) | \ (((unsigned int)(val) & (unsigned int)0x0000ff00U) << 8) | \ (((unsigned int)(val) & (unsigned int)0x00ff0000U) >> 8) | \ (((unsigned int)(val) & (unsigned int)0xff000000U) >> 24))) static int is_little_endian(void) { static int retval = -1; if (retval == -1) { int n = 1; char *p = (char *)&n; char x[] = { 1, 0, 0, 0 }; assert(sizeof(int) == 4); if (memcmp(p, x, 4) == 0) { retval = 1; } else { retval = 0; } } return retval; } static int convert_to_little_endian(int x) { if (is_little_endian()) { return x; } else { return SWAP_ENDIAN(x); } } int read_header(FILE *fp, Header *h) { int buf[3]; if (fread_wrapper(buf, sizeof(int), 3, fp) == 0) { return 0; } h->tv.tv_sec = convert_to_little_endian(buf[0]); h->tv.tv_usec = convert_to_little_endian(buf[1]); h->len = convert_to_little_endian(buf[2]); return 1; } int write_header(FILE *fp, Header *h) { int buf[3]; buf[0] = convert_to_little_endian(h->tv.tv_sec); buf[1] = convert_to_little_endian(h->tv.tv_usec); buf[2] = convert_to_little_endian(h->len); if (fwrite_wrapper(buf, sizeof(int), 3, fp) == 0) { return 0; } return 1; } static char *progname = ""; void set_progname(const char *name) { progname = strdup(name); } FILE *efopen(const char *path, const char *mode) { FILE *fp = fopen(path, mode); if (fp == NULL) { fprintf(stderr, "%s: %s: %s\n", progname, path, strerror(errno)); exit(EXIT_FAILURE); } return fp; } int edup(int oldfd) { int fd = dup(oldfd); if (fd == -1) { fprintf(stderr, "%s: dup failed: %s\n", progname, strerror(errno)); exit(EXIT_FAILURE); } return fd; } int edup2(int oldfd, int newfd) { int fd = dup2(oldfd, newfd); if (fd == -1) { fprintf(stderr, "%s: dup2 failed: %s\n", progname, strerror(errno)); exit(EXIT_FAILURE); } return fd; } FILE *efdopen(int fd, const char *mode) { FILE *fp = fdopen(fd, mode); if (fp == NULL) { fprintf(stderr, "%s: fdopen failed: %s\n", progname, strerror(errno)); exit(EXIT_FAILURE); } return fp; } ovh-ttyrec-1.1.7.1/io.h000066400000000000000000000005211450203670500145430ustar00rootroot00000000000000#ifndef __TTYREC_IO_H__ #define __TTYREC_IO_H__ #include "ttyrec.h" int read_header(FILE *fp, Header *h); int write_header(FILE *fp, Header *h); 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); void set_progname(const char *name); #endif ovh-ttyrec-1.1.7.1/ovh-ttyrec.spec000066400000000000000000000111071450203670500167450ustar00rootroot00000000000000Summary: Extended (but compatible) fork of ttyrec Name: ovh-ttyrec Version: 1.1.7.1 Release: 1 License: BSD Group: Applications/System Source: https://github.com/ovh/ovh-ttyrec/archive/master.zip %description Extended (but compatible) fork of ttyrec. ttyrec is a terminal (tty) recorder, it comes with ttyplay, which is a tty player. Some features of ovh-ttyrec follow: - 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 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 - Supports manually locking or terminating the session via "cheatcodes" (specific keystrokes) - Supports a no-tty mode, relying on pipes instead of pseudottys, while still recording stdout/stderr - Automatically detects whether to use pseudottys or pipes, also overridable from command-line - Supports reporting the number of bytes that were output to the terminal on session exit %prep %setup -q -n ovh-ttyrec %build %configure %make_build %install %make_install find "$RPM_BUILD_ROOT"/usr/bin -type f -exec strip '{}' \; find "$RPM_BUILD_ROOT" %clean rm -rf -- "$RPM_BUILD_ROOT" %files %{_mandir}/man1/ttyplay.* %{_mandir}/man1/ttytime.* %{_mandir}/man1/ttyrec.* %{_bindir}/ttyplay %{_bindir}/ttytime %{_bindir}/ttyrec %changelog * Mon Sep 18 2023 Stéphane Lesimple (deb packages) 1.1.7.1 - fix: ttyplay: playing zstd-compressed files created in append mode halted after first stream * Fri Sep 15 2023 Stéphane Lesimple (deb packages) 1.1.7.0 - feat: add --stealth-stdout and --stealth-stderr * Mon Mar 29 2021 Stéphane Lesimple (deb packages) 1.1.6.7 - fix: rare interlocking on exit - enh: default install prefix is now /usr/local - fix: FreeBSD compilation - chore: autobuild for FreeBSD * Mon Nov 09 2020 Stéphane Lesimple (deb packages) 1.1.6.6 - chore: display machine triplet in -V * Tue Sep 15 2020 Stéphane Lesimple (deb packages) 1.1.6.5 - fix: race condition when running w/o pty * Thu Mar 05 2020 Stéphane Lesimple (deb packages) 1.1.6.4 - fix: -k was not working correctly when used without -t * Thu Oct 10 2019 Stéphane Lesimple (deb packages) 1.1.6.3 - fix: race condition on exit when a sighandler gets called while we're in libc's exit(), fixes #7 * Fri Aug 30 2019 Stéphane Lesimple (deb packages) 1.1.6.2 - fix: race condition on exit where ttyrec could get stuck * Fri Jun 14 2019 Stéphane Lesimple (deb packages) 1.1.6.1 - enh: with -f, auto-append .zst if -Z or --zstd was specified - fix: allow usage of -f even if -F if specified * Tue Jun 04 2019 Stéphane Lesimple (deb packages) 1.1.6.0 - feat: added generic fread/fwrite/fclose wrappers as a framework to support several (de)compression algorithms - feat: add zstd support to ttyrec and ttyplay ttyrec: add -Z option to enable on-the-fly compression if available ttyrec: add --zstd to force on-the-fly zstd compression 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) - feat: implement long-options parsing for ttyrec - feat: add --name-format (-F) to specify a custom file name compatible with strftime() - feat: add --warn-before-lock and --warn-before-kill options - fix: abort if doinput() can't write to master - chore: nicify termios debug output - chore: get rid of help2man requirement - chore: portability fixes, tested under Linux, FreeBSD, NetBSD, OpenBSD, DragonFlyBSD, Darwin, Haiku, OmniOS * Thu May 09 2019 Stéphane Lesimple (deb packages) 1.1.5.0 - First public release - Add -c option to enable cheatcodes, as they're now disabled by default ovh-ttyrec-1.1.7.1/ttyplay.c000066400000000000000000000240731450203670500156450ustar00rootroot00000000000000// 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 2001-2019 The ovh-ttyrec Authors. All rights reserved. * * This work is based on the original ttyrec, whose license text * can be found below unmodified. * * Copyright (c) 2000 Satoru Takabayashi * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include "ttyrec.h" #include "io.h" #include "compress.h" #include "configure.h" typedef double (*WaitFunc) (struct timeval prev, struct timeval cur, double speed); typedef int (*ReadFunc) (FILE *fp, Header *h, char **buf); typedef void (*WriteFunc) (char *buf, int len); typedef void (*ProcessFunc) (FILE *fp, double speed, ReadFunc read_func, WaitFunc wait_func); struct timeval timeval_diff(struct timeval tv1, struct timeval tv2); struct timeval timeval_div(struct timeval tv1, double n); double ttywait(struct timeval prev, struct timeval cur, double speed); double ttynowait(struct timeval prev, struct timeval cur, double speed); int ttyread(FILE *fp, Header *h, char **buf); int ttypread(FILE *fp, Header *h, char **buf); void ttywrite(char *buf, int len); void ttynowrite(char *buf, int len); void ttyplay(FILE *fp, double speed, ReadFunc read_func, WriteFunc write_func, WaitFunc wait_func); void ttyskipall(FILE *fp); void ttyplayback(FILE *fp, double speed, ReadFunc read_func, WaitFunc wait_func); void ttypeek(FILE *fp, double speed, ReadFunc read_func, WaitFunc wait_func); void usage(void); FILE *input_from_stdin(void); struct timeval timeval_diff(struct timeval tv1, struct timeval tv2) { struct timeval diff; diff.tv_sec = tv2.tv_sec - tv1.tv_sec; diff.tv_usec = tv2.tv_usec - tv1.tv_usec; if (diff.tv_usec < 0) { diff.tv_sec--; diff.tv_usec += 1000000; } return diff; } struct timeval timeval_div(struct timeval tv1, double n) { double x = ((double)tv1.tv_sec + (double)tv1.tv_usec / 1000000.0) / n; struct timeval div; div.tv_sec = (int)x; div.tv_usec = (x - (int)x) * 1000000; return div; } double ttywait(struct timeval prev, struct timeval cur, double speed) { static struct timeval drift = { 0, 0 }; struct timeval start; struct timeval diff = timeval_diff(prev, cur); fd_set readfs; gettimeofday(&start, NULL); assert(speed != 0); diff = timeval_diff(drift, timeval_div(diff, speed)); if (diff.tv_sec < 0) { diff.tv_sec = diff.tv_usec = 0; } FD_SET(STDIN_FILENO, &readfs); /* * We use select() for sleeping with subsecond precision. * select() is also used to wait user's input from a keyboard. * * Save "diff" since select(2) may overwrite it to {0, 0}. */ struct timeval orig_diff = diff; select(1, &readfs, NULL, NULL, &diff); diff = orig_diff; /* Restore the original diff value. */ if (FD_ISSET(0, &readfs)) /* a user hits a character? */ { char c; if (read(STDIN_FILENO, &c, 1) == 1) { /* drain the character */ switch (c) { case '+': case 'f': speed *= 2; break; case '-': case 's': speed /= 2; break; case '1': speed = 1.0; break; } } drift.tv_sec = drift.tv_usec = 0; } else { struct timeval stop; gettimeofday(&stop, NULL); /* Hack to accumulate the drift */ if ((diff.tv_sec == 0) && (diff.tv_usec == 0)) { diff = timeval_diff(drift, diff); // diff = 0 - drift. } drift = timeval_diff(diff, timeval_diff(start, stop)); } return speed; } double ttynowait(struct timeval prev, struct timeval cur, double speed) { /* do nothing */ (void)prev; (void)cur; (void)speed; return 0; /* Speed isn't important. */ } int ttyread(FILE *fp, Header *h, char **buf) { if (read_header(fp, h) == 0) { return 0; } *buf = malloc(h->len); if (*buf == NULL) { perror("malloc"); return 1; } if (fread_wrapper(*buf, 1, h->len, fp) == 0) { perror("fread"); } return 1; } int ttypread(FILE *fp, Header *h, char **buf) { /* * Read persistently just like tail -f. */ while (ttyread(fp, h, buf) == 0) { struct timeval w = { 0, 250000 }; select(0, NULL, NULL, NULL, &w); clearerr(fp); } return 1; } void ttywrite(char *buf, int len) { fwrite(buf, 1, len, stdout); } void ttynowrite(char *buf, int len) { /* do nothing */ (void)buf; (void)len; } void ttyplay(FILE *fp, double speed, ReadFunc read_func, WriteFunc write_func, WaitFunc wait_func) { int first_time = 1; struct timeval prev; setbuf(stdout, NULL); setbuf(fp, NULL); while (1) { char *buf; Header h; if (read_func(fp, &h, &buf) == 0) { break; } if (!first_time) { speed = wait_func(prev, h.tv, speed); } first_time = 0; write_func(buf, h.len); prev = h.tv; free(buf); } } void ttyskipall(FILE *fp) { /* * Skip all records. */ ttyplay(fp, 0, ttyread, ttynowrite, ttynowait); } void ttyplayback(FILE *fp, double speed, ReadFunc read_func, WaitFunc wait_func) { (void)read_func; ttyplay(fp, speed, ttyread, ttywrite, wait_func); } void ttypeek(FILE *fp, double speed, ReadFunc read_func, WaitFunc wait_func) { (void)read_func; (void)wait_func; ttyskipall(fp); ttyplay(fp, speed, ttypread, ttywrite, ttynowait); } void usage(void) { printf("Usage: ttyplay [OPTION] [FILE]\n"); 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); } /* * We do some tricks so that select(2) properly works on * STDIN_FILENO in ttywait(). */ FILE *input_from_stdin(void) { int fd = edup(STDIN_FILENO); edup2(STDOUT_FILENO, STDIN_FILENO); return efdopen(fd, "r"); } int main(int argc, char **argv) { double speed = 1.0; ReadFunc read_func = ttyread; WaitFunc wait_func = ttywait; ProcessFunc process = ttyplayback; FILE *input = NULL; struct termios old, new; set_progname(argv[0]); while (1) { #ifdef HAVE_zstd int ch = getopt(argc, argv, "hs:npZ"); #else int ch = getopt(argc, argv, "hs:np"); #endif if (ch == EOF) { break; } switch (ch) { case 's': if (optarg == NULL) { perror("-s option requires an argument"); exit(EXIT_FAILURE); } sscanf(optarg, "%lf", &speed); break; case 'n': wait_func = ttynowait; break; case 'p': process = ttypeek; break; #ifdef HAVE_zstd case 'Z': set_compress_mode(COMPRESS_ZSTD); break; #endif case 'h': default: usage(); } } 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 { input = input_from_stdin(); } assert(input != NULL); tcgetattr(0, &old); /* Get current terminal state */ new = old; /* Make a copy */ new.c_lflag &= ~(ICANON | ECHO | ECHONL); /* unbuffered, no echo */ tcsetattr(0, TCSANOW, &new); /* Make it current */ process(input, speed, read_func, wait_func); tcsetattr(0, TCSANOW, &old); /* Return terminal state */ return 0; } ovh-ttyrec-1.1.7.1/ttyrec.c000066400000000000000000002120551450203670500154500ustar00rootroot00000000000000// vim: noai:sts=4:ts=4:sw=4:et /* Use of this source code is governed by a BSD-style * license that can be found in the LICENSE file. * Copyright 2001-2019 The ovh-ttyrec Authors. All rights reserved. * * This work is based on the original ttyrec, whose license text * can be found below unmodified. * * Copyright (c) 1980 Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* 1999-02-22 Arkadiusz Miśkiewicz * - added Native Language Support */ /* 2000-12-27 Satoru Takabayashi * - modify `script' to create `ttyrec'. */ /* 2001-2010 Several OVH contributors who will recognize themselves * - lots of forgotten things */ /* 2010-2019 Stéphane Lesimple * - bugfixes (SIGWINCH handling and others) * - BSD/MacOS compatibility * - SIGUSR1 handling for ttyrec log rotation * - input timeout locking mechanism * - more things (see features in the README.md file) */ /* * script */ #include // open, waitpid #include // open #include // open, access #include // tcsetattr #include // ioctl #include // gettimeofday #ifdef __HAIKU__ # include // waitpid #else # include // waitpid #endif #include // dirname #include // printf, ... #include // read, write, usleep, ... #include // strlen, memset, ... #include // exit, free, getpt, ... #include // errno #include // pthread_create #include // sigaction #include // uname #include // localtime #include // getopt_long #include "configure.h" #include "ttyrec.h" #include "io.h" #include "compress.h" #ifdef HAVE_openpty # if defined(HAVE_openpty_pty_h) # include # elif defined(HAVE_openpty_util_h) # include # elif defined(HAVE_openpty_libutil_h) # include # endif #endif // for ZSTD_versionNumber() // and zstd_set_max_flush() #ifdef HAVE_zstd # include # include "compress_zstd.h" #endif #if defined(__linux__) # define OS_STR "Linux" #elif defined(__FreeBSD__) # define OS_STR "FreeBSD" #elif defined(__NetBSD__) # define OS_STR "NetBSD" #elif defined(__OpenBSD__) # define OS_STR "OpenBSD" #elif defined(__DragonFly__) # define OS_STR "DragonFlyBSD" #elif defined(__bsdi__) # define OS_STR "BSD" #elif defined(__SVR4) || defined(__svr4__) || defined(sun) || defined(__sun) # define SUN_OS # define OS_STR "SUN" #elif defined(macintosh) || defined(Macintosh) || (defined(__APPLE__) && defined(__MACH__)) # define OS_STR "Darwin" #elif defined(__HAIKU__) # define OS_STR "Haiku" #else # define OS_STR "UnknownOS" #endif #ifdef HAVE_isastream # include #endif #ifdef SUN_OS # include #endif #ifdef SUN_OS # define PID_T_FORMAT "%ld" #else # define PID_T_FORMAT "%d" #endif #define HAVE_inet_aton #define HAVE_scsi_h #define HAVE_kd_h #if !defined(CDEL) # if defined(_POSIX_VDISABLE) # define CDEL _POSIX_VDISABLE # elif defined(CDISABLE) # define CDEL CDISABLE # else /* not _POSIX_VISIBLE && not CDISABLE */ # define CDEL 255 # endif /* not _POSIX_VISIBLE && not CDISABLE */ #endif /* !CDEL */ #define printdbg(...) if (opt_debug > 0) { fprintf(stderr, __VA_ARGS__); } #define printdbg2(...) if (opt_debug > 1) { fprintf(stderr, __VA_ARGS__); } // functions used in the main() before the forks void fixtty(void); void help(void); void set_ttyrec_file_name(char **nameptr); void getmaster(void); // functions used by the parent void doinput(void); void sigwinch_handler_parent(int signal); void *timeout_watcher(void *arg); void do_lock(void); void handle_cheatcodes(char c); // functions used by the child void dooutput(void); void sigwinch_handler_child(int signal); // functions used by the subchild void doshell(const char *, char **); void getslave(void); // sighandlers (parent and child) void swing_output_file(int signal); void unlock_session(int signal); void lock_session(int signal); void finish(int signal); void sigterm_handler(int signal); void sighup_handler(int signal); // other functions used by parent and child void done(int status); void fail(void); void print_termios_info(int fd, const char *prefix); // ansi control codes static const char *ansi_clear = "\033[2J"; static const char *ansi_home = "\033[H"; static const char *ansi_hidecursor = "\033[?25l"; static const char *ansi_showcursor = "\033[?25h"; static const char *ansi_save = "\033[?47h"; static const char *ansi_restore = "\033[?47l"; static const char *ansi_savecursor = "\0337"; static const char *ansi_restorecursor = "\0338"; static time_t last_activity = 0; static time_t locked_since = 0; static int lock_warned = 0; static int kill_warned = 0; static const char version[] = "1.1.7.1"; static FILE *fscript; static int child; static int subchild; static char *me = NULL; #ifdef HAVE_openpty static int openpty_used = 0; static int openpty_disable = 0; #endif // below: only used in notty mode int stdout_pipe[2]; // subchild will write to it, child will read from it int stderr_pipe[2]; // subchild will write to it, child will read from it // below: only used in tty mode static int master; static int slave; static char *fname = NULL; static char *dname = NULL; static char *uuid = NULL; static char *namefmt = NULL; static long timeout_lock = 0; static long timeout_kill = 0; static long warn_before_lock_seconds = 0; static long warn_before_kill_seconds = 0; static struct termios parent_stdin_termios; static struct winsize parent_stdin_winsize; static int parent_stdin_isatty = 0; #if !defined(HAVE_openpty) 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; static int opt_count_bytes = 0; static int opt_cheatcodes = 0; static int opt_stealth_stdout = 0; static int opt_stealth_stderr = 0; static char *opt_custom_message = NULL; static int use_tty = 1; // no=0, yes=1 static int can_exit = 0; static int childexit = 254; int main(int argc, char **argv) { char *command = NULL; char **params = NULL; char *shell = NULL; int legacy = 0; int ch; shell = getenv("SHELL"); if (shell == NULL) { shell = "/bin/sh"; } while (1) { static struct option long_options[] = { /* * "long-opt-name", a, b, c * a: 1 if requires an arg, 0 otherwise * b: always 0 * c: an optional char for the corresponding short-option, 0 otherwise */ { "zstd", 0, 0, 0 }, { "level", 1, 0, 'l' }, { "verbose", 0, 0, 'v' }, { "append", 0, 0, 'a' }, { "cheatcodes", 0, 0, 'c' }, { "no-cheatcodes", 0, 0, 'C' }, { "shell-cmd", 1, 0, 'e' }, { "dir", 1, 0, 'd' }, { "output", 1, 0, 'f' }, { "uuid", 1, 0, 'z' }, { "no-openpty", 0, 0, 'p' }, { "lock-timeout", 1, 0, 'l' }, { "kill-timeout", 1, 0, 'k' }, { "msg", 1, 0, 's' }, { "count-bytes", 0, 0, 'n' }, { "term", 1, 0, 'T' }, { "stealth-stdout", 0, 0, 0 }, { "stealth-stderr", 0, 0, 0 }, { "version", 0, 0, 'V' }, { "help", 0, 0, 'h' }, { "max-flush-time", 1, 0, 0 }, { "name-format", 1, 0, 'F' }, { "warn-before-lock", 1, 0, 0 }, { "warn-before-kill", 1, 0, 0 }, { "help", 0, 0, 'h' }, { "usage", 0, 0, 'h' }, { 0, 0, 0, 0 } }; int option_index = 0; ch = getopt_long(argc, argv, "ZcCupVhvanf:z:d:t:T:k:s:e:l:F:", long_options, &option_index); if (ch == -1) { break; } switch ((char)ch) { // long option without short-option counterpart case 0: if (strcmp(long_options[option_index].name, "zstd") == 0) { if (set_compress_mode(COMPRESS_ZSTD) != 0) { fprintf(stderr, "zstd support has not been enabled at compile time.\r\n"); fail(); } opt_zstd++; } 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 if (strcmp(long_options[option_index].name, "warn-before-lock") == 0) { errno = 0; warn_before_lock_seconds = strtol(optarg, NULL, 10); if ((errno != 0) || (warn_before_lock_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); } } else if (strcmp(long_options[option_index].name, "warn-before-kill") == 0) { errno = 0; warn_before_kill_seconds = strtol(optarg, NULL, 10); if ((errno != 0) || (warn_before_kill_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); } } else if (strcmp(long_options[option_index].name, "stealth-stdout") == 0) { opt_stealth_stdout = 1; } else if (strcmp(long_options[option_index].name, "stealth-stderr") == 0) { opt_stealth_stderr = 1; } else { fprintf(stderr, "Unknown long option %s\r\n", long_options[option_index].name); fail(); } break; // on-the-fly zstd compression case 'Z': if (set_compress_mode(COMPRESS_ZSTD) == 0) { opt_zstd++; } 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++; break; // open ttyrec file in append mode instead of write mode case 'a': opt_append++; break; // inhibit cheatcodes (force lock, force kill) case 'C': opt_cheatcodes = 0; break; // enable cheatcodes (force lock, force kill) case 'c': opt_cheatcodes = 1; break; // ignored (for compatibility with ttyrec classic) case 'u': break; // ttyrec classic way of specifying the command to launch, it uses sh -c case 'e': if (legacy == 1) { help(); fprintf(stderr, "Option -e specified more than once.\r\n"); exit(EXIT_FAILURE); } legacy = 1; params = malloc(sizeof(char *) * 4); command = shell; params[0] = strrchr(shell, '/') + 1; params[1] = "-c"; params[2] = strdup(optarg); params[3] = NULL; break; // directory to write ttyrec files to (autogenerated) case 'd': dname = strdup(optarg); break; // fullpath of ttyrec file to write to (optional, autogenerated if missing) case 'f': fname = strdup(optarg); break; // uuid, will appear in my ttyrec output file names, to keep track even after rotation (if omitted, will default to my pid) case 'z': uuid = strdup(optarg); break; // custom format case 'F': namefmt = strdup(optarg); break; // openpty_disable, don't prefer openpty() on systems that support it case 'p': #ifdef HAVE_openpty openpty_disable++; #else fprintf(stderr, "Ignored option 'p': openpty() not supported on this system.\r\n"); #endif break; // timeout before lock (t) or kill (k) case 't': case 'k': errno = 0; long timeout = strtol(optarg, NULL, 10); if ((errno != 0) || (timeout <= 0)) { help(); fprintf(stderr, "Invalid value passed to -%c (%s), expected a strictly positive integer\r\n", (char)ch, optarg); exit(EXIT_FAILURE); } printdbg("timeout %c=%ld\r\n", ch, timeout_lock); if ((char)ch == 't') { timeout_lock = timeout; } else if ((char)ch == 'k') { timeout_kill = timeout; } break; case 's': opt_custom_message = strdup(optarg); break; // if specified, will count number of bytes out and print it on termination (experimental) case 'n': opt_count_bytes++; break; case 'T': if (strncmp(optarg, "never", strlen("never")) == 0) { opt_want_tty = 0; } else if (strncmp(optarg, "auto", strlen("auto")) == 0) { opt_want_tty = 1; } else if (strncmp(optarg, "always", strlen("always")) == 0) { opt_want_tty = 2; } else { help(); fprintf(stderr, "Invalid value passed to -T (%s), expected either 'never', 'auto' or 'always'\r\n", optarg); exit(EXIT_FAILURE); } break; // version case 'V': printf("ttyrec v%s (%s)\n", version, MACHINE_STR); #ifdef DEFINES_STR printf("%s (%s)\n", DEFINES_STR, OS_STR); #endif #ifdef __VERSION__ printf("compiler version %s (%s)\n", __VERSION__, COMPILER_NAME); #endif #ifdef HAVE_zstd printf("libzstd version %u (%d.%d.%d)\n", ZSTD_versionNumber(), ZSTD_VERSION_MAJOR, ZSTD_VERSION_MINOR, ZSTD_VERSION_RELEASE); #endif exit(0); // 'h', and any other unknown option case 'h': default: help(); exit(EXIT_FAILURE); } } argc -= optind; argv += optind; printdbg("remaining non-parsed options argc=%d\r\n", argc); for (int i = 0; i < argc; i++) { printdbg("option %d: <%s>\r\n", i, argv[i]); } if ((namefmt != NULL) && ((dname != NULL) || (uuid != NULL))) { fprintf(stderr, "Option -F (--name-format) can't be used with -d (--dir) or -z (--uuid)\n"); fail(); } if (uuid == NULL) { uuid = malloc(sizeof(char) * BUFSIZ); snprintf(uuid, BUFSIZ, PID_T_FORMAT, getpid()); } if ((timeout_lock > 0) && (timeout_kill > 0) && (timeout_kill < timeout_lock)) { help(); fprintf(stderr, "specified timeout_lock (%ld) is higher than timeout_kill (%ld), this doesn't make sense\r\n", timeout_lock, timeout_kill); exit(EXIT_FAILURE); } if ((warn_before_lock_seconds > 0) && (timeout_lock == 0)) { help(); fprintf(stderr, "You specified --warn-before-lock without enabling --timeout-lock, this doesn't make sense\r\n"); exit(EXIT_FAILURE); } if (warn_before_lock_seconds > timeout_lock) { help(); fprintf(stderr, "The specified value for --warn-before-lock is higher than --timeout-lock, this doesn't make sense\r\n"); exit(EXIT_FAILURE); } if ((warn_before_kill_seconds > 0) && (timeout_kill == 0)) { help(); fprintf(stderr, "You specified --warn-before-kill without enabling --timeout-kill, this doesn't make sense\r\n"); exit(EXIT_FAILURE); } if (warn_before_kill_seconds > timeout_kill) { help(); fprintf(stderr, "The specified value for --warn-before-kill is higher than --timeout-kill, this doesn't make sense\r\n"); exit(EXIT_FAILURE); } if (legacy) { // strdup: make it free()able fname = (argv[0] == NULL ? strdup("ttyrecord") : strdup(argv[0])); } else { if (argv[0] == NULL) { command = shell; params = malloc(sizeof(char *) * 3); params[0] = strrchr(shell, '/') + 1; params[1] = "-i"; params[2] = NULL; } else { command = argv[0]; params = argv; } } printdbg("will execvp %s with the following params:\r\n", command); if (params == NULL) { printdbg("(none)\r\n"); } else { for (int index = 0; params[index] != NULL; index++) { printdbg("- '%s'\r\n", params[index]); } } // if neither dname nor fname are given, set dname to current dir if ((dname == NULL) && (fname == NULL)) { dname = strdup("."); } // if no file name given, generate it (dname is used as directory) if (fname == NULL) { set_ttyrec_file_name(&fname); } else { // otherwise, append .zst if applicable if (opt_zstd) { fname = realloc(fname, strlen(fname) + 4 + 1); if (fname == NULL) { perror("realloc"); exit(EXIT_FAILURE); } strcat(fname, ".zst"); } } // if dname == ".", it might be because we've set it if (dname == NULL) { char *tmpfname = strdup(fname); // strdup(dirname) because in done() we free() dname dname = strdup(dirname(tmpfname)); free(tmpfname); } if (dname == NULL) { fprintf(stderr, "failed to find a proper dname from fname=<%s>\r\n", fname); exit(EXIT_FAILURE); } printdbg("will use %s as dname\r\n", dname); if ((fscript = fopen(fname, opt_append ? "a" : "w")) == NULL) { perror(fname); exit(EXIT_FAILURE); } free(fname); setbuf(fscript, NULL); { struct sigaction act; memset(&act, '\0', sizeof(act)); act.sa_handler = &finish; act.sa_flags = SA_NOCLDSTOP | SA_RESTART; if (sigaction(SIGCHLD, &act, NULL)) { perror("sigaction"); exit(EXIT_FAILURE); } memset(&act, '\0', sizeof(act)); act.sa_handler = &swing_output_file; act.sa_flags = SA_RESTART; if (sigaction(SIGUSR1, &act, NULL)) { perror("sigaction"); exit(EXIT_FAILURE); } memset(&act, '\0', sizeof(act)); act.sa_handler = &unlock_session; act.sa_flags = SA_RESTART; if (sigaction(SIGUSR2, &act, NULL)) { perror("sigaction"); exit(EXIT_FAILURE); } memset(&act, '\0', sizeof(act)); act.sa_handler = &lock_session; act.sa_flags = SA_RESTART; if (sigaction(SIGURG, &act, NULL)) { perror("sigaction"); exit(EXIT_FAILURE); } memset(&act, '\0', sizeof(act)); act.sa_handler = &sigterm_handler; act.sa_flags = SA_RESTART; if (sigaction(SIGTERM, &act, NULL)) { perror("sigaction"); exit(EXIT_FAILURE); } // we can get SIGHUP if our tty is closed, fclose() properly in that case memset(&act, '\0', sizeof(act)); act.sa_handler = &sighup_handler; act.sa_flags = SA_RESTART; if (sigaction(SIGHUP, &act, NULL)) { perror("sigaction"); exit(EXIT_FAILURE); } } parent_stdin_isatty = isatty(0); switch (opt_want_tty) { case 2: use_tty = 1; break; case 0: use_tty = 0; break; default: use_tty = parent_stdin_isatty; } printdbg("parent: isatty(stdin) == %d, opt_want_tty == %d, use_tty == %d\r\n", parent_stdin_isatty, opt_want_tty, use_tty); if (use_tty) { getmaster(); print_termios_info(master, "parent master"); print_termios_info(0, "parent stdin b4 fixtty"); if (parent_stdin_isatty) { fixtty(); } print_termios_info(0, "parent stdin after fixtty"); } else { // pipe[0] is read, pipe[1] is write if (pipe(stdout_pipe)) { perror("pipe"); exit(EXIT_FAILURE); } if (pipe(stderr_pipe)) { perror("pipe"); exit(EXIT_FAILURE); } } child = fork(); if (child < 0) { perror("fork"); exit(EXIT_FAILURE); } else if (child == 0) { // we are the child printdbg("child pid is %ld\r\n", (long int)getpid()); print_termios_info(0, "child stdin"); subchild = child = fork(); if (child < 0) { perror("fork"); fail(); } else if (child) { // we are still the child (parent of subchild) me = "child"; dooutput(); } else { // we are the subchild printdbg("subchild pid is %ld\r\n", (long int)getpid()); print_termios_info(0, "subchild stdin"); me = "subchild"; doshell(command, params); } } else { // we are the parent me = "parent"; printdbg("parent pid is %ld\r\n", (long int)getpid()); sigwinch_handler_parent(SIGWINCH); if (timeout_lock || timeout_kill) { pthread_t watcher_thread; if (pthread_create(&watcher_thread, NULL, timeout_watcher, NULL) == -1) { perror("pthread"); fail(); } } doinput(); } return 0; } void handle_cheatcodes(char c) { static int lockseq = 0; static int killseq = 0; if (opt_cheatcodes != 1) { return; } // LOCK if (c == '\x0c') // ^L { if (++lockseq >= 8) { lockseq = 0; do_lock(); } } else { lockseq = 0; } // KILL if (((c == '\x0b') && ((killseq == 0) || (killseq == 4))) || // ^K ((c == '\x09') && ((killseq == 1) || (killseq == 5))) || // ^I ((c == '\x0c') && ((killseq == 2) || (killseq == 3) || (killseq == 6) || (killseq == 7)))) // ^L { killseq++; } else { killseq = 0; } if (killseq >= 8) { kill(child, SIGTERM); } } // called by parent void doinput(void) { int cc; char ibuf[BUFSIZ]; (void)fclose_wrapper(fscript); #ifdef HAVE_openpty if (openpty_used) { // openpty opens the master and the slave in a single call to getmaster(), // but in that case we don't want the slave (we won't call getslave()) (void)close(slave); } #endif struct sigaction act; memset(&act, '\0', sizeof(act)); act.sa_handler = &sigwinch_handler_parent; act.sa_flags = SA_RESTART; if (sigaction(SIGWINCH, &act, NULL)) { perror("sigaction"); fail(); } last_activity = time(NULL); lock_warned = 0; kill_warned = 0; if (use_tty) { print_termios_info(master, "parent master in doinput"); #ifdef __HAIKU__ // under Haiku, if we use BUFSIZ as read size, it reads 4 bytes per 4 bytes // instead of returning read data as soon as possible const size_t readsz = 1; #else const size_t readsz = BUFSIZ; #endif while ((cc = read(0, ibuf, readsz)) > 0) { printdbg2("[in:%d]", cc); if (!locked_since) { if (write(master, ibuf, cc) == -1) { perror("write[parent-master]"); fail(); } last_activity = time(NULL); lock_warned = 0; kill_warned = 0; if (cc == 1) { handle_cheatcodes(ibuf[0]); } } } if (opt_debug && (cc == -1)) { perror("read"); } } else { // we won't use a pseudotty, just pipes, but as we're the parent so we don't need those // also our STDIN is passed thru the subchild, so we don't need to handle it ourselves, we'll just wait for our child exit close(0); close(stdout_pipe[0]); close(stdout_pipe[1]); close(stderr_pipe[0]); close(stderr_pipe[1]); } printdbg("%s("PID_T_FORMAT "): end doinput, waiting for child\r\n", me, getpid()); waitpid(-1, &childexit, 0); printdbg("%s("PID_T_FORMAT "): end doinput, child exited with status=%d, exiting too\r\n", me, getpid(), childexit); done(childexit); } // handler of SIGCHLD void finish(int signal) { int waitedpid; int die = 0; (void)signal; printdbg("%s("PID_T_FORMAT "): got SIGCHLD, calling waitpid\r\n", me, getpid()); while ((waitedpid = waitpid(-1, &childexit, WNOHANG)) > 0) { if (waitedpid == subchild) { printdbg("%s("PID_T_FORMAT "): subchild exited with %d, setting can_exit to 1\r\n", me, getpid(), childexit); can_exit = 1; } else if (waitedpid == child) { printdbg("%s("PID_T_FORMAT "): child exited with %d, exiting too\r\n", me, getpid(), childexit); die = 1; } } if (die) { done(childexit); } } void set_ttyrec_file_name(char **nameptr) { struct timeval tv; struct tm *t = NULL; if (gettimeofday(&tv, NULL)) { perror("gettimeofday()"); fail(); } t = localtime((const time_t *)&tv.tv_sec); if (t == NULL) { perror("localtime()"); fail(); } *nameptr = malloc(sizeof(char) * BUFSIZ); if (*nameptr == NULL) { perror("malloc()"); fail(); } if (namefmt == NULL) { // - 4: length of potential ".zst" we might add below if (snprintf(*nameptr, BUFSIZ - 4, "%s/%04u-%02u-%02u.%02u-%02u-%02u.%06lu.%s.ttyrec", dname, t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, (long unsigned int)tv.tv_usec, uuid) == -1) { perror("snprintf()"); free(*nameptr); fail(); } } else { // - 4: length of potential ".zst" we might add below if (strftime(*nameptr, BUFSIZ - 4, namefmt, t) == 0) { perror("strftime()"); free(*nameptr); fail(); } (*nameptr)[BUFSIZ - 5] = '\0'; char usec[7]; if (snprintf(usec, 7, "%06lu", (unsigned long)tv.tv_usec) < 0) { perror("snprintf()"); free(*nameptr); fail(); } char *ptr = strstr(*nameptr, "#usec#"); while (ptr != NULL) { memcpy(ptr, usec, 6); ptr = strstr(ptr + 6, "#usec#"); } } if (opt_zstd) { // we can strcat safely because we used BUFSIZ - 4 above strcat(*nameptr, ".zst"); } } void swing_output_file(int signal) { char *newname = NULL; (void)signal; if (subchild != 0) { set_ttyrec_file_name(&newname); fclose_wrapper(fscript); if ((fscript = fopen(newname, "w")) == NULL) { perror("fopen()"); free(newname); fail(); } free(newname); setbuf(fscript, NULL); } } // SIGUSR2 void unlock_session(int signal) { last_activity = time(NULL); lock_warned = 0; kill_warned = 0; // to avoid signal storm, abort if not locked if (!locked_since) { return; } printdbg("%s("PID_T_FORMAT "): unlock_session()\r\n", me, getpid()); locked_since = 0; // in case only the parent or the child got the SIG, // ensure the other also gets it kill(subchild > 0 ? getppid() : child, signal); if (subchild == 0) { // if we're the parent, force our children to redraw after unlock usleep(1000 * 300); struct winsize tmpwin; (void)ioctl(master, TIOCGWINSZ, (char *)&tmpwin); int pixels_per_row = tmpwin.ws_ypixel / tmpwin.ws_row; tmpwin.ws_row++; tmpwin.ws_ypixel += pixels_per_row; (void)ioctl(master, TIOCSWINSZ, (char *)&tmpwin); kill(child, SIGWINCH); usleep(1000 * 300); tmpwin.ws_row--; tmpwin.ws_ypixel -= pixels_per_row; (void)ioctl(master, TIOCSWINSZ, (char *)&tmpwin); kill(child, SIGWINCH); } else { // child: restore console, make cursor visible again, restore its position (void)fputs(ansi_restore, stdout); (void)fputs(ansi_restorecursor, stdout); (void)fputs(ansi_showcursor, stdout); } } // SIGURG void lock_session(int signal) { (void)signal; // to avoid signal storm, abort if locked if (locked_since) { return; } printdbg("%s("PID_T_FORMAT "): lock_session()\r\n", me, getpid()); locked_since = time(NULL); // in case only the parent or the child got the SIG, // ensure the other also gets it kill(subchild > 0 ? getppid() : child, signal); // if we're the parent, nothing more to do if (subchild == 0) { return; } const char *lock = "\033[31m" \ "██╗ ██████╗ ██████╗██╗ ██╗███████╗██████╗ \r\n" "██║ ██╔═══██╗██╔════╝██║ ██╔╝██╔════╝██╔══██╗\r\n" "██║ ██║ ██║██║ █████╔╝ █████╗ ██║ ██║\r\n" "██║ ██║ ██║██║ ██╔═██╗ ██╔══╝ ██║ ██║\r\n" "███████╗╚██████╔╝╚██████╗██║ ██╗███████╗██████╔╝\r\n" "╚══════╝ ╚═════╝ ╚═════╝╚═╝ ╚═╝╚══════╝╚═════╝ \033[0m\r\n" \ " .--------. \r\n" \ " / .------. \\ \r\n" \ " / / \\ \\ \033[33mThe current session is \033[31mLOCKED\033[0m\r\n" \ " | | | | \r\n" \ " _| |________| |_ \033[33mSince %s\033[0m\r\n" \ ".' |_| |_| '. \r\n" \ "'._____ ____ _____.' \033[33mDue to input inactivity timeout\033[0m\r\n" \ "| .'____'. | \r\n" \ "'.__.'.' '.'.__.' \r\n" \ "'.__ | | __.' \r\n" \ "| '.'.____.'.' | \033[33m%s,\033[0m\r\n" \ "'.____'.____.'____.' \r\n" \ "'.________________.' \033[33m%s.\033[0m\r\n"; char hostname[BUFSIZ]; struct utsname name; if (uname(&name)) { hostname[0] = '\0'; } else { strncpy(hostname, name.nodename, BUFSIZ); hostname[BUFSIZ - 1] = '\0'; } const char *salute[] = { "Kind regards", "Your dearest friend", "Love", "xoxoxo", "XoXoXo", "Respectfully yours", "Best", "Sincerely yours", "Take care", "Cheers", "With deepest sympathy", }; #define salute_len (sizeof(salute) / sizeof(const char *)) // save cursor pos, save buffer, clear screen, put cursor at home position, hide cursor (void)fputs(ansi_savecursor, stdout); (void)fputs(ansi_save, stdout); (void)fputs(ansi_clear, stdout); (void)fputs(ansi_home, stdout); (void)fputs(ansi_hidecursor, stdout); time_t now = time(NULL); struct tm *tm_now = localtime(&now); char ctime_now[BUFSIZ]; strftime(ctime_now, BUFSIZ, "%A %Y-%m-%d %H:%M:%S", tm_now); srand(now); (void)printf(lock, ctime_now, salute[rand() % salute_len], hostname); if (opt_custom_message != NULL) { (void)fputs("\r\n", stdout); (void)puts(opt_custom_message); } } void do_lock(void) { locked_since = time(NULL); kill(child, SIGURG); } void *timeout_watcher(void *arg) { (void)arg; for ( ; ;) { sleep(1); time_t now = time(NULL); if (use_tty && !locked_since) { // handle warn: if input is idle and we didn't already, warn if ((warn_before_lock_seconds > 0) && (lock_warned == 0) && (now - last_activity + warn_before_lock_seconds > timeout_lock)) { lock_warned = 1; fprintf(stderr, "warning: your session will be locked in %lu seconds if no input activity is detected.", warn_before_lock_seconds); } // handle lock: if input is idle, and warn wasn't enough, lock if ((timeout_lock > 0) && (now - last_activity > timeout_lock)) { printdbg("parent: timeout_watcher: do_lock()\r\n"); do_lock(); } } // handle kill if (timeout_kill > 0) { // if we're locked, check against the locked_since (never happens if !use_tty) if (locked_since) { if ((warn_before_kill_seconds > 0) && (kill_warned == 0) && (now - locked_since > timeout_kill - timeout_lock - warn_before_kill_seconds)) { kill_warned = 1; fprintf(stderr, "warning: your session will be killed in %lu seconds if no input activity is detected.", warn_before_kill_seconds); } else if (now - locked_since > timeout_kill - timeout_lock) { printdbg("parent: timeout_watcher: kill (locked)\r\n"); kill(child, SIGTERM); } } // handle kill cont'd: if we're not locked, check against the last_activity else { if ((warn_before_kill_seconds > 0) && (kill_warned == 0) && (now - last_activity > timeout_kill - warn_before_kill_seconds)) { kill_warned = 1; fprintf(stderr, "warning: your session will be killed in %lu seconds if no input activity is detected.", warn_before_kill_seconds); } else if (now - last_activity > timeout_kill) { printdbg("parent: timeout_watcher: kill (unlocked), now=%d last_activity=%d timeout_kill=%ld\r\n", (int)now, (int)last_activity, timeout_kill); kill(child, SIGTERM); } } } } } // called by child void dooutput(void) { int cc; char obuf[BUFSIZ]; int waitedpid; unsigned long long bytes_out = 0; int stdout_pipe_opened = 1; int stderr_pipe_opened = 1; int target_fd = 1; // stdout by default setbuf(stdout, NULL); (void)close(0); // the subchild will consume it, not us #ifdef HAVE_openpty if (openpty_used) { // openpty opens the master and the slave in a single call to getmaster(), // but in that case we don't want the slave (we won't call getslave()) (void)close(slave); } #endif struct sigaction act; memset(&act, '\0', sizeof(act)); act.sa_handler = &sigwinch_handler_child; act.sa_flags = SA_RESTART; if (sigaction(SIGWINCH, &act, NULL)) { perror("sigaction"); fail(); } if (!use_tty) { close(stdout_pipe[1]); close(stderr_pipe[1]); } else { // ensure that our user's terminal is not in altscreen on launch, // or (sh)he might get surprised when (s)he launches then exits programs such as vim (void)fputs(ansi_restore, stdout); } for ( ; ;) { Header h; int dont_write = 0; // we have a tty if (use_tty) { cc = read(master, obuf, BUFSIZ); if (cc == 0) { printdbg("\r\n%s(" PID_T_FORMAT "): got EOF, there's nothing left to read from\r\n", me, getpid()); break; } if (cc < 0) { printdbg2("[out:%d,%s]", cc, strerror(errno)); if (errno != EINTR) { printdbg("\r\n%s(" PID_T_FORMAT "): got fatal error when reading, there's nothing left to read from\r\n", me, getpid()); break; } } } // we don't have a tty and use pipes else { fd_set rfds; int nfds = 0; FD_ZERO(&rfds); if (stdout_pipe_opened) { FD_SET(stdout_pipe[0], &rfds); if (stdout_pipe[0] > nfds) { nfds = stdout_pipe[0]; } } if (stderr_pipe_opened) { FD_SET(stderr_pipe[0], &rfds); if (stderr_pipe[0] > nfds) { nfds = stderr_pipe[0]; } } printdbg2("[select:%d:%d]", stdout_pipe_opened, stderr_pipe_opened); int retval = select(nfds + 1, &rfds, NULL, NULL, NULL); int current_fd = -1; cc = 0; if (retval == -1) // select failed { if (errno != EINTR) { perror("select()"); } continue; } else if (retval) { const char *current_fd_name; int *pipe_flag; if (FD_ISSET(stderr_pipe[0], &rfds)) { current_fd = stderr_pipe[0]; current_fd_name = "stderr"; pipe_flag = &stderr_pipe_opened; target_fd = 2; // stderr if (opt_stealth_stderr) { dont_write = 1; // don't write buffer to ttyrec file, see below } } else if (FD_ISSET(stdout_pipe[0], &rfds)) { current_fd = stdout_pipe[0]; current_fd_name = "stdout"; pipe_flag = &stdout_pipe_opened; target_fd = 1; // stdout if (opt_stealth_stdout) { dont_write = 1; // don't write buffer to ttyrec file, see below } } else { perror("select() returned invalid fd"); continue; } cc = read(current_fd, obuf, BUFSIZ); // Handle EOF or error if ((cc == 0) || ((cc < 0) && (errno != EINTR))) { if (cc == 0) { printdbg2("[%s:eof]", current_fd_name); } else { printdbg2("[%s:err=%d,%s]", current_fd_name, cc, strerror(errno)); } *pipe_flag = 0; close(current_fd); if ((stderr_pipe_opened == 0) && (stdout_pipe_opened == 0)) { printdbg2("[alleof]"); break; } } } } // here, we have cc with the number of bytes read, from either the tty or the pipes printdbg2("[out:%d]", cc); if (!locked_since && (cc > 0)) { h.len = cc; gettimeofday(&h.tv, NULL); if (write(target_fd, obuf, cc) == -1) { if (errno != EINTR) { printdbg("write(child-stdout,len=%d): %s", cc, strerror(errno)); if (stdout_pipe_opened) { close(stdout_pipe[0]); } if (stderr_pipe_opened) { close(stderr_pipe[0]); } break; } } if (!dont_write) { (void)write_header(fscript, &h); (void)fwrite_wrapper(obuf, 1, cc, fscript); } bytes_out += cc; last_activity = time(NULL); lock_warned = 0; kill_warned = 0; } } printdbg("child: end dooutput, waiting can_exit (== %d)\r\n", can_exit); while (can_exit == 0) { waitedpid = waitpid(-1, &childexit, 0); if (waitedpid < 0) // oops, all our children are already dead (ECHILD) { printdbg("child: oops, subchild is already dead!\r\n"); can_exit = 1; } } printdbg("child: end dooutput, can_exit done, status %d, exiting\r\n", childexit); if (opt_count_bytes) { fprintf(stderr, "\r\nTTY_BYTES_OUT=%llu\r\n", bytes_out); } done(childexit); } // called by subchild void doshell(const char *command, char **params) { (void)fclose_wrapper(fscript); if (use_tty) { getslave(); print_termios_info(slave, "subchild slave"); (void)close(master); (void)dup2(slave, 0); (void)dup2(slave, 1); (void)dup2(slave, 2); (void)close(slave); } else { (void)dup2(stdout_pipe[1], 1); (void)dup2(stderr_pipe[1], 2); (void)close(stdout_pipe[1]); (void)close(stderr_pipe[1]); } execvp(command, params); perror(command); fail(); } void fixtty(void) { struct termios rtt; rtt = parent_stdin_termios; #ifdef HAVE_cfmakeraw cfmakeraw(&rtt); rtt.c_lflag &= ~ECHO; #else rtt.c_iflag = 0; rtt.c_lflag &= ~(ISIG | ICANON | XCASE | ECHO | ECHOE | ECHOK | ECHONL); rtt.c_oflag = OPOST; rtt.c_cc[VINTR] = CDEL; rtt.c_cc[VQUIT] = CDEL; rtt.c_cc[VERASE] = CDEL; rtt.c_cc[VKILL] = CDEL; rtt.c_cc[VEOF] = 1; rtt.c_cc[VEOL] = 0; #endif if (tcsetattr(0, TCSAFLUSH, &rtt)) { perror("tcsetattr(0) in fixtty"); } } void fail(void) { fprintf(stderr, "\r\nttyrec: aborting!\r\n"); (void)kill(0, SIGTERM); done(EXIT_FAILURE); } void sigterm_handler(int signal) { (void)signal; if (subchild > 0) { // unlock_session() is also called in done(), but we need to do it first here // or the below message won't be visible if we happen to be locked unlock_session(SIGUSR2); // terminate our subchild BEFORE printing the msg, to avoid an interlock case where // our child is stuck writing to us, while we're writing to our stdout // read by our caller, stuck writing to our child kill(subchild, SIGTERM); (void)puts("\r\nttyrec: ending your session, sorry (kill timeout expired, you manually typed the kill key sequence, or we got a SIGTERM).\r"); } done(EXIT_SUCCESS); } void sighup_handler(int signal) { (void)signal; if (subchild > 0) { kill(subchild, SIGTERM); } done(EXIT_SUCCESS); } void done(int status) { // Sometimes (happens once every ~1 million executions in some environments), we might get a SIGHUP // while we're calling exit() from this function. As the SIGHUP handler ends up calling this function // again, we end up doing double-frees and calling exit() twice, which gets us a segfault. Hereby, ensure // that once we've entered this function once, we'll never re-enter it through a sighandler. static pthread_mutex_t entered_done = PTHREAD_MUTEX_INITIALIZER; if (pthread_mutex_trylock(&entered_done) != 0) { return; } if (subchild) { 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_wrapper(fscript); (void)close(master); } else { printdbg("parent: done, cleaning up and exiting with %d (child=%d subchild=%d)\r\n", WEXITSTATUS(status), child, subchild); if (use_tty && parent_stdin_isatty) { // don't check for result because if it fails, we can't do anything interesting, // not even printing an error (and actually; perror() stucks sometimes if we use it here) tcsetattr(0, TCSAFLUSH, &parent_stdin_termios); } } free(dname); free(uuid); exit(WEXITSTATUS(status)); } void getmaster(void) { if (parent_stdin_isatty) { if (tcgetattr(0, &parent_stdin_termios)) { perror("tcgetattr(0, parent_stdin_termios)"); } if (ioctl(0, TIOCGWINSZ, (char *)&parent_stdin_winsize)) { perror("ioctl(0, TIOCGWINSZ)"); } } #ifdef HAVE_openpty if (openpty_disable) { #endif #ifdef HAVE_grantpt // with getpt, posix_openpt and ptmx, we need grantpt in getslave() # if defined(HAVE_getpt) if ((master = getpt()) < 0) { perror("getpt()"); fail(); } return; # elif defined(HAVE_posix_openpt) if ((master = posix_openpt(O_RDWR | O_NOCTTY)) < 0) { perror("posix_openpt()"); fail(); } return; # else if (access("/dev/ptmx", F_OK) == 0) { if ((master = open("/dev/ptmx", O_RDWR | O_NOCTTY)) < 0) { perror("open(\"/dev/ptmx\", O_RDWR)"); fail(); } return; } # endif #endif /* !HAVE_grantpt */ #ifdef HAVE_openpty } #endif //#if !defined(HAVE_grantpt) || ( !defined(HAVE_getpt) && !defined(HAVE_posix_openpt) ) # ifdef HAVE_openpty // BUG HERE: the getpt(), posix_openpt() and /dev/ptmx ways work correctly under Linux, // where openpty() doesn't in the case of: ssh -T user@bastion -- -t user@remote -- 'pwd' = 0) { char *tp = &line[strlen("/dev/")]; int ok; /* verify slave side is usable */ *tp = 't'; ok = access(line, R_OK | W_OK) == 0; *tp = 'p'; if (ok) { return; } (void)close(master); } } } if (tries == 0) { fprintf(stderr, "Found no way to allocate a pseudo-tty on your system!\r\n"); } else { fprintf(stderr, "Out of pty's (tried %u pty's)\r\n", tries); } fail(); # endif //#endif } void getslave(void) { #ifdef HAVE_openpty printdbg("openpty_used == %d\r\n", openpty_used); if (openpty_used) { if (setsid() < 0) { perror("setsid"); fail(); } if (ioctl(slave, TIOCSCTTY, 0) < 0) { #ifndef SUN_OS perror("ioctl"); fail(); #endif } return; } #endif #if defined(HAVE_grantpt) if (setsid() < 0) { perror("setsid"); fail(); } if (grantpt(master) != 0) { perror("grantpt"); fail(); } if (unlockpt(master) != 0) { perror("unlockpt"); fail(); } const char *slavename = ptsname(master); printdbg("ptsname(master) is %s\r\n", slavename); if ((slave = open(slavename, O_RDWR)) < 0) { perror("slave = open(fd, O_RDWR)"); fail(); } if (ioctl(slave, TIOCSCTTY, 0) < 0) { #ifndef SUN_OS perror("ioctl"); fail(); #endif } # ifdef HAVE_isastream if (isastream(slave)) { if (ioctl(slave, I_PUSH, "ptem") < 0) { perror("ioctl(fd, I_PUSH, ptem)"); fail(); } if (ioctl(slave, I_PUSH, "ldterm") < 0) { perror("ioctl(fd, I_PUSH, ldterm)"); fail(); } # ifndef _HPUX_SOURCE if (ioctl(slave, I_PUSH, "ttcompat") < 0) { perror("ioctl(fd, I_PUSH, ttcompat)"); fail(); } # endif if (parent_stdin_isatty) { if (ioctl(0, TIOCGWINSZ, (char *)&parent_stdin_winsize)) { perror("ioctl(0, TIOCGWINSZ)"); } } } # endif /* !HAVE_isastream */ #elif !defined(HAVE_openpty) /* !HAVE_grantpt */ line[strlen("/dev/")] = 't'; slave = open(line, O_RDWR); if (slave < 0) { perror(line); fail(); } if (parent_stdin_isatty) { if (tcsetattr(slave, TCSAFLUSH, &parent_stdin_termios)) { perror("tcsetattr(slave, TCSAFLUSH, parent_stdin_termios)"); } if (ioctl(slave, TIOCSWINSZ, (char *)&parent_stdin_winsize)) { perror("ioctl(slave, TIOCSWINSZ, parent_stdin_winsize)"); } } #endif /* HAVE_grantpt */ } void sigwinch_handler_parent(int signal) { (void)signal; if (parent_stdin_isatty) { (void)ioctl(0, TIOCGWINSZ, (char *)&parent_stdin_winsize); (void)ioctl(master, TIOCSWINSZ, (char *)&parent_stdin_winsize); } kill(child, SIGWINCH); } void sigwinch_handler_child(int signal) { (void)signal; kill(child, SIGWINCH); } void print_termios_info(int fd, const char *prefix) { struct termios t; if (opt_debug) { memset(&t, '\0', sizeof(t)); if (tcgetattr(fd, &t)) { fprintf(stderr, "%25s: %s\r\n", prefix, strerror(errno)); } else { char dbgline[BUFSIZ]; dbgline[0] = '\0'; snprintf(dbgline, BUFSIZ, "%25s: i=%05lo o=%03lo c=%05lo l=%06lo, i: ", prefix, (unsigned long)t.c_iflag, (unsigned long)t.c_oflag, (unsigned long)t.c_cflag, (unsigned long)t.c_lflag); #define IFLAG(f) \ if (t.c_iflag & f) { strncat(dbgline + strlen(dbgline), # f " ", BUFSIZ - strlen(dbgline)); \ } #define OFLAG(f) if (t.c_oflag & f) { strncat(dbgline + strlen(dbgline), # f " ", BUFSIZ - strlen(dbgline)); } #define CFLAG(f) if (t.c_cflag & f) { strncat(dbgline + strlen(dbgline), # f " ", BUFSIZ - strlen(dbgline)); } #define LFLAG(f) if (t.c_lflag & f) { strncat(dbgline + strlen(dbgline), # f " ", BUFSIZ - strlen(dbgline)); } #define CSWITCH(f) \ case f: \ strncat(dbgline + strlen(dbgline), # f " ", BUFSIZ - strlen(dbgline)); break; IFLAG(IGNBRK); IFLAG(BRKINT); IFLAG(IGNPAR); IFLAG(PARMRK); IFLAG(INPCK); IFLAG(ISTRIP); IFLAG(INLCR); IFLAG(IGNCR); IFLAG(ICRNL); #ifdef IUCLC /* undef under NetBSD */ IFLAG(IUCLC); #endif IFLAG(IXON); #ifdef IXANY /* undef under NetBSD */ IFLAG(IXANY); #endif IFLAG(IXOFF); #ifdef IMAXBEL /* undef at least under Haiku */ IFLAG(IMAXBEL); #endif #ifdef IUTF8 IFLAG(IUTF8); #endif strncat(dbgline + strlen(dbgline), "o: ", BUFSIZ - strlen(dbgline)); OFLAG(OPOST); #ifdef OLCUC /* undef under NetBSD */ OFLAG(OLCUC); #endif OFLAG(ONLCR); OFLAG(OCRNL); OFLAG(ONLRET); #ifdef OFILL /* undef under NetBSD */ OFLAG(OFILL); #endif #ifdef OFDEL /* undef under NetBSD */ OFLAG(OFDEL); #endif strncat(dbgline + strlen(dbgline), "c: ", BUFSIZ - strlen(dbgline)); switch (t.c_cflag & B38400) { CSWITCH(B38400); CSWITCH(B19200); CSWITCH(B9600); CSWITCH(B4800); CSWITCH(B2400); CSWITCH(B1200); CSWITCH(B600); CSWITCH(B300); CSWITCH(B150); CSWITCH(B75); CSWITCH(B50); CSWITCH(B0); } switch (t.c_cflag & CSIZE) { CSWITCH(CS8); CSWITCH(CS7); #if (CS6 != CS5) && (CS6 != CS7) && (CS6 != CS8) /* Haiku defines CS4 and CS5 to 0x00 */ CSWITCH(CS6); #endif #if (CS5 != CS6) && (CS5 != CS7) && (CS5 != CS8) CSWITCH(CS5); #endif } CFLAG(CSTOPB); CFLAG(CREAD); CFLAG(PARENB); CFLAG(PARODD); CFLAG(CLOCAL); strncat(dbgline + strlen(dbgline), "l: ", BUFSIZ - strlen(dbgline)); LFLAG(ISIG); LFLAG(ICANON); #ifdef XCASE /* undef under NetBSD */ LFLAG(XCASE); #endif LFLAG(ECHO); LFLAG(ECHOE); LFLAG(ECHOK); LFLAG(ECHONL); LFLAG(NOFLSH); LFLAG(TOSTOP); #ifdef ECHOCTL /* undef under NetBSD */ LFLAG(ECHOCTL); #endif #ifdef ECHOPRT /* undef under NetBSD */ LFLAG(ECHOPRT); #endif #ifdef ECHOKE /* undef under NetBSD */ LFLAG(ECHOKE); #endif #ifdef FLUSH0 /* undef under NetBSD */ LFLAG(FLUSHO); #endif #ifdef PENDIN /* undef under NetBSD */ LFLAG(PENDIN); #endif LFLAG(IEXTEN); #undef IFLAG #undef OFLAG #undef CFLAG #undef LFLAG #undef CSWITCH strncat(dbgline + strlen(dbgline), "cc: ", BUFSIZ - strlen(dbgline)); for (cc_t i = 0; i < NCCS; i++) { snprintf(dbgline + strlen(dbgline), BUFSIZ - strlen(dbgline), "%02x/", t.c_cc[i]); } fprintf(stderr, "%s\r\n", dbgline); } } } void help(void) { fprintf(stderr, \ "Usage: ttyrec [options] -- [command options]\n" \ "\n" \ "Usage (legacy compatibility mode): ttyrec -e [options] [ttyrec file name]\n" \ "\n" \ "Options:\n" \ " -z, --uuid UUID specify an UUID (can be any string) that will appear in the ttyrec output file names,\n" \ " and kept with SIGUSR1 rotations (default: own PID)\n" \ " -f, --output FILE full path of the first ttyrec file to write to (autogenerated if omitted)\n" \ " -d, --dir FOLDER folder where to write the ttyrec files (taken from -f if omitted,\n" \ " defaulting to working directory if both -f and -d are omitted)\n" \ " -F, --name-format FMT custom strftime-compatible format string to qualify the full path of the output files,\n" \ " including the SIGUSR1 rotated ones\n" \ " -a, --append open the ttyrec output file in append mode instead of write-clobber mode\n"); #ifdef HAVE_zstd fprintf(stderr, \ " -Z enable on-the-fly compression if available, silently fallback to no compression if not\n" \ " --zstd force on-the-fly compression of output file using zstd,\n" \ " the resulting file will have a '.ttyrec.zst' extension\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" \ , ZSTD_MAX_FLUSH_SECONDS_DEFAULT); #endif fprintf(stderr, \ " -n, --count-bytes count the number of bytes out and print it on termination (experimental)\n" \ " -t, --lock-timeout S lock session on input timeout after S seconds\n" \ " --warn-before-lock S warn S seconds before locking (see --lock-timeout)\n" \ " -k, --kill-timeout S kill session on input timeout after S seconds\n" \ " --warn-before-kill S warn S seconds before killing (see --kill-timeout)\n" \ " -C, --no-cheatcodes disable cheat-codes (see below), this is the default\n" \ " -c, --cheatcodes enable cheat-codes (see below)\n" \ " -p, --no-openpty don't use openpty() even when it's available\n" \ " -T, --term MODE MODE can be either 'never' (never allocate a pseudotty, even if stdin is a tty, and use pipes to\n" \ " handle stdout/stderr instead), 'always' (always allocate a pseudotty, even if stdin is not a tty)\n" \ " or 'auto' (default, allocate a pseudotty if stdin is a tty, uses pipes otherwise)\n" \ " --stealth-stdout when no pseudotty is allocated, don't record stdout\n" \ " --stealth-stderr when no pseudotty is allocated, don't record stderr\n" \ " -v, --verbose verbose (debug) mode, use twice for more verbosity\n" \ " -V, --version show version information\n" \ " -e, --shell-cmd CMD enables legacy compatibility mode and specifies the command to be run under the user's $SHELL -c\n" \ "\n" \ "Examples:\n" \ " Run some shell commands in legacy mode: ttyrec -e 'for i in a b c; do echo $i; done' outfile.ttyrec\n" \ " Run some shell commands in normal mode: ttyrec -f /tmp/normal.ttyrec -- sh -c 'for i in a b c; do echo $i; done'\n" \ " Connect to a remote machine interactively: ttyrec -t 60 -k 300 -- ssh remoteserver\n" \ " Execute a local script remotely with the default remote shell: ttyrec -- ssh remoteserver < script.sh\n" \ " Record a screen session: ttyrec screen\n" \ "\n" \ "Handled signals:\n" \ " SIGUSR1 close current ttyrec file and reopen a new one (log rotation)\n" \ " SIGURG lock session\n" \ " SIGUSR2 unlock session\n" \ "\n" \ "Cheat-codes (magic keystrokes combinations):\n" \ " ^L^L^L^L^L^L^L^L lock your session (that's 8 CTRL+L's)\n" \ " ^K^I^L^L^K^I^L^L kill your session\n" \ "\n" \ "Remark about session lock and session kill:\n" \ " If we don't have a tty, we can't lock, so -t will be ignored,\n" \ " whereas -k will be applied without warning, as there's no tty to output a warning to.\n" \ ); } ovh-ttyrec-1.1.7.1/ttyrec.h000066400000000000000000000002341450203670500154470ustar00rootroot00000000000000#ifndef __TTYREC_H__ #define __TTYREC_H__ #include typedef struct header { struct timeval tv; int len; } Header; #endif ovh-ttyrec-1.1.7.1/ttytime.c000066400000000000000000000055671450203670500156450ustar00rootroot00000000000000/* Use of this source code is governed by a BSD-style * license that can be found in the LICENSE file. * Copyright 2001-2019 The ovh-ttyrec Authors. All rights reserved. * * This work is based on the original ttyrec, whose license text * can be found below unmodified. * * Copyright (c) 1980 Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include "ttyrec.h" #include "io.h" int calc_time(const char *filename); int calc_time(const char *filename) { Header start, end; FILE *fp = efopen(filename, "r"); read_header(fp, &start); end.tv.tv_sec = start.tv.tv_sec; // to avoid can-be-uninit warning fseek(fp, start.len, SEEK_CUR); while (1) { Header h; if (read_header(fp, &h) == 0) { break; } end = h; fseek(fp, h.len, SEEK_CUR); } return end.tv.tv_sec - start.tv.tv_sec; } int main(int argc, char **argv) { int i; set_progname(argv[0]); for (i = 1; i < argc; i++) { char *filename = argv[i]; printf("%7d %s\n", calc_time(filename), filename); } return 0; } ovh-ttyrec-1.1.7.1/uncrustify.cfg000066400000000000000000002363761450203670500167020ustar00rootroot00000000000000# Uncrustify 0.62 # # General options # # The type of line endings newlines = lf # auto/lf/crlf/cr # The original size of tabs in the input input_tab_size = 8 # number # The size of tabs in the output (only used if align_with_tabs=true) output_tab_size = 4 # number # The ASCII value of the string escape char, usually 92 (\) or 94 (^). (Pawn) string_escape_char = 92 # number # Alternate string escape char for Pawn. Only works right before the quote char. string_escape_char2 = 0 # number # Replace tab characters found in string literals with the escape sequence \t instead. string_replace_tab_chars = false # false/true # Allow interpreting '>=' and '>>=' as part of a template in 'void f(list>=val);'. # If true (default), 'assert(x<0 && y>=3)' will be broken. # Improvements to template detection may make this option obsolete. tok_split_gte = false # false/true # Override the default ' *INDENT-OFF*' in comments for disabling processing of part of the file. disable_processing_cmt = "" # string # Override the default ' *INDENT-ON*' in comments for enabling processing of part of the file. enable_processing_cmt = "" # string # Enable parsing of digraphs. Default=false enable_digraphs = false # false/true # Control what to do with the UTF-8 BOM (recommend 'remove') utf8_bom = ignore # ignore/add/remove/force # If the file contains bytes with values between 128 and 255, but is not UTF-8, then output as UTF-8 utf8_byte = false # false/true # Force the output encoding to UTF-8 utf8_force = false # false/true # # Indenting # # The number of columns to indent per level. # Usually 2, 3, 4, or 8. indent_columns = 4 # number # The continuation indent. If non-zero, this overrides the indent of '(' and '=' continuation indents. # For FreeBSD, this is set to 4. Negative value is absolute and not increased for each ( level indent_continue = 0 # number # How to use tabs when indenting code # 0=spaces only # 1=indent with tabs to brace level, align with spaces # 2=indent and align with tabs, using spaces when not on a tabstop indent_with_tabs = 0 # number # Comments that are not a brace level are indented with tabs on a tabstop. # Requires indent_with_tabs=2. If false, will use spaces. indent_cmt_with_tabs = false # false/true # Whether to indent strings broken by '\' so that they line up indent_align_string = true # false/true # The number of spaces to indent multi-line XML strings. # Requires indent_align_string=True indent_xml_string = 0 # number # Spaces to indent '{' from level indent_brace = 0 # number # Whether braces are indented to the body level indent_braces = false # false/true # Disabled indenting function braces if indent_braces is true indent_braces_no_func = false # false/true # Disabled indenting class braces if indent_braces is true indent_braces_no_class = false # false/true # Disabled indenting struct braces if indent_braces is true indent_braces_no_struct = false # false/true # Indent based on the size of the brace parent, i.e. 'if' => 3 spaces, 'for' => 4 spaces, etc. indent_brace_parent = false # false/true # Indent based on the paren open instead of the brace open in '({\n', default is to indent by brace. indent_paren_open_brace = false # false/true # Whether the 'namespace' body is indented indent_namespace = false # false/true # Only indent one namespace and no sub-namespaces. # Requires indent_namespace=true. indent_namespace_single_indent = false # false/true # The number of spaces to indent a namespace block indent_namespace_level = 0 # number # If the body of the namespace is longer than this number, it won't be indented. # Requires indent_namespace=true. Default=0 (no limit) indent_namespace_limit = 0 # number # Whether the 'extern "C"' body is indented indent_extern = false # false/true # Whether the 'class' body is indented indent_class = true # false/true # Whether to indent the stuff after a leading base class colon indent_class_colon = true # false/true # Indent based on a class colon instead of the stuff after the colon. # Requires indent_class_colon=true. Default=false indent_class_on_colon = false # false/true # Whether to indent the stuff after a leading class initializer colon indent_constr_colon = false # false/true # Virtual indent from the ':' for member initializers. Default is 2 indent_ctor_init_leading = 2 # number # Additional indenting for constructor initializer list indent_ctor_init = 0 # number # False=treat 'else\nif' as 'else if' for indenting purposes # True=indent the 'if' one level indent_else_if = false # false/true # Amount to indent variable declarations after a open brace. neg=relative, pos=absolute indent_var_def_blk = 0 # number # Indent continued variable declarations instead of aligning. indent_var_def_cont = false # false/true # Indent continued shift expressions ('<<' and '>>') instead of aligning. # Turn align_left_shift off when enabling this. indent_shift = false # false/true # True: force indentation of function definition to start in column 1 # False: use the default behavior indent_func_def_force_col1 = false # false/true # True: indent continued function call parameters one indent level # False: align parameters under the open paren indent_func_call_param = false # false/true # Same as indent_func_call_param, but for function defs indent_func_def_param = false # false/true # Same as indent_func_call_param, but for function protos indent_func_proto_param = false # false/true # Same as indent_func_call_param, but for class declarations indent_func_class_param = false # false/true # Same as indent_func_call_param, but for class variable constructors indent_func_ctor_var_param = false # false/true # Same as indent_func_call_param, but for templates indent_template_param = false # false/true # Double the indent for indent_func_xxx_param options indent_func_param_double = false # false/true # Indentation column for standalone 'const' function decl/proto qualifier indent_func_const = 0 # number # Indentation column for standalone 'throw' function decl/proto qualifier indent_func_throw = 0 # number # The number of spaces to indent a continued '->' or '.' # Usually set to 0, 1, or indent_columns. indent_member = 4 # number # Spaces to indent single line ('//') comments on lines before code indent_sing_line_comments = 0 # number # If set, will indent trailing single line ('//') comments relative # to the code instead of trying to keep the same absolute column indent_relative_single_line_comments = false # false/true # Spaces to indent 'case' from 'switch' # Usually 0 or indent_columns. indent_switch_case = 0 # number # Spaces to shift the 'case' line, without affecting any other lines # Usually 0. indent_case_shift = 0 # number # Spaces to indent '{' from 'case'. # By default, the brace will appear under the 'c' in case. # Usually set to 0 or indent_columns. indent_case_brace = 4 # number # Whether to indent comments found in first column indent_col1_comment = false # false/true # How to indent goto labels # >0 : absolute column where 1 is the leftmost column # <=0 : subtract from brace indent indent_label = 1 # number # Same as indent_label, but for access specifiers that are followed by a colon indent_access_spec = 1 # number # Indent the code after an access specifier by one level. # If set, this option forces 'indent_access_spec=0' indent_access_spec_body = false # false/true # If an open paren is followed by a newline, indent the next line so that it lines up after the open paren (not recommended) indent_paren_nl = false # false/true # Controls the indent of a close paren after a newline. # 0: Indent to body level # 1: Align under the open paren # 2: Indent to the brace level indent_paren_close = 0 # number # Controls the indent of a comma when inside a paren.If TRUE, aligns under the open paren indent_comma_paren = false # false/true # Controls the indent of a BOOL operator when inside a paren.If TRUE, aligns under the open paren indent_bool_paren = false # false/true # If 'indent_bool_paren' is true, controls the indent of the first expression. If TRUE, aligns the first expression to the following ones indent_first_bool_expr = false # false/true # If an open square is followed by a newline, indent the next line so that it lines up after the open square (not recommended) indent_square_nl = false # false/true # Don't change the relative indent of ESQL/C 'EXEC SQL' bodies indent_preserve_sql = false # false/true # Align continued statements at the '='. Default=True # If FALSE or the '=' is followed by a newline, the next line is indent one tab. indent_align_assign = true # false/true # Indent OC blocks at brace level instead of usual rules. indent_oc_block = false # false/true # Indent OC blocks in a message relative to the parameter name. # 0=use indent_oc_block rules, 1+=spaces to indent indent_oc_block_msg = 0 # number # Minimum indent for subsequent parameters indent_oc_msg_colon = 0 # number # If true, prioritize aligning with initial colon (and stripping spaces from lines, if necessary). # Default is true. indent_oc_msg_prioritize_first_colon = true # false/true # If indent_oc_block_msg and this option are on, blocks will be indented the way that Xcode does by default (from keyword if the parameter is on its own line; otherwise, from the previous indentation level). indent_oc_block_msg_xcode_style = false # false/true # If indent_oc_block_msg and this option are on, blocks will be indented from where the brace is relative to a msg keyword. indent_oc_block_msg_from_keyword = false # false/true # If indent_oc_block_msg and this option are on, blocks will be indented from where the brace is relative to a msg colon. indent_oc_block_msg_from_colon = false # false/true # If indent_oc_block_msg and this option are on, blocks will be indented from where the block caret is. indent_oc_block_msg_from_caret = false # false/true # If indent_oc_block_msg and this option are on, blocks will be indented from where the brace is. indent_oc_block_msg_from_brace = false # false/true # When identing after virtual brace open and newline add further spaces to reach this min. indent. indent_min_vbrace_open = 0 # number # TRUE: When identing after virtual brace open and newline add further spaces after regular indent to reach next tabstop. indent_vbrace_open_on_tabstop = false # false/true # # Spacing options # # Add or remove space around arithmetic operator '+', '-', '/', '*', etc # also '>>>' '<<' '>>' '%' '|' sp_arith = force # ignore/add/remove/force # Add or remove space around assignment operator '=', '+=', etc sp_assign = force # ignore/add/remove/force # Add or remove space around '=' in C++11 lambda capture specifications. Overrides sp_assign sp_cpp_lambda_assign = ignore # ignore/add/remove/force # Add or remove space after the capture specification in C++11 lambda. sp_cpp_lambda_paren = ignore # ignore/add/remove/force # Add or remove space around assignment operator '=' in a prototype sp_assign_default = ignore # ignore/add/remove/force # Add or remove space before assignment operator '=', '+=', etc. Overrides sp_assign. sp_before_assign = ignore # ignore/add/remove/force # Add or remove space after assignment operator '=', '+=', etc. Overrides sp_assign. sp_after_assign = ignore # ignore/add/remove/force # Add or remove space in 'NS_ENUM (' sp_enum_paren = ignore # ignore/add/remove/force # Add or remove space around assignment '=' in enum sp_enum_assign = ignore # ignore/add/remove/force # Add or remove space before assignment '=' in enum. Overrides sp_enum_assign. sp_enum_before_assign = ignore # ignore/add/remove/force # Add or remove space after assignment '=' in enum. Overrides sp_enum_assign. sp_enum_after_assign = ignore # ignore/add/remove/force # Add or remove space around preprocessor '##' concatenation operator. Default=Add sp_pp_concat = add # ignore/add/remove/force # Add or remove space after preprocessor '#' stringify operator. Also affects the '#@' charizing operator. sp_pp_stringify = add # ignore/add/remove/force # Add or remove space before preprocessor '#' stringify operator as in '#define x(y) L#y'. sp_before_pp_stringify = ignore # ignore/add/remove/force # Add or remove space around boolean operators '&&' and '||' sp_bool = force # ignore/add/remove/force # Add or remove space around compare operator '<', '>', '==', etc sp_compare = force # ignore/add/remove/force # Add or remove space inside '(' and ')' sp_inside_paren = remove # ignore/add/remove/force # Add or remove space between nested parens: '((' vs ') )' sp_paren_paren = remove # ignore/add/remove/force # Add or remove space between back-to-back parens: ')(' vs ') (' sp_cparen_oparen = ignore # ignore/add/remove/force # Whether to balance spaces inside nested parens sp_balance_nested_parens = false # false/true # Add or remove space between ')' and '{' sp_paren_brace = ignore # ignore/add/remove/force # Add or remove space before pointer star '*' sp_before_ptr_star = force # ignore/add/remove/force # Add or remove space before pointer star '*' that isn't followed by a variable name # If set to 'ignore', sp_before_ptr_star is used instead. sp_before_unnamed_ptr_star = ignore # ignore/add/remove/force # Add or remove space between pointer stars '*' sp_between_ptr_star = remove # ignore/add/remove/force # Add or remove space after pointer star '*', if followed by a word. sp_after_ptr_star = remove # ignore/add/remove/force # Add or remove space after pointer star '*', if followed by a qualifier. sp_after_ptr_star_qualifier = ignore # ignore/add/remove/force # Add or remove space after a pointer star '*', if followed by a func proto/def. sp_after_ptr_star_func = ignore # ignore/add/remove/force # Add or remove space after a pointer star '*', if followed by an open paren (function types). sp_ptr_star_paren = ignore # ignore/add/remove/force # Add or remove space before a pointer star '*', if followed by a func proto/def. sp_before_ptr_star_func = ignore # ignore/add/remove/force # Add or remove space before a reference sign '&' sp_before_byref = remove # ignore/add/remove/force # Add or remove space before a reference sign '&' that isn't followed by a variable name # If set to 'ignore', sp_before_byref is used instead. sp_before_unnamed_byref = ignore # ignore/add/remove/force # Add or remove space after reference sign '&', if followed by a word. sp_after_byref = force # ignore/add/remove/force # Add or remove space after a reference sign '&', if followed by a func proto/def. sp_after_byref_func = ignore # ignore/add/remove/force # Add or remove space before a reference sign '&', if followed by a func proto/def. sp_before_byref_func = ignore # ignore/add/remove/force # Add or remove space between type and word. Default=Force sp_after_type = force # ignore/add/remove/force # Add or remove space before the paren in the D constructs 'template Foo(' and 'class Foo('. sp_before_template_paren = ignore # ignore/add/remove/force # Add or remove space in 'template <' vs 'template<'. # If set to ignore, sp_before_angle is used. sp_template_angle = ignore # ignore/add/remove/force # Add or remove space before '<>' sp_before_angle = remove # ignore/add/remove/force # Add or remove space inside '<' and '>' sp_inside_angle = remove # ignore/add/remove/force # Add or remove space after '<>' sp_after_angle = force # ignore/add/remove/force # Add or remove space between '<>' and '(' as found in 'new List();' sp_angle_paren = ignore # ignore/add/remove/force # Add or remove space between '<>' and a word as in 'List m;' or 'template static ...' sp_angle_word = ignore # ignore/add/remove/force # Add or remove space between '>' and '>' in '>>' (template stuff C++/C# only). Default=Add sp_angle_shift = add # ignore/add/remove/force # Permit removal of the space between '>>' in 'foo >' (C++11 only). Default=False # sp_angle_shift cannot remove the space without this option. sp_permit_cpp11_shift = false # false/true # Add or remove space before '(' of 'if', 'for', 'switch', 'while', etc. sp_before_sparen = force # ignore/add/remove/force # Add or remove space inside if-condition '(' and ')' sp_inside_sparen = remove # ignore/add/remove/force # Add or remove space before if-condition ')'. Overrides sp_inside_sparen. sp_inside_sparen_close = ignore # ignore/add/remove/force # Add or remove space after if-condition '('. Overrides sp_inside_sparen. sp_inside_sparen_open = ignore # ignore/add/remove/force # Add or remove space after ')' of 'if', 'for', 'switch', and 'while', etc. sp_after_sparen = force # ignore/add/remove/force # Add or remove space between ')' and '{' of 'if', 'for', 'switch', and 'while', etc. sp_sparen_brace = add # ignore/add/remove/force # Add or remove space between 'invariant' and '(' in the D language. sp_invariant_paren = ignore # ignore/add/remove/force # Add or remove space after the ')' in 'invariant (C) c' in the D language. sp_after_invariant_paren = ignore # ignore/add/remove/force # Add or remove space before empty statement ';' on 'if', 'for' and 'while' sp_special_semi = ignore # ignore/add/remove/force # Add or remove space before ';'. Default=Remove sp_before_semi = remove # ignore/add/remove/force # Add or remove space before ';' in non-empty 'for' statements sp_before_semi_for = ignore # ignore/add/remove/force # Add or remove space before a semicolon of an empty part of a for statement. sp_before_semi_for_empty = force # ignore/add/remove/force # Add or remove space after ';', except when followed by a comment. Default=Add sp_after_semi = add # ignore/add/remove/force # Add or remove space after ';' in non-empty 'for' statements. Default=Force sp_after_semi_for = force # ignore/add/remove/force # Add or remove space after the final semicolon of an empty part of a for statement: for ( ; ; ). sp_after_semi_for_empty = ignore # ignore/add/remove/force # Add or remove space before '[' (except '[]') sp_before_square = ignore # ignore/add/remove/force # Add or remove space before '[]' sp_before_squares = ignore # ignore/add/remove/force # Add or remove space inside a non-empty '[' and ']' sp_inside_square = remove # ignore/add/remove/force # Add or remove space after ',' sp_after_comma = force # ignore/add/remove/force # Add or remove space before ',' sp_before_comma = remove # ignore/add/remove/force # Add or remove space between ',' and ']' in multidimensional array type 'int[,,]' sp_after_mdatype_commas = ignore # ignore/add/remove/force # Add or remove space between '[' and ',' in multidimensional array type 'int[,,]' sp_before_mdatype_commas = ignore # ignore/add/remove/force # Add or remove space between ',' in multidimensional array type 'int[,,]' sp_between_mdatype_commas = ignore # ignore/add/remove/force # Add or remove space between an open paren and comma: '(,' vs '( ,' sp_paren_comma = force # ignore/add/remove/force # Add or remove space before the variadic '...' when preceded by a non-punctuator sp_before_ellipsis = ignore # ignore/add/remove/force # Add or remove space after class ':' sp_after_class_colon = ignore # ignore/add/remove/force # Add or remove space before class ':' sp_before_class_colon = ignore # ignore/add/remove/force # Add or remove space after class constructor ':' sp_after_constr_colon = ignore # ignore/add/remove/force # Add or remove space before class constructor ':' sp_before_constr_colon = ignore # ignore/add/remove/force # Add or remove space before case ':'. Default=Remove sp_before_case_colon = remove # ignore/add/remove/force # Add or remove space between 'operator' and operator sign sp_after_operator = ignore # ignore/add/remove/force # Add or remove space between the operator symbol and the open paren, as in 'operator ++(' sp_after_operator_sym = ignore # ignore/add/remove/force # Add or remove space after C/D cast, i.e. 'cast(int)a' vs 'cast(int) a' or '(int)a' vs '(int) a' sp_after_cast = remove # ignore/add/remove/force # Add or remove spaces inside cast parens sp_inside_paren_cast = ignore # ignore/add/remove/force # Add or remove space between the type and open paren in a C++ cast, i.e. 'int(exp)' vs 'int (exp)' sp_cpp_cast_paren = ignore # ignore/add/remove/force # Add or remove space between 'sizeof' and '(' sp_sizeof_paren = remove # ignore/add/remove/force # Add or remove space after the tag keyword (Pawn) sp_after_tag = ignore # ignore/add/remove/force # Add or remove space inside enum '{' and '}' sp_inside_braces_enum = force # ignore/add/remove/force # Add or remove space inside struct/union '{' and '}' sp_inside_braces_struct = force # ignore/add/remove/force # Add or remove space inside '{' and '}' sp_inside_braces = force # ignore/add/remove/force # Add or remove space inside '{}' sp_inside_braces_empty = ignore # ignore/add/remove/force # Add or remove space between return type and function name # A minimum of 1 is forced except for pointer return types. sp_type_func = force # ignore/add/remove/force # Add or remove space between function name and '(' on function declaration sp_func_proto_paren = remove # ignore/add/remove/force # Add or remove space between function name and '(' on function definition sp_func_def_paren = remove # ignore/add/remove/force # Add or remove space inside empty function '()' sp_inside_fparens = ignore # ignore/add/remove/force # Add or remove space inside function '(' and ')' sp_inside_fparen = remove # ignore/add/remove/force # Add or remove space inside the first parens in the function type: 'void (*x)(...)' sp_inside_tparen = ignore # ignore/add/remove/force # Add or remove between the parens in the function type: 'void (*x)(...)' sp_after_tparen_close = ignore # ignore/add/remove/force # Add or remove space between ']' and '(' when part of a function call. sp_square_fparen = ignore # ignore/add/remove/force # Add or remove space between ')' and '{' of function sp_fparen_brace = add # ignore/add/remove/force # Java: Add or remove space between ')' and '{{' of double brace initializer. sp_fparen_dbrace = ignore # ignore/add/remove/force # Add or remove space between function name and '(' on function calls sp_func_call_paren = remove # ignore/add/remove/force # Add or remove space between function name and '()' on function calls without parameters. # If set to 'ignore' (the default), sp_func_call_paren is used. sp_func_call_paren_empty = ignore # ignore/add/remove/force # Add or remove space between the user function name and '(' on function calls # You need to set a keyword to be a user function, like this: 'set func_call_user _' in the config file. sp_func_call_user_paren = ignore # ignore/add/remove/force # Add or remove space between a constructor/destructor and the open paren sp_func_class_paren = remove # ignore/add/remove/force # Add or remove space between 'return' and '(' sp_return_paren = remove # ignore/add/remove/force # Add or remove space between '__attribute__' and '(' sp_attribute_paren = ignore # ignore/add/remove/force # Add or remove space between 'defined' and '(' in '#if defined (FOO)' sp_defined_paren = ignore # ignore/add/remove/force # Add or remove space between 'throw' and '(' in 'throw (something)' sp_throw_paren = ignore # ignore/add/remove/force # Add or remove space between 'throw' and anything other than '(' as in '@throw [...];' sp_after_throw = ignore # ignore/add/remove/force # Add or remove space between 'catch' and '(' in 'catch (something) { }' # If set to ignore, sp_before_sparen is used. sp_catch_paren = ignore # ignore/add/remove/force # Add or remove space between 'version' and '(' in 'version (something) { }' (D language) # If set to ignore, sp_before_sparen is used. sp_version_paren = ignore # ignore/add/remove/force # Add or remove space between 'scope' and '(' in 'scope (something) { }' (D language) # If set to ignore, sp_before_sparen is used. sp_scope_paren = ignore # ignore/add/remove/force # Add or remove space between macro and value sp_macro = ignore # ignore/add/remove/force # Add or remove space between macro function ')' and value sp_macro_func = ignore # ignore/add/remove/force # Add or remove space between 'else' and '{' if on the same line sp_else_brace = ignore # ignore/add/remove/force # Add or remove space between '}' and 'else' if on the same line sp_brace_else = ignore # ignore/add/remove/force # Add or remove space between '}' and the name of a typedef on the same line sp_brace_typedef = force # ignore/add/remove/force # Add or remove space between 'catch' and '{' if on the same line sp_catch_brace = ignore # ignore/add/remove/force # Add or remove space between '}' and 'catch' if on the same line sp_brace_catch = ignore # ignore/add/remove/force # Add or remove space between 'finally' and '{' if on the same line sp_finally_brace = ignore # ignore/add/remove/force # Add or remove space between '}' and 'finally' if on the same line sp_brace_finally = ignore # ignore/add/remove/force # Add or remove space between 'try' and '{' if on the same line sp_try_brace = ignore # ignore/add/remove/force # Add or remove space between get/set and '{' if on the same line sp_getset_brace = ignore # ignore/add/remove/force # Add or remove space between a variable and '{' for C++ uniform initialization sp_word_brace = add # ignore/add/remove/force # Add or remove space between a variable and '{' for a namespace sp_word_brace_ns = add # ignore/add/remove/force # Add or remove space before the '::' operator sp_before_dc = remove # ignore/add/remove/force # Add or remove space after the '::' operator sp_after_dc = remove # ignore/add/remove/force # Add or remove around the D named array initializer ':' operator sp_d_array_colon = ignore # ignore/add/remove/force # Add or remove space after the '!' (not) operator. Default=Remove sp_not = remove # ignore/add/remove/force # Add or remove space after the '~' (invert) operator. Default=Remove sp_inv = remove # ignore/add/remove/force # Add or remove space after the '&' (address-of) operator. Default=Remove # This does not affect the spacing after a '&' that is part of a type. sp_addr = remove # ignore/add/remove/force # Add or remove space around the '.' or '->' operators. Default=Remove sp_member = remove # ignore/add/remove/force # Add or remove space after the '*' (dereference) operator. Default=Remove # This does not affect the spacing after a '*' that is part of a type. sp_deref = remove # ignore/add/remove/force # Add or remove space after '+' or '-', as in 'x = -5' or 'y = +7'. Default=Remove sp_sign = remove # ignore/add/remove/force # Add or remove space before or after '++' and '--', as in '(--x)' or 'y++;'. Default=Remove sp_incdec = remove # ignore/add/remove/force # Add or remove space before a backslash-newline at the end of a line. Default=Add sp_before_nl_cont = add # ignore/add/remove/force # Add or remove space after the scope '+' or '-', as in '-(void) foo;' or '+(int) bar;' sp_after_oc_scope = ignore # ignore/add/remove/force # Add or remove space after the colon in message specs # '-(int) f:(int) x;' vs '-(int) f: (int) x;' sp_after_oc_colon = ignore # ignore/add/remove/force # Add or remove space before the colon in message specs # '-(int) f: (int) x;' vs '-(int) f : (int) x;' sp_before_oc_colon = ignore # ignore/add/remove/force # Add or remove space after the colon in immutable dictionary expression # 'NSDictionary *test = @{@"foo" :@"bar"};' sp_after_oc_dict_colon = ignore # ignore/add/remove/force # Add or remove space before the colon in immutable dictionary expression # 'NSDictionary *test = @{@"foo" :@"bar"};' sp_before_oc_dict_colon = ignore # ignore/add/remove/force # Add or remove space after the colon in message specs # '[object setValue:1];' vs '[object setValue: 1];' sp_after_send_oc_colon = ignore # ignore/add/remove/force # Add or remove space before the colon in message specs # '[object setValue:1];' vs '[object setValue :1];' sp_before_send_oc_colon = ignore # ignore/add/remove/force # Add or remove space after the (type) in message specs # '-(int)f: (int) x;' vs '-(int)f: (int)x;' sp_after_oc_type = ignore # ignore/add/remove/force # Add or remove space after the first (type) in message specs # '-(int) f:(int)x;' vs '-(int)f:(int)x;' sp_after_oc_return_type = ignore # ignore/add/remove/force # Add or remove space between '@selector' and '(' # '@selector(msgName)' vs '@selector (msgName)' # Also applies to @protocol() constructs sp_after_oc_at_sel = ignore # ignore/add/remove/force # Add or remove space between '@selector(x)' and the following word # '@selector(foo) a:' vs '@selector(foo)a:' sp_after_oc_at_sel_parens = ignore # ignore/add/remove/force # Add or remove space inside '@selector' parens # '@selector(foo)' vs '@selector( foo )' # Also applies to @protocol() constructs sp_inside_oc_at_sel_parens = ignore # ignore/add/remove/force # Add or remove space before a block pointer caret # '^int (int arg){...}' vs. ' ^int (int arg){...}' sp_before_oc_block_caret = ignore # ignore/add/remove/force # Add or remove space after a block pointer caret # '^int (int arg){...}' vs. '^ int (int arg){...}' sp_after_oc_block_caret = ignore # ignore/add/remove/force # Add or remove space between the receiver and selector in a message. # '[receiver selector ...]' sp_after_oc_msg_receiver = ignore # ignore/add/remove/force # Add or remove space after @property. sp_after_oc_property = ignore # ignore/add/remove/force # Add or remove space around the ':' in 'b ? t : f' sp_cond_colon = ignore # ignore/add/remove/force # Add or remove space before the ':' in 'b ? t : f'. Overrides sp_cond_colon. sp_cond_colon_before = ignore # ignore/add/remove/force # Add or remove space after the ':' in 'b ? t : f'. Overrides sp_cond_colon. sp_cond_colon_after = ignore # ignore/add/remove/force # Add or remove space around the '?' in 'b ? t : f' sp_cond_question = ignore # ignore/add/remove/force # Add or remove space before the '?' in 'b ? t : f'. Overrides sp_cond_question. sp_cond_question_before = ignore # ignore/add/remove/force # Add or remove space after the '?' in 'b ? t : f'. Overrides sp_cond_question. sp_cond_question_after = ignore # ignore/add/remove/force # In the abbreviated ternary form (a ?: b), add/remove space between ? and :.'. Overrides all other sp_cond_* options. sp_cond_ternary_short = ignore # ignore/add/remove/force # Fix the spacing between 'case' and the label. Only 'ignore' and 'force' make sense here. sp_case_label = remove # ignore/add/remove/force # Control the space around the D '..' operator. sp_range = ignore # ignore/add/remove/force # Control the spacing after ':' in 'for (TYPE VAR : EXPR)' sp_after_for_colon = ignore # ignore/add/remove/force # Control the spacing before ':' in 'for (TYPE VAR : EXPR)' sp_before_for_colon = ignore # ignore/add/remove/force # Control the spacing in 'extern (C)' (D) sp_extern_paren = ignore # ignore/add/remove/force # Control the space after the opening of a C++ comment '// A' vs '//A' sp_cmt_cpp_start = ignore # ignore/add/remove/force # TRUE: If space is added with sp_cmt_cpp_start, do it after doxygen sequences like '///', '///<', '//!' and '//!<'. sp_cmt_cpp_doxygen = false # false/true # TRUE: If space is added with sp_cmt_cpp_start, do it after Qt translator or meta-data comments like '//:', '//=', and '//~'. sp_cmt_cpp_qttr = false # false/true # Controls the spaces between #else or #endif and a trailing comment sp_endif_cmt = ignore # ignore/add/remove/force # Controls the spaces after 'new', 'delete', and 'delete[]' sp_after_new = ignore # ignore/add/remove/force # Controls the spaces between new and '(' in 'new()' sp_between_new_paren = ignore # ignore/add/remove/force # Controls the spaces before a trailing or embedded comment sp_before_tr_emb_cmt = ignore # ignore/add/remove/force # Number of spaces before a trailing or embedded comment sp_num_before_tr_emb_cmt = 0 # number # Control space between a Java annotation and the open paren. sp_annotation_paren = ignore # ignore/add/remove/force # # Code alignment (not left column spaces/tabs) # # Whether to keep non-indenting tabs align_keep_tabs = false # false/true # Whether to use tabs for aligning align_with_tabs = false # false/true # Whether to bump out to the next tab when aligning align_on_tabstop = false # false/true # Whether to right-align numbers #align_number_right = true # false/true # Whether to keep whitespace not required for alignment. align_keep_extra_space = false # false/true # Align variable definitions in prototypes and functions align_func_params = true # false/true # Align parameters in single-line functions that have the same name. # The function names must already be aligned with each other. align_same_func_call_params = false # false/true # The span for aligning variable definitions (0=don't align) align_var_def_span = 1 # number # How to align the star in variable definitions. # 0=Part of the type 'void * foo;' # 1=Part of the variable 'void *foo;' # 2=Dangling 'void *foo;' align_var_def_star_style = 1 # number # How to align the '&' in variable definitions. # 0=Part of the type # 1=Part of the variable # 2=Dangling align_var_def_amp_style = 0 # number # The threshold for aligning variable definitions (0=no limit) align_var_def_thresh = 16 # number # The gap for aligning variable definitions align_var_def_gap = 0 # number # Whether to align the colon in struct bit fields align_var_def_colon = true # false/true # Whether to align any attribute after the variable name align_var_def_attribute = false # false/true # Whether to align inline struct/enum/union variable definitions align_var_def_inline = true # false/true # The span for aligning on '=' in assignments (0=don't align) align_assign_span = 1 # number # The threshold for aligning on '=' in assignments (0=no limit) align_assign_thresh = 12 # number # The span for aligning on '=' in enums (0=don't align) align_enum_equ_span = 16 # number # The threshold for aligning on '=' in enums (0=no limit) align_enum_equ_thresh = 0 # number # The span for aligning struct/union (0=don't align) align_var_struct_span = 99 # number # The threshold for aligning struct/union member definitions (0=no limit) align_var_struct_thresh = 0 # number # The gap for aligning struct/union member definitions align_var_struct_gap = 0 # number # The span for aligning struct initializer values (0=don't align) align_struct_init_span = 4 # number # The minimum space between the type and the synonym of a typedef align_typedef_gap = 4 # number # The span for aligning single-line typedefs (0=don't align) align_typedef_span = 5 # number # How to align typedef'd functions with other typedefs # 0: Don't mix them at all # 1: align the open paren with the types # 2: align the function type name with the other type names align_typedef_func = 0 # number # Controls the positioning of the '*' in typedefs. Just try it. # 0: Align on typedef type, ignore '*' # 1: The '*' is part of type name: typedef int *pint; # 2: The '*' is part of the type, but dangling: typedef int *pint; align_typedef_star_style = 0 # number # Controls the positioning of the '&' in typedefs. Just try it. # 0: Align on typedef type, ignore '&' # 1: The '&' is part of type name: typedef int &pint; # 2: The '&' is part of the type, but dangling: typedef int &pint; align_typedef_amp_style = 0 # number # The span for aligning comments that end lines (0=don't align) align_right_cmt_span = 4 # number # If aligning comments, mix with comments after '}' and #endif with less than 3 spaces before the comment align_right_cmt_mix = false # false/true # If a trailing comment is more than this number of columns away from the text it follows, # it will qualify for being aligned. This has to be > 0 to do anything. align_right_cmt_gap = 0 # number # Align trailing comment at or beyond column N; 'pulls in' comments as a bonus side effect (0=ignore) align_right_cmt_at_col = 0 # number # The span for aligning function prototypes (0=don't align) align_func_proto_span = 0 # number # Minimum gap between the return type and the function name. align_func_proto_gap = 0 # number # Align function protos on the 'operator' keyword instead of what follows align_on_operator = false # false/true # Whether to mix aligning prototype and variable declarations. # If true, align_var_def_XXX options are used instead of align_func_proto_XXX options. align_mix_var_proto = false # false/true # Align single-line functions with function prototypes, uses align_func_proto_span align_single_line_func = false # false/true # Aligning the open brace of single-line functions. # Requires align_single_line_func=true, uses align_func_proto_span align_single_line_brace = false # false/true # Gap for align_single_line_brace. align_single_line_brace_gap = 0 # number # The span for aligning ObjC msg spec (0=don't align) align_oc_msg_spec_span = 0 # number # Whether to align macros wrapped with a backslash and a newline. # This will not work right if the macro contains a multi-line comment. align_nl_cont = true # false/true # # Align macro functions and variables together align_pp_define_together = false # false/true # The minimum space between label and value of a preprocessor define align_pp_define_gap = 4 # number # The span for aligning on '#define' bodies (0=don't align, other=number of lines including comments between blocks) align_pp_define_span = 3 # number # Align lines that start with '<<' with previous '<<'. Default=true align_left_shift = true # false/true # Align text after asm volatile () colons. align_asm_colon = true # false/true # Span for aligning parameters in an Obj-C message call on the ':' (0=don't align) align_oc_msg_colon_span = 0 # number # If true, always align with the first parameter, even if it is too short. align_oc_msg_colon_first = false # false/true # Aligning parameters in an Obj-C '+' or '-' declaration on the ':' align_oc_decl_colon = false # false/true # # Newline adding and removing options # # Whether to collapse empty blocks between '{' and '}' nl_collapse_empty_body = false # false/true # Don't split one-line braced assignments - 'foo_t f = { 1, 2 };' nl_assign_leave_one_liners = true # false/true # Don't split one-line braced statements inside a class xx { } body nl_class_leave_one_liners = true # false/true # Don't split one-line enums: 'enum foo { BAR = 15 };' nl_enum_leave_one_liners = false # false/true # Don't split one-line get or set functions nl_getset_leave_one_liners = false # false/true # Don't split one-line function definitions - 'int foo() { return 0; }' nl_func_leave_one_liners = false # false/true # Don't split one-line C++11 lambdas - '[]() { return 0; }' nl_cpp_lambda_leave_one_liners = false # false/true # Don't split one-line if/else statements - 'if(a) b++;' nl_if_leave_one_liners = false # false/true # Don't split one-line while statements - 'while(a) b++;' nl_while_leave_one_liners = false # false/true # Don't split one-line OC messages nl_oc_msg_leave_one_liner = false # false/true # Add or remove newlines at the start of the file nl_start_of_file = remove # ignore/add/remove/force # The number of newlines at the start of the file (only used if nl_start_of_file is 'add' or 'force' nl_start_of_file_min = 0 # number # Add or remove newline at the end of the file nl_end_of_file = force # ignore/add/remove/force # The number of newlines at the end of the file (only used if nl_end_of_file is 'add' or 'force') nl_end_of_file_min = 1 # number # Add or remove newline between '=' and '{' nl_assign_brace = add # ignore/add/remove/force # Add or remove newline between '=' and '[' (D only) nl_assign_square = ignore # ignore/add/remove/force # Add or remove newline after '= [' (D only). Will also affect the newline before the ']' nl_after_square_assign = ignore # ignore/add/remove/force # The number of blank lines after a block of variable definitions at the top of a function body # 0 = No change (default) nl_func_var_def_blk = 1 # number # The number of newlines before a block of typedefs # 0 = No change (default) nl_typedef_blk_start = 0 # number # The number of newlines after a block of typedefs # 0 = No change (default) nl_typedef_blk_end = 0 # number # The maximum consecutive newlines within a block of typedefs # 0 = No change (default) nl_typedef_blk_in = 0 # number # The number of newlines before a block of variable definitions not at the top of a function body # 0 = No change (default) nl_var_def_blk_start = 0 # number # The number of newlines after a block of variable definitions not at the top of a function body # 0 = No change (default) nl_var_def_blk_end = 0 # number # The maximum consecutive newlines within a block of variable definitions # 0 = No change (default) nl_var_def_blk_in = 0 # number # Add or remove newline between a function call's ')' and '{', as in: # list_for_each(item, &list) { } nl_fcall_brace = add # ignore/add/remove/force # Add or remove newline between 'enum' and '{' nl_enum_brace = force # ignore/add/remove/force # Add or remove newline between 'struct and '{' nl_struct_brace = force # ignore/add/remove/force # Add or remove newline between 'union' and '{' nl_union_brace = force # ignore/add/remove/force # Add or remove newline between 'if' and '{' nl_if_brace = add # ignore/add/remove/force # Add or remove newline between '}' and 'else' nl_brace_else = add # ignore/add/remove/force # Add or remove newline between 'else if' and '{' # If set to ignore, nl_if_brace is used instead nl_elseif_brace = ignore # ignore/add/remove/force # Add or remove newline between 'else' and '{' nl_else_brace = add # ignore/add/remove/force # Add or remove newline between 'else' and 'if' nl_else_if = ignore # ignore/add/remove/force # Add or remove newline between '}' and 'finally' nl_brace_finally = ignore # ignore/add/remove/force # Add or remove newline between 'finally' and '{' nl_finally_brace = ignore # ignore/add/remove/force # Add or remove newline between 'try' and '{' nl_try_brace = ignore # ignore/add/remove/force # Add or remove newline between get/set and '{' nl_getset_brace = force # ignore/add/remove/force # Add or remove newline between 'for' and '{' nl_for_brace = add # ignore/add/remove/force # Add or remove newline between 'catch' and '{' nl_catch_brace = ignore # ignore/add/remove/force # Add or remove newline between '}' and 'catch' nl_brace_catch = ignore # ignore/add/remove/force # Add or remove newline between '}' and ']' nl_brace_square = ignore # ignore/add/remove/force # Add or remove newline between '}' and ')' in a function invocation nl_brace_fparen = ignore # ignore/add/remove/force # Add or remove newline between 'while' and '{' nl_while_brace = add # ignore/add/remove/force # Add or remove newline between 'scope (x)' and '{' (D) nl_scope_brace = ignore # ignore/add/remove/force # Add or remove newline between 'unittest' and '{' (D) nl_unittest_brace = ignore # ignore/add/remove/force # Add or remove newline between 'version (x)' and '{' (D) nl_version_brace = ignore # ignore/add/remove/force # Add or remove newline between 'using' and '{' nl_using_brace = ignore # ignore/add/remove/force # Add or remove newline between two open or close braces. # Due to general newline/brace handling, REMOVE may not work. nl_brace_brace = ignore # ignore/add/remove/force # Add or remove newline between 'do' and '{' nl_do_brace = add # ignore/add/remove/force # Add or remove newline between '}' and 'while' of 'do' statement nl_brace_while = remove # ignore/add/remove/force # Add or remove newline between 'switch' and '{' nl_switch_brace = add # ignore/add/remove/force # Add or remove newline between 'synchronized' and '{' nl_synchronized_brace = ignore # ignore/add/remove/force # Add a newline between ')' and '{' if the ')' is on a different line than the if/for/etc. # Overrides nl_for_brace, nl_if_brace, nl_switch_brace, nl_while_switch, and nl_catch_brace. nl_multi_line_cond = false # false/true # Force a newline in a define after the macro name for multi-line defines. nl_multi_line_define = true # false/true # Whether to put a newline before 'case' statement nl_before_case = true # false/true # Add or remove newline between ')' and 'throw' nl_before_throw = ignore # ignore/add/remove/force # Whether to put a newline after 'case' statement nl_after_case = true # false/true # Add or remove a newline between a case ':' and '{'. Overrides nl_after_case. nl_case_colon_brace = ignore # ignore/add/remove/force # Newline between namespace and { nl_namespace_brace = ignore # ignore/add/remove/force # Add or remove newline between 'template<>' and whatever follows. nl_template_class = ignore # ignore/add/remove/force # Add or remove newline between 'class' and '{' nl_class_brace = ignore # ignore/add/remove/force # Add or remove newline after each ',' in the class base list nl_class_init_args = ignore # ignore/add/remove/force # Add or remove newline after each ',' in the constructor member initialization nl_constr_init_args = ignore # ignore/add/remove/force # Add or remove newline between return type and function name in a function definition nl_func_type_name = ignore # ignore/add/remove/force # Add or remove newline between return type and function name inside a class {} # Uses nl_func_type_name or nl_func_proto_type_name if set to ignore. nl_func_type_name_class = ignore # ignore/add/remove/force # Add or remove newline between function scope and name in a definition # Controls the newline after '::' in 'void A::f() { }' nl_func_scope_name = ignore # ignore/add/remove/force # Add or remove newline between return type and function name in a prototype nl_func_proto_type_name = ignore # ignore/add/remove/force # Add or remove newline between a function name and the opening '(' nl_func_paren = remove # ignore/add/remove/force # Add or remove newline between a function name and the opening '(' in the definition nl_func_def_paren = ignore # ignore/add/remove/force # Add or remove newline after '(' in a function declaration nl_func_decl_start = ignore # ignore/add/remove/force # Add or remove newline after '(' in a function definition nl_func_def_start = ignore # ignore/add/remove/force # Overrides nl_func_decl_start when there is only one parameter. nl_func_decl_start_single = ignore # ignore/add/remove/force # Overrides nl_func_def_start when there is only one parameter. nl_func_def_start_single = ignore # ignore/add/remove/force # Add or remove newline after each ',' in a function declaration nl_func_decl_args = ignore # ignore/add/remove/force # Add or remove newline after each ',' in a function definition nl_func_def_args = ignore # ignore/add/remove/force # Add or remove newline before the ')' in a function declaration nl_func_decl_end = ignore # ignore/add/remove/force # Add or remove newline before the ')' in a function definition nl_func_def_end = ignore # ignore/add/remove/force # Overrides nl_func_decl_end when there is only one parameter. nl_func_decl_end_single = ignore # ignore/add/remove/force # Overrides nl_func_def_end when there is only one parameter. nl_func_def_end_single = ignore # ignore/add/remove/force # Add or remove newline between '()' in a function declaration. nl_func_decl_empty = ignore # ignore/add/remove/force # Add or remove newline between '()' in a function definition. nl_func_def_empty = ignore # ignore/add/remove/force # Whether to put each OC message parameter on a separate line # See nl_oc_msg_leave_one_liner nl_oc_msg_args = false # false/true # Add or remove newline between function signature and '{' nl_fdef_brace = add # ignore/add/remove/force # Add or remove newline between C++11 lambda signature and '{' nl_cpp_ldef_brace = ignore # ignore/add/remove/force # Add or remove a newline between the return keyword and return expression. nl_return_expr = ignore # ignore/add/remove/force # Whether to put a newline after semicolons, except in 'for' statements nl_after_semicolon = true # false/true # Java: Control the newline between the ')' and '{{' of the double brace initializer. nl_paren_dbrace_open = ignore # ignore/add/remove/force # Whether to put a newline after brace open. # This also adds a newline before the matching brace close. nl_after_brace_open = true # false/true # If nl_after_brace_open and nl_after_brace_open_cmt are true, a newline is # placed between the open brace and a trailing single-line comment. nl_after_brace_open_cmt = false # false/true # Whether to put a newline after a virtual brace open with a non-empty body. # These occur in un-braced if/while/do/for statement bodies. nl_after_vbrace_open = false # false/true # Whether to put a newline after a virtual brace open with an empty body. # These occur in un-braced if/while/do/for statement bodies. nl_after_vbrace_open_empty = false # false/true # Whether to put a newline after a brace close. # Does not apply if followed by a necessary ';'. nl_after_brace_close = true # false/true # Whether to put a newline after a virtual brace close. # Would add a newline before return in: 'if (foo) a++; return;' nl_after_vbrace_close = false # false/true # Control the newline between the close brace and 'b' in: 'struct { int a; } b;' # Affects enums, unions, and structures. If set to ignore, uses nl_after_brace_close nl_brace_struct_var = ignore # ignore/add/remove/force # Whether to alter newlines in '#define' macros nl_define_macro = false # false/true # Whether to not put blanks after '#ifxx', '#elxx', or before '#endif'. Does not affect the whole-file #ifdef. nl_squeeze_ifdef = true # false/true # Add or remove blank line before 'if' nl_before_if = ignore # ignore/add/remove/force # Add or remove blank line after 'if' statement nl_after_if = ignore # ignore/add/remove/force # Add or remove blank line before 'for' nl_before_for = ignore # ignore/add/remove/force # Add or remove blank line after 'for' statement nl_after_for = ignore # ignore/add/remove/force # Add or remove blank line before 'while' nl_before_while = ignore # ignore/add/remove/force # Add or remove blank line after 'while' statement nl_after_while = ignore # ignore/add/remove/force # Add or remove blank line before 'switch' nl_before_switch = ignore # ignore/add/remove/force # Add or remove blank line after 'switch' statement nl_after_switch = ignore # ignore/add/remove/force # Add or remove blank line before 'synchronized' nl_before_synchronized = ignore # ignore/add/remove/force # Add or remove blank line after 'synchronized' statement nl_after_synchronized = ignore # ignore/add/remove/force # Add or remove blank line before 'do' nl_before_do = ignore # ignore/add/remove/force # Add or remove blank line after 'do/while' statement nl_after_do = ignore # ignore/add/remove/force # Whether to double-space commented-entries in struct/enum nl_ds_struct_enum_cmt = false # false/true # Whether to double-space before the close brace of a struct/union/enum # (lower priority than 'eat_blanks_before_close_brace') nl_ds_struct_enum_close_brace = false # false/true # Add or remove a newline around a class colon. # Related to pos_class_colon, nl_class_init_args, and pos_class_comma. nl_class_colon = ignore # ignore/add/remove/force # Add or remove a newline around a class constructor colon. # Related to pos_constr_colon, nl_constr_init_args, and pos_constr_comma. nl_constr_colon = ignore # ignore/add/remove/force # Change simple unbraced if statements into a one-liner # 'if(b)\n i++;' => 'if(b) i++;' nl_create_if_one_liner = false # false/true # Change simple unbraced for statements into a one-liner # 'for (i=0;i<5;i++)\n foo(i);' => 'for (i=0;i<5;i++) foo(i);' nl_create_for_one_liner = false # false/true # Change simple unbraced while statements into a one-liner # 'while (i<5)\n foo(i++);' => 'while (i<5) foo(i++);' nl_create_while_one_liner = false # false/true # # Positioning options # # The position of arithmetic operators in wrapped expressions pos_arith = ignore # ignore/join/lead/lead_break/lead_force/trail/trail_break/trail_force # The position of assignment in wrapped expressions. # Do not affect '=' followed by '{' pos_assign = ignore # ignore/join/lead/lead_break/lead_force/trail/trail_break/trail_force # The position of boolean operators in wrapped expressions pos_bool = trail # ignore/join/lead/lead_break/lead_force/trail/trail_break/trail_force # The position of comparison operators in wrapped expressions pos_compare = ignore # ignore/join/lead/lead_break/lead_force/trail/trail_break/trail_force # The position of conditional (b ? t : f) operators in wrapped expressions pos_conditional = ignore # ignore/join/lead/lead_break/lead_force/trail/trail_break/trail_force # The position of the comma in wrapped expressions pos_comma = ignore # ignore/join/lead/lead_break/lead_force/trail/trail_break/trail_force # The position of the comma in the class base list pos_class_comma = ignore # ignore/join/lead/lead_break/lead_force/trail/trail_break/trail_force # The position of the comma in the constructor initialization list pos_constr_comma = ignore # ignore/join/lead/lead_break/lead_force/trail/trail_break/trail_force # The position of colons between class and base class list pos_class_colon = ignore # ignore/join/lead/lead_break/lead_force/trail/trail_break/trail_force # The position of colons between constructor and member initialization pos_constr_colon = ignore # ignore/join/lead/lead_break/lead_force/trail/trail_break/trail_force # # Line Splitting options # # Try to limit code width to N number of columns code_width = 0 # number # Whether to fully split long 'for' statements at semi-colons ls_for_split_full = false # false/true # Whether to fully split long function protos/calls at commas ls_func_split_full = false # false/true # Whether to split lines as close to code_width as possible and ignore some groupings ls_code_width = false # false/true # # Blank line options # # The maximum consecutive newlines nl_max = 4 # number # The number of newlines after a function prototype, if followed by another function prototype nl_after_func_proto = 0 # number # The number of newlines after a function prototype, if not followed by another function prototype nl_after_func_proto_group = 2 # number # The number of newlines after '}' of a multi-line function body nl_after_func_body = 3 # number # The number of newlines after '}' of a multi-line function body in a class declaration nl_after_func_body_class = 2 # number # The number of newlines after '}' of a single line function body nl_after_func_body_one_liner = 0 # number # The minimum number of newlines before a multi-line comment. # Doesn't apply if after a brace open or another multi-line comment. nl_before_block_comment = 2 # number # The minimum number of newlines before a single-line C comment. # Doesn't apply if after a brace open or other single-line C comments. nl_before_c_comment = 0 # number # The minimum number of newlines before a CPP comment. # Doesn't apply if after a brace open or other CPP comments. nl_before_cpp_comment = 0 # number # Whether to force a newline after a multi-line comment. nl_after_multiline_comment = false # false/true # Whether to force a newline after a label's colon. nl_after_label_colon = false # false/true # The number of newlines after '}' or ';' of a struct/enum/union definition nl_after_struct = 0 # number # The number of newlines after '}' or ';' of a class definition nl_after_class = 0 # number # The number of newlines before a 'private:', 'public:', 'protected:', 'signals:', or 'slots:' label. # Will not change the newline count if after a brace open. # 0 = No change. nl_before_access_spec = 0 # number # The number of newlines after a 'private:', 'public:', 'protected:', 'signals:', or 'slots:' label. # 0 = No change. nl_after_access_spec = 0 # number # The number of newlines between a function def and the function comment. # 0 = No change. nl_comment_func_def = 1 # number # The number of newlines after a try-catch-finally block that isn't followed by a brace close. # 0 = No change. nl_after_try_catch_finally = 0 # number # The number of newlines before and after a property, indexer or event decl. # 0 = No change. nl_around_cs_property = 0 # number # The number of newlines between the get/set/add/remove handlers in C#. # 0 = No change. nl_between_get_set = 0 # number # Add or remove newline between C# property and the '{' nl_property_brace = ignore # ignore/add/remove/force # Whether to remove blank lines after '{' eat_blanks_after_open_brace = true # false/true # Whether to remove blank lines before '}' eat_blanks_before_close_brace = true # false/true # How aggressively to remove extra newlines not in preproc. # 0: No change # 1: Remove most newlines not handled by other config # 2: Remove all newlines and reformat completely by config nl_remove_extra_newlines = 0 # number # Whether to put a blank line before 'return' statements, unless after an open brace. nl_before_return = false # false/true # Whether to put a blank line after 'return' statements, unless followed by a close brace. nl_after_return = true # false/true # Whether to put a newline after a Java annotation statement. # Only affects annotations that are after a newline. nl_after_annotation = ignore # ignore/add/remove/force # Controls the newline between two annotations. nl_between_annotation = ignore # ignore/add/remove/force # # Code modifying options (non-whitespace) # # Add or remove braces on single-line 'do' statement mod_full_brace_do = add # ignore/add/remove/force # Add or remove braces on single-line 'for' statement mod_full_brace_for = add # ignore/add/remove/force # Add or remove braces on single-line function definitions. (Pawn) mod_full_brace_function = ignore # ignore/add/remove/force # Add or remove braces on single-line 'if' statement. Will not remove the braces if they contain an 'else'. mod_full_brace_if = add # ignore/add/remove/force # Make all if/elseif/else statements in a chain be braced or not. Overrides mod_full_brace_if. # If any must be braced, they are all braced. If all can be unbraced, then the braces are removed. mod_full_brace_if_chain = false # false/true # Don't remove braces around statements that span N newlines mod_full_brace_nl = 0 # number # Add or remove braces on single-line 'while' statement mod_full_brace_while = add # ignore/add/remove/force # Add or remove braces on single-line 'using ()' statement mod_full_brace_using = ignore # ignore/add/remove/force # Add or remove unnecessary paren on 'return' statement mod_paren_on_return = ignore # ignore/add/remove/force # Whether to change optional semicolons to real semicolons mod_pawn_semicolon = false # false/true # Add parens on 'while' and 'if' statement around bools mod_full_paren_if_bool = true # false/true # Whether to remove superfluous semicolons mod_remove_extra_semicolon = true # false/true # If a function body exceeds the specified number of newlines and doesn't have a comment after # the close brace, a comment will be added. mod_add_long_function_closebrace_comment = 0 # number # If a namespace body exceeds the specified number of newlines and doesn't have a comment after # the close brace, a comment will be added. mod_add_long_namespace_closebrace_comment = 0 # number # If a switch body exceeds the specified number of newlines and doesn't have a comment after # the close brace, a comment will be added. mod_add_long_switch_closebrace_comment = 0 # number # If an #ifdef body exceeds the specified number of newlines and doesn't have a comment after # the #endif, a comment will be added. mod_add_long_ifdef_endif_comment = 0 # number # If an #ifdef or #else body exceeds the specified number of newlines and doesn't have a comment after # the #else, a comment will be added. mod_add_long_ifdef_else_comment = 0 # number # If TRUE, will sort consecutive single-line 'import' statements [Java, D] mod_sort_import = false # false/true # If TRUE, will sort consecutive single-line 'using' statements [C#] mod_sort_using = false # false/true # If TRUE, will sort consecutive single-line '#include' statements [C/C++] and '#import' statements [Obj-C] # This is generally a bad idea, as it may break your code. mod_sort_include = false # false/true # If TRUE, it will move a 'break' that appears after a fully braced 'case' before the close brace. mod_move_case_break = false # false/true # Will add or remove the braces around a fully braced case statement. # Will only remove the braces if there are no variable declarations in the block. mod_case_brace = remove # ignore/add/remove/force # If TRUE, it will remove a void 'return;' that appears as the last statement in a function. mod_remove_empty_return = true # false/true # # Comment modifications # # Try to wrap comments at cmt_width columns cmt_width = 0 # number # Set the comment reflow mode (default: 0) # 0: no reflowing (apart from the line wrapping due to cmt_width) # 1: no touching at all # 2: full reflow cmt_reflow_mode = 0 # number # Whether to convert all tabs to spaces in comments. Default is to leave tabs inside comments alone, unless used for indenting. cmt_convert_tab_to_spaces = false # false/true # If false, disable all multi-line comment changes, including cmt_width. keyword substitution, and leading chars. # Default is true. cmt_indent_multi = true # false/true # Whether to group c-comments that look like they are in a block cmt_c_group = false # false/true # Whether to put an empty '/*' on the first line of the combined c-comment cmt_c_nl_start = false # false/true # Whether to put a newline before the closing '*/' of the combined c-comment cmt_c_nl_end = false # false/true # Whether to group cpp-comments that look like they are in a block cmt_cpp_group = false # false/true # Whether to put an empty '/*' on the first line of the combined cpp-comment cmt_cpp_nl_start = false # false/true # Whether to put a newline before the closing '*/' of the combined cpp-comment cmt_cpp_nl_end = false # false/true # Whether to change cpp-comments into c-comments cmt_cpp_to_c = false # false/true # Whether to put a star on subsequent comment lines cmt_star_cont = true # false/true # The number of spaces to insert at the start of subsequent comment lines cmt_sp_before_star_cont = 0 # number # The number of spaces to insert after the star on subsequent comment lines cmt_sp_after_star_cont = 0 # number # For multi-line comments with a '*' lead, remove leading spaces if the first and last lines of # the comment are the same length. Default=True cmt_multi_check_last = true # false/true # The filename that contains text to insert at the head of a file if the file doesn't start with a C/C++ comment. # Will substitute $(filename) with the current file's name. cmt_insert_file_header = "" # string # The filename that contains text to insert at the end of a file if the file doesn't end with a C/C++ comment. # Will substitute $(filename) with the current file's name. cmt_insert_file_footer = "" # string # The filename that contains text to insert before a function implementation if the function isn't preceded with a C/C++ comment. # Will substitute $(function) with the function name and $(javaparam) with the javadoc @param and @return stuff. # Will also substitute $(fclass) with the class name: void CFoo::Bar() { ... } cmt_insert_func_header = "" # string # The filename that contains text to insert before a class if the class isn't preceded with a C/C++ comment. # Will substitute $(class) with the class name. cmt_insert_class_header = "" # string # The filename that contains text to insert before a Obj-C message specification if the method isn't preceded with a C/C++ comment. # Will substitute $(message) with the function name and $(javaparam) with the javadoc @param and @return stuff. cmt_insert_oc_msg_header = "" # string # If a preprocessor is encountered when stepping backwards from a function name, then # this option decides whether the comment should be inserted. # Affects cmt_insert_oc_msg_header, cmt_insert_func_header and cmt_insert_class_header. cmt_insert_before_preproc = false # false/true # # Preprocessor options # # Control indent of preprocessors inside #if blocks at brace level 0 (file-level) pp_indent = remove # ignore/add/remove/force # Whether to indent #if/#else/#endif at the brace level (true) or from column 1 (false) pp_indent_at_level = false # false/true # Specifies the number of columns to indent preprocessors per level at brace level 0 (file-level). # If pp_indent_at_level=false, specifies the number of columns to indent preprocessors per level at brace level > 0 (function-level). # Default=1. pp_indent_count = 1 # number # Add or remove space after # based on pp_level of #if blocks pp_space = ignore # ignore/add/remove/force # Sets the number of spaces added with pp_space pp_space_count = 0 # number # The indent for #region and #endregion in C# and '#pragma region' in C/C++ pp_indent_region = 0 # number # Whether to indent the code between #region and #endregion pp_region_indent_code = false # false/true # If pp_indent_at_level=true, sets the indent for #if, #else, and #endif when not at file-level. # 0: indent preprocessors using output_tab_size. # >0: column at which all preprocessors will be indented. pp_indent_if = 0 # number # Control whether to indent the code between #if, #else and #endif. pp_if_indent_code = false # false/true # Whether to indent '#define' at the brace level (true) or from column 1 (false) pp_define_at_level = false # false/true # # Use or Do not Use options # # True: indent_func_call_param will be used # False: indent_func_call_param will NOT be used use_indent_func_call_param = true # false/true # True: indent_continue will be used only once # False: indent_continue will be used every time (default) use_indent_continue_only_once = false # false/true # You can force a token to be a type with the 'type' option. # Example: # type myfoo1 myfoo2 # # You can create custom macro-based indentation using macro-open, # macro-else and macro-close. # Example: # macro-open BEGIN_TEMPLATE_MESSAGE_MAP # macro-open BEGIN_MESSAGE_MAP # macro-close END_MESSAGE_MAP # # You can assign any keyword to any type with the set option. # set func_call_user _ N_ # # The full syntax description of all custom definition config entries # is shown below: # # define custom tokens as: # - embed whitespace in token using '' escape character, or # put token in quotes # - these: ' " and ` are recognized as quote delimiters # # type token1 token2 token3 ... # ^ optionally specify multiple tokens on a single line # define def_token output_token # ^ output_token is optional, then NULL is assumed # macro-open token # macro-close token # macro-else token # set id token1 token2 ... # ^ optionally specify multiple tokens on a single line # ^ id is one of the names in token_enum.h sans the CT_ prefix, # e.g. PP_PRAGMA # # all tokens are separated by any mix of ',' commas, '=' equal signs # and whitespace (space, tab) # # You can add support for other file extensions using the 'file_ext' command. # The first arg is the language name used with the '-l' option. # The remaining args are file extensions, matched with 'endswith'. # file_ext CPP .ch .cxx .cpp.in # ovh-ttyrec-1.1.7.1/update-spec-file-changelog-from-debian.pl000077500000000000000000000025141450203670500235640ustar00rootroot00000000000000#! /usr/bin/perl use strict; use warnings; use File::Copy; my $ver; my $latestver; my @changes; my $changelog_fd; my $oldspec_fd; my $newspec_fd; my $first = 1; open($oldspec_fd, '<', 'ovh-ttyrec.spec') or die $!; # first, copy all the old spec contents up to %changelog my @contents; while (<$oldspec_fd>) { push @contents, $_; if (/^%changelog/) { last; } } close($oldspec_fd); open($changelog_fd, '<', 'debian/changelog') or die $!; while (<$changelog_fd>) { if (m{^ovh-ttyrec \(([^)]+)\)}) { $ver = $1; $latestver = $ver if not defined $latestver; } elsif (m{^ -- (.+)\s+(...), (..) (...) (....)}) { my ($author,$wday,$day,$month,$year) = ($1,$2,$3,$4,$5); # from: Thu, 15 Sep 2020 10:59:22 +0200 # to: Wed Nov 04 2020 my $date = "$wday $month $day $year"; if (@changes) { s/^\*/-/ for @changes; } push @contents, "\n" if $first == 0; $first = 0; push @contents, "* $date $author $ver\n"; push @contents, join("\n", @changes); push @contents, "\n"; undef $ver; @changes = (); } elsif (m{^ (\* .+)}) { push @changes, $1; } elsif (m{^ (.+)}) { push @changes, $1; } } close($changelog_fd); s/^Version: .*/Version: $latestver/ for @contents; open($newspec_fd, '>', 'ovh-ttyrec.spec.tmp') or die $!; print $newspec_fd join("", @contents); close($newspec_fd); move("ovh-ttyrec.spec.tmp", "ovh-ttyrec.spec");