├── .copr └── Makefile ├── .gitmodules ├── .travis.yml ├── CMakeLists.txt ├── COPYING ├── README.md ├── addrof.sh ├── adjtimex.c ├── arsort.sh ├── ascii.py ├── benchmark.py ├── benchmark.sh ├── build-rpm.sh ├── build-tar.sh ├── check-bat.py ├── check-cert.py ├── check-dnsbl.py ├── check.dbx ├── check2junit.py ├── chromium-extensions.py ├── ci ├── docker │ ├── build.sh │ └── run.sh └── travis │ └── linux │ ├── before_install.sh │ ├── before_script.sh │ ├── config.sh │ ├── install.sh │ └── script.sh ├── config.h.in ├── cpufreq.py ├── dcat.cc ├── dcheck.sh ├── detect-size.py ├── devof.sh ├── disas.sh ├── dtmemtime ├── dtmemtime.d ├── example ├── libcheck.xml ├── libcheck2.xml └── libcheck_junit.xml ├── exec.c ├── firefox-addons.py ├── gen_csv_link_stats64.py ├── gen_pp_link_stats64.sh ├── gms-utils.spec ├── gs-ext.py ├── hc_mail.py ├── hcheck.c ├── inhibit.py ├── isempty.py ├── latest-kernel-running.sh ├── link_stats64.c ├── lockf.c ├── lsata.sh ├── macgen.py ├── macgen.sh ├── matrixto.py ├── netio2csv.py ├── oldprocs.cc ├── otherhost.py ├── pargs.c ├── pargs_tmpl.c ├── pdfmerge.py ├── perfstat.sh ├── ping.py ├── pldd.py ├── pldd.sh ├── pq.cc ├── pwhatch.sh ├── remove.py ├── reset-tmux.sh ├── ripdvd ├── rtcdelta.c ├── searchb-rs ├── Cargo.toml └── src │ └── main.rs ├── searchb.c ├── searchb.cc ├── searchb.go ├── searchb.py ├── silence.c ├── silence.cc ├── swap.c ├── syscalls.cc ├── syscalls.hh ├── tailuart.py ├── test ├── arsort.py ├── busy_snooze.c ├── dcat.py ├── echo.sh ├── fail.c ├── in │ ├── README.md │ ├── core.snooze.ppc64.1400.xz │ ├── core.snooze.ppc64.abrt.1622.xz │ ├── core.snooze.x86_64.11685.xz │ ├── core.snooze.x86_64.coredumpctl.11677.xz │ ├── core.snooze32.ppc64.1630.xz │ ├── core.snooze32.x86_64.11699.xz │ └── mk_cores.py ├── lockf.py ├── main.py ├── pargs.py ├── silence.py ├── snooze.c └── swap.py ├── train-spam.sh ├── unrpm.sh ├── user-installed.py ├── utility.h └── wipedev.py /.copr/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: srpm 2 | srpm: 3 | dnf -y install git-core 4 | # work around new git safety checks ... 5 | # we are running as root but Copr checked out this repository as mockbuild:1000 ... 6 | git config --global --add safe.directory $$PWD/libixxx 7 | git config --global --add safe.directory $$PWD/libixxxutil 8 | ./build-tar.sh 9 | rpmbuild --define "_sourcedir $$PWD" --define "_specdir $$PWD" --define "_builddir $$PWD" --define "_rpmdir $(outdir)" --define "_srcrpmdir $(outdir)" -bs gms-utils.spec 10 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libixxx"] 2 | path = libixxx 3 | url = https://github.com/gsauthof/libixxx.git 4 | [submodule "libixxxutil"] 5 | path = libixxxutil 6 | url = https://github.com/gsauthof/libixxxutil.git 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | 2 | # default is ruby, we set python in case we need to do some 3 | # advanced scripting in our setup of the C++ tests 4 | language: python 5 | 6 | matrix: 7 | include: 8 | # ## Temporarily disable docker build, as Travis current docker version 9 | # on Ubuntu 16.04/18.04, i.e. Docker 18.06.0-ce/1.38 breaks 10 | # prctl(PR_SET_PDEATHSIG, SIGTERM) in silence test ... 11 | # - os: linux 12 | # services: docker 13 | # env: 14 | # docker_img=gsauthof/fedora-cxx-devel:29 15 | # docker_flags="--cap-add=SYS_PTRACE --security-opt apparmor:unconfined" 16 | # CMAKE_BUILD_TYPE=Debug 17 | # python: 3.5 18 | - os: linux 19 | dist: focal 20 | addons: 21 | apt: 22 | packages: 23 | - gcc-multilib 24 | - gdb 25 | - libc6-dev-i386 26 | - ninja-build 27 | # this gets us clang-10 on Ubuntu 20.04 28 | # instead of the broken /usr/local/clang-7.0.0/bin/ mess travis provides 29 | - clang 30 | - libc++-dev 31 | - libc++abi-dev 32 | env: 33 | CMAKE_BUILD_TYPE=Release 34 | CC=/usr/lib/llvm-10/bin/clang 35 | CXX=/usr/lib/llvm-10/bin/clang++ 36 | CFLAGS="-O3 -Wall -Wextra -Wno-missing-field-initializers -Wno-parentheses -Wno-missing-braces -Wmissing-prototypes -Wfloat-equal -Wwrite-strings -Wpointer-arith -Wcast-align -Wnull-dereference -Werror=multichar -Werror=sizeof-pointer-memaccess -Werror=return-type -fstrict-aliasing " 37 | CXXFLAGS="-O3 -std=gnu++17 -stdlib=libc++ -Wall -Wextra -Wno-missing-field-initializers -Wno-parentheses -Wno-missing-braces -Wno-unused-local-typedefs -Wfloat-equal -Wpointer-arith -Wcast-align -Wnull-dereference -Wnon-virtual-dtor -Wmissing-declarations -Werror=multichar -Werror=sizeof-pointer-memaccess -Werror=return-type -Werror=delete-non-virtual-dtor -fstrict-aliasing " 38 | python: 3.6 39 | compiler: clang 40 | - os: linux 41 | dist: focal 42 | addons: 43 | apt: 44 | packages: 45 | - gcc-multilib 46 | - gdb 47 | - libc6-dev-i386 48 | - ninja-build 49 | env: 50 | CMAKE_BUILD_TYPE=Debug 51 | CFLAGS="-Og -Wall -Wextra -Wno-missing-field-initializers -Wno-parentheses -Wno-missing-braces -Wmissing-prototypes -Wfloat-equal -Wwrite-strings -Wpointer-arith -Wcast-align -Wnull-dereference -Werror=multichar -Werror=sizeof-pointer-memaccess -Werror=return-type -fstrict-aliasing " 52 | CXXFLAGS="-Og -std=gnu++17 -Wall -Wextra -Wno-missing-field-initializers -Wno-parentheses -Wno-missing-braces -Wno-unused-local-typedefs -Wfloat-equal -Wpointer-arith -Wcast-align -Wnull-dereference -Wnon-virtual-dtor -Wmissing-declarations -Werror=multichar -Werror=sizeof-pointer-memaccess -Werror=return-type -Werror=delete-non-virtual-dtor -fstrict-aliasing " 53 | python: 3.6 54 | 55 | before_install: 56 | - ./ci/travis/"$TRAVIS_OS_NAME$tag"/before_install.sh 57 | 58 | install: 59 | - ./ci/travis/"$TRAVIS_OS_NAME$tag"/install.sh 60 | 61 | before_script: 62 | - ./ci/travis/"$TRAVIS_OS_NAME$tag"/before_script.sh 63 | 64 | script: 65 | - ./ci/travis/"$TRAVIS_OS_NAME$tag"/script.sh 66 | 67 | 68 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.7...3.26) 2 | 3 | project(utility C CXX) 4 | 5 | include(CheckFunctionExists) 6 | include(CheckLibraryExists) 7 | include(CMakePushCheckState) 8 | 9 | CMAKE_PUSH_CHECK_STATE(RESET) 10 | set(CMAKE_REQUIRED_DEFINITIONS -D_GNU_SOURCE) 11 | check_function_exists(renameat2 HAVE_RENAMEAT2) 12 | cmake_pop_check_state() 13 | configure_file(config.h.in config.h) 14 | 15 | add_subdirectory(libixxx) 16 | add_subdirectory(libixxxutil) 17 | 18 | if((CMAKE_C_COMPILER_ID STREQUAL "GNU" 19 | AND CMAKE_C_COMPILER_VERSION VERSION_LESS "5") 20 | OR 21 | (CMAKE_C_COMPILER_ID STREQUAL "Clang" 22 | AND CMAKE_C_COMPILER_VERSION VERSION_LESS "4") 23 | ) 24 | if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 25 | set(NO_SANITIZE "-fno-sanitize=address") 26 | endif() 27 | else() 28 | set(NO_SANITIZE "-fno-sanitize=all") 29 | endif() 30 | 31 | check_library_exists(c clock_gettime "" CLOCK_GETTIME_IN_C) 32 | if (CLOCK_GETTIME_IN_C) 33 | set(RT_LIB "") 34 | else() 35 | set(RT_LIB "-lrt") 36 | endif() 37 | 38 | add_executable(silence silence.c) 39 | target_compile_definitions(silence PRIVATE _GNU_SOURCE) 40 | 41 | add_executable(fail test/fail.c) 42 | 43 | 44 | add_executable(silencce silence.cc) 45 | target_link_libraries(silencce PRIVATE 46 | ixxxutil_static 47 | ixxx_static 48 | ) 49 | 50 | 51 | add_executable(oldprocs oldprocs.cc) 52 | target_link_libraries(oldprocs PRIVATE 53 | ixxxutil_static 54 | ixxx_static 55 | ) 56 | 57 | add_executable(lockf lockf.c) 58 | 59 | 60 | add_executable(dcat dcat.cc) 61 | target_link_libraries(dcat PRIVATE 62 | ixxxutil_static 63 | ixxx_static 64 | ) 65 | 66 | add_executable(swap swap.c) 67 | target_include_directories(swap PRIVATE ${CMAKE_BINARY_DIR}) 68 | 69 | add_executable(pargs pargs.c) 70 | target_compile_definitions(pargs PRIVATE _GNU_SOURCE) 71 | 72 | add_executable (pargs32 pargs.c) 73 | target_compile_definitions(pargs32 PRIVATE _GNU_SOURCE) 74 | target_compile_options (pargs32 PRIVATE -m32 ${NO_SANITIZE}) 75 | target_link_options (pargs32 PRIVATE -m32 ${NO_SANITIZE}) 76 | 77 | # NOTE: when compiling with sanitizers (address etc.) it's important 78 | # that these test programs are compiled without those flags because 79 | # we need to create some core files of them. 80 | # Apparently, the sanitizing might blow up the core files, i.e. yielding: 81 | # warning: Failed to write corefile contents (No space left on device). 82 | add_executable (snooze test/snooze.c) 83 | target_compile_options(snooze PRIVATE ${NO_SANITIZE}) 84 | target_link_options (snooze PRIVATE ${NO_SANITIZE}) 85 | add_executable (snooze32 test/snooze.c) 86 | target_compile_options(snooze32 PRIVATE -m32 ${NO_SANITIZE}) 87 | target_link_options (snooze32 PRIVATE -m32 ${NO_SANITIZE}) 88 | add_executable (busy_snooze test/busy_snooze.c) 89 | target_compile_options(busy_snooze PRIVATE ${NO_SANITIZE}) 90 | target_link_options (busy_snooze PRIVATE ${NO_SANITIZE}) 91 | target_link_libraries (busy_snooze PRIVATE ${RT_LIB}) 92 | 93 | add_executable(searchb searchb.c) 94 | 95 | add_executable(searchbxx searchb.cc) 96 | target_link_libraries(searchbxx PRIVATE 97 | ixxxutil_static 98 | ixxx_static 99 | ) 100 | 101 | add_executable(exec exec.c) 102 | 103 | add_executable(adjtimex adjtimex.c) 104 | 105 | add_executable(pq pq.cc syscalls.cc) 106 | target_link_libraries(pq PRIVATE 107 | ixxxutil_static 108 | ixxx_static 109 | ) 110 | 111 | 112 | add_custom_command(OUTPUT pp_link_stats64.c 113 | COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/gen_pp_link_stats64.sh 114 | ARGS pp_link_stats64.c 115 | COMMENT "generate rtnl_link_stats64 pretty-printer" 116 | DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/gen_pp_link_stats64.sh 117 | ) 118 | add_custom_target(pp_link_stats64 DEPENDS pp_link_stats64.c) 119 | 120 | add_custom_command(OUTPUT csv_link_stats64.c 121 | COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/gen_csv_link_stats64.py 122 | ARGS csv_link_stats64.c 123 | COMMENT "generate rtnl_link_stats64 csv printer" 124 | DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/gen_csv_link_stats64.py 125 | ) 126 | add_custom_target(csv_link_stats64 DEPENDS csv_link_stats64.c) 127 | 128 | add_executable(link_stats64 link_stats64.c) 129 | add_dependencies(link_stats64 pp_link_stats64 csv_link_stats64) 130 | target_include_directories(link_stats64 PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) 131 | 132 | 133 | find_package(CURL REQUIRED) 134 | add_executable(hcheck hcheck.c) 135 | target_link_libraries(hcheck PRIVATE CURL::libcurl) 136 | 137 | 138 | add_executable(rtcdelta rtcdelta.c) 139 | 140 | 141 | add_custom_target(check-old 142 | COMMAND env src_dir=${CMAKE_CURRENT_SOURCE_DIR} 143 | ${CMAKE_CURRENT_SOURCE_DIR}/test/main.py 144 | DEPENDS silence silencce fail lockf 145 | COMMENT "run unittests" 146 | ) 147 | 148 | add_custom_target(check-new 149 | # work around py.test-3 pytest etc. system differences 150 | COMMAND python3 -m pytest -v 151 | ${CMAKE_CURRENT_SOURCE_DIR}/user-installed.py 152 | ${CMAKE_CURRENT_SOURCE_DIR}/ascii.py 153 | ${CMAKE_CURRENT_SOURCE_DIR}/test/pargs.py 154 | ${CMAKE_CURRENT_SOURCE_DIR}/test/dcat.py 155 | DEPENDS dcat pargs pargs32 snooze32 snooze busy_snooze swap 156 | COMMENT "run pytests" 157 | ) 158 | 159 | add_custom_target(check DEPENDS check-old check-new) 160 | 161 | 162 | install(TARGETS adjtimex dcat exec hcheck lockf oldprocs pargs pq searchb silence swap 163 | RUNTIME DESTINATION bin) 164 | set(scripts 165 | addrof.sh 166 | arsort.sh 167 | ascii.py 168 | check-cert.py 169 | check-dnsbl.py 170 | chromium-extensions.py 171 | cpufreq.py 172 | detect-size.py 173 | devof.sh 174 | disas.sh 175 | firefox-addons.py 176 | gs-ext.py 177 | inhibit.py 178 | isempty.py 179 | latest-kernel-running.sh 180 | lsata.sh 181 | macgen.py 182 | matrixto.py 183 | pdfmerge.py 184 | pldd.py 185 | pwhatch.sh 186 | remove.py 187 | reset-tmux.sh 188 | ripdvd 189 | unrpm.sh 190 | user-installed.py 191 | wipedev.py 192 | ) 193 | foreach(script ${scripts}) 194 | STRING(REGEX REPLACE "\\.[^.]*$" "" name ${script}) 195 | install(PROGRAMS ${script} DESTINATION bin RENAME ${name}) 196 | endforeach() 197 | -------------------------------------------------------------------------------- /addrof.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # SPDX-FileCopyrightText: © 2020 Georg Sauthoff 4 | # SPDX-License-Identifier: GPL-3.0-or-later 5 | 6 | function help 7 | { 8 | cat <&2 37 | exit 1 38 | fi 39 | 40 | eval set -- "$t" 41 | 42 | while :; do 43 | case "$1" in 44 | -a|--all) 45 | scopeflag= 46 | shift 47 | ;; 48 | -p|--primary) 49 | primaryflag=primary 50 | shift 51 | ;; 52 | -4) 53 | addrflag=-4 54 | shift 55 | ;; 56 | -6) 57 | addrflag=-6 58 | shift 59 | ;; 60 | -h|--help) 61 | help 62 | exit 0 63 | shift 64 | ;; 65 | --) 66 | shift 67 | break 68 | ;; 69 | esac 70 | done 71 | if [ $# -lt 1 ]; then 72 | echo 'device name missing' >&2 73 | exit 1 74 | fi 75 | dev=$1 76 | } 77 | 78 | parse_args "$@" 79 | 80 | ip $addrflag -o addr show dev "$dev" up $primaryflag $scopeflag \ 81 | | awk -F' +|/' '{ print $4}' 82 | 83 | -------------------------------------------------------------------------------- /adjtimex.c: -------------------------------------------------------------------------------- 1 | // 2020, Georg Sauthoff 2 | // 3 | // Display some clock related system settings. 4 | // 5 | // SPDX-License-Identifier: GPL-3.0-or-later 6 | 7 | #include 8 | 9 | #include 10 | 11 | int main() 12 | { 13 | struct timex t = {0}; 14 | 15 | int r = adjtimex(&t); 16 | if (r == -1) { 17 | perror("adjtimex"); 18 | return 1; 19 | } 20 | 21 | // this flag is removed by NTP/daemons such as chrony/ptp4l 22 | // exception: the Solarflare PTPd doesn't remove this flag 23 | // with STA_UNSYNC unset the kernel writes to the RTC every 11 minutes 24 | printf("Clock is %ssynchronized (%s)\n", t.status & STA_UNSYNC ? "un" : "", 25 | t.status & STA_UNSYNC ? "STA_UNSYNC" : "STA_UNSYNC unset"); 26 | 27 | printf("Maxerror: %ld us\n", t.maxerror); 28 | 29 | // the offset the kernel uses for CLOCK_TAI, 30 | // i.e. clock_gettime(CLOCK_TAI) == clock_gettime(CLOCK_REALTIME) + tai_off 31 | printf("TAI offset: %d s\n", t.tai); 32 | 33 | printf("PPS frequency discipline (STA_PPSFREQ): %s\n", 34 | t.status & STA_PPSFREQ ? "enabled" : "disabled"); 35 | printf("PPS time discipline (STA_PPSTIME): %s\n", 36 | t.status & STA_PPSTIME ? "enabled" : "disabled"); 37 | 38 | return 0; 39 | } 40 | -------------------------------------------------------------------------------- /arsort.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$1" = '-h' -o "$1" = '--help' ]; then 4 | cat < 10 | EOF 11 | 12 | exit 0 13 | fi 14 | 15 | set -e 16 | set -u 17 | 18 | # in general, assuming POSIX and/or GNU versions 19 | # e.g. on Solaris 10, the nm comes from /usr/ccs/bin and 20 | # the rest should come from /opt/csw/gnu 21 | # should be portable, because nm is used with the -P option 22 | : ${awk:=awk} 23 | : ${grep:=grep} 24 | : ${nm:=nm} 25 | : ${sort:=sort} 26 | : ${tac:=tac} 27 | : ${tr:=tr} 28 | : ${tsort:=tsort} 29 | 30 | for i in "$@"; do 31 | 32 | if [ "${i%.a}" = "$i" ]; then continue; fi 33 | 34 | "$nm" -P -g "$i" \ 35 | | "$awk" ' $2 ~ /[UT]/ { print $1 , $2 }' \ 36 | | "$sort" -u \ 37 | | "$sort" -k2 \ 38 | | "$awk" ' $2 ~ /T/ { d[$1]=1; print "'"$i"'", $1 } 39 | $2 ~ /U/ && !d[$1] { print $1 , "'"$i"'" }' \ 40 | 41 | done | "$tsort" \ 42 | | "$tac" \ 43 | | "$grep" '\.a' \ 44 | | "$tr" '\n' ' ' 45 | -------------------------------------------------------------------------------- /ascii.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Pretty print the ASCII table 4 | # 5 | # 2017, Georg Sauthoff , GPLv3+ 6 | 7 | import argparse 8 | import collections 9 | import sys 10 | 11 | def mk_arg_parser(): 12 | p = argparse.ArgumentParser( 13 | formatter_class=argparse.RawDescriptionHelpFormatter, 14 | description='Pretty-print ASCII table', 15 | epilog='...') 16 | p.add_argument('--cols', '-c', type=int, default=4, 17 | help='number of columns') 18 | p.add_argument('--explain', '-x', nargs='?', const='all', 19 | help='lookup some abbreviation Example: HT') 20 | return p 21 | 22 | def parse_args(*a): 23 | arg_parser = mk_arg_parser() 24 | args = arg_parser.parse_args(*a) 25 | if 128 % args.cols != 0: 26 | raise RuntimeError('#columns not a factor of #chars - try e.g. 4 or 8') 27 | return args 28 | 29 | # Source: https://en.wikipedia.org/wiki/ASCII 30 | abbr = { 31 | 0 : ( 'NUL' , 'Null' ), 32 | 1 : ( 'SOH' , 'Start of Heading' ), 33 | 2 : ( 'STX' , 'Start of Text' ), 34 | 3 : ( 'ETX' , 'End of Text' ), 35 | 4 : ( 'EOT' , 'End of Transmission' ), 36 | 5 : ( 'ENQ' , 'Enquiry' ), 37 | 6 : ( 'ACK' , 'Acknowledgement' ), 38 | 7 : ( 'BEL' , 'Bell' ), 39 | 8 : ( 'BS' , 'Backspace' ), 40 | 9 : ( 'HT' , 'Horizontal Tab' ), 41 | 10 : ( 'LF' , 'Line Feed' ), 42 | 11 : ( 'VT' , 'Vertical Tab' ), 43 | 12 : ( 'FF' , 'Form Feed' ), 44 | 13 : ( 'CR' , 'Carriage Return' ), 45 | 14 : ( 'SO' , 'Shift Out' ), 46 | 15 : ( 'SI' , 'Shift In' ), 47 | 16 : ( 'DLE' , 'Data Link Escape' ), 48 | 17 : ( 'DC1' , 'Device Control 1 - often XON' ), 49 | 18 : ( 'DC2' , 'Device Control 2' ), 50 | 19 : ( 'DC3' , 'Device Control 3 - often XOFF' ), 51 | 20 : ( 'DC4' , 'Device Control 4' ), 52 | 21 : ( 'NAK' , 'Negative Acknowledgement' ), 53 | 22 : ( 'SYN' , 'Synchronous Idle' ), 54 | 23 : ( 'ETB' , 'End of Transmission Block' ), 55 | 24 : ( 'CAN' , 'Cancel' ), 56 | 25 : ( 'EM' , 'End of Medium' ), 57 | 26 : ( 'SS' , 'Substitute' ), 58 | 27 : ( 'ESC' , 'Escape' ), 59 | 28 : ( 'FS' , 'File Separator' ), 60 | 29 : ( 'GS' , 'Group Separator' ), 61 | 30 : ( 'RS' , 'Record Separator' ), 62 | 31 : ( 'US' , 'Unit Separator' ), 63 | 32 : ( 'SPC' , 'Space' ), 64 | 127 : ( 'DEL' , 'Delete' ) 65 | } 66 | 67 | desc = collections.OrderedDict((v[0], (v[1], k)) for k, v in abbr.items()) 68 | 69 | def pp_char(p): 70 | if p < 33 or p == 127: 71 | return abbr[p][0] 72 | else: 73 | return chr(p) 74 | 75 | def pp_head(i, w): 76 | return ('{:0' + str(w) + 'b}').format(i) 77 | 78 | def pp_row_head(i, w): 79 | return ('{:0' + str(w) + 'b}').format(i) 80 | 81 | def pp_table(cols=4): 82 | x = int(128/cols) 83 | rhw = cols.bit_length()-1 84 | chw = 7 - cols.bit_length() + 1 85 | if cols > 1: 86 | for j in range(cols): 87 | print('{:>5}'.format(pp_head(j, rhw)), end='') 88 | print() 89 | for i in range(x): 90 | for j in range(cols): 91 | p = i+j*x 92 | print('{:>5}'.format(pp_char(p)), end='') 93 | print(' {}'.format(pp_row_head(i, chw))) 94 | 95 | def explain(s): 96 | if s == 'all': 97 | for k, (v, i) in desc.items(): 98 | print('{:>3} = {} ({})'.format(k, v, i)) 99 | else: 100 | print('{:>3} = {} ({})'.format(s, *desc[s])) 101 | 102 | if __name__ != '__main__': 103 | import io 104 | import unittest.mock as mock 105 | 106 | def test_default_table(): 107 | with mock.patch('sys.stdout', new=io.StringIO()) as fake_out: 108 | main([]) 109 | assert fake_out.getvalue() == ''' 00 01 10 11 110 | NUL SPC @ ` 00000 111 | SOH ! A a 00001 112 | STX " B b 00010 113 | ETX # C c 00011 114 | EOT $ D d 00100 115 | ENQ % E e 00101 116 | ACK & F f 00110 117 | BEL ' G g 00111 118 | BS ( H h 01000 119 | HT ) I i 01001 120 | LF * J j 01010 121 | VT + K k 01011 122 | FF , L l 01100 123 | CR - M m 01101 124 | SO . N n 01110 125 | SI / O o 01111 126 | DLE 0 P p 10000 127 | DC1 1 Q q 10001 128 | DC2 2 R r 10010 129 | DC3 3 S s 10011 130 | DC4 4 T t 10100 131 | NAK 5 U u 10101 132 | SYN 6 V v 10110 133 | ETB 7 W w 10111 134 | CAN 8 X x 11000 135 | EM 9 Y y 11001 136 | SS : Z z 11010 137 | ESC ; [ { 11011 138 | FS < \\ | 11100 139 | GS = ] } 11101 140 | RS > ^ ~ 11110 141 | US ? _ DEL 11111 142 | ''' 143 | 144 | def test_8col_table(): 145 | with mock.patch('sys.stdout', new=io.StringIO()) as fake_out: 146 | main(['-c', '8']) 147 | assert fake_out.getvalue() == ''' 000 001 010 011 100 101 110 111 148 | NUL DLE SPC 0 @ P ` p 0000 149 | SOH DC1 ! 1 A Q a q 0001 150 | STX DC2 " 2 B R b r 0010 151 | ETX DC3 # 3 C S c s 0011 152 | EOT DC4 $ 4 D T d t 0100 153 | ENQ NAK % 5 E U e u 0101 154 | ACK SYN & 6 F V f v 0110 155 | BEL ETB ' 7 G W g w 0111 156 | BS CAN ( 8 H X h x 1000 157 | HT EM ) 9 I Y i y 1001 158 | LF SS * : J Z j z 1010 159 | VT ESC + ; K [ k { 1011 160 | FF FS , < L \\ l | 1100 161 | CR GS - = M ] m } 1101 162 | SO RS . > N ^ n ~ 1110 163 | SI US / ? O _ o DEL 1111 164 | ''' 165 | 166 | def test_explain(): 167 | with mock.patch('sys.stdout', new=io.StringIO()) as fake_out: 168 | main(['-x', 'EOT']) 169 | assert fake_out.getvalue() == 'EOT = End of Transmission (4)\n' 170 | 171 | def test_explain_all(): 172 | with mock.patch('sys.stdout', new=io.StringIO()) as fake_out: 173 | main(['-x']) 174 | o = fake_out.getvalue() 175 | assert 'EOT = End of Transmission (4)\n' in o 176 | assert ' RS = Record Separator (30)' in o 177 | assert o.splitlines().__len__() == 34 178 | 179 | def main(*a): 180 | args = parse_args(*a) 181 | if args.explain: 182 | explain(args.explain) 183 | else: 184 | pp_table(args.cols) 185 | return 0 186 | 187 | if __name__ == '__main__': 188 | sys.exit(main()) 189 | 190 | -------------------------------------------------------------------------------- /benchmark.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 2016, Georg Sauthoff 4 | 5 | set -eu 6 | 7 | time=/usr/bin/time 8 | Rscript=Rscript 9 | 10 | f=$(mktemp) 11 | 12 | function cleanup 13 | { 14 | rm -f "$f" 15 | } 16 | trap cleanup EXIT 17 | 18 | if [ "$1" = -h -o "$1" = --help ]; then 19 | cat < "$f" 39 | for i in $(seq $n); do 40 | "$time" --output "$f" --append --format '%e,%U,%S,%M' "$@" >/dev/null 41 | if [ "$s" -gt 0 ]; then 42 | sleep "$s" 43 | fi 44 | done 45 | 46 | { 47 | echo "n="$n" min Q1 med mean Q3 max std" 48 | "$Rscript" --vanilla -e \ 49 | "b=read.csv(file='$f'); summary(b); options(digits=3); sapply(b, sd);" \ 50 | | sed 's/ [^:]\+:/ /g' \ 51 | | grep -v 'a' \ 52 | | tr ' ' '\n' \ 53 | | grep '[0-9]' \ 54 | | awk 'BEGIN { b[0]="wall"; b[1]="user"; b[2]="sys"; b[3]="rss" } 55 | { a[(NR-1)%4] = a[(NR-1)%4]$0" " } 56 | END {for (i=0; i<4; ++i) 57 | print(b[i]" "a[i]) } ' 58 | } | column -t -o ' | ' 59 | 60 | 61 | -------------------------------------------------------------------------------- /build-rpm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eux 4 | 5 | outdir=$PWD 6 | if [ $# -gt 0 ]; then 7 | outdir=$1 8 | fi 9 | 10 | rpmbuild --without srpm --define "_sourcedir $PWD" \ 11 | --define "_specdir $PWD" --define "_builddir $PWD" \ 12 | --define "_rpmdir $outdir" --define "_srcrpmdir $outdir" \ 13 | -bb gms-utils.spec 14 | -------------------------------------------------------------------------------- /build-tar.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eux 4 | 5 | outdir=$PWD 6 | if [ $# -gt 0 ]; then 7 | outdir=$1 8 | fi 9 | 10 | for i in libixxx libixxxutil ; do 11 | echo $i 12 | pushd $i 13 | git archive --prefix gms-utils/$i/ -o "$outdir/$i.tar" HEAD 14 | popd 15 | done 16 | git archive --prefix gms-utils/ -o "$outdir/main.tar" HEAD 17 | 18 | pushd "$outdir" 19 | for i in {main,libixxx,libixxxutil}.tar; do 20 | tar xf $i 21 | done 22 | tar cfz gms-utils.tar.gz gms-utils 23 | rm {main,libixxx,libixxxutil}.tar 24 | rm -rf gms-utils 25 | 26 | 27 | -------------------------------------------------------------------------------- /check-bat.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Monitor battery charging on an Android device and alarm user when 4 | # the 80 % capacity mark is reached. 5 | # 6 | # Alternatively, it switches off a networked power socket if 7 | # the threshold is reached. 8 | # 9 | # Has to run inside termux environment, in a local session. 10 | # 11 | # SPDX-FileCopyrightText: © 2020 Georg Sauthoff 12 | # SPDX-License-Identifier: GPL-3.0-or-later 13 | 14 | import argparse 15 | import json 16 | import logging 17 | import subprocess 18 | import sys 19 | import time 20 | 21 | import urllib.request 22 | import base64 23 | import os 24 | import configparser 25 | 26 | log = logging.getLogger(__name__) 27 | 28 | 29 | class Relative_Formatter(logging.Formatter): 30 | def format(self, rec): 31 | rec.rel_secs = rec.relativeCreated/1000.0 32 | return super(Relative_Formatter, self).format(rec) 33 | 34 | def setup_logging(): 35 | logging.basicConfig(level=logging.INFO, stream=sys.stdout) 36 | log_format = '{rel_secs:6.1f} {message}' 37 | logging.getLogger().handlers[0].setFormatter( 38 | Relative_Formatter(log_format, style='{')) 39 | 40 | def parse_args(*xs): 41 | p = argparse.ArgumentParser() 42 | p.add_argument('--netio', action='store_true', 43 | help='remote control Netio power socket instead of alarm') 44 | p.add_argument('--config', '-c', default=os.getenv('HOME', '.') + '/.config/check-bat.ini', 45 | help='config file for netio settings (default: %(default)s)') 46 | p.add_argument('--high', type=int, default=80, 47 | help='alarm/power-off threshold (default: %(default)d)') 48 | p.add_argument('--no-wake-lock', dest='wake_lock', action='store_false', default=True, 49 | help="don't obtain wake-lock") 50 | args = p.parse_args(*xs) 51 | return args 52 | 53 | def status(): 54 | o = subprocess.check_output(['termux-battery-status'], universal_newlines=True) 55 | d = json.loads(o) 56 | return d 57 | 58 | 59 | def power_switch(user, password, host, no, action): 60 | # host == http://example.org or https://example.org 61 | req = urllib.request.Request(f'{host}/netio.json', 62 | f'{{"Outputs":[{{"ID":{no},"Action":{action}}}]}}'.encode()) 63 | req.add_header('Authorization', 64 | 'Basic ' + base64.b64encode(':'.join((user, password)).encode()).decode()) 65 | urllib.request.urlopen(req) 66 | 67 | 68 | def alarm(): 69 | p = subprocess.Popen(['termux-tts-speak'], 70 | stdin=subprocess.PIPE, universal_newlines=True, bufsize=1) 71 | for i in range(20): 72 | log.warning('Alarm!') 73 | p.stdin.write('Alarm!\n') 74 | #p.stdin.flush() 75 | time.sleep(1.2) 76 | if i % 10 == 0: 77 | d = status() 78 | if d['plugged'] == 'UNPLUGGED': 79 | break 80 | p.stdin.close() 81 | p.wait() 82 | 83 | class Stay_Awake: 84 | def __init__(self, enabled=True): 85 | self.enabled = enabled 86 | def __enter__(self): 87 | if not self.enabled: 88 | return 89 | subprocess.check_output(['termux-wake-lock']) 90 | return self 91 | def __exit__(self, typ, value, traceback): 92 | if not self.enabled: 93 | return 94 | subprocess.check_output(['termux-wake-unlock']) 95 | 96 | 97 | def main(): 98 | setup_logging() 99 | args = parse_args() 100 | 101 | with Stay_Awake(args.wake_lock): 102 | conf = None 103 | if args.netio: 104 | conf = configparser.ConfigParser() 105 | conf.read(args.config) 106 | power_switch(conf['netio']['user'], conf['netio']['password'], 107 | conf['netio']['host'], conf['netio']['no'], 1) 108 | 109 | i = 0 110 | while True: 111 | d = status() 112 | p = d['percentage'] 113 | t = d['temperature'] 114 | log.info(f'{p:3d} % - {t:6.2f} C') 115 | 116 | if p > args.high: 117 | if args.netio: 118 | return power_switch(conf['netio']['user'], conf['netio']['password'], 119 | conf['netio']['host'], conf['netio']['no'], 0) 120 | else: 121 | return alarm() 122 | 123 | if d['plugged'] == 'UNPLUGGED': 124 | i += 1 125 | else: 126 | i = 0 127 | if i > 1: 128 | break 129 | 130 | time.sleep(60) 131 | 132 | 133 | if __name__ == '__main__': 134 | try: 135 | sys.exit(main()) 136 | except KeyboardInterrupt: 137 | print() 138 | 139 | -------------------------------------------------------------------------------- /check-cert.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | 4 | 5 | # 2016, Georg Sauthoff , GPLv3+ 6 | 7 | 8 | 9 | import sys 10 | import subprocess 11 | import re 12 | import datetime 13 | import logging 14 | 15 | 16 | 17 | logging.basicConfig() 18 | log = logging.getLogger(__name__) 19 | 20 | 21 | 22 | class Cert_Error(Exception): 23 | def __init__(self, msg, output): 24 | super(Cert_Error, self).__init__(msg) 25 | self.output = output 26 | 27 | 28 | 29 | def check_not_expired(lines, now = datetime.datetime.now(datetime.UTC)): 30 | exp = re.compile("expires `([^']+) UTC'") 31 | thresh = now + datetime.timedelta(days=20) 32 | for l in lines: 33 | m = exp.search(l) 34 | if m: 35 | d = datetime.datetime.strptime(m.group(1), '%Y-%m-%d %H:%M:%S').replace(tzinfo=datetime.timezone.utc) 36 | if d <= now: 37 | # should not happen, as gnutls-cli should exit with code != 0 38 | raise ValueError('cert already expired') 39 | if thresh > d: 40 | raise ValueError('cert expires in less than 20 days') 41 | 42 | 43 | 44 | def check_cert(host, port, proto=None): 45 | start = [] 46 | if proto: 47 | start = [ '--starttls-proto', proto] 48 | s = subprocess.check_output(['gnutls-cli', host, '--port', port, '--ocsp'] + start, 49 | stdin=subprocess.DEVNULL, stderr=subprocess.STDOUT) 50 | t = s.decode('utf8') 51 | lines = t.splitlines() 52 | try: 53 | check_not_expired(lines) 54 | except ValueError as e: 55 | raise Cert_Error(str(e), t) 56 | 57 | 58 | 59 | def check_certs(args): 60 | errors = 0 61 | for arg in args: 62 | v = arg.split('_') 63 | if v.__len__() < 2: 64 | raise LookupError('Not enough _ delimited components: {}' + arg) 65 | host = v[0] 66 | port = v[1] 67 | if v.__len__() > 2: 68 | proto = v[2] 69 | else: 70 | proto = None 71 | try: 72 | o = check_cert(host, port, proto) 73 | except (Cert_Error, subprocess.CalledProcessError) as e: 74 | o = e.output if type(e.output) is str else e.output.decode('utf8') 75 | log.error('{}:{} => {} - gnutls-cli output:\n{}'.format(host, port, str(e), o)) 76 | errors = errors + 1 77 | return errors 78 | 79 | 80 | 81 | def main(argv): 82 | if '-h' in argv or '--help' in argv: 83 | print('call: {} HOST1_PORT HOST2_PORT_STARTTLSPROTO ...'.format(argv[0])) 84 | return 0 85 | errors = check_certs(argv[1:]) 86 | return int(errors>0) 87 | 88 | 89 | 90 | if __name__ == '__main__': 91 | sys.exit(main(sys.argv)) 92 | 93 | 94 | -------------------------------------------------------------------------------- /check.dbx: -------------------------------------------------------------------------------- 1 | # See also dcheck.sh for wrapper helper. 2 | # 3 | # 4 | # In comparison with `check -all` plus auto-continue setting enabled, this 5 | # function additionally: 6 | # 7 | # - prints the offending for each issue 8 | # - prints some context 9 | # - doesn't redirect the output to a file 10 | # 11 | # Build for the DBX that is bundled with Solaris Studio 12. 12 | # 13 | # 2017, Georg Sauthoff , WTFPL 14 | 15 | rcheck() { 16 | check -all 17 | if [ 0 -eq 1 ]; then 18 | # skip issues before main() is entered, e.g. caused by constructor 19 | # functions in 3rd party shared lbraries. 20 | # fails when there are multiple definitions of main 21 | stop in main 22 | dbxenv rtc_auto_continue on 23 | run 24 | delete 2 25 | dbxenv rtc_auto_continue off 26 | cont 27 | else 28 | run 29 | fi 30 | while [ -z "$exitcode" ]; do 31 | where 32 | list -w 33 | cont 34 | done 35 | # At least with Solaris Studio 12.3, exitcode is just a boolean, 36 | # meaning that exit-status != 0 are converted to 1. 37 | exit $exitcode 38 | } 39 | 40 | -------------------------------------------------------------------------------- /check2junit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # 2016, Georg Sauthoff , GPLv3+ 4 | 5 | import copy 6 | import lxml 7 | from lxml.builder import E 8 | from lxml import etree 9 | import re 10 | import sys 11 | 12 | # necessary for xpath 13 | ns = etree.FunctionNamespace('http://check.sourceforge.net/ns') 14 | ns.prefix='c' 15 | 16 | # not necessary for xpath 17 | #etree.register_namespace('c', 'http://check.sourceforge.net/ns') 18 | 19 | 20 | # see also 21 | # http://nelsonwells.net/2012/09/how-jenkins-ci-parses-and-displays-junit-output/ 22 | # for a discussion of the JUnit format, as used by Jenkins 23 | 24 | def mk_testcase(case): 25 | name = case.xpath('./c:id')[0].text 26 | iteration = case.xpath('./c:iteration')[0].text 27 | if iteration != '0': 28 | name = name + '_' + iteration 29 | fn = case.xpath('./c:fn')[0].text 30 | fn = re.sub(r'\.[^.]+:.+$', '', fn) 31 | result = case.attrib['result'] 32 | if result == 'success': 33 | duration = case.xpath('./c:duration')[0].text 34 | else: 35 | duration = '0' 36 | r = E('testcase', name=name, classname=fn, time=duration) 37 | if result == 'failure': 38 | message = case.xpath('./c:message')[0].text 39 | err = E('error', message=message) 40 | r.append(err) 41 | return r 42 | 43 | def mk_testsuite(suite, timestamp): 44 | title = suite.xpath('./c:title')[0] 45 | tests = suite.xpath('./c:test') 46 | failures = suite.xpath('./c:test[@result="failure"]') 47 | r = E('testsuite', name=title.text, timestamp=timestamp, 48 | tests=str(tests.__len__()), failures=str(failures.__len__())) 49 | for test in tests: 50 | r.append(mk_testcase(test)) 51 | return r 52 | 53 | def mk_testsuites_P(r, ts): 54 | datetime = ts.xpath('./c:datetime')[0] 55 | timestamp = datetime.text.replace(' ', 'T') 56 | suites = ts.xpath('./c:suite') 57 | for suite in suites: 58 | r.append(mk_testsuite(suite, timestamp)) 59 | return r 60 | 61 | def mk_testsuites(fs): 62 | if type(fs) is list: 63 | filenames = fs 64 | else: 65 | filenames = [fs] 66 | r = E('testsuites') 67 | for filename in filenames: 68 | d = etree.parse(filename) 69 | root = d.getroot() 70 | mk_testsuites_P(r, root) 71 | return r 72 | 73 | def main(argv): 74 | if '-h' in argv or '--help' in argv: 75 | print('''Convert one or many libcheck XML reports into JUnit 76 | compatible output (as understood by Jenkins)''') 77 | return 0 78 | print('''''') 80 | etree.dump(mk_testsuites(argv[1:])) 81 | return 0 82 | 83 | 84 | if __name__ == '__main__': 85 | sys.exit(main(sys.argv)) 86 | 87 | -------------------------------------------------------------------------------- /chromium-extensions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # 2017, Georg Sauthoff , GPLv3+ 4 | 5 | import argparse 6 | import glob 7 | import json 8 | import os 9 | import sys 10 | 11 | home = os.environ['HOME'] 12 | 13 | def mk_arg_parser(): 14 | p = argparse.ArgumentParser( 15 | formatter_class=argparse.RawDescriptionHelpFormatter, 16 | description='List installed chromium extensions', 17 | epilog=''' 18 | 19 | 2017, Georg Sauthoff , GPLv3+''') 20 | p.add_argument('--output', '-o', 21 | metavar='CSVFILE', help='store extension list to a file (default: STDOUT)') 22 | return p 23 | 24 | def parse_args(*a): 25 | arg_parser = mk_arg_parser() 26 | args = arg_parser.parse_args(*a) 27 | args.o = open(args.output, 'w') if args.output else sys.stdout 28 | return args 29 | 30 | def mk_slug(s): 31 | s = s.lower() 32 | s = s.replace(' ', '-') 33 | return s 34 | 35 | def website_in_package(guid): 36 | ps = glob.glob(home 37 | + '/.config/chromium/Default/Extensions/{}/*/package.json'.format( 38 | guid)) 39 | if ps: 40 | d = json.load(open(ps[-1])) 41 | if 'repository' in d: 42 | r = d['repository'] 43 | if 'url' in r: 44 | return r['url'] 45 | 46 | def website_in_manifest(guid): 47 | ms = glob.glob(home 48 | + '/.config/chromium/Default/Extensions/{}/*/manifest.json'.format( 49 | guid)) 50 | if ms: 51 | d = json.load(open(ms[-1])) 52 | if 'homepage_url' in d: 53 | return d['homepage_url'] 54 | 55 | def website(guid): 56 | url = website_in_package(guid) 57 | if not url: 58 | url = website_in_manifest(guid) 59 | if not url: 60 | url = '' 61 | return url 62 | 63 | def run(args): 64 | prefs = json.load(open(home + '/.config/chromium/Default/Preferences')) 65 | settings = prefs['extensions']['settings'] 66 | es = sorted(filter(lambda x:x[1]['was_installed_by_default'] == False, 67 | filter(lambda x:x[1]['path'].startswith('/') == False, 68 | filter(lambda x:'manifest' in x[1], settings.items()))), 69 | key=lambda x:x[1]['manifest']['name']) 70 | print('chrome_url,name,guid,url', file=args.o) 71 | for guid, v in es: 72 | name = v['manifest']['name'] 73 | chrome_url = 'https://chrome.google.com/webstore/detail/{}/{}'\ 74 | .format(mk_slug(name), guid) 75 | url = website(guid) 76 | print('{},{},{},{}'.format(chrome_url, name, guid, url), 77 | file=args.o) 78 | args.o.flush() 79 | 80 | def main(*argv): 81 | args = parse_args(*argv) 82 | return run(args) 83 | 84 | if __name__ == '__main__': 85 | sys.exit(main()) 86 | 87 | -------------------------------------------------------------------------------- /ci/docker/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ex 4 | 5 | CFLAGS="-Wall -Wextra -Wno-missing-field-initializers \ 6 | -Wno-parentheses -Wno-missing-braces \ 7 | -Wmissing-prototypes -Wfloat-equal \ 8 | -Wwrite-strings -Wpointer-arith -Wcast-align \ 9 | -Wnull-dereference \ 10 | -Werror=multichar -Werror=sizeof-pointer-memaccess -Werror=return-type \ 11 | -fstrict-aliasing" 12 | if [ "$CMAKE_BUILD_TYPE" = Release ]; then 13 | CFLAGS="-Og $CFLAGS" 14 | fi 15 | export CFLAGS 16 | 17 | CXXFLAGS="-Wall -Wextra -Wno-missing-field-initializers \ 18 | -Wno-parentheses -Wno-missing-braces \ 19 | -Wno-unused-local-typedefs \ 20 | -Wfloat-equal \ 21 | -Wpointer-arith -Wcast-align \ 22 | -Wnull-dereference \ 23 | -Wnon-virtual-dtor -Wmissing-declarations \ 24 | -Werror=multichar -Werror=sizeof-pointer-memaccess -Werror=return-type \ 25 | -Werror=delete-non-virtual-dtor \ 26 | -fstrict-aliasing" 27 | if [ "$CMAKE_BUILD_TYPE" = Release ]; then 28 | CXXFLAGS="-Og $CXXFLAGS" 29 | fi 30 | export CXXFLAGS 31 | 32 | 33 | cd /srv/build 34 | cmake -G Ninja -DCMAKE_BUILD_TYPE="$CMAKE_BUILD_TYPE" /srv/src 35 | ninja-build $targets 36 | 37 | 38 | -------------------------------------------------------------------------------- /ci/docker/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eux 4 | 5 | cd /srv/build 6 | 7 | # the overlayfs docker 1.12.3/Ubuntu 14/Travis doesn't 8 | # support O_TMPFILE (operation not supported) 9 | # thus using the mapped FS - which is ext4 10 | mkdir -p tmp 11 | export TMPDIR=$PWD/tmp 12 | 13 | stat -f -c %T . 14 | ninja-build check 15 | -------------------------------------------------------------------------------- /ci/travis/linux/before_install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eux 4 | 5 | . "${BASH_SOURCE%/*}"/config.sh 6 | 7 | : ${docker_flags:=} 8 | : ${docker_img:=} 9 | 10 | function setup_dirs 11 | { 12 | mkdir -p "$build" 13 | chmod 770 "$build"/.. 14 | chmod 777 "$build" 15 | } 16 | 17 | # the Docker 1.12.3 under Ubuntu 14/Trusty on Travis 18 | # uses aufs, by default - we want overlayfs instead 19 | # because it is part of the kernel, aufs is abandoned 20 | # and aufs doesn't support renameat2(..., RENAME_EXCHANGE) 21 | function configure_overlayfs 22 | { 23 | pwd; id; uname -a 24 | 25 | sudo service docker stop 26 | ls -l /etc/default/docker 27 | sudo cat /etc/default/docker 28 | sudo bash -c 'echo "DOCKER_OPTS=\"--storage-driver=overlay\"" \ 29 | >> /etc/default/docker' 30 | sudo service docker start 31 | } 32 | 33 | function start_docker 34 | { 35 | docker version 36 | docker create --name devel \ 37 | -v "$src":/srv/src:ro,Z \ 38 | -v "$build":/srv/build:Z \ 39 | $docker_flags \ 40 | $docker_img 41 | 42 | docker start devel 43 | 44 | sleep 4 45 | 46 | docker ps 47 | } 48 | 49 | function enable_ptrace 50 | { 51 | # for e.g. gcore 52 | cat /proc/sys/kernel/yama/ptrace_scope 53 | sudo sysctl kernel.yama.ptrace_scope=0 54 | cat /proc/sys/kernel/yama/ptrace_scope 55 | } 56 | 57 | enable_ptrace 58 | if [ "$docker_img" ]; then 59 | configure_overlayfs 60 | setup_dirs 61 | start_docker 62 | else 63 | # we have to install via pip (instead of apt-get) for travis where 64 | # the python comes from /opt - e.g. /opt/python/3.6.10 65 | pip3 install psutil pytest distro 66 | exit 0 67 | fi 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /ci/travis/linux/before_script.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ex 4 | 5 | . "${BASH_SOURCE%/*}"/config.sh 6 | 7 | function switch_container 8 | { 9 | if [ "$docker_img" -a "$docker_img_b" ] ; then 10 | docker stop devel 11 | docker rm devel 12 | 13 | docker create --name devel \ 14 | -v "$src":/srv/src:ro,Z \ 15 | -v "$build":/srv/build:Z \ 16 | "$docker_img_b" 17 | 18 | docker start devel 19 | fi 20 | } 21 | 22 | function compile 23 | { 24 | mkdir build 25 | cd build 26 | pwd 27 | cmake -DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE -G Ninja .. 28 | ninja -v 29 | } 30 | 31 | if [ "$docker_img" ]; then 32 | switch_container 33 | else 34 | compile 35 | fi 36 | -------------------------------------------------------------------------------- /ci/travis/linux/config.sh: -------------------------------------------------------------------------------- 1 | 2 | # i.e. the checked out git working directory 3 | # currently travis uses $HOME/build/$TRAVIS_REPO_SLUG 4 | # as CWD of a build job 5 | src=$PWD 6 | build_root=$HOME/art 7 | build=$build_root/$TRAVIS_REPO_SLUG 8 | -------------------------------------------------------------------------------- /ci/travis/linux/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ex 4 | 5 | if [ -z "$docker_img" ]; then 6 | exit 0 7 | fi 8 | 9 | # Docker 17.03 has the --env option, e.g. 10 | # --env CMAKE_BUILD_TYPE="$CMAKE_BUILD_TYPE" 11 | # but Travis currently is at 1.12.3 which doesn't have it 12 | # 13 | # -> as of 2018-01, Travis Trusty (Ubuntu 12) is at 14 | # docker 17.09.0.ce 15 | 16 | docker exec --user root --workdir /root devel dnf -y install python3-distro 17 | docker exec devel env \ 18 | CMAKE_BUILD_TYPE="$CMAKE_BUILD_TYPE" \ 19 | targets="$targets" \ 20 | /srv/src/ci/docker/build.sh 21 | -------------------------------------------------------------------------------- /ci/travis/linux/script.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eux 4 | 5 | : ${docker_img:=} 6 | 7 | if [ "$docker_img" ]; then 8 | docker exec devel \ 9 | /srv/src/ci/docker/run.sh 10 | else 11 | cd build 12 | pwd 13 | ninja -v check 14 | fi 15 | -------------------------------------------------------------------------------- /config.h.in: -------------------------------------------------------------------------------- 1 | #ifndef UTILITY_CONFIG_H 2 | #define UTILITY_CONFIG_H 3 | 4 | #cmakedefine HAVE_RENAMEAT2 1 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /cpufreq.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | # SPDX-FileCopyrightText: © 2020 Georg Sauthoff 5 | 6 | import argparse 7 | from decimal import Decimal 8 | import re 9 | import subprocess 10 | import sys 11 | 12 | def mk_arg_parser(): 13 | p = argparse.ArgumentParser(description='Print current CPU frequency in MHz', 14 | epilog=('This tool reads two CPU performance counters (a.k.a. MSRs)' 15 | ' two times. The first counter is incremented in proportion' 16 | ' to the current CPU frequency while the other is incremented' 17 | ' in proportion to the TSC frequency. Thus, this tool also' 18 | ' determines the TSC frequency to compute the current CPU frequency' 19 | ' of each core.' 20 | " In case the CPU frequency isn't stable between the two reads" 21 | " the resulting CPU frequency is the average frequency over" 22 | " the measurement period.") 23 | ) 24 | p.add_argument('--cpu', '-c', 25 | help='Only measure on given CPU/Core (default: all)') 26 | p.add_argument('--delta', '-d', default='1', 27 | help='Duration between the two MSR reads (default: %(default)s)') 28 | p.add_argument('--int', '-i', action='store_true', 29 | help='Just print the integer part of the frequency') 30 | p.add_argument('--verbose', '-v', action='store_true', 31 | help='Display header') 32 | return p 33 | 34 | def parse_args(*a): 35 | arg_parser = mk_arg_parser() 36 | args = arg_parser.parse_args(*a) 37 | return args 38 | 39 | def read_msr(args): 40 | aperf = {} 41 | mperf = {} 42 | cpus = '--cpu=' + args.cpu if args.cpu else '-a' 43 | o = subprocess.check_output( 44 | ['perf', 'stat', '-e', 'msr/aperf/', '-e', 'msr/mperf/', '-A', 45 | cpus, '-x', ' ', '--', 'sleep', args.delta], 46 | stderr=subprocess.STDOUT, universal_newlines=True) 47 | for l in o.splitlines(): 48 | cpu, v, msr = l.split()[0:3] 49 | cpu = int(cpu[3:]) 50 | if 'not' in v: 51 | raise RuntimeError("Can't read MSR - not running as root?") 52 | v = int(v) 53 | if msr.endswith('/aperf/'): 54 | aperf[cpu] = v 55 | elif msr.endswith('/mperf/'): 56 | mperf[cpu] = v 57 | xs = [ (cpu, aperf[cpu], mperf[cpu]) for cpu in sorted(aperf.keys()) ] 58 | return xs 59 | 60 | tsc_re = re.compile(r'^.* ([0-9]+)\.([0-9]{3}) MHz') 61 | 62 | def read_tsc_khz_cmd(cmd): 63 | khz = None 64 | with subprocess.Popen(cmd, stdout=subprocess.PIPE, 65 | universal_newlines=True) as p: 66 | for line in p.stdout: 67 | if ' tsc:' in line and ' MHz' in line: 68 | x, y = tsc_re.match(line).groups() 69 | khz = x + y 70 | if not khz: 71 | raise LookupError('tsc not found') 72 | return khz 73 | 74 | 75 | # cf. https://github.com/gsauthof/osjitter/util.c 76 | def read_tsc_khz(): 77 | try: 78 | return int(open('/sys/devices/system/cpu/cpu0/tsc_freq_khz').read()) 79 | except FileNotFoundError: 80 | pass 81 | try: 82 | return read_tsc_khz_cmd(['journalctl', '-k']) 83 | except LookupError: 84 | pass 85 | return read_tsc_khz_cmd(['dmesg']) 86 | 87 | 88 | # Sources: 89 | # Len Brown (@intel.com): [PATCH] x86: Calculate MHz using APERF/MPERF 90 | # for cpuinfo and scaling_cur_freq. 2016-04-01, LKML, message id 91 | # 52f711be59539723358bea1aa3c368910a68b46d.1459485198.git.len.brown@intel.com, 92 | # patch was not applied 93 | # https://lore.kernel.org/lkml/52f711be59539723358bea1aa3c368910a68b46d.1459485198.git.len.brown@intel.com/ 94 | # Jonathan Corbet (LWN): Frequency-invariant utilization tracking for x86. 2020-04-02, LWN.net 95 | # https://lwn.net/Articles/816388/ 96 | def compute_freq(tsc, aperf, mperf): 97 | return Decimal(tsc) * Decimal(aperf) / Decimal(mperf) 98 | 99 | def main(): 100 | args = parse_args() 101 | tsc_khz = read_tsc_khz() 102 | xs = read_msr(args) 103 | q = Decimal('0') if args.int else Decimal('0.00') 104 | if args.verbose: 105 | print('CPU MHz') 106 | for x in xs: 107 | khz = compute_freq(tsc_khz, *x[1:]) 108 | mhz = khz/Decimal(1000) 109 | print(f'{x[0]} {mhz.quantize(q)}') 110 | 111 | if __name__ == '__main__': 112 | sys.exit(main()) 113 | 114 | -------------------------------------------------------------------------------- /dcat.cc: -------------------------------------------------------------------------------- 1 | // dcat - a decompressing cat 2 | // 3 | // 2018, Georg Sauthoff 4 | // 5 | // SPDX-License-Identifier: GPL-3.0-or-later 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | 19 | #include 20 | #include 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | using namespace std; 27 | 28 | struct Args { 29 | deque filenames; 30 | bool read_from_stdin{false}; 31 | 32 | Args() {} 33 | Args(int argc, char **argv) 34 | { 35 | bool look_for_option = true; 36 | for (int i = 1; i < argc; ++i) { 37 | if (argv[i][0] == '-' && !argv[i][1]) { 38 | read_from_stdin = true; 39 | continue; 40 | } else if (look_for_option) { 41 | if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) { 42 | help(cout, argv[0]); 43 | exit(0); 44 | } 45 | if (argv[i][0] == '-') { 46 | if (argv[i][1] == '-' && !argv[i][2]) { 47 | look_for_option = false; 48 | continue; 49 | } 50 | cerr << "Unknown option: " << argv[i] << '\n'; 51 | help(cerr, argv[0]); 52 | exit(2); 53 | } 54 | } 55 | filenames.push_back(argv[i]); 56 | } 57 | if (read_from_stdin && !filenames.empty()) { 58 | cerr << "Can't mix - (stdin) with some filenames\n"; 59 | exit(2); 60 | } 61 | if (filenames.empty()) 62 | read_from_stdin = true; 63 | } 64 | void help(ostream &o, const char *argv0) 65 | { 66 | o << "Usage: " << argv0 << " [OPTION]... [FILE]...\n" 67 | "\n" 68 | "dcat - decompressing cat\n" 69 | "\n" 70 | "Detects compressed on decompresses them on-the-fly with\n" 71 | "the right helper. Reads from stdin when FILE is - or left out.\n" 72 | "\n" 73 | "Options:\n" 74 | " -h, --help This help screen\n" 75 | "\n"; 76 | } 77 | 78 | }; 79 | 80 | enum class Magic { 81 | NONE, 82 | GZIP, 83 | ZSTANDARD, 84 | LZ4, 85 | XZ, 86 | BZ2 87 | }; 88 | static const map magic2cat = { 89 | { Magic::NONE , "cat" }, 90 | { Magic::GZIP , "zcat" }, 91 | { Magic::ZSTANDARD, "zstdcat" }, 92 | { Magic::LZ4 , "lz4cat" }, 93 | { Magic::XZ , "xzcat" }, 94 | { Magic::BZ2 , "bzcat" } 95 | }; 96 | static const vector, Magic>> bytes2magic = { 97 | { { 0x1f, 0x8b }, Magic::GZIP }, 98 | { { 0x28, 0xb5, 0x2f, 0xfd }, Magic::ZSTANDARD }, 99 | { { 0x04, 0x22, 0x4d, 0x18 }, Magic::LZ4 }, 100 | { { 0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00, 0x00 }, Magic::XZ }, 101 | { { 0x42, 0x5a, 0x68 }, Magic::BZ2 } 102 | }; 103 | 104 | static Magic detect_cat(const unsigned char *begin, 105 | const unsigned char *end) 106 | { 107 | size_t n = end-begin; 108 | for (auto &x : bytes2magic) { 109 | if (x.first.size() <= n 110 | && equal(x.first.begin(), x.first.end(), begin)) { 111 | return x.second; 112 | } 113 | } 114 | return Magic::NONE; 115 | } 116 | 117 | static void exec_cat(Magic magic) 118 | { 119 | const char *s = magic2cat.at(magic); 120 | char *v[2]; 121 | v[0] = const_cast(s); 122 | v[1] = nullptr; 123 | ixxx::posix::execvp(s, v); 124 | } 125 | 126 | static void cat_file(const char *filename) 127 | { 128 | int fd = ixxx::posix::open(filename, O_RDONLY); 129 | vector v(8); 130 | ixxx::util::read_all(fd, v); 131 | Magic magic = detect_cat(&*v.begin(), &*v.end()); 132 | ixxx::posix::lseek(fd, 0, SEEK_SET); 133 | ixxx::posix::dup2(fd, 0); 134 | exec_cat(magic); 135 | } 136 | 137 | static void cat_files(const deque &filenames) 138 | { 139 | for (auto filename : filenames) { 140 | int pid = ixxx::posix::fork(); 141 | if (pid == 0) { // child 142 | cat_file(filename); 143 | } else { // parent 144 | siginfo_t info; 145 | ixxx::posix::waitid(P_PID, pid, &info, WEXITED); 146 | if (info.si_code == CLD_EXITED) { 147 | if (info.si_status) 148 | throw runtime_error("decompress failed (" 149 | + string(filename) + " => " 150 | + to_string(info.si_status) + ")"); 151 | } else { 152 | if (info.si_status == SIGPIPE) { 153 | raise(SIGPIPE); 154 | } else { 155 | throw runtime_error("decompress command terminated by a signal (" 156 | + string(filename) + " => " 157 | + to_string(info.si_status) + ")"); 158 | } 159 | } 160 | } 161 | } 162 | } 163 | 164 | static void cat_stdin() 165 | { 166 | int fd = 0; 167 | vector v(8); 168 | ixxx::util::read_all(fd, v); 169 | Magic magic = detect_cat(&*v.begin(), &*v.end()); 170 | 171 | int pipefd[2]; 172 | ixxx::posix::pipe(pipefd); 173 | int pid = ixxx::posix::fork(); 174 | if (pid == 0) { // child 175 | ixxx::posix::close(pipefd[1]); 176 | ixxx::posix::dup2(pipefd[0], 0); 177 | exec_cat(magic); 178 | } else { // parent 179 | ixxx::posix::close(pipefd[0]); 180 | ixxx::util::write_all(pipefd[1], v); 181 | 182 | size_t n = v.size(); 183 | const size_t N = 128*1024; 184 | v.resize(N - n); 185 | ixxx::util::read_all(fd, v); 186 | ixxx::util::write_all(pipefd[1], v); 187 | v.resize(N); 188 | do { 189 | ixxx::util::read_all(fd, v); 190 | ixxx::util::write_all(pipefd[1], v); 191 | } while (v.size() == N); 192 | ixxx::posix::close(pipefd[1]); 193 | 194 | siginfo_t info; 195 | ixxx::posix::waitid(P_PID, pid, &info, WEXITED); 196 | if (info.si_code == CLD_EXITED) { 197 | if (info.si_status) 198 | throw runtime_error("stdin decompressor failed"); 199 | } else { 200 | throw runtime_error("stdin decompressor terminated by a signal"); 201 | } 202 | } 203 | 204 | } 205 | 206 | int main(int argc, char **argv) 207 | { 208 | Args args(argc, argv); 209 | try { 210 | if (args.filenames.size() == 1) 211 | cat_file(args.filenames.front()); 212 | else if (args.filenames.size() > 1) 213 | cat_files(args.filenames); 214 | else if (args.read_from_stdin) 215 | cat_stdin(); 216 | } catch (const exception &e) { 217 | cerr << "Error: " << e.what() << '\n'; 218 | exit(1); 219 | } 220 | return 0; 221 | } 222 | 223 | -------------------------------------------------------------------------------- /dcheck.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Wrapper around Solaris Studio's DBX runtime checking feature. 4 | # 5 | # It is similar to the `bcheck` utility that is bundled with Solaris 6 | # Studio, although this wrapper prints additional information 7 | # and doesn't redirect the messages to a file. 8 | # 9 | # 2017, Georg Sauthoff , WTFPL 10 | 11 | dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 12 | 13 | prog=$1 14 | shift 15 | 16 | exec dbx -c 'runargs '"$*"'; source '"$dir"'/check.dbx; rcheck' "$prog" 17 | -------------------------------------------------------------------------------- /detect-size.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # detect-size - detect and set real height/width of a terminal 4 | # 5 | # replacement for xterm's resize command for systems that 6 | # don't package it separately (e.g. in xterm-resize as Fedora does) 7 | # 8 | # useful for terminals that are attached to a serial line, 9 | # including emulated ones (e.g. on a VM) 10 | # 11 | # SPDX-License-Identifier: GPL-3.0-or-later 12 | # SPDX-FileCopyrightText: © 2021 Georg Sauthoff 13 | 14 | import fcntl 15 | import os 16 | import struct 17 | import sys 18 | import termios 19 | import tty 20 | 21 | def main(): 22 | # source: https://sources.debian.org/src/xterm/366-1/resize.c/?hl=137#L139 23 | print('\x1b' '7' '\x1b' '[r' '\x1b' '[9999;9999H' '\x1b' '[6n', 24 | flush=True, end='') 25 | 26 | # cf. https://stackoverflow.com/q/40931467/427158 27 | 28 | bak = termios.tcgetattr(sys.stdin.fileno()) 29 | tty.setcbreak(sys.stdin.fileno()) 30 | r = b''.join(iter(lambda : os.read(sys.stdin.fileno(), 1), b'R')) 31 | termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, bak) 32 | 33 | i = r.rindex(b'[') 34 | j = r.index(b';') 35 | 36 | h = int(r[i+1:j]) 37 | w = int(r[j+1:]) 38 | 39 | x = struct.pack('HHHH', h, w, 0, 0) 40 | 41 | print(f'\rheight: {h}, width: {w}') 42 | 43 | fcntl.ioctl(sys.stdout, termios.TIOCSWINSZ, x) 44 | 45 | # alternatively: ioctl() on /dev/tty 46 | 47 | if __name__ == '__main__': 48 | sys.exit(main()) 49 | 50 | -------------------------------------------------------------------------------- /devof.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # SPDX-FileCopyrightText: © 2020 Georg Sauthoff 4 | # SPDX-License-Identifier: GPL-3.0-or-later 5 | 6 | function help 7 | { 8 | cat <&2 30 | exit 1 31 | fi 32 | 33 | eval set -- "$t" 34 | 35 | while :; do 36 | case "$1" in 37 | -h|--help) 38 | help 39 | exit 0 40 | shift 41 | ;; 42 | --) 43 | shift 44 | break 45 | ;; 46 | esac 47 | done 48 | if [ $# -lt 1 ]; then 49 | echo 'device name missing' >&2 50 | exit 1 51 | fi 52 | addr=$1 53 | } 54 | 55 | parse_args "$@" 56 | 57 | ip -o addr show up to "$addr" | cut -d' ' -f2 58 | -------------------------------------------------------------------------------- /disas.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # disas - disassemble single functions 4 | # 5 | # 2020, Georg Sauthoff 6 | # 7 | # SPDX-License-Identifier: GPL-3.0-or-later 8 | 9 | bin= 10 | query_prefix='<' 11 | query= 12 | query_suffix='>:' 13 | use_gdb= 14 | show_next=0 15 | dump=${OBJDUMP:-objdump} 16 | 17 | function parse_args 18 | { 19 | local t 20 | t=$(getopt -o acgnd:h --long address,call,gdb,next,help -n disas -- "$@") 21 | 22 | if [ $? -ne 0 ]; then 23 | echo 'Option parse error' >&2 24 | exit 1 25 | fi 26 | 27 | eval set -- "$t" 28 | 29 | while :; do 30 | case "$1" in 31 | -a|--address) 32 | query_prefix='\n *' 33 | query_suffix=':' 34 | shift 35 | ;; 36 | -c|--call) 37 | query_prefix='<' 38 | query_suffix='[@>]' 39 | shift 40 | ;; 41 | -g|--gdb) 42 | use_gdb=1 43 | dump=${GDB:-gdb} 44 | shift 45 | ;; 46 | -n|--next) 47 | show_next=1 48 | shift 49 | ;; 50 | -d) 51 | dump=$2 52 | shift 2 53 | ;; 54 | -h|--help) 55 | cat <&2 94 | exit 1 95 | fi 96 | bin=$1 97 | shift 98 | query=$1 99 | } 100 | 101 | 102 | function main 103 | { 104 | parse_args "$@" 105 | if [ "$use_gdb" ]; then 106 | "$dump" --batch -q -ex 'set height 0' -ex 'disas '"$query" "$bin" \ 107 | | head -n -1 108 | else 109 | local intel_flags 110 | if file -b "$bin" | grep '\(80386\|x86-64\),' > /dev/null; then 111 | intel_flags='-M intel' 112 | fi 113 | "$dump" $intel_flags -dC $ODFLAGS "$bin" \ 114 | | awk -F'\n' -vRS='\n\n' /"$query_prefix$query$query_suffix"/' { 115 | q=1; print; next } q && 1=='"$show_next"' { print $1 } q { q=0 }' 116 | fi 117 | } 118 | 119 | main "$@" 120 | -------------------------------------------------------------------------------- /dtmemtime: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 2018, Georg Sauthoff , GPLv3+ 4 | 5 | dtrace=/usr/sbin/dtrace 6 | 7 | # cf. https://stackoverflow.com/q/59895/427158 8 | # 9 | origin="$(cd "$( dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" 10 | 11 | script=$origin/dtmemtime.d 12 | 13 | if [ $# = 0 -o "$1" = -h -o "$1" = --help ]; then 14 | cat <, GPLv3+ 21 | EOF 22 | exit 0 23 | fi 24 | 25 | dflags= 26 | if [ "$1" = -o ]; then 27 | dflags="$1 $2" 28 | shift 29 | shift 30 | fi 31 | 32 | exec "$dtrace" $dflags -C -s "$script" -c "$*" 33 | 34 | -------------------------------------------------------------------------------- /dtmemtime.d: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Measure high-water memory usage of a process and its descendants. 4 | 5 | To be called from the shell script wrapper dtmemtime 6 | which basically invokes it as: 7 | 8 | /usr/sbin/dtrace -C -s dtmemtime.d -c 'cmd arg1 ... argn' 9 | 10 | (where -C => run the script through cpp) 11 | 12 | 2018, Georg Sauthoff , GPLv3+ 13 | 14 | */ 15 | 16 | #pragma D option quiet 17 | 18 | #include 19 | 20 | /* Global variables */ 21 | uint64_t global_highwater; 22 | uint64_t global_mem; 23 | uint64_t global_start; 24 | /* uint64_t global_end; */ 25 | 26 | /* Thread local variables */ 27 | self uint64_t highwater; 28 | self uint64_t mem; 29 | 30 | self uint64_t first_brk; 31 | self uint64_t brk_mem; 32 | 33 | self uint64_t proc_start; 34 | self uint64_t proc_end; 35 | 36 | /* Associative array */ 37 | self int mapping[void*]; 38 | 39 | BEGIN 40 | { 41 | global_start = timestamp; 42 | } 43 | 44 | END 45 | { 46 | /* in DTrace format strings, the length modifier is optional, 47 | and when omitted, DTrace automatically recognizes the 48 | right length of a variable for printing */ 49 | printf("total runtime: %d ms, highwater memory: %d bytes\n", 50 | (timestamp-global_start)/1000000, global_highwater); 51 | } 52 | 53 | proc:::lwp-exit 54 | /progenyof($target)/ 55 | { 56 | self->proc_end = timestamp; 57 | self->proc_start = self->proc_start ? self->proc_start : global_start; 58 | 59 | global_mem -= self->mem; 60 | 61 | printf("pid %d (#%d) (%s) runtime: %d ms, highwater memory: %d bytes\n", 62 | pid, tid, execname, 63 | (self->proc_end - self->proc_start)/1000000, 64 | self->highwater); 65 | } 66 | 67 | 68 | syscall::exec*:return 69 | /progenyof($target)/ 70 | { 71 | /* otherwise we may easily get very wrong numbers */ 72 | self->first_brk = 0; 73 | } 74 | 75 | syscall::fork*:return 76 | /progenyof($target) && arg0 == 0/ 77 | { 78 | /* due to copy-on-write, this is probably for the best */ 79 | self->first_brk = 0; 80 | } 81 | 82 | proc:::lwp-start 83 | /progenyof($target)/ 84 | { 85 | self->proc_start = timestamp; 86 | } 87 | 88 | syscall::brk:entry 89 | /progenyof($target) && !self->first_brk/ 90 | { 91 | self->first_brk = (uint64_t)arg0; 92 | } 93 | 94 | syscall::brk:entry 95 | /progenyof($target)/ 96 | { 97 | self->mem -= self->brk_mem; 98 | global_mem -= self->brk_mem; 99 | self->brk_mem = ((uint64_t)arg0) - self->first_brk; 100 | self->mem += self->brk_mem; 101 | global_mem += self->brk_mem; 102 | 103 | self->highwater = self->mem > self->highwater ? 104 | self->mem : self->highwater; 105 | global_highwater = global_mem > global_highwater ? 106 | global_mem : global_highwater; 107 | } 108 | 109 | syscall::mmap*:entry 110 | /progenyof($target) && ((arg3 & MAP_ANON) == MAP_ANON) && ((arg2 & PROT_WRITE)==PROT_WRITE)/ 111 | { 112 | self->mem += (uint64_t)arg1; 113 | self->highwater = self->mem > self->highwater ? 114 | self->mem : self->highwater; 115 | 116 | global_mem += (uint64_t)arg1; 117 | global_highwater = global_mem > global_highwater ? 118 | global_mem : global_highwater; 119 | 120 | self->mapping[(void*)arg0] = 1; 121 | } 122 | 123 | /* gross simplification because: 124 | * 1. it's ok to unmap non-mapped paged 125 | * 2. it's possible to specify the length in non page-multiples 126 | * but still the complete pages are unmapped */ 127 | syscall::munmap*:entry 128 | /progenyof($target) && self->mapping[(void*)arg0]/ 129 | { 130 | self->mem -= (uint64_t)arg1; 131 | global_mem -= (uint64_t)arg1; 132 | 133 | /* zeroing it implicitly deletes the entry in the associative array */ 134 | self->mapping[(void*)arg0] = 0; 135 | } 136 | 137 | 138 | -------------------------------------------------------------------------------- /example/libcheck.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 2016-11-03 09:42:47 5 | 6 | S1 7 | 8 | . 9 | ex_output.c:11 10 | test_pass 11 | 0 12 | 0.000016 13 | Core 14 | Passed 15 | 16 | 17 | . 18 | ex_output.c:17 19 | test_fail 20 | 0 21 | -1.000000 22 | Core 23 | Failure 24 | 25 | 26 | . 27 | ex_output.c:26 28 | test_exit 29 | 0 30 | -1.000000 31 | Core 32 | Early exit with return value 1 33 | 34 | 35 | 36 | S2 37 | 38 | . 39 | ex_output.c:46 40 | test_pass2 41 | 0 42 | 0.000015 43 | Core 44 | Passed 45 | 46 | 47 | . 48 | ex_output.c:52 49 | test_loop 50 | 0 51 | -1.000000 52 | Core 53 | Iteration 0 failed 54 | 55 | 56 | . 57 | ex_output.c:52 58 | test_loop 59 | 1 60 | 0.000015 61 | Core 62 | Passed 63 | 64 | 65 | . 66 | ex_output.c:52 67 | test_loop 68 | 2 69 | -1.000000 70 | Core 71 | Iteration 2 failed 72 | 73 | 74 | 75 | XML escape " ' < > & tests 76 | 77 | . 78 | ex_output.c:58 79 | test_xml_esc_fail_msg 80 | 0 81 | -1.000000 82 | description " ' < > & 83 | fail " ' < > & message 84 | 85 | 86 | 0.003183 87 | 88 | -------------------------------------------------------------------------------- /example/libcheck2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 2016-11-03 21:48:23 5 | 6 | NoFork 7 | 8 | . 9 | check_nofork.c:16 10 | test_nofork_exit 11 | 0 12 | 0.000028 13 | Exit 14 | Assertion '((void *)0) != s' failed 15 | 16 | 17 | 0.000210 18 | 19 | -------------------------------------------------------------------------------- /example/libcheck_junit.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /exec.c: -------------------------------------------------------------------------------- 1 | // 2019, Georg Sauthoff 2 | // 3 | // SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | static void help(FILE *f, const char *argv0) 10 | { 11 | fprintf(f, "call: %s CMD ARGV0 [ARGV1]...\n" 12 | "\n" 13 | "Execute command with a non-default ARGV[0] value.\n" 14 | "\n" 15 | "Standalone replacement for e.g.:\n" 16 | "\n" 17 | " bash -c 'exec -a ARGV0 CMD ARGV1'\n" 18 | "\n" 19 | , argv0 20 | ); 21 | } 22 | 23 | int main(int argc, char **argv) 24 | { 25 | if (argc == 2 && (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))) { 26 | help(stderr, argv[0]); 27 | return 0; 28 | } 29 | if (argc < 3) { 30 | help(stderr, argv[0]); 31 | return 1; 32 | } 33 | int r = execvp(argv[1], argv+2); 34 | if (r == -1) { 35 | perror("execvp"); 36 | return 127; 37 | } 38 | return 0; 39 | } 40 | -------------------------------------------------------------------------------- /firefox-addons.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # 2017, Georg Sauthoff , GPLv3+ 4 | 5 | import argparse 6 | import csv 7 | import glob 8 | import json 9 | import logging 10 | import os 11 | import requests 12 | import sys 13 | 14 | def setup_logging(): 15 | log_format = '%(levelname)-8s - %(message)s' 16 | logging.basicConfig(format=log_format, level=logging.INFO) 17 | 18 | log = logging.getLogger(__name__) 19 | 20 | def mk_arg_parser(): 21 | p = argparse.ArgumentParser( 22 | formatter_class=argparse.RawDescriptionHelpFormatter, 23 | description='List installed Firefox addons', 24 | epilog='''Examples: 25 | 26 | Generate the list of installed addons: 27 | 28 | $ ./firefox-addons.py -o addons.csv 29 | 30 | Open all addon pages in a Firefox window (e.g. for bulk installation): 31 | 32 | $ cut -f1 -d, addon.csv | tail -n +2 | xargs firefox 33 | 34 | 2017, Georg Sauthoff , GPLv3+''') 35 | p.add_argument('--profile', 36 | metavar='DIRECTORY', help='Specify the default directory (default: use the newestet one unter ~/.mozilla/firefox that ends in .default') 37 | p.add_argument('--output', '-o', default='addon.csv', 38 | metavar='CSVFILE', help='information about the installed addons (default: addon.csv)') 39 | return p 40 | 41 | def base(): 42 | return os.environ['HOME'] + '/.mozilla/firefox' 43 | 44 | def default_profile(): 45 | b = base() 46 | return max( 47 | ( (fn, os.stat(fn).st_mtime) 48 | for fn in glob.glob(b + '/*.default') ), 49 | key=lambda x: x[1])[0] 50 | 51 | def parse_args(*a): 52 | arg_parser = mk_arg_parser() 53 | args = arg_parser.parse_args(*a) 54 | if not args.profile: 55 | args.profile = default_profile() 56 | log.info('Using profile: {}'.format(args.profile)) 57 | return args 58 | 59 | def args_exts(profile): 60 | a = json.load(open(profile + '/addons.json')) 61 | a = a['addons'] 62 | a.sort(key=lambda x:x['reviewURL']) 63 | e = json.load(open(profile + '/extensions.json')) 64 | e = e['addons'] 65 | e = dict((x['id'], x) for x in e) 66 | return (a, e) 67 | 68 | # cf. http://addons-server.readthedocs.io/en/latest/topics/api/addons.html#detail 69 | def details(slug, session): 70 | r = session.get('https://addons.mozilla.org//api/v3/addons/addon/{}/' 71 | .format(slug)) 72 | r.raise_for_status() 73 | d = json.loads(r.text) 74 | return d 75 | 76 | def run(args): 77 | session = requests.Session() 78 | addons, exts = args_exts(args.profile) 79 | with open(args.output, 'w', newline='') as f: 80 | w = csv.writer(f, lineterminator=os.linesep) 81 | w.writerow(['mozilla_url', 'slug', 'guid', 'name', 82 | 'compatible_android', 'url' ]) 83 | for a in addons: 84 | if a['id'] not in exts or not exts[a['id']]['active']: 85 | continue 86 | mozilla_url = a['reviewURL'] 87 | if mozilla_url.endswith('reviews/'): 88 | mozilla_url = mozilla_url[:-8] 89 | slug = mozilla_url.split('/')[-2] 90 | guid = a['id'] 91 | d = details(slug, session) 92 | compatible_android = str( 93 | 'android' in d['current_version']['compatibility']).lower() 94 | url = a['homepageURL'] 95 | if url: 96 | url = url.replace('?src=api', '') 97 | w.writerow([ mozilla_url, slug, guid, a['name'], 98 | compatible_android, url ]) 99 | 100 | def imain(*argv): 101 | args = parse_args(*argv) 102 | return run(args) 103 | 104 | def main(*argv): 105 | setup_logging() 106 | return imain(*argv) 107 | 108 | if __name__ == '__main__': 109 | sys.exit(main()) 110 | 111 | -------------------------------------------------------------------------------- /gen_csv_link_stats64.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | 5 | def main(): 6 | ofilename = sys.argv[1] 7 | 8 | xs = [] 9 | with open('/usr/include/linux/if_link.h') as f: 10 | state = 0 11 | for line in f: 12 | if line.startswith('struct rtnl_link_stats64'): 13 | state = 1 14 | elif line.startswith('};'): 15 | state = 0 16 | elif state == 1 and ';' in line: 17 | ks = line.split() 18 | xs.append(ks[1].rstrip(';')) 19 | with open(ofilename, 'w') as f: 20 | header = ','.join(xs) 21 | print(f'''static void pp_csv_header(FILE *o) 22 | {{ 23 | fputs("epoch,name,{header}\\n", o); 24 | }} 25 | ''', file=f) 26 | n = len(xs) 27 | fstr = '",%" PRIu64 ' * n 28 | fstr = '"%zu,%s"' + fstr + '"\\n"' 29 | fstr = fstr.replace('""', '') 30 | args = ', '.join(f'(uint64_t) s->{x}' for x in xs) 31 | print(f'''static void pp_csv_row(FILE *o, time_t epoch, const char *name, const struct rtnl_link_stats64 *s) 32 | {{ 33 | fprintf(o, {fstr}, 34 | (size_t) epoch, name, {args}); 35 | }} 36 | ''', file=f) 37 | 38 | if __name__ == '__main__': 39 | sys.exit(main()) 40 | -------------------------------------------------------------------------------- /gen_pp_link_stats64.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | ofilename="$1" 5 | 6 | awk 'BEGIN { print "// autogenerated"; } 7 | /^struct rtnl_link_stats64/ {x=1; next} 8 | /^};/ {x=0; next} 9 | x && /;/ { 10 | sub(";", "", $2); 11 | printf("if (dump_all || s->%s)\n fprintf(o, \"%%s.%s: %%\" PRIu64 \"\\n\", name, (uint64_t)s->%s);\n", $2, $2, $2); 12 | }' /usr/include/linux/if_link.h > "$ofilename" 13 | -------------------------------------------------------------------------------- /gms-utils.spec: -------------------------------------------------------------------------------- 1 | %bcond_without srpm 2 | 3 | # Do out-of-source-tree builds 4 | # Only required in Fedora < f33 as this is the new default 5 | %if 0%{?fedora} < 33 6 | %undefine __cmake_in_source_build 7 | %endif 8 | 9 | # pq require C++17 - only required for Fedora <= 33 10 | %if 0%{?fedora} <= 33 11 | %global optflags %{optflags} -std=gnu++17 12 | %endif 13 | 14 | Name: gms-utils 15 | Version: 0.6.8 16 | Release: 1%{?dist} 17 | Summary: Collection of command line utilities 18 | URL: https://github.com/gsauthof/utility 19 | License: GPLv3+ 20 | Source: https://example.org/gms-utils.tar.gz 21 | 22 | BuildRequires: cmake 23 | BuildRequires: gcc-c++ 24 | 25 | # Required by the check target 26 | BuildRequires: python3-pytest 27 | BuildRequires: python3-distro 28 | BuildRequires: python3-psutil 29 | # i.e. because of pgrep 30 | BuildRequires: procps-ng 31 | # i.e. because of gcore 32 | BuildRequires: gdb 33 | BuildRequires: python3-dnf 34 | # i.e. for test suite 35 | BuildRequires: lz4 36 | # i.e. for hcheck 37 | BuildRequires: libcurl-devel 38 | 39 | %if %{__isa_bits} == 64 40 | BuildRequires: glibc-devel(%{__isa_name}-32) 41 | %endif 42 | 43 | # for check-cert 44 | Requires: gnutls-utils 45 | Requires: python3-dns 46 | # for matrixto 47 | Requires: python3-matrix-nio 48 | # workaround missing matrix-nio depedency: https://bugzilla.redhat.com/show_bug.cgi?id=1925689 49 | Requires: python3-crypto 50 | # for user_installed 51 | Requires: python3-dnf 52 | 53 | 54 | %description 55 | Collection of command line utilities. 56 | 57 | %prep 58 | %if %{with srpm} 59 | %autosetup -n gms-utils 60 | %endif 61 | 62 | %build 63 | %cmake 64 | %cmake_build 65 | 66 | %install 67 | %cmake_install 68 | mkdir -p %{buildroot}/usr/local/bin 69 | # resolve conflict with glibc-common 70 | mv %{buildroot}/usr/bin/pldd %{buildroot}/usr/local/bin/ 71 | # resolve conflict with obs-build 72 | mv %{buildroot}/usr/bin/unrpm %{buildroot}/usr/local/bin/ 73 | 74 | %check 75 | # for ctests there is also the ctest macro 76 | %cmake_build --target check 77 | 78 | 79 | %files 80 | /usr/bin/addrof 81 | /usr/bin/adjtimex 82 | /usr/bin/arsort 83 | /usr/bin/ascii 84 | /usr/bin/check-cert 85 | /usr/bin/check-dnsbl 86 | /usr/bin/chromium-extensions 87 | /usr/bin/cpufreq 88 | /usr/bin/dcat 89 | /usr/bin/detect-size 90 | /usr/bin/devof 91 | /usr/bin/disas 92 | /usr/bin/exec 93 | /usr/bin/firefox-addons 94 | /usr/bin/gs-ext 95 | /usr/bin/hcheck 96 | /usr/bin/inhibit 97 | /usr/bin/isempty 98 | /usr/bin/latest-kernel-running 99 | /usr/bin/lockf 100 | /usr/bin/lsata 101 | /usr/bin/macgen 102 | /usr/bin/matrixto 103 | /usr/bin/oldprocs 104 | /usr/bin/pargs 105 | /usr/bin/pdfmerge 106 | /usr/local/bin/pldd 107 | /usr/bin/pq 108 | /usr/bin/pwhatch 109 | /usr/bin/remove 110 | /usr/bin/reset-tmux 111 | /usr/bin/ripdvd 112 | /usr/bin/searchb 113 | /usr/bin/silence 114 | /usr/bin/swap 115 | /usr/local/bin/unrpm 116 | /usr/bin/user-installed 117 | /usr/bin/wipedev 118 | %doc README.md 119 | 120 | 121 | %changelog 122 | * Sun Jan 31 2021 Georg Sauthoff - 0.5.4-1 123 | - add matrixto, inhibit 124 | * Fri Jan 1 2021 Georg Sauthoff - 0.5.3-1 125 | - add wipedev 126 | * Sun Dec 13 2020 Georg Sauthoff - 0.5.2-1 127 | - add pq 128 | * Sat Sep 19 2020 Georg Sauthoff - 0.5.1-1 129 | - bump version 130 | * Sun Sep 06 2020 Georg Sauthoff - 0.5.0-3 131 | - fix check-cert depedency 132 | * Fri Aug 21 2020 Georg Sauthoff - 0.5.0-2 133 | - fix check-dnsbl depedency 134 | * Wed Aug 12 2020 Georg Sauthoff - 0.5.0-1 135 | - initial packaging 136 | -------------------------------------------------------------------------------- /gs-ext.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # 2017, Georg Sauthoff 4 | # SPDX-License-Identifier: GPL-3.0-or-later 5 | 6 | import argparse 7 | import json 8 | import os 9 | import shutil 10 | import subprocess 11 | import sys 12 | import tempfile 13 | import zipfile 14 | 15 | def mk_arg_parser(): 16 | p = argparse.ArgumentParser( 17 | formatter_class=argparse.RawDescriptionHelpFormatter, 18 | description='Manage Gnome-Shell Extensions', 19 | epilog='''Without any options this tool prints a list of 20 | installed extensions. 21 | 22 | Get even more extensions: https://extensions.gnome.org/ 23 | 24 | See also https://github.com/gsauthof/playbook/gnome-shell/ for 25 | a curated list of essentiell extensions and some good default settings. 26 | 27 | ## The `--uuid URL/ID` switch basically automates 28 | 29 | Map extensions.gnome.org extension web ID to UUID: 30 | 31 | $ curl -s "https://extensions.gnome.org/extension-info/?pk=$eid&shell_version=$gsv" | jq -r .uuid 32 | 33 | (e.g. for eid=7 and gsv=3.24 34 | 35 | ## A more interactive alternative variant to `--install` 36 | 37 | Given a UUID trigger the install: 38 | 39 | $ qdbus org.gnome.Shell /org/gnome/Shell org.gnome.Shell.Extensions.InstallRemoteExtension $someuuid 40 | 41 | In contrast to `--install`, this doesn't require a GNOME shell restart. 42 | 43 | ## Dependencies 44 | 45 | Under Fedora: 46 | 47 | dnf -y install python3-pydbus python3-requests 48 | 49 | Optional (only for manual inspection): 50 | 51 | dnf -y install qt5-qttools 52 | 53 | 2018, Georg Sauthoff 54 | ''') 55 | p.add_argument('--disabled', '-d', action='store_true', 56 | help='List installed but disabled extensions') 57 | p.add_argument('--enable', metavar='UUIDs', nargs='+', 58 | help='Enable an extension') 59 | p.add_argument('--disable', metavar='UUIDs', nargs='+', 60 | help='Disable an extension') 61 | p.add_argument('--pref', metavar='UUID', help='call preferences dialog') 62 | p.add_argument('--install', metavar='UUIDs', nargs='+', 63 | help='Install one or many extensions. They are downloaded from the official repository and unzipped. See --dest for the destination. GNOME shells sees those extensions after a session restart.') 64 | p.add_argument('--remove', metavar='UUIDs', nargs='+', 65 | help='Remove one or many locally install extensions.') 66 | p.add_argument('--dest', help='install destination (default: ~/.local/share/gnome-shell/extensions)') 67 | p.add_argument('--version', '-v', action='store_true', 68 | help='Display GNOME shell version') 69 | p.add_argument('--version-db', action='store_true', 70 | help='Display GNOME shell version (obtained via DBus)') 71 | p.add_argument('--uuid', metavar='URL/ID', help='translate an extensions.gnome.org URL/ID to a UUID') 72 | return p 73 | 74 | def parse_args(*a): 75 | arg_parser = mk_arg_parser() 76 | args = arg_parser.parse_args(*a) 77 | if not args.dest: 78 | args.dest = os.environ['HOME'] + '/.local/share/gnome-shell/extensions' 79 | return args 80 | 81 | 82 | def get_dbus(dbus=[None], gshell=[None], gext=[None]): 83 | import pydbus 84 | if not dbus[0]: 85 | dbus[0] = pydbus.SessionBus() 86 | gshell[0] = dbus[0].get('org.gnome.Shell', '/org/gnome/Shell') 87 | gext[0] = gshell[0]['org.gnome.Shell.Extensions'] 88 | return (gshell[0], gext[0]) 89 | 90 | def pp_row(filename): 91 | if not os.path.exists(filename): 92 | return 93 | with open(filename) as f: 94 | d = json.load(f) 95 | sys_wide = 'true' if filename.startswith('/usr/share') else 'false' 96 | print('{},{},{},{}'.format(d['uuid'], d['name'], d['url'], sys_wide)) 97 | 98 | def get_enabled(): 99 | s = subprocess.check_output(['gsettings', 'get', 'org.gnome.shell', 'enabled-extensions'], universal_newlines=True) 100 | ls = s[2:-3].split("', '") 101 | return ls 102 | 103 | def list_ext(args): 104 | home = os.environ['HOME'] 105 | ss = [ home + '/.local/share/gnome-shell/extensions', 106 | '/usr/share/gnome-shell/extensions' ] 107 | enabled = set(get_enabled()) 108 | print('uuid,name,url,system') 109 | ls = ( (base, x) for base in ss if os.path.exists(base) 110 | for x in os.listdir(base) ) 111 | for base, l in sorted(ls, key=lambda x:x[1]): 112 | if ( args.disabled and l not in enabled ) \ 113 | or (not args.disabled and l in enabled): 114 | pp_row('{}/{}/metadata.json'.format(base, l)) 115 | 116 | def toggle_extension(uuids, on): 117 | ls = get_enabled() 118 | for uuid in uuids: 119 | if on: 120 | if uuid not in ls: 121 | ls.append(uuid) 122 | else: 123 | if uuid in ls: 124 | ls.remove(uuid) 125 | a = '[{}]'.format(', '.join("'{}'".format(x) for x in ls)) 126 | s = subprocess.check_output(['gsettings', 'set', 'org.gnome.shell', 'enabled-extensions', a], universal_newlines=True) 127 | 128 | def show_preferences(uuid): 129 | _, ext = get_dbus() 130 | ext.LaunchExtensionPrefs(uuid) 131 | # subprocess.check_output(['qdbus', 'org.gnome.Shell', '/org/gnome/Shell', 132 | # 'org.gnome.Shell.Extensions.LaunchExtensionPrefs', uuid]) 133 | 134 | def gnome_shell_version_dbus(): 135 | _, ext = get_dbus() 136 | o = ext.ShellVersion 137 | #o = subprocess.check_output(['qdbus', 'org.gnome.Shell', 138 | # '/org/gnome/Shell', 'org.gnome.Shell.Extensions.ShellVersion'], 139 | # universal_newlines=True) 140 | o = o.strip() 141 | if o.find('.') != o.rfind('.'): 142 | o = o[0:o.rfind('.')] 143 | return o 144 | 145 | def gnome_shell_version(): 146 | o = subprocess.check_output(['rpm', '-q', '--qf', '%{version}', 147 | 'gnome-shell'], universal_newlines=True) 148 | o = o.strip() 149 | if o.find('.') != o.rfind('.'): 150 | o = o[0:o.rfind('.')] 151 | return o 152 | 153 | def download(uuid, version, s, f): 154 | r = s.get(f'https://extensions.gnome.org/download-extension/{uuid}.shell-extension.zip?&shell_version={version}', stream=True) 155 | r.raise_for_status() 156 | for chunk in r.iter_content(8*1024): 157 | f.write(chunk) 158 | f.flush() 159 | 160 | def verify_zip(z): 161 | for i in z.infolist(): 162 | if i.filename.startswith('/') or '..' in i.filename: 163 | raise RuntimeError('Weird filename: ' + i.filename) 164 | 165 | def install(uuids, dest): 166 | import requests 167 | v = gnome_shell_version() 168 | s = requests.Session() 169 | for uuid in uuids: 170 | with tempfile.TemporaryFile() as f: 171 | download(uuid, v, s, f) 172 | with zipfile.ZipFile(f) as z: 173 | verify_zip(z) 174 | d = f'{dest}/{uuid}' 175 | if os.path.exists(d): 176 | shutil.rmtree(d) 177 | os.makedirs(d) 178 | z.extractall(d) 179 | 180 | def remove(uuids, dest): 181 | rc = 0 182 | for uuid in uuids: 183 | d = f'{dest}/{uuid}' 184 | if not os.path.exists(d): 185 | rc = 1 186 | print(f'Extension {uuid} is not installed, locally.', file=sys.stderr) 187 | continue 188 | shutil.rmtree(d) 189 | return rc 190 | 191 | def parse_id(url_or_id): 192 | if '/' in url_or_id: 193 | url = url_or_id 194 | q = '/extension/' 195 | a = url.index(q) 196 | i = url[a + q.__len__():] 197 | i = i[:i.index('/')] 198 | else: 199 | i = url_or_id 200 | return i 201 | 202 | def test_parse_id(): 203 | assert parse_id('https://extensions.gnome.org/extension/15/alternatetab/') \ 204 | == '15' 205 | assert parse_id('23') == '23' 206 | 207 | def get_uuid(url_or_id): 208 | import requests 209 | i = parse_id(url_or_id) 210 | v = gnome_shell_version() 211 | r = requests.get(f'https://extensions.gnome.org/extension-info/?pk={i}&shell_version={v}') 212 | r.raise_for_status() 213 | d = json.loads(r.text) 214 | return d['uuid'] 215 | 216 | def main(*a): 217 | args = parse_args(*a) 218 | if args.enable: 219 | toggle_extension(args.enable, True) 220 | elif args.disable: 221 | toggle_extension(args.disable, False) 222 | elif args.pref: 223 | show_preferences(args.pref) 224 | elif args.install: 225 | install(args.install, args.dest) 226 | elif args.remove: 227 | return remove(args.remove, args.dest) 228 | elif args.version: 229 | print(gnome_shell_version()) 230 | elif args.version_db: 231 | print(gnome_shell_version_dbus()) 232 | elif args.uuid: 233 | print(get_uuid(args.uuid)) 234 | else: 235 | list_ext(args) 236 | return 0 237 | 238 | if __name__ == '__main__': 239 | sys.exit(main()) 240 | -------------------------------------------------------------------------------- /hc_mail.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 -E 2 | 3 | # health check mail delivery mail filter 4 | # 5 | # SPDX-FileCopyrightText: © 2020 Georg Sauthoff 6 | # SPDX-License-Identifier: GPL-3.0-or-later 7 | 8 | import argparse 9 | import email.message 10 | import email.policy 11 | # provided by Fedora package: python3-pynacl 12 | import nacl.encoding 13 | import nacl.signing 14 | import nacl.exceptions 15 | import os 16 | import re 17 | import smtplib 18 | import sys 19 | import time 20 | 21 | 22 | result_header = 'x-hc-result' 23 | signature_header = 'x-hc-signature' 24 | loop_header = 'x-hc-loop' 25 | hc_result_re = re.compile(result_header + ':', re.IGNORECASE) 26 | hc_sig_re = re.compile(signature_header + ':', re.IGNORECASE) 27 | hc_loop_re = re.compile(loop_header + ':', re.IGNORECASE) 28 | 29 | 30 | def verify(vkey, delta, s): 31 | try: 32 | m = vkey.verify(s, encoder=nacl.encoding.HexEncoder) 33 | except nacl.exceptions.BadSignatureError: 34 | return False 35 | now = int(time.time()) 36 | ts = int(m) 37 | d = now - ts 38 | if d < -1 or d > delta: 39 | return False 40 | return True 41 | 42 | def process(f, vkey, delta): 43 | state = 0 44 | verified = False 45 | loop = 1 46 | for line in f: 47 | if state == 0: 48 | if line == '\n': 49 | state = 1 50 | print(f'{loop_header}: {loop}') 51 | if verified: 52 | print(f'{result_header}: ok') 53 | print() 54 | continue 55 | elif hc_result_re.match(line): 56 | continue 57 | elif hc_loop_re.match(line): 58 | loop += 1 59 | continue 60 | elif hc_sig_re.match(line): 61 | _, v = line.split(maxsplit=2) 62 | verified = verify(vkey, delta, v) 63 | print(line, end='') 64 | else: 65 | pass 66 | print(line, end='') 67 | else: 68 | print(line, end='') 69 | 70 | def parse_args(): 71 | p = argparse.ArgumentParser(description='Mail filter and verify signature') 72 | p.add_argument('--input', '-i', default='-', 73 | help='input file (default: - for stdin)') 74 | p.add_argument('--sign', action='store_true', help='sign current time of day') 75 | p.add_argument('--mail', action='store_true', help='sign and mail') 76 | p.add_argument('--to', help='mail recipient') 77 | p.add_argument('--from', dest='frm', help='mail sender') 78 | p.add_argument('--subject', default='1337 health check beacon', help='mail subject (default: %(default)s)') 79 | p.add_argument('--mta', default='localhost', help='mail server to use with --mail (default: %(default)s)') 80 | p.add_argument('key', metavar='HEXKEY', help='signature verification key (or gen-key/genkey for key generation - or env for reading key from hc_mail_key environment variable)') 81 | p.add_argument('--delta', '-d', type=int, default=10, 82 | help='timestamp delta window in seconds during verification (default: %(default)d') 83 | args = p.parse_args() 84 | if args.key == 'env': 85 | args.key = os.getenv('hc_mail_key') 86 | if not args.key: 87 | raise RuntimeError('no key in hc_mail_key environment variable') 88 | return args 89 | 90 | def gen_key(): 91 | skey = nacl.signing.SigningKey.generate() 92 | vkey = skey.verify_key 93 | print(skey.encode(nacl.encoding.HexEncoder).decode()) 94 | print(vkey.encode(nacl.encoding.HexEncoder).decode()) 95 | 96 | def sign(key): 97 | skey = nacl.signing.SigningKey(key, encoder=nacl.encoding.HexEncoder) 98 | m = skey.sign(str(int(time.time())).encode(), encoder=nacl.encoding.HexEncoder) 99 | return m.decode() 100 | 101 | def mail(frm, to, subject, signature, host): 102 | # to simplify things we don't want our signature_header to get folded, 103 | # thus the policy change 104 | p = email.policy.EmailPolicy().clone(max_line_length=200) 105 | m = email.message.EmailMessage(policy=p) 106 | m.set_content('health check beacon') 107 | m['from'] = frm 108 | m['to'] = to 109 | m['subject'] = subject 110 | m[signature_header] = signature 111 | with smtplib.SMTP(host) as mta: 112 | mta.send_message(m) 113 | 114 | def main(): 115 | args = parse_args() 116 | if args.key in ('gen-key', 'genkey'): 117 | gen_key() 118 | return 119 | if args.sign: 120 | print(sign(args.key)) 121 | return 122 | if args.mail: 123 | mail(args.frm, args.to, args.subject, sign(args.key), args.mta) 124 | return 125 | if args.input == '-': 126 | f = sys.stdin 127 | else: 128 | f = open(args.input) 129 | vkey = nacl.signing.VerifyKey(args.key, encoder=nacl.encoding.HexEncoder) 130 | process(f, vkey, args.delta) 131 | 132 | if __name__ == '__main__': 133 | sys.exit(main()) 134 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /hcheck.c: -------------------------------------------------------------------------------- 1 | // hcheck - health-check a command with a healthchecks.io instance 2 | // 3 | // SPDX-License-Identifier: GPL-3.0-or-later 4 | // SPDX-FileCopyrightText: © 2023 Georg Sauthoff 5 | 6 | 7 | #include // ENOENT 8 | #include 9 | #include // fprintf(), ... 10 | #include // calloc(), ... 11 | #include // strerror(), ... 12 | 13 | #include // posix_spawnp() 14 | #include // waitid() 15 | #include // getopt() 16 | 17 | #include 18 | 19 | 20 | extern char **environ; 21 | 22 | 23 | static const int hc_error_code = 23; 24 | 25 | struct Args { 26 | bool dry; 27 | const char *url; 28 | const char *uuid; 29 | int argc; 30 | char **argv; 31 | }; 32 | typedef struct Args Args; 33 | static const char default_url[] = "https://hc-ping.com"; 34 | static void help(FILE *o, const char *argv0) 35 | { 36 | fprintf(o, "%s - healthcheck command\n" 37 | "Usage: %s [OPTS] COMMAND [COMMAND_OPTS]\n" 38 | "\n" 39 | "Options:\n" 40 | " -d dry run, i.e. just execute the child without healthchecks\n" 41 | " -h show help\n" 42 | " -u UUID UUID of the healthcheck\n" 43 | " NB: use hcheck_uuid environment variable for hiding the UUID from other users\n" 44 | " -l URL Healthchecks instance URL (default: %s)\n" 45 | "\n" 46 | "In case of healthchecks transmission failures the command is still executed.\n" 47 | "Exit status is the exit status of the child command, unless:\n" 48 | "\n" 49 | "- spawning failed, then it's 127 (not found) or 126 (other error)\n" 50 | "- healthchecks communicateion error, then it's 23\n" 51 | "\n" 52 | "2023, Georg Sauthoff , GPLv3+\n" 53 | , 54 | argv0, 55 | argv0, 56 | default_url); 57 | } 58 | static void parse_args(int argc, char **argv, Args *args) 59 | { 60 | char c = 0; 61 | *args = (const Args){ 62 | .url = default_url 63 | }; 64 | // '+' prefix: no reordering of arguments 65 | // ':': preceding opting takes a mandatory argument 66 | while ((c = getopt(argc, argv, "+dhl:u:")) != -1) { 67 | switch (c) { 68 | case '?': 69 | fprintf(stderr, "unexpected option character: %c\n", optopt); 70 | exit(hc_error_code); 71 | break; 72 | case 'd': 73 | args->dry = 1; 74 | break; 75 | case 'h': 76 | help(stdout, argv[0]); 77 | exit(0); 78 | break; 79 | case 'l': 80 | args->url = optarg; 81 | break; 82 | case 'u': 83 | args->uuid = optarg; 84 | break; 85 | } 86 | } 87 | if (!args->uuid) 88 | args->uuid = getenv("hcheck_uuid"); 89 | if (!args->uuid) { 90 | fprintf(stderr, "no healtlcheck uuid specified (cf. hcheck_uuid environment variable or -u option)\n"); 91 | exit(hc_error_code); 92 | } 93 | 94 | if (optind >= argc) { 95 | fprintf(stderr, "positional arguments are missing\n"); 96 | exit(hc_error_code); 97 | } 98 | args->argc = argc - optind; 99 | args->argv = argv + optind; 100 | } 101 | 102 | 103 | static int run(int argc, char **argv) 104 | { 105 | if (!argc) 106 | return hc_error_code; 107 | pid_t pid = 0; 108 | int r = posix_spawnp(&pid, argv[0], 0, 0, argv, environ); 109 | if (r) { 110 | fprintf(stderr, "failed to execute %s: %s\n", argv[0], strerror(r)); 111 | return r == ENOENT ? 127 : 126; 112 | } 113 | siginfo_t info; 114 | r = waitid(P_PID, pid, &info, WEXITED); 115 | if (r == -1) { 116 | perror("waitid failed"); 117 | return hc_error_code; 118 | } 119 | return info.si_code == CLD_EXITED ? info.si_status : 128 + info.si_status; 120 | } 121 | 122 | static size_t write_ignore(char *ptr, size_t size, size_t nmemb, void *userdata) 123 | { 124 | (void)ptr; 125 | (void)userdata; 126 | return size * nmemb; 127 | } 128 | static size_t read_nothing(char *buffer, size_t size, size_t nitems, void *userdata) 129 | { 130 | (void)buffer; 131 | (void)size; 132 | (void)nitems; 133 | (void)userdata; 134 | return 0; 135 | } 136 | 137 | static CURL *mk_curl_handle(char *curl_msg, int *rc) 138 | { 139 | CURL *h = curl_easy_init(); 140 | if (!h) { 141 | fprintf(stderr, "curl_easy_init failed\n"); 142 | return h; 143 | } 144 | 145 | CURLcode r = curl_easy_setopt(h, CURLOPT_FAILONERROR, 1L); 146 | if (r) { 147 | fprintf(stderr, "CURLOPT_FAILONERROR failed: %s\n", curl_easy_strerror(r)); 148 | *rc = hc_error_code; 149 | } 150 | r = curl_easy_setopt(h, CURLOPT_TIMEOUT_MS, 10000L); 151 | if (r) { 152 | fprintf(stderr, "CURLOPT_TIMEOUT_MS failed: %s\n", curl_easy_strerror(r)); 153 | *rc = hc_error_code; 154 | } 155 | r = curl_easy_setopt(h, CURLOPT_USERAGENT, "hcheck-0.1/curl"); 156 | if (r) { 157 | fprintf(stderr, "CURLOPT_USERAGENT failed: %s\n", curl_easy_strerror(r)); 158 | *rc = hc_error_code; 159 | } 160 | r = curl_easy_setopt(h, CURLOPT_ERRORBUFFER, curl_msg); 161 | if (r) { 162 | fprintf(stderr, "CURLOPT_ERRORBUFFER failed: %s\n", curl_easy_strerror(r)); 163 | *rc = hc_error_code; 164 | } 165 | r = curl_easy_setopt(h, CURLOPT_WRITEFUNCTION, write_ignore); 166 | if (r) { 167 | fprintf(stderr, "CURLOPT_WRITEFUNCTION failed: %s\n", curl_easy_strerror(r)); 168 | *rc = hc_error_code; 169 | } 170 | // with an HTTP GET request, libcurl shouldn't read anything, anyways, 171 | // but to be extra defensive here ... 172 | r = curl_easy_setopt(h, CURLOPT_READFUNCTION, read_nothing); 173 | if (r) { 174 | fprintf(stderr, "CURLOPT_READFUNCTION failed: %s\n", curl_easy_strerror(r)); 175 | *rc = hc_error_code; 176 | } 177 | // tell curl to sned HTTP POST instead of the default GET requests; 178 | // healthchecks.io checks accept GET/HEAD/POST, by default, 179 | // but it's also possible to create POST-only checks 180 | r = curl_easy_setopt(h, CURLOPT_POSTFIELDS, ""); 181 | if (r) { 182 | fprintf(stderr, "CURLOPT_POSTFIELDS failed: %s\n", curl_easy_strerror(r)); 183 | *rc = hc_error_code; 184 | } 185 | 186 | return h; 187 | } 188 | 189 | static CURL *init_curl(bool dry, char *curl_msg, int *rc) 190 | { 191 | if (dry) 192 | return 0; 193 | CURLcode cc = curl_global_init(CURL_GLOBAL_DEFAULT); 194 | if (cc) { 195 | fprintf(stderr, "curl_global_init() failed: %s\n", curl_easy_strerror(cc)); 196 | *rc = hc_error_code; 197 | } 198 | CURL *h = mk_curl_handle(curl_msg, rc); 199 | return h; 200 | } 201 | 202 | static void cleanup_curl(bool dry, CURL *h) 203 | { 204 | if (dry) 205 | return; 206 | if (h) 207 | curl_easy_cleanup(h); 208 | curl_global_cleanup(); 209 | } 210 | 211 | static void transmit_start(CURL *h, const char *url, const char *uuid, 212 | char *curl_msg, int *rc) 213 | { 214 | if (!h) 215 | return; 216 | 217 | char buf[1024] = {0}; 218 | int r = snprintf(buf, sizeof buf, "%s/%s/start", url, uuid); 219 | if (r < 0) { 220 | perror("Constructing start URL failed"); 221 | *rc = hc_error_code; 222 | return; 223 | } 224 | if ((size_t) r >= sizeof buf) { 225 | fprintf(stderr, "start URL truncated\n"); 226 | *rc = hc_error_code; 227 | return; 228 | } 229 | CURLcode cc = curl_easy_setopt(h, CURLOPT_URL, buf); 230 | if (cc) { 231 | fprintf(stderr, "CURLOPT_URL failed: %s\n", curl_easy_strerror(r)); 232 | *rc = hc_error_code; 233 | return; 234 | } 235 | cc = curl_easy_perform(h); 236 | if (cc) { 237 | fprintf(stderr, "curl perform failed: %s (%s)\n", curl_easy_strerror(cc), curl_msg); 238 | *curl_msg = 0; 239 | *rc = hc_error_code; 240 | } 241 | } 242 | 243 | static void transmit_exit(CURL *h, const char *url, const char *uuid, int code, 244 | char *curl_msg, int *rc) 245 | { 246 | if (!h) 247 | return; 248 | 249 | char buf[1024] = {0}; 250 | int r = snprintf(buf, sizeof buf, "%s/%s/%d", url, uuid, code); 251 | if (r < 0) { 252 | perror("Constructing exit URL failed"); 253 | *rc = hc_error_code; 254 | return; 255 | } 256 | if ((size_t) r >= sizeof buf) { 257 | fprintf(stderr, "exit URL truncated\n"); 258 | *rc = hc_error_code; 259 | return; 260 | } 261 | CURLcode cc = curl_easy_setopt(h, CURLOPT_URL, buf); 262 | if (cc) { 263 | fprintf(stderr, "CURLOPT_URL failed: %s\n", curl_easy_strerror(r)); 264 | *rc = hc_error_code; 265 | return; 266 | } 267 | cc = curl_easy_perform(h); 268 | if (cc) { 269 | fprintf(stderr, "curl perform failed: %s (%s)\n", curl_easy_strerror(cc), curl_msg); 270 | *curl_msg = 0; 271 | *rc = hc_error_code; 272 | } 273 | } 274 | 275 | 276 | int main(int argc, char **argv) 277 | { 278 | Args args; 279 | parse_args(argc, argv, &args); 280 | 281 | int rc = 0; 282 | 283 | char *curl_msg = calloc(CURL_ERROR_SIZE, 1); 284 | if (!curl_msg) { 285 | perror("calloc"); 286 | return hc_error_code; 287 | } 288 | CURL *h = init_curl(args.dry, curl_msg, &rc); 289 | 290 | transmit_start(h, args.url, args.uuid, curl_msg, &rc); 291 | int r = run(args.argc, args.argv); 292 | transmit_exit(h, args.url, args.uuid, r, curl_msg, &rc); 293 | 294 | cleanup_curl(args.dry, h); 295 | free(curl_msg); 296 | 297 | if (r) 298 | return r; 299 | return rc; 300 | } 301 | -------------------------------------------------------------------------------- /inhibit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Disable/Inhibit Gnome-Shell screen blanking on idle until this program 4 | # terminates. 5 | # 6 | # SPDX-FileCopyrightText: © 2020 Georg Sauthoff 7 | # SPDX-License-Identifier: GPL-3.0-or-later 8 | 9 | import argparse 10 | import pydbus 11 | import sys 12 | import time 13 | 14 | 15 | # cf. /usr/include/gtk-3.0/gtk/gtkapplication.h 16 | INHIBIT_LOGOUT = 1 17 | INHIBIT_SWITCH = 2 18 | INHIBIT_SUSPEND = 4 19 | INHIBIT_IDLE = 8 20 | 21 | 22 | def reasons(x): 23 | rs = [] 24 | if x & INHIBIT_LOGOUT: 25 | rs.append('LOGOUT') 26 | if x & INHIBIT_SWITCH: 27 | rs.append('SWITCH') 28 | if x & INHIBIT_SUSPEND: 29 | rs.append('SUSPEND') 30 | if x & INHIBIT_IDLE: 31 | rs.append('IDLE') 32 | return rs 33 | 34 | def parse_args(*a): 35 | p = argparse.ArgumentParser(description='Inhibit screen blanking in Gnome-Shell') 36 | p.add_argument('--list', '-l', action='store_true', 37 | help='list active inhibitors') 38 | p.add_argument('--check', '-c', type=int, metavar='FLAGS', 39 | help='check for active inhibitors and set exit status accordingly (1|2|4|8 => LOGOUT|SWITCH|SUSPEND|IDLE)') 40 | p.add_argument('--flags', type=int, metavar='FLAGS', default=INHIBIT_IDLE, 41 | help='flags to use for inhibit action (1|2|4|8 => LOGOUT|SWITCH|SUSPEND|IDLE) (default: %(default)i)') 42 | p.add_argument('--time', '-t', type=int, metavar='SECONDS', default=2**32-1, 43 | help='sleep time, i.e. time the inhibitor is active (default: %(default)i)') 44 | return p.parse_args(*a) 45 | 46 | 47 | # def inhibit_old(): 48 | # import dbus 49 | # bus = dbus.SessionBus() 50 | # sm = bus.get_object('org.gnome.SessionManager','/org/gnome/SessionManager') 51 | # sm.Inhibit("coffee", dbus.UInt32(0), "keeps you busy", dbus.UInt32(8), 52 | # dbus_interface='org.gnome.SessionManager') 53 | # time.sleep(2**32-1) 54 | 55 | def inhibit(flags, secs): 56 | bus = pydbus.SessionBus() 57 | # we could leave out object_path because it defaults to bus_name 58 | # with . replaced by / ... 59 | sm = bus.get(bus_name='org.gnome.SessionManager', 60 | object_path='/org/gnome/SessionManager') 61 | # 'org.gnome.SesssionManager' is also the interface name ... 62 | xid = 0 63 | # again, we could leave out interface name, as it defaults to that 64 | sm['org.gnome.SessionManager'].Inhibit('inhibit.py', xid, 65 | 'user requested idle-off from terminal', flags) 66 | time.sleep(secs) 67 | 68 | def check_inhibited(flags): 69 | bus = pydbus.SessionBus() 70 | sm = bus.get(bus_name='org.gnome.SessionManager') 71 | r = sm.IsInhibited(flags) 72 | return r 73 | 74 | def list_inhibitors(): 75 | bus = pydbus.SessionBus() 76 | sm = bus.get(bus_name='org.gnome.SessionManager') 77 | xs = sm.GetInhibitors() 78 | for x in xs: 79 | p = bus.get(bus_name='org.gnome.SessionManager', object_path=x) 80 | print(f'{p.GetAppId()} {p.GetReason()} flags={"|".join(reasons(p.GetFlags()))}') 81 | 82 | 83 | def main(): 84 | args = parse_args() 85 | if args.list: 86 | list_inhibitors() 87 | elif args.check: 88 | return(not check_inhibited(args.check)) 89 | else: 90 | try: 91 | inhibit(args.flags, args.time) 92 | except KeyboardInterrupt: 93 | pass 94 | 95 | if __name__ == '__main__': 96 | sys.exit(main()) 97 | 98 | 99 | -------------------------------------------------------------------------------- /isempty.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Detect empty images - e.g. in scanned images 4 | # 5 | # Stand-alone version of the empty-page-detection code 6 | # in https://github.com/gsauthof/adf2pdf/blob/master/adf2pdf.py 7 | # 8 | # 2018, Georg Sauthoff 9 | # SPDX-License-Identifier: GPL-3.0-or-later 10 | 11 | import argparse 12 | import logging 13 | import PIL.Image 14 | import PIL.ImageFilter 15 | import PIL.ImageStat 16 | import sys 17 | 18 | log = logging.getLogger(__name__) 19 | 20 | def avg_brightness(filename): 21 | img = PIL.Image.open(filename) 22 | log.debug('Dimensions (W x H): {}'.format(img.size)) 23 | img = img.convert('L') 24 | # exclude the margins to ignore punch holes etc. 25 | img = img.crop((args.margin, args.margin, img.size[0] - args.margin, img.size[1] - args.margin)) 26 | stat = PIL.ImageStat.Stat(img) 27 | # shifting the mean to deal with empty pages where the 28 | # reverse page shines through a little 29 | m = stat.mean[0] - 50 30 | log.debug('Image avg brightness: {}'.format(m)) 31 | log.debug('Image rms brightness: {}'.format(stat.rms[0])) 32 | return img, m 33 | 34 | def binarize(input_img, thresh): 35 | # with grayscale, 0 is black and 255 is white (bightest) 36 | # to simplify the counting we binarize to: 0=white, 1=black 37 | img = input_img.point(lambda v : v < thresh) 38 | return img 39 | 40 | def erode(input_img): 41 | img = input_img.filter(PIL.ImageFilter.MinFilter(args.erode)) 42 | return img 43 | 44 | def count_black_px(img): 45 | n = img.size[0] * img.size[1] 46 | x = sum(img.getdata()) 47 | log.debug('{} of {} pixels are black ({:.2f} %)'.format(x, n, x/n*100)) 48 | return x 49 | 50 | # cf. https://dsp.stackexchange.com/a/48837/35404 51 | def is_empty(filename): 52 | img, thresh = avg_brightness(filename) 53 | img = binarize(img, thresh) 54 | count_black_px(img) 55 | img = erode(img) 56 | x = count_black_px(img) 57 | return x < 100 58 | 59 | 60 | 61 | def setup_logging(verbose): 62 | log_format = '%(asctime)s - %(levelname)-8s - %(message)s' 63 | log_date_format = '%Y-%m-%d %H:%M:%S' 64 | logging.basicConfig(format=log_format, datefmt=log_date_format, 65 | level=(logging.DEBUG if verbose else logging.INFO)) 66 | 67 | def parse_args(): 68 | p = argparse.ArgumentParser( 69 | description='Detect empty images', 70 | epilog='Exits with status equal 0 if the image is empty and 1 otherwise.') 71 | p.add_argument('filename', metavar='IMAGE', nargs=1, 72 | help='the filename of the image') 73 | p.add_argument('--verbose', '-v', action='store_true', 74 | help='print debug messages') 75 | p.add_argument('--margin', type=int, default=420, 76 | help='margin to ignore - e.g. where punchholes are located (default: 420 pixels)') 77 | p.add_argument('--erode', type=int, default=5, 78 | help='erode threshold - odd number of pixels (default: 5)') 79 | args = p.parse_args() 80 | return args 81 | 82 | if __name__ == '__main__': 83 | global args 84 | args = parse_args() 85 | setup_logging(args.verbose) 86 | sys.exit(not is_empty(args.filename[0])) 87 | -------------------------------------------------------------------------------- /latest-kernel-running.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Check if the currently running kernel is latest available one. 4 | # 5 | # Tested on Fedora 24/CentOS 7. 6 | # 7 | # 2014, Georg Sauthoff 8 | 9 | set -e 10 | set -u 11 | 12 | latest=$(find /boot -maxdepth 1 -name 'vmlinuz-*' \ 13 | -not -name '*rescue*' -printf '%f\n'\ 14 | | sed 's/vmlinuz-\([0-9.-]\+\)\.[A-Za-z].*$/\1/' \ 15 | | sort -V -r | head -n 1) 16 | 17 | running=$(uname -r | sed 's/\([0-9.-]\+\)\.[A-Za-z].*$/\1/') 18 | 19 | verbose=false 20 | if [ $# -ge 1 ]; then 21 | if [ "$1" = "-v" ]; then 22 | verbose=true 23 | fi 24 | fi 25 | 26 | if [ "$latest" != "$running" ]; then 27 | if [ "$verbose" = true ]; then 28 | echo We are running vmlinuz-"$running",\ 29 | but the most current installed kernel is "$latest". 30 | fi 31 | exit 1 32 | fi 33 | -------------------------------------------------------------------------------- /lsata.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # List disk device for each ataX identifier 4 | # 5 | # cf. https://unix.stackexchange.com/q/13960/1131 6 | # 7 | # 2017, Georg Sauthoff 8 | 9 | set -eu 10 | 11 | echo /sys/class/ata_port/ata*/../../host*/target*/*/block/s* \ 12 | | tr ' ' '\n' \ 13 | | awk -F/ '{printf("%s => /dev/%s\n", $5, $NF)}' 14 | -------------------------------------------------------------------------------- /macgen.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Randomly generate a locally administered unicast MAC address. 4 | # 5 | # 2017-01, Georg Sauthoff 6 | 7 | from random import randrange, choice 8 | 9 | print(hex(randrange(0, 16))[-1] 10 | + hex(choice(list(i for i in range(16) if i & 0b11 == 0b10)))[-1] + ':' 11 | + ':'.join('{:02x}'.format(randrange(0, 256))[-2:] for i in range(5))) 12 | -------------------------------------------------------------------------------- /macgen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Randomly generate a locally administered unicast MAC address. 4 | # 5 | # 2017-01, Georg Sauthoff 6 | 7 | od -An -N6 -tx1 /dev/urandom \ 8 | | tr ' ' ':' \ 9 | | sed 's/^.\(.\)./\1'$(shuf -e 2 -e 6 -e a -e e -n1)'/' 10 | 11 | -------------------------------------------------------------------------------- /matrixto.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # SPDX-FileCopyrightText: © 2023 Georg Sauthoff 4 | # SPDX-License-Identifier: GPL-3.0-or-later 5 | 6 | import argparse 7 | import asyncio 8 | import configparser 9 | import logging 10 | import nio 11 | import os 12 | import sys 13 | 14 | 15 | log = logging.getLogger(__name__) 16 | 17 | 18 | def parse_args(*a): 19 | p = argparse.ArgumentParser( 20 | formatter_class=argparse.RawDescriptionHelpFormatter, 21 | description='Send a message via Matrix', 22 | epilog=''' 23 | Examples: 24 | 25 | Send a direct message (to an existing private room): 26 | 27 | matrixto -u @juser:matrix.example.de -m "Host $(hostname) is back at it since $(date -Is)!" 28 | 29 | Send a message to some room: 30 | 31 | fortune | matrixto --room '!deadcafe:matrix.example.org' 32 | 33 | Send multiple messages: 34 | 35 | dstat -cdngy 60 10 | matrixto --line -u @juser:matrix.example.de 36 | 37 | 38 | Initial Setup: 39 | 40 | mkdir ~/.config/matrixto 41 | cd !$ 42 | cat > config.ini 43 | [user] 44 | server = https://matrix.myhomeserver.example.net 45 | user = @juser:myhandle.example.net 46 | password = sehr-geheim 47 | 48 | 49 | 2021, Georg Sauthoff , GPLv3+ 50 | ''') 51 | 52 | p.add_argument('--message', '-m', 53 | help='message to send instead of reading it from stdin') 54 | p.add_argument('--room', '-r', help='room ID to message') 55 | p.add_argument('--user', '-u', help='user ID to message to (i.e. looks for a direct messaging room the user is part of') 56 | p.add_argument('--profile', '-p', default='user', 57 | help='profile name to lookup homeserver, password, etc. (default: %(default)s)') 58 | p.add_argument('--config', '-c', metavar='CONFIG_FILENAME', help='configuration file') 59 | p.add_argument('--state', dest='state_filename', 60 | help='state file where access token etc. is stored') 61 | p.add_argument('--no-state', action='store_false', default=True, dest='store_state', 62 | help="don't store access token etc. in state file after successful login") 63 | p.add_argument('--line', action='store_true', help='read and send stdin line by line') 64 | 65 | args = p.parse_args(*a) 66 | 67 | if not args.config: 68 | args.config = os.getenv('HOME') + '/.config/matrixto/config.ini' 69 | 70 | cp = configparser.ConfigParser() 71 | cp.read(args.config) 72 | if args.profile not in cp: 73 | raise RuntimeError(f"Couldn't find any profile '{args.profile}' in {args.config}") 74 | args.config = cp[args.profile] 75 | 76 | default_state_filename = os.getenv('HOME') + '/.config/matrixto/state.ini' 77 | if not args.state_filename: 78 | fn = default_state_filename 79 | if os.path.exists(fn): 80 | args.state_filename = fn 81 | if args.state_filename: 82 | cp = configparser.ConfigParser() 83 | cp.read(args.state_filename) 84 | if args.profile in cp: 85 | args.state = cp[args.profile] 86 | else: 87 | log.warning(f"Couldn't find any profile '{args.profile}' in {args.state_filename}") 88 | args.state = None 89 | else: 90 | args.state = None 91 | args.state_filename = default_state_filename 92 | 93 | return args 94 | 95 | 96 | class Response_Error(RuntimeError): 97 | def __init__(self, msg, resp): 98 | super().__init__(msg) 99 | self.resp = resp 100 | 101 | def raise_for_response(r, prefix=''): 102 | if r.transport_response.status != 200: 103 | raise Response_Error(f'{prefix}{r.message}', r) 104 | 105 | 106 | def is_private_room(my_id, friend_id, joined_members): 107 | if len(joined_members) != 2: 108 | return False 109 | xs = [ a.user_id for a in joined_members ] 110 | if my_id not in xs: 111 | return False 112 | if friend_id not in xs: 113 | return False 114 | return True 115 | 116 | 117 | async def private_room(client, friend_id): 118 | rs = await client.joined_rooms() 119 | raise_for_response(rs) 120 | for room in rs.rooms: 121 | ms = await client.joined_members(room) 122 | raise_for_response(ms) 123 | if is_private_room(client.user_id, friend_id, ms.members): 124 | return room 125 | return 126 | 127 | async def send_message(client, room, msg): 128 | r = await client.room_send(room_id=room, message_type='m.room.message', 129 | content={ 'msgtype': 'm.text', 'body': msg }) 130 | raise_for_response(r) 131 | 132 | async def do_with_relogin(client, args, f, *xs, **kws): 133 | try: 134 | x = await f(*xs, **kws) 135 | except Response_Error as e: 136 | if e.resp.transport_response.status == 401: 137 | r = await client.login(args.config['password']) 138 | raise_for_response(r) 139 | if args.store_state: 140 | update_state(client, args.state_filename, args.profile) 141 | x = await f(*xs, **kws) 142 | else: 143 | raise 144 | return x 145 | 146 | def update_state(client, filename, profile): 147 | cp = configparser.ConfigParser() 148 | if os.path.exists(filename): 149 | cp.read(filename) 150 | if profile not in cp: 151 | cp[profile] = {} 152 | h = cp[profile] 153 | h['access_token'] = client.access_token 154 | h['user_id'] = client.user_id 155 | h['device_id'] = client.device_id 156 | with open(filename, 'w') as f: 157 | cp.write(f) 158 | 159 | async def run(args): 160 | profile = args.config 161 | client = nio.AsyncClient(profile['server'], profile['user']) 162 | if args.state: 163 | state = args.state 164 | client.access_token = state['access_token'] 165 | client.user_id = state['user_id'] 166 | client.device_id = state['device_id'] 167 | else: 168 | r = await client.login(profile['password']) 169 | raise_for_response(r) 170 | if args.store_state: 171 | update_state(client, args.state_filename, args.profile) 172 | 173 | if args.user: 174 | room = await do_with_relogin(client, args, private_room, client, args.user) 175 | if not room: 176 | raise RuntimeError(f'Could not find private direct message room user {args.user}') 177 | else: 178 | room = args.room 179 | 180 | if args.line: 181 | return await send_line_by_line(client, args, room) 182 | 183 | if args.message: 184 | msg = args.message 185 | else: 186 | msg = sys.stdin.read() 187 | 188 | await do_with_relogin(client, args, send_message, client, room, msg) 189 | 190 | await client.close() 191 | 192 | 193 | async def send_line_by_line(client, args, room): 194 | for line in sys.stdin: 195 | await do_with_relogin(client, args, send_message, client, room, line) 196 | 197 | await client.close() 198 | 199 | 200 | def main(): 201 | args = parse_args() 202 | logging.basicConfig(level=logging.WARN) 203 | asyncio.run(run(args)) 204 | 205 | if __name__ == '__main__': 206 | sys.exit(main()) 207 | -------------------------------------------------------------------------------- /netio2csv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Collect metering data from a Netio power sockets into CSV files. 4 | # 5 | # SPDX-FileCopyrightText: © 2020 Georg Sauthoff 6 | # SPDX-License-Identifier: GPL-3.0-or-later 7 | 8 | import argparse 9 | import collections 10 | import configparser 11 | import json 12 | import logging 13 | import os 14 | import requests 15 | import signal 16 | import sys 17 | import time 18 | 19 | log = logging.getLogger(__name__) 20 | 21 | 22 | def parse_args(*xs): 23 | p = argparse.ArgumentParser() 24 | p.add_argument('--config', '-c', default=os.getenv('HOME', '.') + '/.config/netio.ini', 25 | help='config file for netio settings (default: %(default)s)') 26 | p.add_argument('--socket', '-s', dest='sockets', type=int, nargs='*', default=[1], 27 | help='power socket number(s) (default: %(default)s)') 28 | p.add_argument('--interval', '-i', type=int, default=1, 29 | help='collection interval (default: %(default)d)') 30 | p.add_argument('-n', type=int, default=-1, 31 | help='collect n times (default: %(default)d)') 32 | p.add_argument('--output', '-o', default='output.csv', 33 | help='output filename (default: %(default)s)') 34 | args = p.parse_args(*xs) 35 | return args 36 | 37 | def parse_conf(filename): 38 | conf = configparser.ConfigParser() 39 | conf.read(filename) 40 | Config = collections.namedtuple('Config', ['user', 'password', 'host']) 41 | return Config(conf['netio'].get('user', None), conf['netio'].get('password', None), 42 | conf['netio']['host']) 43 | 44 | def set_proc_name(name): 45 | if not os.path.exists('/proc/self/comm'): 46 | return 47 | with open(f'/proc/self/comm', 'w') as f: 48 | f.write(name) 49 | 50 | def measure_one(url, session, auth, args, f): 51 | r = requests.get(url, **auth) 52 | 53 | d = json.loads(r.text, parse_float=str, parse_int=str) 54 | 55 | xs = [ str(int(time.time())), d['Agent']['DeviceName'] ] 56 | xs.extend(d['GlobalMeasure'][n] for n in ('Voltage', 'Frequency')) 57 | 58 | for k in args.sockets: 59 | xs.extend(d['Outputs'][k-1][n] for n in ('State', 'Current', 'PowerFactor')) 60 | 61 | print(','.join(xs), file=f) 62 | 63 | 64 | def measure(url, session, auth, args, f): 65 | i = 0 66 | 67 | hs = [ 's', 'name', 'V', 'Hz' ] 68 | for k in args.sockets: 69 | # pf == true power factor -> https://en.wikipedia.org/wiki/Power_factor 70 | hs.extend(f'{n}_{k}' for n in ('state', 'mA', 'pf')) 71 | print(','.join(hs), file=f) 72 | 73 | while i != args.n: 74 | 75 | try: 76 | measure_one(url, session, auth, args, f) 77 | except Exception as e: 78 | log.error(f'measurement failed: {e}') 79 | 80 | time.sleep(args.interval) 81 | if args.n > 0: 82 | i += 1 83 | 84 | class Sig_Term(Exception): 85 | pass 86 | 87 | class Sig_Hup(Exception): 88 | pass 89 | 90 | def terminate(signo, frame): 91 | raise Sig_Term() 92 | 93 | def hup(signo, frame): 94 | raise Sig_Hup() 95 | 96 | def main(): 97 | args = parse_args() 98 | conf = parse_conf(args.config) 99 | set_proc_name('netio2csv') 100 | signal.signal(signal.SIGTERM, terminate) 101 | signal.signal(signal.SIGHUP, hup) 102 | 103 | session = requests.Session() 104 | auth = {} 105 | if conf.password: 106 | auth['auth'] = (conf.user, conf.password) 107 | url = f'{conf.host}/netio.json' 108 | 109 | while True: 110 | try: 111 | with open(args.output, 'w') as f: 112 | measure(url, session, auth, args, f) 113 | break 114 | except Sig_Hup: 115 | pass 116 | 117 | if __name__ == '__main__': 118 | try: 119 | sys.exit(main()) 120 | except (KeyboardInterrupt, Sig_Term): 121 | print() 122 | 123 | -------------------------------------------------------------------------------- /otherhost.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env drgn 2 | 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | # SPDX-FileCopyrightText: © 2023 Georg Sauthoff 5 | 6 | # hlist_for_each_entry(), netdev_get_by_name(), ... 7 | from drgn.helpers.linux import * 8 | import drgn.helpers.linux.net 9 | 10 | 11 | # cf. dev_get_stats() in net/core/dev.c: 12 | # https://elixir.bootlin.com/linux/v6.2.15/source/net/core/dev.c#L10431 13 | def rx_otherhost_dropped(dev): 14 | r = 0 15 | for i in for_each_present_cpu(prog): 16 | r += per_cpu_ptr(dev.core_stats, i).rx_otherhost_dropped.value_() 17 | return r 18 | 19 | 20 | def for_each_netdev(prog): 21 | net = prog['init_net'] 22 | for i in range(drgn.helpers.linux.net._NETDEV_HASHENTRIES): 23 | h = net.dev_index_head[i] 24 | for d in hlist_for_each_entry("struct net_device", h, "index_hlist"): 25 | yield d 26 | 27 | 28 | for d in for_each_netdev(prog): 29 | name = d.name.string_().decode() 30 | x = rx_otherhost_dropped(d) 31 | print(f'{name:15} {x}') 32 | 33 | 34 | # d = netdev_get_by_name(prog, 'enp4s0u2u1u2') 35 | # print(rx_otherhost_dropped(d)) 36 | -------------------------------------------------------------------------------- /pdfmerge.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # 2018, Georg Sauthoff 4 | # 5 | # SPDX-License-Identifier: GPL-3.0-or-later 6 | 7 | import argparse 8 | import sys 9 | 10 | # import PyPDF2 # imported inside merge() 11 | # import pdfrw # imported inside merge_pdfrw() 12 | 13 | def mk_arg_parser(): 14 | p = argparse.ArgumentParser( 15 | description='Merge two PDF documents', 16 | epilog='''Merging two PDF documents means that the pages of the 17 | second document are put on top of the pages of the first one. A 18 | common use case is the merging of two PDF layers, e.g. a text layer 19 | (from an OCR software) and and an image layer. This is similar to 20 | watermarking, although the watermark is usually just a single page 21 | that is repeatedly merged with each page. 22 | 23 | If one input document has less pages than the other one then the 24 | remaining pages are copied into the output document, as-is.''') 25 | p.add_argument('filename1', metavar='INPUT1', nargs=1, 26 | help='first input PDF file') 27 | p.add_argument('filename2', metavar='INPUT2', nargs=1, 28 | help='second input PDF file') 29 | p.add_argument('ofilename', metavar='OUTPUT', nargs=1, 30 | help='the merged PDF file') 31 | p.add_argument('--pdfrw', action='store_true', 32 | help='Use the pdfrw package instead of PyPDF2') 33 | return p 34 | 35 | def parse_args(*a): 36 | arg_parser = mk_arg_parser() 37 | args = arg_parser.parse_args(*a) 38 | args.filename1 = args.filename1[0] 39 | args.filename2 = args.filename2[0] 40 | args.ofilename = args.ofilename[0] 41 | return args 42 | 43 | # cf. https://github.com/tesseract-ocr/tesseract/issues/660#issuecomment-273629726 44 | # e.g. text, images, merged 45 | def merge(filename1, filename2, ofilename): 46 | import PyPDF2 47 | with open(filename1, 'rb') as f1, open(filename2, 'rb') as f2: 48 | # PdfFileReader isn't usable as context-manager 49 | pdf1, pdf2 = (PyPDF2.PdfFileReader(x) for x in (f1, f2)) 50 | opdf = PyPDF2.PdfFileWriter() 51 | for page1, page2 in zip(pdf1.pages, pdf2.pages): 52 | page1.mergePage(page2) 53 | opdf.addPage(page1) 54 | n1, n2 = len(pdf1.pages), len(pdf2.pages) 55 | if n1 != n2: 56 | for page in (pdf2.pages[n1:] if n1 < n2 else pdf1.pages[n2:]): 57 | opdf.addPage(page) 58 | with open(ofilename, 'wb') as g: 59 | opdf.write(g) 60 | 61 | def merge_pdfrw(filename1, filename2, ofilename): 62 | import pdfrw 63 | # PdfReader isn't usable as context-manager 64 | pdf1, pdf2 = (pdfrw.PdfReader(x) for x in (filename1, filename2)) 65 | w = pdfrw.PdfWriter() 66 | for page1, page2 in zip(pdf1.pages, pdf2.pages): 67 | m = pdfrw.PageMerge(page1) 68 | m.add(page2) 69 | m.render() 70 | w.addpage(page1) 71 | # is sufficient if both files contain the same number of pages: 72 | # w.write(ofilename, pdf1) 73 | n1, n2 = len(pdf1.pages), len(pdf2.pages) 74 | if n1 != n2: 75 | for page in (pdf2.pages[n1:] if n1 < n2 else pdf1.pages[n2:]): 76 | w.addpage(page) 77 | w.write(ofilename) 78 | 79 | def main(): 80 | args = parse_args() 81 | if args.pdfrw: 82 | merge_pdfrw(args.filename1, args.filename2, args.ofilename) 83 | else: 84 | merge(args.filename1, args.filename2, args.ofilename) 85 | 86 | if __name__ == '__main__': 87 | sys.exit(main()) 88 | -------------------------------------------------------------------------------- /perfstat.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 2016, Georg Sauthoff 4 | 5 | if [ "$1" = -h -o "$1" = --help -o $# -lt 1 ]; then 6 | cat <&2 25 | exit 1 26 | fi 27 | 28 | set -eu 29 | 30 | set -o pipefail 31 | 32 | function cleanup 33 | { 34 | rm -f "$tfile" 35 | # echo "$tfile" >&2 36 | } 37 | trap cleanup EXIT 38 | 39 | tfile=$(mktemp) 40 | 41 | 42 | end_clause='END { 43 | OFS=","; 44 | # CentOS 7 perf does not print those with -x ... 45 | if (!a["ghz"]) 46 | a["ghz"]=a["cycles"]/a["nsec"]; 47 | if (!a["ins_cyc"]) 48 | a["ins_cyc"]=a["ins"]/a["cycles"]; 49 | if (!a["br_mis_rate"]) 50 | a["br_mis_rate"]=a["br_mis"]/a["br"]*100.0; 51 | 52 | print a["nsec"],a["cswitch"],a["cpu_migr"],a["page_fault"],a["cycles"],a["ghz"],a["ins"],a["ins_cyc"],a["br"],a["br_mis"],a["br_mis_rate"] 53 | } 54 | ' 55 | 56 | function perfstat_Linux 57 | { 58 | perf stat -x, -o "$tfile" "$@" 59 | 60 | awk -F, ' 61 | # on Fedora 23 the value have the :u suffix, on CentOS they do not 62 | $3 ~ /^task-clock(:u)?$/ { a["nsec"]=$4; next } 63 | $3 ~ /^context-switches(:u)?$/ { a["cswitch"]=$1; next } 64 | $3 ~ /^cpu-migrations(:u)?$/ { a["cpu_migr"]=$1; next } 65 | $3 ~ /^page-faults(:u)?$/ { a["page_fault"]=$1; next } 66 | $3 ~ /^cycles(:u)?$/ { a["cycles"]=$1; a["ghz"]=$6; next } 67 | $3 ~ /^instructions(:u)?$/ { a["ins"]=$1; a["ins_cyc"]=$6; next } 68 | $3 ~ /^branches(:u)?$/ { a["br"]=$1; next } 69 | $3 ~ /^branch-misses(:u)?$/ { a["br_mis"]=$1; a["br_mis_rate"]=$6; } 70 | '"$end_clause" "$tfile" > "$ofile" 71 | } 72 | 73 | function perfstat_SunOS 74 | { 75 | cputrack -o "$tfile" -c Cycles_user,PAPI_tot_ins,PAPI_br_cn,PAPI_br_msp "$@" 76 | awk ' 77 | $3 == "exit" { 78 | a["nsec"]=$1*1000000000; 79 | a["cycles"]=$4; 80 | a["ins"]=$5; 81 | a["br"]=$6; 82 | a["br_mis"]=$7; 83 | } 84 | '"$end_clause" "$tfile" > "$ofile" 85 | } 86 | 87 | sys=$(uname -s) 88 | perfstat_"$sys" "$@" 89 | 90 | -------------------------------------------------------------------------------- /ping.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Send ICMP ping requests out of an interface 4 | # with arbitrary destination ethernet adress 5 | # and arbitrary IP source and destination addresses. 6 | # 7 | # Useful for example for testing switch unicast flooding. 8 | # 9 | # SPDX-License-Identifier: GPL-3.0-or-later 10 | # SPDX-FileCopyrightText: © 2023 Georg Sauthoff 11 | 12 | import dpkt 13 | import socket 14 | import sys 15 | 16 | def mk_icmp_reply(eth_src, eth_dst, ip_src, ip_dst): 17 | ic = dpkt.icmp.ICMP( 18 | type = dpkt.icmp.ICMP_ECHO, 19 | data = dpkt.icmp.ICMP.Echo( 20 | id = 23, 21 | seq = 42, 22 | data = b'Hello World!' 23 | ) 24 | ) 25 | ip = dpkt.ip.IP( 26 | id = 0, 27 | src = ip_src, 28 | dst = ip_dst, 29 | p = dpkt.ip.IP_PROTO_ICMP, 30 | data = ic 31 | ) 32 | e = dpkt.ethernet.Ethernet( 33 | src = eth_src, 34 | dst = eth_dst, 35 | type = dpkt.ethernet.ETH_TYPE_IP, 36 | data = ip 37 | ) 38 | return bytes(e) 39 | 40 | 41 | def encode_eth_addr(s): 42 | xs = s.split(':') 43 | b = bytes( int(x, 16) for x in xs) 44 | return b 45 | 46 | def encode_ip_addr(s): 47 | xs = s.split('.') 48 | b = bytes( int(x) for x in xs) 49 | return b 50 | 51 | def if_eth_addr(name): 52 | s = open(f'/sys/class/net/{name}/address').read().strip() 53 | bs = encode_eth_addr(s) 54 | return bs 55 | 56 | def send_frame(iface, frame): 57 | s = socket.socket(socket.AF_PACKET, socket.SOCK_RAW) 58 | s.bind((iface, 0)) 59 | s.send(frame) 60 | 61 | def main(): 62 | iface = sys.argv[1] 63 | eth_src = if_eth_addr(iface) 64 | eth_dst = encode_eth_addr(sys.argv[2]) 65 | ip_src = encode_ip_addr(sys.argv[3]) 66 | ip_dst = encode_ip_addr(sys.argv[4]) 67 | frame = mk_icmp_reply(eth_src, eth_dst, ip_src, ip_dst) 68 | print(frame.hex()) 69 | 70 | send_frame(iface, frame) 71 | 72 | 73 | if __name__ == '__main__': 74 | sys.exit(main()) 75 | -------------------------------------------------------------------------------- /pldd.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # pldd - print shared libraries loaded into a running process 4 | # 5 | # inspired by Solaris' pldd 6 | # 7 | # 2018, Georg Sauthoff 8 | # 9 | # SPDX-License-Identifier: GPL-3.0-or-later 10 | 11 | import argparse 12 | import os 13 | import sys 14 | 15 | def parse_args(*a): 16 | p = argparse.ArgumentParser( 17 | description='print shared libraries loaded into a running process') 18 | p.add_argument('pids', metavar='PID', type=int, nargs=1, 19 | help='process identifier (PID)') 20 | return p.parse_args(*a) 21 | 22 | def pldd(pid): 23 | exe = os.readlink('/proc/{}/exe'.format(pid)) 24 | xs = [] 25 | with open('/proc/{}/maps'.format(pid)) as f: 26 | for line in f: 27 | row = line.split() 28 | if row[1].endswith('r-xp'): 29 | i = line.find('/') 30 | if i > 0: 31 | name = line[i:-1] 32 | if name != exe: 33 | xs.append(name) 34 | xs.sort() 35 | print('\n'.join(xs)) 36 | 37 | def main(): 38 | args = parse_args() 39 | pid = args.pids[0] 40 | return pldd(pid) 41 | 42 | if __name__ == '__main__': 43 | sys.exit(main()) 44 | 45 | -------------------------------------------------------------------------------- /pldd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # pldd - print shared libraries loaded into a running process 4 | # 5 | # inspired by Solaris' pldd 6 | # and the NOTES Section of glibc's pldd(1) man page 7 | # (note that glibc's pldd is broken for years) 8 | # 9 | # 2018, Georg Sauthoff 10 | # 11 | # SPDX-License-Identifier: GPL-3.0-or-later 12 | 13 | set -eu 14 | 15 | exe=$0 16 | 17 | function help 18 | { 19 | cat >&"$1" <, GPLv3+ 47 | 48 | EOF 49 | if [ $1 -gt 1 ]; then 50 | add_help 51 | fi 52 | } 53 | 54 | 55 | function add_help 56 | { 57 | cat < 119 | EOF 120 | } 121 | 122 | set -eu 123 | 124 | function check_entropy 125 | { 126 | local entropy=$1 127 | if [ $quiet -eq 0 ]; then 128 | echo "$entropy bits of entropy" >&2 129 | fi 130 | if [ "$entropy" -lt "$min_entropy" ]; then 131 | echo "not enough entropy ($min_entropy)\ 132 | - use bigger dictionary and/or more words" >&2 133 | exit 1 134 | fi 135 | } 136 | 137 | function gen_nodict 138 | { 139 | entropy=$(echo "l((26*2+10)^$words)/l(2)" | bc -l | sed 's/\..*$//') 140 | check_entropy $entropy 141 | 142 | dd if=/dev/urandom status=none | LC_ALL=C tr -d -c 'A-Za-z0-9' \ 143 | | dd bs=1 count=$words status=none 144 | echo 145 | } 146 | 147 | function parse_args 148 | { 149 | if [ $# -gt 0 ]; then 150 | if [ "$1" = '--help' ]; then help 1; exit 0; fi 151 | fi 152 | quiet=0 153 | use_dict=1 154 | use_help=0 155 | if command -v "$shuf" > /dev/null ; then : ; else shuf=""; fi 156 | while getopts "cf:hm:nqw:" arg; do 157 | case $arg in 158 | c) 159 | words=12 160 | use_dict=0 161 | ;; 162 | f) 163 | wfile=$OPTARG 164 | ;; 165 | h) 166 | use_help=$((use_help+1)) 167 | ;; 168 | m) 169 | min_entropy=$OPTARG 170 | ;; 171 | n) 172 | shuf="" 173 | ;; 174 | q) 175 | quiet=1 176 | ;; 177 | w) 178 | words=$OPTARG 179 | ;; 180 | ?) 181 | exit 1 182 | ;; 183 | esac 184 | done 185 | if [ $OPTIND -eq $# -o $OPTIND -lt $# ]; then 186 | words=${!OPTIND} 187 | fi 188 | if [ $use_help -gt 0 ]; then 189 | help $use_help 190 | exit 0 191 | fi 192 | } 193 | 194 | function init_wordlist 195 | { 196 | if [ -f "$wfile" ]; then 197 | return 198 | fi 199 | if command -v "$aspell" > /dev/null ; then 200 | for i in $lang; do 201 | "$aspell" -d "$i" dump master | "$aspell" -l $i expand 202 | done | "$sort" -u | gzip > "$wfile" 203 | elif [ -f "/usr/share/myspell/en_US.dic" ]; then 204 | for i in $lang; do 205 | sed 's@/.*$@@' /usr/share/myspell/"$i"*.dic 206 | done | "$sort" -u | gzip > "$wfile" 207 | fi 208 | 209 | } 210 | 211 | function gen_dict 212 | { 213 | init_wordlist 214 | 215 | local n=$(zcat "$wfile" | wc -l) 216 | if [ "$n" -eq 0 ]; then 217 | echo "Wordlist $wfile is empty" >&2 218 | exit 1 219 | fi 220 | 221 | local entropy=$(echo "l($n^$words)/l(2)" | bc -l | sed 's/\..*$//') 222 | check_entropy "$entropy" 223 | 224 | if [ -n "$shuf" ]; then 225 | zcat "$wfile" | "$shuf" --random-source /dev/urandom -r -n $words \ 226 | | "$awk" '{print(tolower($0))}' \ 227 | | tr -d "'" \ 228 | | "$paste" -sd '-' 229 | else 230 | # NB: 1048576 = 2**20 231 | if [ "$n" -ge 1048576 ]; then 232 | echo "Wordlist $wfile is too large" >&2 233 | exit 1 234 | fi 235 | for i in `seq 1 $words`; do 236 | zcat "$wfile" \ 237 | | "$sed" -n $(< /dev/urandom od -tu4 -An -v -w4 | awk '{ $1 = $1 % 1048576 } $1 < '$n' { print $1+1; exit; }')p \ 238 | | "$awk" '{print(tolower($0))}' \ 239 | | tr -d "'" 240 | done | "$paste" -sd '-' 241 | fi 242 | } 243 | 244 | 245 | : ${aspell:=aspell} 246 | : ${awk:=awk} 247 | : ${lang:=en} 248 | min_entropy=60 249 | # GNU coreutils paste 250 | : ${paste:=paste} 251 | : ${sed:=sed} 252 | # GNU coreutils shuf 253 | : ${shuf:=shuf} 254 | : ${sort:=sort} 255 | : ${wfile:=~/.cache/words.gz} 256 | words=4 257 | 258 | function main 259 | { 260 | parse_args "$@" 261 | if [ $use_dict -ne 0 ]; then 262 | gen_dict 263 | else 264 | gen_nodict 265 | fi 266 | } 267 | 268 | main "$@" 269 | 270 | -------------------------------------------------------------------------------- /remove.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # 2018, Georg Sauthoff 4 | # SPDX-License-Identifier: GPL-3.0-or-later 5 | 6 | import argparse 7 | import os 8 | import time 9 | import subprocess 10 | import sys 11 | 12 | # cf. https://unix.stackexchange.com/q/444611/1131 13 | def remove(dev): 14 | filename = '/sys/block/{}/device/../../../../remove'.format(dev) 15 | with open(filename, 'wb', buffering=0) as f: 16 | f.write(b'1\n') 17 | 18 | # probably also tirggered by the remove, just being paranoid here 19 | # the flushbufs generally makes sense when there were some raw writes 20 | # to the device 21 | def flush(dev): 22 | subprocess.check_output(['blockdev', '--flushbufs', dev]) 23 | 24 | def detach(filename): 25 | devname = os.path.realpath(filename) 26 | dev = os.path.basename(devname) 27 | flush(devname) 28 | # in case the remove isn't properly implemented/paranoia 29 | time.sleep(13) 30 | remove(dev) 31 | try: 32 | os.stat(devname) 33 | raise RuntimeError( 34 | 'Device {} still present after removal'.format(devname)) 35 | except FileNotFoundError: 36 | pass 37 | time.sleep(3) 38 | 39 | def main(): 40 | p = argparse.ArgumentParser( 41 | description='Sync USB drive cache, power-off and remove device ') 42 | p.add_argument('filenames', metavar='DEVICE_PATH', nargs='+', 43 | help='path to USB disk device (e.g. /dev/sdb)') 44 | args = p.parse_args() 45 | try: 46 | for filename in args.filenames: 47 | detach(filename) 48 | except Exception as e: 49 | print('error: {}'.format(e), file=sys.stderr) 50 | return 1 51 | 52 | if __name__ == '__main__': 53 | sys.exit(main()) 54 | -------------------------------------------------------------------------------- /reset-tmux.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Reset a garbled tmux session, e.g. after some random bytes 4 | # were accidentally sent to stdout 5 | 6 | 7 | # source: https://unix.stackexchange.com/a/253369/1131 8 | 9 | stty sane 10 | printf '\033k%s\033\\\033]2;%s\007' "`basename "$SHELL"`" "`uname -n`" 11 | tput reset 12 | tmux refresh 13 | 14 | tmux list-windows -a | while IFS=: read -r a b c; do 15 | tmux set-window-option -t "$a:$b" automatic-rename on 16 | done 17 | -------------------------------------------------------------------------------- /ripdvd: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 2011-2018, Georg Sauthoff , GPLv3+ 4 | # 5 | # ripdvd - copy each DVD track into a nicely named .vob file 6 | 7 | set -e 8 | 9 | #dev=/dev/dvd 10 | dev=/dev/cdrom 11 | 12 | titles=$(mplayer -identify -vo null -frames 0 -dvd-device $dev dvd:// 2>&1 \ 13 | | awk '/There are [0-9]+ titles/ { print $3 }') 14 | 15 | vname=$(isoinfo -d dev=$dev | grep '^Volume id:' | cut -d' ' -f3`) 16 | 17 | while [ -d "$vname" ] ; do 18 | vname=${vname}1 19 | done 20 | 21 | mkdir "$vname" 22 | cd "$vname" 23 | 24 | echo "Ripping DVD $vname ($titles titles)" 25 | 26 | from=${1:-1} 27 | 28 | set +e 29 | 30 | for i in $(seq "$from" $titles); do 31 | echo -n " Track $i ... " 32 | m=$(mplayer -dvd-device $dev dvd://$i -quiet -dumpstream -dumpfile \ 33 | t$(echo $i | sed 's/^\([0-9]\)$/0\1/').vob 2>&1 > /dev/null ) 34 | r=$? 35 | g=$(echo $m | grep -v '^libdvdread:\|joystick\|socket\|LIRC\|^$\|^mplayer: No such file or directory$') 36 | echo -n $g 37 | if [ "$r" != "0" ]; then 38 | echo failed 39 | exit 1 40 | else 41 | echo OK 42 | fi 43 | done 44 | 45 | eject $dev 46 | -------------------------------------------------------------------------------- /rtcdelta.c: -------------------------------------------------------------------------------- 1 | // 2025, Georg Sauthoff 2 | // 3 | // Compute difference between RTC and system clock. 4 | // 5 | // SPDX-License-Identifier: GPL-3.0-or-later 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | 18 | static void help(const char *argv0) 19 | { 20 | printf("call: %s [RTC_DEVICE]\n", argv0); 21 | exit(0); 22 | } 23 | 24 | 25 | int main(int argc, char **argv) 26 | { 27 | const char *rtc = "/dev/rtc"; 28 | if (argc > 1) 29 | rtc = argv[1]; 30 | if (!strcmp(rtc, "-h") || !strcmp(rtc, "--help")) 31 | help(argv[0]); 32 | 33 | int fd = open(rtc, O_RDONLY); 34 | if (fd == -1) { 35 | perror("open rtc"); 36 | return 1; 37 | } 38 | 39 | struct rtc_time rt = { 0 }; 40 | int r = ioctl(fd, RTC_RD_TIME, &rt); 41 | if (r) { 42 | perror("RTC_RD_TIME"); 43 | return 1; 44 | } 45 | struct tm rtm = { 46 | .tm_sec = rt.tm_sec , 47 | .tm_min = rt.tm_min , 48 | .tm_hour = rt.tm_hour , 49 | .tm_mday = rt.tm_mday , 50 | .tm_mon = rt.tm_mon , 51 | .tm_year = rt.tm_year 52 | }; 53 | r = setenv("TZ", "", 1); 54 | if (r == -1) { 55 | perror("setenv"); 56 | return 1; 57 | } 58 | time_t rtc_epoch = mktime(&rtm); 59 | if (rtc_epoch == (time_t)-1) { 60 | perror("mktime"); 61 | return 1; 62 | } 63 | 64 | struct timespec ts = {0}; 65 | r = clock_gettime(CLOCK_REALTIME, &ts); 66 | if (r == -1) { 67 | perror("clock_gettime"); 68 | return 1; 69 | } 70 | 71 | ssize_t delta = rtc_epoch; 72 | delta -= ts.tv_sec; 73 | 74 | printf("rtc-sys: %zd\n", delta); 75 | 76 | return 0; 77 | } 78 | -------------------------------------------------------------------------------- /searchb-rs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "searchb-rs" 3 | version = "0.1.0" 4 | authors = ["Georg Sauthoff "] 5 | 6 | [dependencies] 7 | memmap = "0.6.2" 8 | 9 | [dependencies.twoway] 10 | version = "0.1.8" 11 | # this depends on feature "asm" which isn't available in Rust 1.25 12 | # features = ["pcmp" ] 13 | -------------------------------------------------------------------------------- /searchb-rs/src/main.rs: -------------------------------------------------------------------------------- 1 | // 2018, Georg Sauthoff 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | // 4 | // How to build: 5 | // cargo build --release 6 | 7 | extern crate memmap; 8 | extern crate twoway; 9 | 10 | 11 | fn mmap(filename: &str) -> Result 12 | { 13 | let file = std::fs::File::open(filename)?; 14 | let p = unsafe { memmap::Mmap::map(&file)? }; 15 | Ok(p) 16 | } 17 | 18 | /* 19 | fn naive_find_bytes(t : &[u8], q : &[u8]) -> Option { 20 | t.windows(q.len()).position(|w| w == q) 21 | } 22 | */ 23 | 24 | fn real_main(args : & mut std::env::Args) -> Result, String> { 25 | args.next(); 26 | let qfilename = args.next().ok_or("query filename missing")?; 27 | let filename = args.next().ok_or("target filename missing")?; 28 | let q = match mmap(&qfilename) { 29 | Ok(x) => x, 30 | Err(ref e) if e.kind() == std::io::ErrorKind::InvalidInput 31 | => return Ok(Some(0)), 32 | Err(e) => return Err(e.to_string()) 33 | }; 34 | let t = match mmap(&filename) { 35 | Ok(x) => x, 36 | Err(ref e) if e.kind() == std::io::ErrorKind::InvalidInput 37 | => return Ok(None), 38 | Err(e) => return Err(e.to_string()) 39 | }; 40 | Ok(twoway::find_bytes(&t, &q)) // equivalent to below notation 41 | //Ok(twoway::find_bytes(&t[..], &q[..])) 42 | //Ok(naive_find_bytes(&t, &q)) 43 | } 44 | 45 | fn main() { 46 | std::process::exit( 47 | match real_main(&mut std::env::args()) { 48 | Ok(t) => match t { 49 | Some(n) => { println!("{}", n); 0 }, 50 | None => 1 51 | }, 52 | Err(e) => {eprintln!("error: {}", e); 1 } 53 | }) 54 | } 55 | -------------------------------------------------------------------------------- /searchb.c: -------------------------------------------------------------------------------- 1 | // 2018, Georg Sauthoff 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | #define _GNU_SOURCE 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | static void *mmap_file(const char *filename, size_t *n) 15 | { 16 | static unsigned char empty[0]; 17 | int fd = open(filename, O_RDONLY); 18 | if (fd == -1) 19 | return 0; 20 | struct stat st; 21 | int r = fstat(fd, &st); 22 | if (r == -1) { 23 | close(fd); 24 | return 0; 25 | } 26 | *n = st.st_size; 27 | if (!*n) { 28 | close(fd); 29 | return empty; 30 | } 31 | void *p = mmap(0, *n, PROT_READ, MAP_SHARED, fd, 0); 32 | if (p == (void*)-1) { 33 | close(fd); 34 | return 0; 35 | } 36 | r = close(fd); 37 | if (r == -1) { 38 | munmap(p, *n); 39 | return 0; 40 | } 41 | return p; 42 | } 43 | 44 | int main(int argc, char **argv) 45 | { 46 | if (argc < 3) { 47 | fprintf(stderr, "call: %s queryfile file\n", argv[0]); 48 | return 1; 49 | } 50 | size_t m; 51 | const char *q = mmap_file(argv[1], &m); 52 | if (!q) { 53 | perror(0); 54 | return 1; 55 | } 56 | size_t n; 57 | const char *t = mmap_file(argv[2], &n); 58 | if (!q) { 59 | perror(0); 60 | return 1; 61 | } 62 | const char *p = memmem(t, n, q, m); 63 | if (!p) 64 | return 1; 65 | printf("%zd\n", p-t); 66 | return 0; 67 | } 68 | -------------------------------------------------------------------------------- /searchb.cc: -------------------------------------------------------------------------------- 1 | // 2018, Georg Sauthoff 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | #include 5 | #include 6 | 7 | // oh no! just uses naive implementation: 8 | // https://www.boost.org/doc/libs/1_67_0/doc/html/string_algo/design.html#string_algo.find 9 | // #include 10 | // there is also KMP (and BMH) - but no two-way (as of boost 1.64): 11 | // #include 12 | // #include 13 | // better would be: a wrapper that calls different search algorithms 14 | // depending on the sizes of q and t 15 | 16 | #include 17 | 18 | static int real_main(int argc, char **argv) 19 | { 20 | if (argc != 3) { 21 | std::cerr << "Call: " << argv[0] << " PATTERN_FILE SRC_FILE\n"; 22 | return 2; 23 | } 24 | auto q = ixxx::util::mmap_file(argv[1]); 25 | auto t = ixxx::util::mmap_file(argv[2]); 26 | auto r = std::search(t.begin(), t.end(), q.begin(), q.end()); 27 | //auto r = boost::algorithm::boyer_moore_horspool_search(t.begin(), t.end(), q.begin(), q.end()).first; 28 | //auto r = boost::algorithm::knuth_morris_pratt_search(t.begin(), t.end(), q.begin(), q.end()).first; 29 | if (r == t.end()) 30 | return 1; 31 | else 32 | std::cout << (r - t.begin()) << '\n'; 33 | return 0; 34 | } 35 | 36 | int main(int argc, char **argv) 37 | { 38 | try { 39 | return real_main(argc, argv); 40 | } catch (std::exception &e) { 41 | std::cerr << e.what() << '\n'; 42 | return 1; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /searchb.go: -------------------------------------------------------------------------------- 1 | // 2018, Georg Sauthoff 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | // How to build: 5 | // 6 | // sudo dnf install golang-github-golang-sys-devel 7 | // export GOPATH=$HOME/go:/usr/share/gocode 8 | // mkdir build 9 | // cd build 10 | // go build -o searchb-go ../searchb.go 11 | 12 | 13 | package main 14 | 15 | import ( 16 | "golang.org/x/sys/unix" 17 | "bytes" 18 | "fmt" 19 | "os" 20 | ) 21 | 22 | func mmap(name string) ([]byte, error) { 23 | f, err := os.Open(name) 24 | if err != nil { 25 | return nil, err 26 | } 27 | st, err := f.Stat() 28 | if err != nil { 29 | f.Close() 30 | return nil, err 31 | } 32 | if st.Size() == 0 { 33 | f.Close() 34 | return nil, nil // nil is also the empty byte slice 35 | } 36 | m, err := unix.Mmap(int(f.Fd()), 0, int(st.Size()), unix.PROT_READ, 37 | unix.MAP_SHARED) 38 | if err != nil { 39 | f.Close() 40 | return nil, err 41 | } 42 | err = f.Close() 43 | if err != nil { 44 | unix.Munmap(m) 45 | return nil, err 46 | } 47 | return m, err 48 | } 49 | 50 | func main() { 51 | q, err := mmap(os.Args[1]) 52 | if err != nil { 53 | panic(err.Error()) 54 | } 55 | t, err := mmap(os.Args[2]) 56 | if err != nil { 57 | panic(err.Error()) 58 | } 59 | i := bytes.Index(t, q) 60 | if i == -1 { 61 | os.Exit(1) 62 | } 63 | fmt.Println(i) 64 | os.Exit(0) 65 | } 66 | -------------------------------------------------------------------------------- /searchb.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # 2018, Georg Sauthoff 4 | # SPDX-License-Identifier: GPL-3.0-or-later 5 | 6 | import mmap 7 | import sys 8 | 9 | def map_file(filename): 10 | with open(filename, 'rb', buffering=0) as f: 11 | try: 12 | b = mmap.mmap(f.fileno(), 0, prot=mmap.PROT_READ) 13 | return b 14 | except ValueError: 15 | return bytes() 16 | 17 | def main(qfilename, filename): 18 | q, t = (map_file(x) for x in (qfilename, filename)) 19 | off = 0 20 | while True: 21 | i = t.find(q, off) 22 | if i == -1: 23 | return 1 24 | print(i) 25 | off = i + len(q) 26 | 27 | if __name__ == '__main__': 28 | sys.exit(main(sys.argv[1], sys.argv[2])) 29 | -------------------------------------------------------------------------------- /silence.c: -------------------------------------------------------------------------------- 1 | static const char help_str[] = 2 | "Call: %s [OPT..] COMMAND [ARG..]\n" 3 | "\n" 4 | "Silence stdout/stderr of COMMAND unless its return code is unequal to 0.\n" 5 | "\n" 6 | "Usecases:\n" 7 | "\n" 8 | " - wrap commands that are called from a job scheduler like cron\n" 9 | " - increase the signal-to-noise-ratio in the terminal\n" 10 | "\n" 11 | "Options:\n" 12 | "\n" 13 | "-e N interpret other return codes besides 0 as success\n" 14 | "-h,--help this screen\n" 15 | "-k,-K enable/disable suicide on parent exit (default: disabled)\n" 16 | " On Linux, a parent death signal is installed in the child\n" 17 | " that execs COMMAND, otherwise the TERM signal handler kills\n" 18 | " the child.\n" 19 | "\n" 20 | "It honors the TMPDIR environment and defaults to /tmp in case\n" 21 | "it isn't set.\n" 22 | "\n" 23 | "This is a C reimplementation of chronic from moreutils\n" 24 | "(which is a Perl script). See also the README.md for details\n" 25 | "on the differences.\n" 26 | "\n" 27 | "\n" 28 | "2016, Georg Sauthoff , GPLv3+\n" 29 | "cf. https://github.com/gsauthof/utility\n" 30 | "\n"; 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | 45 | #if defined(__linux__) 46 | #include 47 | #endif 48 | 49 | #include "utility.h" 50 | 51 | #ifndef USE_TMPFILE 52 | #if defined(__linux__) 53 | #if defined(O_TMPFILE) 54 | #define USE_TMPFILE 1 55 | #else 56 | // not available e.g. on CentOS/RHEL 7 57 | #define USE_TMPFILE 0 58 | #warning "this Linux is so old it doesn't even have O_TMPFILE ..." 59 | #endif 60 | #else 61 | #define USE_TMPFILE 0 62 | #endif 63 | #endif 64 | 65 | #ifndef USE_PRCTL 66 | #if defined(__linux__) 67 | #define USE_PRCTL 1 68 | #else 69 | #define USE_PRCTL 0 70 | #endif 71 | #endif 72 | 73 | static void help(FILE *f, const char *argv0) 74 | { 75 | fprintf(f, help_str , argv0); 76 | } 77 | 78 | struct Arguments { 79 | const char *tmpdir; 80 | bool suicide; 81 | int *success_codes; 82 | size_t success_codes_size; 83 | }; 84 | typedef struct Arguments Arguments; 85 | 86 | static char **parse_arguments(int argc, char **argv, Arguments *a) 87 | { 88 | *a = (Arguments) { 89 | .tmpdir = "/tmp", 90 | .suicide = false 91 | }; 92 | const char *tmpdir = getenv("TMPDIR"); 93 | if (tmpdir) 94 | a->tmpdir = tmpdir; 95 | if (argc > 1 && !strcmp(argv[1], "--help")) { 96 | help(stdout, argv[0]); 97 | exit(0); 98 | } 99 | char c = 0; 100 | const char opt_str[] = "+e:hkK"; 101 | while ((c = getopt(argc, argv, opt_str)) != -1) { 102 | switch (c) { 103 | case '?': help(stderr, argv[0]); exit(1); break; 104 | case 'h': help(stdout, argv[0]); exit(0); break; 105 | case 'e': ++a->success_codes_size; break; 106 | case 'k': a->suicide = true ; break; 107 | case 'K': a->suicide = false; break; 108 | } 109 | } 110 | if (optind == argc) { 111 | help(stderr, argv[0]); 112 | exit(1); 113 | } 114 | if (a->success_codes_size) { 115 | a->success_codes = calloc(a->success_codes_size, sizeof(int)); 116 | if (!a->success_codes) 117 | exit(1); 118 | int *v = a->success_codes; 119 | optind = 1; 120 | while ((c = getopt(argc, argv, opt_str)) != -1) { 121 | switch (c) { 122 | case 'e': 123 | { 124 | errno = 0; 125 | char *s = 0; 126 | *v++ = strtol(optarg, &s, 10); 127 | if (errno || s == optarg) { 128 | perror("converting -e argument"); 129 | exit(1); 130 | } 131 | } 132 | break; 133 | } 134 | } 135 | } 136 | return argv + optind; 137 | } 138 | 139 | static int create_unlinked_temp_file(const char *tmpdir) 140 | { 141 | #if USE_TMPFILE 142 | int fd = open(tmpdir, O_RDWR | O_TMPFILE | O_EXCL, 0600); 143 | check_exit(fd, "opening temp file"); 144 | return fd; 145 | #else 146 | // POSIX.1-2001 doesn't specify the mode of mkstemp(), 147 | // POSIX.1-2008 does specify 0600 148 | mode_t old_mask = umask(0177); 149 | const char suffix[] = "/silence_XXXXXX"; 150 | size_t n = strlen(tmpdir); 151 | char s[n+sizeof(suffix)]; 152 | memcpy(s, tmpdir, n); 153 | memcpy(s+n, suffix, sizeof(suffix)); 154 | int fd = mkstemp(s); 155 | check_exit(fd, "creating temp file"); 156 | umask(old_mask); 157 | int r = unlink(s); 158 | check_exit(r, "unlinking temp file"); 159 | return fd; 160 | #endif 161 | } 162 | 163 | static ssize_t write_auto_resume(int fd, const void *buffer, size_t n) 164 | { 165 | ssize_t m = 0; 166 | do { 167 | m = write(fd, buffer, n); 168 | } while (m == -1 && errno == EINTR); 169 | return m; 170 | } 171 | static ssize_t read_auto_resume(int fd, void *buffer, size_t n) 172 | { 173 | ssize_t m = 0; 174 | do { 175 | m = read(fd, buffer, n); 176 | } while (m == -1 && errno == EINTR); 177 | return m; 178 | } 179 | static ssize_t write_all(int fd, const char *buffer, size_t n_) 180 | { 181 | ssize_t n = n_; 182 | do { 183 | ssize_t m = write_auto_resume(fd, buffer, n); 184 | if (m == -1) 185 | return -1; 186 | buffer += m; 187 | n -= m; 188 | } while (n); 189 | return n_; 190 | } 191 | 192 | static void dump(int fd, int d) 193 | { 194 | int r = lseek(fd, 0, SEEK_SET); 195 | check_exit(r, "seeking output file"); 196 | char buffer[128 * 1024]; 197 | for (;;) { 198 | ssize_t n = read_auto_resume(fd, buffer, sizeof(buffer)); 199 | check_exit(n, "reading output file"); 200 | if (!n) 201 | break; 202 | ssize_t m = write_all(d, buffer, n); 203 | check_exit(m, "writing output"); 204 | } 205 | } 206 | 207 | #if !USE_PRCTL 208 | 209 | static pid_t child_pid_ = 0; 210 | 211 | static void kill_child(int sig) 212 | { 213 | if (child_pid_) 214 | kill(child_pid_, SIGTERM); 215 | exit(128 + SIGTERM); 216 | } 217 | 218 | #endif 219 | 220 | static bool is_successful(int code, const Arguments *a) 221 | { 222 | for (size_t i = 0; i < a->success_codes_size; ++i) 223 | if (code == a->success_codes[i]) 224 | return true; 225 | return !code; 226 | } 227 | 228 | static void supervise_child(int fd_o, int fd_e, pid_t pid, const Arguments *a) 229 | { 230 | // we ignore QUIT/INT because when issued via Ctrl+\/Ctrl+C in the terminal, 231 | // UNIX sends them both to the parent and the child 232 | // (cf. http://unix.stackexchange.com/questions/176235/fork-and-how-signals-are-delivered-to-processes) 233 | // ignoring them in the parent thus makes sure that any 234 | // collected output is printed after the child terminates 235 | // because of those signals (the default action) 236 | struct sigaction ignore_action = { .sa_handler = SIG_IGN }; 237 | struct sigaction old_int_action; 238 | int r = sigaction(SIGINT, &ignore_action, &old_int_action); 239 | check_exit(r, "ignoring SIGINT"); 240 | struct sigaction old_quit_action; 241 | r = sigaction(SIGQUIT, &ignore_action, &old_quit_action); 242 | check_exit(r, "ignoring SIGQUIT"); 243 | #if !USE_PRCTL 244 | child_pid_ = pid; 245 | struct sigaction term_action = { .sa_handler = kill_child }; 246 | struct sigaction old_term_action; 247 | if (a->suicide) { 248 | r = sigaction(SIGTERM, &term_action, &old_term_action); 249 | check_exit(r, "installing SIGTERM handler"); 250 | } 251 | #endif 252 | int status = 0; 253 | r = waitpid(pid, &status, 0); 254 | check_exit(r, "waiting on child"); 255 | #if !USE_PRCTL 256 | if (a->suicide) { 257 | r = sigaction(SIGTERM, &old_term_action, 0); 258 | check_exit(r, "restoring SIGTERM"); 259 | } 260 | #endif 261 | r = sigaction(SIGINT, &old_int_action, 0); 262 | check_exit(r, "restoring SIGINT"); 263 | r = sigaction(SIGQUIT, &old_quit_action, 0); 264 | check_exit(r, "restoring SIGQUIT"); 265 | int code = WIFEXITED(status) ? WEXITSTATUS(status) 266 | : (WIFSIGNALED(status) ? 128 + WTERMSIG(status) : 1); 267 | if (is_successful(code, a)) { 268 | exit(0); 269 | } else { 270 | dump(fd_o, 1); 271 | dump(fd_e, 2); 272 | exit(code); 273 | } 274 | } 275 | 276 | static void exec_child(int fd_o, int fd_e, char **argv) 277 | { 278 | int r = dup2(fd_o, 1); 279 | check_exit(r, "redirecting stdout"); 280 | r = dup2(fd_e, 2); 281 | check_exit(r, "redirecting stderr"); 282 | r = execvp(*argv, argv); 283 | if (r == -1) { 284 | perror("executing command"); 285 | // cf. http://tldp.org/LDP/abs/html/exitcodes.html 286 | exit(errno == ENOENT ? 127 : 126); 287 | } 288 | } 289 | 290 | 291 | /* 292 | 293 | ## Properties 294 | 295 | exit_code(x) == exit_code(silence x) # where x terminates due to an signal 296 | exit_code(x) == exit_code(silence x) 297 | silence x ; x is long running; kill silence; => x is still running 298 | silence x ; x is long running; kill x ; 299 | => silence exits with exit_code == 128+SIGTERM 300 | silence x ; in terminal ; Ctrl+C ; 301 | => Ctrl+C (SIGINT) is both sent to silence and x; 302 | silence ignores it while waiting 303 | silence x ; in terminal ; Ctrl+\ ; 304 | => Ctrl+\ (SIGQUIT) is both sent to silence and x; 305 | silence ignores it while waiting 306 | silence x ; x not found ; => exit_code == 127 307 | silence x ; exec fails ; => exit_code = 128 308 | silence ; => help is display, exit_code = 1 309 | silence ... ; any other syscall fails ; => exit_code = 1 310 | silence ... ; read/write syscall is interrupted ; 311 | => it is called again (i.e. resumed) 312 | silence ... ; write does not write the complete buffer ; 313 | => it is called again with the remaining buffer 314 | 315 | */ 316 | int main(int argc, char **argv) 317 | { 318 | Arguments a = {0}; 319 | char **childs_argv = parse_arguments(argc, argv, &a); 320 | int fd_o = create_unlinked_temp_file(a.tmpdir); 321 | int fd_e = create_unlinked_temp_file(a.tmpdir); 322 | #if USE_PRCTL 323 | pid_t ppid_before_fork = getpid(); 324 | #endif 325 | pid_t pid = fork(); 326 | check_exit(pid, "forking child"); 327 | if (pid) { 328 | supervise_child(fd_o, fd_e, pid, &a); 329 | } else { 330 | #if USE_PRCTL 331 | if (a.suicide) { 332 | // is valid through the exec, but is not inherited into children 333 | int r = prctl(PR_SET_PDEATHSIG, SIGTERM); 334 | check_exit(r, "installing parent death signal"); 335 | // cf. http://stackoverflow.com/a/36945270/427158 336 | if (getppid() != ppid_before_fork) 337 | exit(1); 338 | } 339 | #endif 340 | exec_child(fd_o, fd_e, childs_argv); 341 | } 342 | return 0; 343 | } 344 | -------------------------------------------------------------------------------- /silence.cc: -------------------------------------------------------------------------------- 1 | static const char help_str[] = 2 | "Call: %s [OPT..] COMMAND [ARG..]\n" 3 | "\n" 4 | "Silence stdout/stderr of COMMAND unless its return code is unequal to 0.\n" 5 | "\n" 6 | "Usecases:\n" 7 | "\n" 8 | " - wrap commands that are called from a job scheduler like cron\n" 9 | " - increase the signal-to-noise-ratio in the terminal\n" 10 | "\n" 11 | "Options:\n" 12 | "\n" 13 | "-h,--help this screen\n" 14 | "-k,-K enable/disable suicide on parent exit (default: disabled)\n" 15 | " On Linux, a parent death signal is installed in the child\n" 16 | " that execs COMMAND, otherwise the TERM signal handler kills\n" 17 | " the child.\n" 18 | "\n" 19 | "It honors the TMPDIR environment and defaults to /tmp in case\n" 20 | "it isn't set.\n" 21 | "\n" 22 | "This is a C++ reimplementation of chronic from moreutils\n" 23 | "(which is a Perl script). See also the README.md for details\n" 24 | "on the differences.\n" 25 | "\n" 26 | "\n" 27 | "2016, Georg Sauthoff , GPLv3+\n" 28 | "cf. https://github.com/gsauthof/utility\n" 29 | "\n"; 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | #if defined(__linux__) 43 | #include 44 | #endif 45 | 46 | #include 47 | #include 48 | #include 49 | #include 50 | 51 | #ifndef USE_PRCTL 52 | #if defined(__linux__) 53 | #define USE_PRCTL 1 54 | #else 55 | #define USE_PRCTL 0 56 | #endif 57 | #endif 58 | 59 | #ifndef USE_TMPFILE 60 | #if defined(__linux__) 61 | #if defined(O_TMPFILE) 62 | #define USE_TMPFILE 1 63 | #else 64 | // not available e.g. on CentOS/RHEL 7 65 | #define USE_TMPFILE 0 66 | #warning "this Linux is so old it doesn't even have O_TMPFILE ..." 67 | #endif 68 | #else 69 | #define USE_TMPFILE 0 70 | #endif 71 | #endif 72 | 73 | using namespace ixxx; 74 | using namespace std; 75 | 76 | static void help(FILE *f, const char *argv0) 77 | { 78 | fprintf(f, help_str , argv0); 79 | } 80 | 81 | struct Arguments { 82 | const char *tmpdir { "/tmp" }; 83 | bool suicide { false}; 84 | vector success_codes; 85 | }; 86 | 87 | static char **parse_arguments(int argc, char **argv, Arguments &a) 88 | { 89 | const char *tmpdir = getenv("TMPDIR"); 90 | if (tmpdir) 91 | a.tmpdir = tmpdir; 92 | if (argc > 1 && !strcmp(argv[1], "--help")) { 93 | help(stdout, argv[0]); 94 | exit(0); 95 | } 96 | char c = 0; 97 | const char opt_str[] = "+e:hkK"; 98 | size_t success_codes_size = 0; 99 | while ((c = getopt(argc, argv, opt_str)) != -1) { 100 | switch (c) { 101 | case '?': help(stderr, argv[0]); exit(1); break; 102 | case 'h': help(stdout, argv[0]); exit(0); break; 103 | case 'e': ++success_codes_size; break; 104 | case 'k': a.suicide = true ; break; 105 | case 'K': a.suicide = false; break; 106 | } 107 | } 108 | if (optind == argc) { 109 | help(stderr, argv[0]); 110 | exit(1); 111 | } 112 | if (success_codes_size) { 113 | a.success_codes.reserve(success_codes_size); 114 | optind = 1; 115 | while ((c = getopt(argc, argv, opt_str)) != -1) { 116 | switch (c) { 117 | case 'e': a.success_codes.push_back(ansi::strtol(optarg, 0, 10)); break; 118 | } 119 | } 120 | } 121 | return argv + optind; 122 | } 123 | 124 | 125 | static int create_unlinked_temp_file(const char *tmpdir) 126 | { 127 | #if USE_TMPFILE 128 | int fd = posix::open(tmpdir, O_RDWR | O_TMPFILE | O_EXCL, 0600); 129 | return fd; 130 | #else 131 | // POSIX.1-2001 doesn't specify the mode of mkstemp(), 132 | // POSIX.1-2008 does specify 0600 133 | mode_t old_mask = umask(0177); 134 | const char suffix[] = "/silence_XXXXXX"; 135 | size_t n = strlen(tmpdir); 136 | char s[n+sizeof(suffix)]; 137 | memcpy(s, tmpdir, n); 138 | memcpy(s+n, suffix, sizeof(suffix)); 139 | int fd = posix:: mkstemp(s); 140 | umask(old_mask); 141 | posix::unlink(s); 142 | return fd; 143 | #endif 144 | } 145 | 146 | static void dump(int fd, int d) 147 | { 148 | posix::lseek(fd, 0, SEEK_SET); 149 | char buffer[128 * 1024]; 150 | for (;;) { 151 | ssize_t n = ixxx::util::read_retry(fd, buffer, sizeof(buffer)); 152 | if (!n) 153 | break; 154 | ixxx::util::write_all(d, buffer, n); 155 | } 156 | } 157 | 158 | #if !USE_PRCTL 159 | 160 | static pid_t child_pid_ = 0; 161 | 162 | static void kill_child(int sig) 163 | { 164 | if (child_pid_) 165 | kill(child_pid_, SIGTERM); 166 | exit(128 + SIGTERM); 167 | } 168 | 169 | #endif 170 | 171 | static bool is_successful(int code, const vector &codes) 172 | { 173 | if (count(codes.begin(), codes.end(), code)) 174 | return true; 175 | return !code; 176 | } 177 | 178 | static void supervise_child(int fd_o, int fd_e, pid_t pid, const Arguments &a) 179 | { 180 | // we ignore QUIT/INT because when issued via Ctrl+\/Ctrl+C in the terminal, 181 | // UNIX sends them both to the parent and the child 182 | // (cf. http://unix.stackexchange.com/questions/176235/fork-and-how-signals-are-delivered-to-processes) 183 | // ignoring them in the parent thus makes sure that any 184 | // collected output is printed after the child terminates 185 | // because of those signals (the default action) 186 | struct sigaction ignore_action = {}; 187 | ignore_action.sa_handler = SIG_IGN; 188 | struct sigaction old_int_action; 189 | posix::sigaction(SIGINT, &ignore_action, &old_int_action); 190 | struct sigaction old_quit_action; 191 | posix::sigaction(SIGQUIT, &ignore_action, &old_quit_action); 192 | #if !USE_PRCTL 193 | child_pid_ = pid; 194 | struct sigaction term_action = {}; 195 | term_action.sa_handler = kill_child; 196 | struct sigaction old_term_action; 197 | if (a.suicide) 198 | posix::sigaction(SIGTERM, &term_action, &old_term_action); 199 | #endif 200 | siginfo_t siginfo; 201 | posix:: waitid(P_PID, pid, &siginfo, WEXITED); 202 | #if !USE_PRCTL 203 | if (a.suicide) 204 | posix::sigaction(SIGTERM, &old_term_action, 0); 205 | #endif 206 | posix::sigaction(SIGINT, &old_int_action, 0); 207 | posix::sigaction(SIGQUIT, &old_quit_action, 0); 208 | int code = siginfo.si_code == CLD_EXITED 209 | ? siginfo.si_status : 128 + siginfo.si_status; 210 | if (is_successful(code, a.success_codes)) { 211 | exit(0); 212 | } else { 213 | dump(fd_o, 1); 214 | dump(fd_e, 2); 215 | exit(code); 216 | } 217 | } 218 | 219 | static void exec_child(int fd_o, int fd_e, char **argv) 220 | { 221 | posix::dup2(fd_o, 1); 222 | posix::dup2(fd_e, 2); 223 | int r = execvp(*argv, argv); 224 | if (r == -1) { 225 | perror("executing command"); 226 | // cf. http://tldp.org/LDP/abs/html/exitcodes.html 227 | exit(errno == ENOENT ? 127 : 126); 228 | } 229 | } 230 | 231 | int main(int argc, char **argv) 232 | { 233 | try { 234 | Arguments a; 235 | char **childs_argv = parse_arguments(argc, argv, a); 236 | int fd_o = create_unlinked_temp_file(a.tmpdir); 237 | int fd_e = create_unlinked_temp_file(a.tmpdir); 238 | #if USE_PRCTL 239 | pid_t ppid_before_fork = getpid(); 240 | #endif 241 | pid_t pid = posix::fork(); 242 | if (pid) { 243 | supervise_child(fd_o, fd_e, pid, a); 244 | } else { 245 | #if USE_PRCTL 246 | if (a.suicide) { 247 | linux::prctl(PR_SET_PDEATHSIG, SIGTERM); 248 | if (getppid() != ppid_before_fork) 249 | exit(1); 250 | } 251 | #endif 252 | exec_child(fd_o, fd_e, childs_argv); 253 | } 254 | } catch (const ixxx::sys_error &e) { 255 | fprintf(stderr, "%s\n", e.what()); 256 | return 1; 257 | } 258 | return 0; 259 | } 260 | -------------------------------------------------------------------------------- /swap.c: -------------------------------------------------------------------------------- 1 | // 2017, Georg Sauthoff , GPLv3+ 2 | 3 | #define _GNU_SOURCE 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include // for AT_FDCWD 10 | #ifndef HAVE_RENAMEAT2 11 | #include // for RENAME_EXCHANGE 12 | #include 13 | #endif 14 | #include 15 | 16 | #include "config.h" 17 | 18 | static void help(FILE *f, const char *argv0) 19 | { 20 | fprintf(f, "swap - atomically exchange two filenames\n" 21 | "\n" 22 | "Usage: %s [OPTION] SOURCE DEST\n" 23 | "\n" 24 | " -h, --help this screen\n" 25 | "\n" 26 | "2017, Georg Sauthoff \n", argv0); 27 | } 28 | 29 | static bool help_requested(int argc, char **argv) 30 | { 31 | for (int i = 1; i < argc; ++i) 32 | if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) 33 | return true; 34 | return false; 35 | } 36 | 37 | #ifndef HAVE_RENAMEAT2 38 | // cf. Glibc wrappers for (nearly all) Linux system calls 39 | // https://lwn.net/Articles/655028/ 40 | static int renameat2(int olddirfd, const char *oldpath, 41 | int newdirfd, const char *newpath, unsigned int flags) 42 | { 43 | long r = syscall(SYS_renameat2, olddirfd, oldpath, newdirfd, newpath, 44 | flags); 45 | return r; 46 | } 47 | 48 | // e.g. necessary on Ubuntu 14.04 (Trusty) 49 | #ifndef RENAME_EXCHANGE 50 | #define RENAME_EXCHANGE (1 << 1) 51 | #endif 52 | #endif 53 | 54 | int main(int argc, char **argv) 55 | { 56 | if (help_requested(argc, argv)) { 57 | help(stdout, argv[0]); 58 | return 0; 59 | } 60 | if (argc != 3) { 61 | fprintf(stderr, "expecting 2 arguments - cf. --help\n"); 62 | return 2; 63 | } 64 | // cf. Exchanging two files 65 | // https://lwn.net/Articles/569134/ 66 | int r = renameat2(AT_FDCWD, argv[1], AT_FDCWD, argv[2], RENAME_EXCHANGE); 67 | if (r == -1) { 68 | perror(0); 69 | return 1; 70 | } 71 | return 0; 72 | } 73 | -------------------------------------------------------------------------------- /syscalls.hh: -------------------------------------------------------------------------------- 1 | #ifndef SYSCALLS_HH 2 | #define SYSCALLS_HH 3 | 4 | #include 5 | 6 | std::string_view syscall2str_x86_64(unsigned i); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /tailuart.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Read and timestamp lines from a serial device 4 | 5 | # SPDX-License-Identifier: GPL-3.0-or-later 6 | # SPDX-FileCopyrightText: © 2022 Georg Sauthoff 7 | 8 | 9 | import argparse 10 | import serial 11 | import sys 12 | import time 13 | 14 | def parse_args(): 15 | p = argparse.ArgumentParser() 16 | p.add_argument('--device', '-d', default='/dev/ttyUSB0', 17 | help='UART device (default: %(default)s)') 18 | p.add_argument('--baud', '-b', type=int, default=9600, 19 | help='baud (default: %(default)d)') 20 | p.add_argument('--flush', '-f', type=bool, default=True, 21 | help='flush each line (default: %(default)s)') 22 | return p.parse_args() 23 | 24 | def main(): 25 | args = parse_args() 26 | with serial.Serial(args.device, baudrate=args.baud) as p: 27 | start = time.time() 28 | while True: 29 | line = p.readline().decode().strip() 30 | t = time.time() 31 | delta = t - start 32 | print(f'{delta} {line}', flush=args.flush) 33 | 34 | if __name__ == '__main__': 35 | try: 36 | sys.exit(main()) 37 | except KeyboardInterrupt: 38 | pass 39 | -------------------------------------------------------------------------------- /test/arsort.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import unittest 4 | import logging 5 | 6 | import subprocess 7 | import tempfile 8 | import shutil 9 | import os 10 | import sys 11 | 12 | # cf. test/main.py for the global defaults 13 | bin_dir = os.getenv('bin_dir', os.getcwd()) 14 | src_dir = os.getenv('src_dir', os.getcwd()+'/..') 15 | arsort = src_dir + '/arsort.sh' 16 | 17 | class Basic(unittest.TestCase): 18 | 19 | def setUp(self): 20 | self.base_dir = tempfile.mkdtemp() 21 | self.log = logging.getLogger(__name__) 22 | self.log.debug('using tempdir: ' + self.base_dir) 23 | self.old_dir = os.getcwd() 24 | os.chdir(self.base_dir) 25 | 26 | def tearDown(self): 27 | shutil.rmtree(self.base_dir) 28 | self.base_dir = None 29 | os.chdir(self.old_dir) 30 | 31 | def create_ar(self, name, text): 32 | with open(name + '.c', 'w') as f: 33 | f.write(text) 34 | subprocess.check_output(['cc', '-c', name+'.c'], stderr=subprocess.STDOUT) 35 | subprocess.check_output(['ar', 'r', name+'.a', name+'.o'], 36 | stderr=subprocess.STDOUT) 37 | 38 | def test_sort(self): 39 | self.create_ar('zoo', ''' 40 | #include 41 | void yar(); 42 | void zoo() 43 | { 44 | puts("zoo"); 45 | yar(); 46 | }''') 47 | self.create_ar('yar', ''' 48 | #include 49 | void xaz(); 50 | void yar() 51 | { 52 | puts("yar"); 53 | xaz(); 54 | }''') 55 | self.create_ar('xaz', ''' 56 | #include 57 | void xaz() 58 | { 59 | puts("xaz"); 60 | }''') 61 | r = subprocess.check_output([arsort, 'zoo.a', 'yar.a', 'xaz.a']) 62 | self.assertEqual(r, b'zoo.a yar.a xaz.a ') 63 | s = subprocess.check_output([arsort, 'xaz.a', 'yar.a', 'zoo.a']) 64 | self.assertEqual(s, b'zoo.a yar.a xaz.a ') 65 | t = subprocess.check_output([arsort, 'zoo.a', 'xaz.a', 'yar.a']) 66 | self.assertEqual(t, b'zoo.a yar.a xaz.a ') 67 | 68 | 69 | 70 | if __name__ == '__main__': 71 | logging.basicConfig(stream=sys.stderr) 72 | log = logging.getLogger(__name__) 73 | log.setLevel(logging.DEBUG) 74 | unittest.main() 75 | 76 | 77 | -------------------------------------------------------------------------------- /test/busy_snooze.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | static bool is_expired(struct timespec *a, struct timespec *b, unsigned delta) 7 | { 8 | return (b->tv_sec == a->tv_sec + delta && b->tv_nsec >= a->tv_nsec) 9 | || (b->tv_sec > a->tv_sec + delta); 10 | } 11 | 12 | int main(int argc, char **argv) 13 | { 14 | if (argc < 2) { 15 | fputs("Argument missing.\n", stderr); 16 | return 2; 17 | } 18 | unsigned delta = atoi(argv[1]); 19 | struct timespec start, cur; 20 | int r = clock_gettime(CLOCK_MONOTONIC, &start); 21 | if (r == -1) { 22 | perror(0); 23 | return 1; 24 | } 25 | for (size_t i = 0; ;++i) { 26 | printf("%zu\n", i); 27 | if (i % 1000 == 0) { 28 | int r = clock_gettime(CLOCK_MONOTONIC, &cur); 29 | if (r == -1) { 30 | perror(0); 31 | return 1; 32 | } 33 | if (is_expired(&start, &cur, delta)) 34 | break; 35 | } 36 | } 37 | return 0; 38 | } 39 | -------------------------------------------------------------------------------- /test/dcat.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # cat unittests 4 | # 5 | # 2018, Georg Sauthoff 6 | # 7 | # SPDX-License-Identifier: GPL-3.0-or-later 8 | 9 | import glob 10 | import os 11 | import pytest 12 | import re 13 | import shutil 14 | import subprocess 15 | import tempfile 16 | 17 | src_dir = os.getenv('src_dir', os.getcwd()+'/..') 18 | test_dir = src_dir + '/test/in' 19 | 20 | dcat = os.getenv('dcat', './dcat') 21 | 22 | @pytest.mark.parametrize('c', ( 'gzip', 'bzip2', 'xz', 'lz4', 'zstd' )) 23 | def test_basic(c): 24 | if c in ('zstd', 'lz4') and not shutil.which(c): 25 | pytest.skip('Command {} not found in PATH'.format(c)) 26 | cmd = 'echo Hello World | {} -c | {}'.format(c, dcat) 27 | o = subprocess.check_output(cmd, shell=True, universal_newlines=True) 28 | assert o == 'Hello World\n' 29 | 30 | @pytest.mark.parametrize('suf', ('', ' -')) 31 | def test_not_compressed(suf): 32 | s = dcat + suf 33 | cmd = 'echo foo | {}'.format(s) 34 | o = subprocess.check_output(cmd, shell=True, universal_newlines=True) 35 | assert o == 'foo\n' 36 | 37 | @pytest.mark.parametrize('hstr', ['-h', '--help']) 38 | def test_help(hstr): 39 | p = subprocess.run([dcat, hstr], stdout=subprocess.PIPE, 40 | stderr=subprocess.PIPE, universal_newlines=True) 41 | assert p.returncode == 0 42 | assert p.stderr == '' 43 | assert 'help' in p.stdout 44 | 45 | def test_multiple(): 46 | cs = [ 'gzip', 'bzip2', 'xz' ] 47 | es = [ 'lz4', 'zstd' ] 48 | for e in es: 49 | if shutil.which(e): 50 | cs.append(e) 51 | with tempfile.TemporaryDirectory() as d: 52 | for c in cs: 53 | with open('{}/txt.{}'.format(d, c), 'wt') as f: 54 | subprocess.run([c, '-c'], input='blah ', universal_newlines=True, 55 | stdout=f, check=True) 56 | with open('{}/txt'.format(d), 'wt') as f: 57 | f.write('blah ') 58 | p = subprocess.run([dcat] + glob.glob(d+'/txt*'), universal_newlines=True, 59 | stdout=subprocess.PIPE) 60 | assert p.returncode == 0 61 | p.stdout = 'blah\n'*(1+len(cs)) 62 | 63 | 64 | -------------------------------------------------------------------------------- /test/echo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | echo "$1" >&$2 5 | 6 | [[ $# > 3 ]] && sleep $4 7 | 8 | exit $3 9 | -------------------------------------------------------------------------------- /test/fail.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #if __GNUC__ 5 | #pragma GCC diagnostic push 6 | // we want to deliberately crash puts() 7 | #pragma GCC diagnostic ignored "-Wnonnull" 8 | #endif 9 | 10 | int main(int argc, char **argv) 11 | { 12 | (void)argv; 13 | if (argc < 2) 14 | abort(); 15 | else 16 | puts((const char*)0); 17 | return 0; 18 | } 19 | 20 | #if __GNUC__ 21 | #pragma GCC diagnostic pop 22 | #endif 23 | 24 | -------------------------------------------------------------------------------- /test/in/README.md: -------------------------------------------------------------------------------- 1 | This directory contains the following files. 2 | 3 | ## Generated on Fedora 26/x86-64 4 | 5 | - `core.snooze32.x86_64.11699.xz` - compiled with `-m32`, 6 | generated with `gcore`, contains sections 7 | - `core.snooze.x86_64.11685.xz` - generated with `gcore`, 8 | contains sections 9 | - `core.snooze.x86_64.coredumpctl.11677.xz` - generated by the 10 | kernel (in cooperation with `systemd-coredump`), does NOT 11 | contain any sections 12 | - `mk_cores.py` - script for generating the above files 13 | 14 | 15 | -------------------------------------------------------------------------------- /test/in/core.snooze.ppc64.1400.xz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gsauthof/utility/a175fc9cf4fb0778da57e70cd6ac20a105e8ca9e/test/in/core.snooze.ppc64.1400.xz -------------------------------------------------------------------------------- /test/in/core.snooze.ppc64.abrt.1622.xz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gsauthof/utility/a175fc9cf4fb0778da57e70cd6ac20a105e8ca9e/test/in/core.snooze.ppc64.abrt.1622.xz -------------------------------------------------------------------------------- /test/in/core.snooze.x86_64.11685.xz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gsauthof/utility/a175fc9cf4fb0778da57e70cd6ac20a105e8ca9e/test/in/core.snooze.x86_64.11685.xz -------------------------------------------------------------------------------- /test/in/core.snooze.x86_64.coredumpctl.11677.xz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gsauthof/utility/a175fc9cf4fb0778da57e70cd6ac20a105e8ca9e/test/in/core.snooze.x86_64.coredumpctl.11677.xz -------------------------------------------------------------------------------- /test/in/core.snooze32.ppc64.1630.xz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gsauthof/utility/a175fc9cf4fb0778da57e70cd6ac20a105e8ca9e/test/in/core.snooze32.ppc64.1630.xz -------------------------------------------------------------------------------- /test/in/core.snooze32.x86_64.11699.xz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gsauthof/utility/a175fc9cf4fb0778da57e70cd6ac20a105e8ca9e/test/in/core.snooze32.x86_64.11699.xz -------------------------------------------------------------------------------- /test/in/mk_cores.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # generate the stored core files for test/pargs.py unittests 4 | # 5 | # 2018, Georg Sauthoff 6 | # 7 | # SPDX-License-Identifier: GPL-3.0-or-later 8 | 9 | import os 10 | import resource 11 | import subprocess 12 | import sys 13 | import time 14 | 15 | std_env = { 16 | 'EDITOR' : 'vim', 17 | 'LESS' : 'FR', 18 | 'LESSOPEN' : '||/usr/bin/lesspipe.sh %s', 19 | 'PAGER' : 'less', 20 | 'PATH' : '/usr/bin:/bin', 21 | 'SYSTEMD_LESS' : 'FRXMK', 22 | 'VISUAL' : 'vim', 23 | } 24 | args = [ '10', 'hello', 'world' ] 25 | arch = os.uname().machine 26 | 27 | have_cdctl = os.path.exists('/usr/bin/coredumpctl') 28 | if not have_cdctl: 29 | resource.setrlimit(resource.RLIMIT_CORE, (resource.RLIM_INFINITY, resource.RLIM_INFINITY)) 30 | if os.path.exists('core'): 31 | os.unlink('core') 32 | 33 | p = subprocess.Popen(['./snooze'] + args, env=std_env) 34 | time.sleep(1) 35 | p.send_signal(6) 36 | p.wait() 37 | time.sleep(2) 38 | filename = 'core.snooze.{}.abrt.{}'.format(arch, p.pid) 39 | if have_cdctl: 40 | subprocess.check_output(['coredumpctl', 'dump', str(p.pid), '-o', filename]) 41 | else: 42 | os.rename('core', filename) 43 | subprocess.check_output(['xz', '--compress', filename]) 44 | 45 | for exe in [ 'snooze', 'snooze32' ]: 46 | p = subprocess.Popen(['./{}'.format(exe)] + args, env=std_env) 47 | time.sleep(1) 48 | subprocess.check_output(['gcore', str(p.pid)]) 49 | filename = 'core.{}.{}.{}'.format(exe, arch, p.pid) 50 | os.rename('core.{}'.format(p.pid), filename) 51 | subprocess.check_output(['xz', '--compress', filename]) 52 | 53 | -------------------------------------------------------------------------------- /test/lockf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import unittest 4 | 5 | import subprocess 6 | import timeit 7 | 8 | # fedora: python3-psutil 9 | # -> https://github.com/giampaolo/psutil 10 | import psutil 11 | import threading 12 | import signal 13 | import time 14 | import os 15 | 16 | import tempfile 17 | import shutil 18 | 19 | # cf. test/main.py for the global defaults 20 | lockf = os.getenv('lockf', './lockf') 21 | fail = os.getenv('fail', './fail') 22 | echo = os.getenv('echo', './echo.sh') 23 | 24 | 25 | class Basic(unittest.TestCase): 26 | 27 | def setUp(self): 28 | self.base_dir = tempfile.mkdtemp() 29 | 30 | def tearDown(self): 31 | shutil.rmtree(self.base_dir) 32 | self.base_dir = None 33 | 34 | def test_wait(self): 35 | begin = timeit.default_timer() 36 | subprocess.check_output([lockf, '-c', self.base_dir + '/foo', 'sleep', '1'], 37 | stderr=subprocess.STDOUT) 38 | end = timeit.default_timer() 39 | self.assertTrue(end-begin >= 1) 40 | 41 | def test_true(self): 42 | begin = timeit.default_timer() 43 | subprocess.check_output([lockf, '-c', self.base_dir + '/foo', 'true'], stderr=subprocess.STDOUT) 44 | end = timeit.default_timer() 45 | # too slow for docker inside Travis-CI VM 46 | self.assertTrue(end-begin <= 0.07) 47 | 48 | def test_false(self): 49 | begin = timeit.default_timer() 50 | code = subprocess.call([lockf, '-c', self.base_dir + '/foo', 'false']) 51 | end = timeit.default_timer() 52 | self.assertTrue(end-begin <= 0.07) 53 | self.assertEqual(code, 1) 54 | 55 | def test_abort(self): 56 | code = subprocess.call([lockf, '-c', self.base_dir + '/foo', fail]) 57 | # Coreutils flock also returns the exit code of the child 58 | self.assertEqual(code, 134) 59 | 60 | def test_block(self): 61 | begin = timeit.default_timer() 62 | p = subprocess.Popen([lockf, '-c', self.base_dir + '/foo', echo, 'x', '1', '0', '1'], 63 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 64 | time.sleep(0.02) 65 | q = subprocess.Popen([lockf, '-c', self.base_dir + '/foo', '-b', echo, 'x', '1', '0', '1'], 66 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 67 | p.communicate() 68 | self.assertEqual(p.returncode, 0); 69 | end = timeit.default_timer() 70 | self.assertTrue(abs((end-begin)-1) < 0.04) 71 | q.communicate() 72 | self.assertEqual(q.returncode, 0); 73 | end = timeit.default_timer() 74 | self.assertTrue(abs((end-begin)-2) < 0.04) 75 | 76 | def test_directly_fail(self): 77 | begin = timeit.default_timer() 78 | p = subprocess.Popen([lockf, '-c', self.base_dir + '/foo', echo, 'x', '1', '0', '1'], 79 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 80 | time.sleep(0.02) 81 | q = subprocess.Popen([lockf, '-c', self.base_dir + '/foo', echo, 'x', '1', '0', '1'], 82 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 83 | 84 | q.communicate() 85 | self.assertEqual(q.returncode, 1); 86 | end = timeit.default_timer() 87 | self.assertTrue(abs((end-begin)) < 0.04) 88 | 89 | p.communicate() 90 | self.assertEqual(p.returncode, 0); 91 | end = timeit.default_timer() 92 | self.assertTrue(abs((end-begin)-1) < 0.04) 93 | 94 | def test_block_flock(self): 95 | begin = timeit.default_timer() 96 | p = subprocess.Popen([lockf, '-l', '-c', self.base_dir + '/foo', echo, 'x', '1', '0', '1'], 97 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 98 | time.sleep(0.02) 99 | q = subprocess.Popen([lockf, '-c', self.base_dir + '/foo', '-bl', echo, 'x', '1', '0', '1'], 100 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 101 | p.communicate() 102 | self.assertEqual(p.returncode, 0); 103 | end = timeit.default_timer() 104 | self.assertTrue(abs((end-begin)-1) < 0.04) 105 | q.communicate() 106 | self.assertEqual(q.returncode, 0); 107 | end = timeit.default_timer() 108 | self.assertTrue(abs((end-begin)-2) < 0.04) 109 | 110 | def test_directly_fail_flock(self): 111 | begin = timeit.default_timer() 112 | p = subprocess.Popen([lockf, '-l', '-c', self.base_dir + '/foo', echo, 'x', '1', '0', '1'], 113 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 114 | time.sleep(0.02) 115 | q = subprocess.Popen([lockf, '-l', '-c', self.base_dir + '/foo', echo, 'x', '1', '0', '1'], 116 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 117 | 118 | q.communicate() 119 | self.assertEqual(q.returncode, 1); 120 | end = timeit.default_timer() 121 | self.assertTrue(abs((end-begin)) < 0.04) 122 | 123 | p.communicate() 124 | self.assertEqual(p.returncode, 0); 125 | end = timeit.default_timer() 126 | self.assertTrue(abs((end-begin)-1) < 0.04) 127 | 128 | def test_block_fcntl(self): 129 | begin = timeit.default_timer() 130 | p = subprocess.Popen([lockf, '-n', '-c', self.base_dir + '/foo', echo, 'x', '1', '0', '1'], 131 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 132 | time.sleep(0.02) 133 | q = subprocess.Popen([lockf, '-c', self.base_dir + '/foo', '-bn', echo, 'x', '1', '0', '1'], 134 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 135 | p.communicate() 136 | self.assertEqual(p.returncode, 0); 137 | end = timeit.default_timer() 138 | self.assertTrue(abs((end-begin)-1) < 0.04) 139 | q.communicate() 140 | self.assertEqual(q.returncode, 0); 141 | end = timeit.default_timer() 142 | self.assertTrue(abs((end-begin)-2) < 0.04) 143 | 144 | def test_directly_fail_fcntl(self): 145 | begin = timeit.default_timer() 146 | p = subprocess.Popen([lockf, '-n', '-c', self.base_dir + '/foo', echo, 'x', '1', '0', '1'], 147 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 148 | time.sleep(0.02) 149 | q = subprocess.Popen([lockf, '-n', '-c', self.base_dir + '/foo', echo, 'x', '1', '0', '1'], 150 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 151 | 152 | q.communicate() 153 | self.assertEqual(q.returncode, 1); 154 | end = timeit.default_timer() 155 | self.assertTrue(abs((end-begin)) < 0.1) 156 | 157 | p.communicate() 158 | self.assertEqual(p.returncode, 0); 159 | end = timeit.default_timer() 160 | self.assertTrue(abs((end-begin)-1) < 0.04) 161 | 162 | def test_directly_fail_excl(self): 163 | begin = timeit.default_timer() 164 | p = subprocess.Popen([lockf, '-e', self.base_dir + '/foo', echo, 'x', '1', '0', '1'], 165 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 166 | time.sleep(0.02) 167 | q = subprocess.Popen([lockf, '-e', self.base_dir + '/foo', echo, 'x', '1', '0', '1'], 168 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 169 | 170 | q.communicate() 171 | self.assertEqual(q.returncode, 1); 172 | end = timeit.default_timer() 173 | self.assertTrue(abs((end-begin)) < 0.04) 174 | 175 | p.communicate() 176 | self.assertEqual(p.returncode, 0); 177 | end = timeit.default_timer() 178 | self.assertTrue(abs((end-begin)-1) < 0.04) 179 | 180 | def test_excl_unlink(self): 181 | p = subprocess.Popen([lockf, '-u', '-e', self.base_dir + '/foo', echo, 'x', '1', '0', '1'], 182 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 183 | time.sleep(0.2) 184 | self.assertTrue(os.path.isfile(self.base_dir + '/foo')) 185 | p.communicate() 186 | self.assertEqual(p.returncode, 0) 187 | self.assertFalse(os.path.isfile(self.base_dir + '/foo')) 188 | 189 | def test_directly_fail_mkdir(self): 190 | begin = timeit.default_timer() 191 | p = subprocess.Popen([lockf, '-m', self.base_dir + '/foo', echo, 'x', '1', '0', '1'], 192 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 193 | time.sleep(0.02) 194 | q = subprocess.Popen([lockf, '-m', self.base_dir + '/foo', echo, 'x', '1', '0', '1'], 195 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 196 | 197 | q.communicate() 198 | self.assertEqual(q.returncode, 1); 199 | end = timeit.default_timer() 200 | self.assertTrue(abs((end-begin)) < 0.04) 201 | 202 | p.communicate() 203 | self.assertEqual(p.returncode, 0); 204 | end = timeit.default_timer() 205 | self.assertTrue(abs((end-begin)-1) < 0.04) 206 | 207 | def test_directly_fail_link(self): 208 | begin = timeit.default_timer() 209 | p = subprocess.Popen([lockf, '-i', self.base_dir + '/foo', 210 | echo, 'x', '1', '0', '1'], 211 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 212 | time.sleep(0.02) 213 | q = subprocess.Popen([lockf, '-i', self.base_dir + '/foo', 214 | echo, 'x', '1', '0', '1'], 215 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 216 | 217 | q.communicate() 218 | self.assertEqual(q.returncode, 1); 219 | end = timeit.default_timer() 220 | self.assertTrue(abs((end-begin)) < 0.04) 221 | 222 | p.communicate() 223 | self.assertEqual(p.returncode, 0); 224 | end = timeit.default_timer() 225 | self.assertTrue(abs((end-begin)-1) < 0.04) 226 | 227 | def test_directly_fail_rename(self): 228 | with open(self.base_dir + '/foo', 'x') as f: 229 | pass 230 | begin = timeit.default_timer() 231 | p = subprocess.Popen([lockf, '-r', self.base_dir + '/foo', 232 | echo, 'x', '1', '0', '1'], 233 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 234 | time.sleep(0.02) 235 | q = subprocess.Popen([lockf, '-r', self.base_dir + '/foo', 236 | echo, 'x', '1', '0', '1'], 237 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 238 | 239 | q.communicate() 240 | self.assertEqual(q.returncode, 1); 241 | end = timeit.default_timer() 242 | self.assertTrue(abs((end-begin)) < 0.04) 243 | 244 | p.communicate() 245 | self.assertEqual(p.returncode, 0); 246 | end = timeit.default_timer() 247 | self.assertTrue(abs((end-begin)-1) < 0.04) 248 | 249 | 250 | if __name__ == '__main__': 251 | unittest.main() 252 | 253 | 254 | -------------------------------------------------------------------------------- /test/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import unittest 4 | import sys 5 | import os 6 | 7 | 8 | if __name__ == '__main__': 9 | 10 | if 'src_dir' not in os.environ: 11 | os.environ['src_dir'] = os.getcwd() + '/..' 12 | if 'bin_dir' not in os.environ: 13 | os.environ['bin_dir'] = os.getcwd() 14 | src_dir = os.environ['src_dir'] 15 | bin_dir = os.environ['bin_dir'] 16 | for i in [ 'silence', 'fail', 'lockf' ]: 17 | if i not in os.environ: 18 | os.environ[i] = bin_dir + '/' + i 19 | if 'echo' not in os.environ: 20 | os.environ['echo'] = src_dir + '/test/echo.sh' 21 | 22 | unittest.main(module=None, argv=[sys.argv[0], 'discover', 23 | '--start-directory', src_dir + '/test', '--pattern', '*.py', 24 | ]) 25 | # not necessary, problematic for out-of-source builds: 26 | # '--top-level-directory', src_dir]) 27 | 28 | 29 | -------------------------------------------------------------------------------- /test/silence.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import unittest 4 | 5 | import subprocess 6 | import timeit 7 | 8 | # fedora: python3-psutil 9 | # -> https://github.com/giampaolo/psutil 10 | import psutil 11 | import threading 12 | import signal 13 | import time 14 | import os 15 | 16 | import tempfile 17 | import shutil 18 | 19 | # cf. test/main.py for the global defaults 20 | src_dir = os.getenv('src_dir', os.getcwd()+'/..') 21 | silence = os.getenv('silence', './silence') 22 | fail = os.getenv('fail', './fail') 23 | echo = os.getenv('echo', src_dir + '/test/echo.sh') 24 | 25 | 26 | 27 | class Ctrl_C_Thread(threading.Thread): 28 | 29 | def __init__(self, pid): 30 | self.pid = pid 31 | threading.Thread.__init__(self) 32 | 33 | def run(self): 34 | time.sleep(1) 35 | p = psutil.Process(self.pid) 36 | self.children = p.children() 37 | if len(self.children) != 1: 38 | return 39 | q = self.children[0] 40 | if len(q.children()) != 1: 41 | return 42 | r = q.children()[0] 43 | q.send_signal(signal.SIGINT) 44 | time.sleep(0.2) 45 | r.send_signal(signal.SIGINT) 46 | 47 | class Kill_Thread(threading.Thread): 48 | 49 | def __init__(self, pid): 50 | self.pid = pid 51 | threading.Thread.__init__(self) 52 | 53 | def run(self): 54 | time.sleep(1) 55 | p = psutil.Process(self.pid) 56 | if len(p.children()) != 1: 57 | return 58 | q = p.children()[0] 59 | # i.e. kill(pid, SIGTERM) 60 | p.terminate() 61 | time.sleep(1) 62 | self.was_still_running = q.is_running(); 63 | if self.was_still_running: 64 | q.kill() 65 | 66 | class Basic(unittest.TestCase): 67 | 68 | silence = silence 69 | 70 | def test_wait(self): 71 | begin = timeit.default_timer() 72 | subprocess.check_output([self.silence, 'sleep', '1'], stderr=subprocess.STDOUT) 73 | end = timeit.default_timer() 74 | self.assertTrue(end-begin >= 1) 75 | 76 | def test_true(self): 77 | begin = timeit.default_timer() 78 | subprocess.check_output([self.silence, 'true'], stderr=subprocess.STDOUT) 79 | end = timeit.default_timer() 80 | # moreutils chronic fails this on an i7 with SSD 81 | self.assertTrue(end-begin <= 0.05) 82 | 83 | def test_false(self): 84 | begin = timeit.default_timer() 85 | code = subprocess.call([self.silence, 'false']) 86 | end = timeit.default_timer() 87 | # moreutils chronic fails with <= 0.01 on an i7 with SSD 88 | self.assertTrue(end-begin <= 0.03) 89 | self.assertEqual(code, 1) 90 | 91 | def test_abort(self): 92 | code = subprocess.call([fail]) 93 | self.assertEqual(code, -6) 94 | code = subprocess.call([self.silence, fail]) 95 | # GNU time returns 6 96 | # moreutils chronic returns 1 97 | self.assertEqual(code, 134) 98 | 99 | 100 | def test_no_out(self): 101 | o = subprocess.check_output([self.silence, echo, 'Hello World\n23', '1', '0']) 102 | self.assertEqual(o, b'') 103 | 104 | def test_no_out_other(self): 105 | o = subprocess.check_output([self.silence, '-e', '11', echo, 'xyz', '1', '11']) 106 | self.assertEqual(o, b'') 107 | 108 | def test_no_err(self): 109 | o = subprocess.check_output([self.silence, echo, 'Hello World\n23', '2', '0']) 110 | self.assertEqual(o, b'') 111 | 112 | def test_out(self): 113 | p = subprocess.Popen([self.silence, echo, 'Hello World\n23', '1', '1'], 114 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 115 | o, e = p.communicate() 116 | self.assertEqual(o, b'Hello World\n23\n') 117 | self.assertEqual(e, b'') 118 | self.assertEqual(p.returncode, 1) 119 | 120 | def test_opt_ordering(self): 121 | p = subprocess.Popen([self.silence, echo, '-k', '1', '23'], 122 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 123 | o, e = p.communicate() 124 | self.assertEqual(o, b'-k\n') 125 | self.assertEqual(e, b'') 126 | self.assertEqual(p.returncode, 23) 127 | 128 | def test_err(self): 129 | p = subprocess.Popen([self.silence, echo, 'Hello World\n23', '2', '1'], 130 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 131 | o, e = p.communicate() 132 | self.assertEqual(o, b'') 133 | self.assertEqual(e, b'Hello World\n23\n') 134 | self.assertEqual(p.returncode, 1) 135 | 136 | def test_exit_code(self): 137 | o = b'' 138 | code = 0 139 | try: 140 | o = subprocess.check_output([self.silence, echo, 'Hello', '1', '23']) 141 | except subprocess.CalledProcessError as e: 142 | o = e.output 143 | code = e.returncode 144 | self.assertEqual(o, b'Hello\n') 145 | self.assertEqual(code, 23) 146 | 147 | 148 | def test_ctrl_c(self): 149 | p = subprocess.Popen([self.silence, echo, 'Hello World\n23', '2', '0', '3'], 150 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 151 | t = Ctrl_C_Thread(p.pid) 152 | t.start() 153 | o, e = p.communicate() 154 | t.join() 155 | self.assertEqual(len(t.children), 1) 156 | # GNU time returns 2 157 | # moreutils chronic returns 1 158 | self.assertEqual(p.returncode, 130) 159 | self.assertEqual(o, b'') 160 | self.assertEqual(e, b'Hello World\n23\n') 161 | p.wait() 162 | 163 | def test_tmp_empty(self): 164 | with tempfile.TemporaryDirectory() as base_dir: 165 | old_tmpdir = None 166 | if 'TMPDIR' in os.environ: 167 | old_tmpdir = os.environ['TMPDIR'] 168 | os.environ['TMPDIR'] = base_dir 169 | try: 170 | o = subprocess.check_output([self.silence, echo, 'Foo', '1', '0']) 171 | e = subprocess.check_output([self.silence, echo, 'Bar', '2', '0']) 172 | self.assertFalse(os.listdir(base_dir)) 173 | finally: 174 | if old_tmpdir: 175 | os.environ['TMPDIR'] = old_tmpdir 176 | else: 177 | os.environ.pop('TMPDIR') 178 | 179 | 180 | def test_tmpdir_is_used(self): 181 | old_tmpdir = None 182 | if 'TMPDIR' in os.environ: 183 | old_tmpdir = os.environ['TMPDIR'] 184 | os.environ['TMPDIR'] = '/does/not/exist/like/never/ever' 185 | try: 186 | p = subprocess.Popen([self.silence, echo, 'Foo', '1', '0'], 187 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 188 | o, e = p.communicate() 189 | finally: 190 | if old_tmpdir: 191 | os.environ['TMPDIR'] = old_tmpdir 192 | else: 193 | os.environ.pop('TMPDIR') 194 | 195 | # moreutils chronic return 0, i.e. doesn't honor TMPDIR 196 | self.assertEqual(p.returncode, 1) 197 | self.assertTrue(b'No such file or directory' in e) 198 | # errno string doesn't include the function everywhere 199 | # self.assertTrue(b'open' in e or b'mkstemp' in e) 200 | 201 | def test_keep_running(self): 202 | p = subprocess.Popen([self.silence, echo, 'Foo Bar', '1', '0', '3'], 203 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 204 | t = Kill_Thread(p.pid) 205 | t.start() 206 | o, e = p.communicate() 207 | t.join() 208 | self.assertEqual(p.returncode, -15) 209 | self.assertEqual(o, b'') 210 | self.assertEqual(e, b'') 211 | self.assertTrue(t.was_still_running) 212 | 213 | def test_keep_running_not(self): 214 | p = subprocess.Popen([self.silence, '-k', echo, 'Foo Bar', '1', '0', '3'], 215 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 216 | t = Kill_Thread(p.pid) 217 | t.start() 218 | o, e = p.communicate() 219 | t.join() 220 | self.assertIn(p.returncode, [-15, 143]) 221 | self.assertEqual(o, b'') 222 | self.assertEqual(e, b'') 223 | self.assertFalse(t.was_still_running) 224 | 225 | # using inheritence for parametrizing the above tests 226 | # for the C++ version 227 | class BasicXX(Basic): 228 | silence = silence.replace('silence', 'silencce') 229 | 230 | if __name__ == '__main__': 231 | unittest.main() 232 | 233 | -------------------------------------------------------------------------------- /test/snooze.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main(int argc, char **argv) 6 | { 7 | if (argc < 2) { 8 | fputs("Not enough arguments\n", stderr); 9 | return 2; 10 | } 11 | sleep(atoi(argv[1])); 12 | return 0; 13 | } 14 | -------------------------------------------------------------------------------- /test/swap.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import unittest 4 | 5 | import subprocess 6 | import timeit 7 | 8 | import time 9 | import os 10 | 11 | import tempfile 12 | import shutil 13 | 14 | bin_dir = os.getenv('bin_dir', '.') 15 | swap = os.getenv('swap', bin_dir + '/swap') 16 | 17 | class Basic(unittest.TestCase): 18 | 19 | def setUp(self): 20 | self.base_dir = tempfile.mkdtemp() 21 | 22 | def tearDown(self): 23 | shutil.rmtree(self.base_dir) 24 | self.base_dir = None 25 | 26 | 27 | def test_swap(self): 28 | with open(self.base_dir + '/foo', 'w') as f, \ 29 | open(self.base_dir + '/bar', 'w') as g: 30 | print('foo', file=f) 31 | print('bar', file=g) 32 | 33 | self.assertNotEqual(os.fstat(f.fileno()).st_ino, 34 | os.fstat(g.fileno()).st_ino) 35 | 36 | subprocess.check_output([swap, self.base_dir + '/foo', 37 | self.base_dir + '/bar']) 38 | with open(self.base_dir + '/foo', 'w') as f2, \ 39 | open(self.base_dir + '/bar', 'w') as g2: 40 | 41 | self.assertEqual(os.fstat(f.fileno()).st_ino, 42 | os.fstat(g2.fileno()).st_ino) 43 | self.assertEqual(os.fstat(g.fileno()).st_ino, 44 | os.fstat(f2.fileno()).st_ino) 45 | 46 | subprocess.check_output([swap, self.base_dir + '/foo', 47 | self.base_dir + '/bar']) 48 | with open(self.base_dir + '/foo', 'w') as f2, \ 49 | open(self.base_dir + '/bar', 'w') as g2: 50 | 51 | self.assertEqual(os.fstat(f.fileno()).st_ino, 52 | os.fstat(f2.fileno()).st_ino) 53 | self.assertEqual(os.fstat(g.fileno()).st_ino, 54 | os.fstat(g2.fileno()).st_ino) 55 | 56 | 57 | -------------------------------------------------------------------------------- /train-spam.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -u 5 | 6 | # Script trains and deletes Spam messages which are 7 | # located in a base directory. 8 | # The base directory is filled e.g. by marking messages as SPAM 9 | # in an IMAP client. 10 | # Can be called from cron. 11 | # 12 | # 2014-12-01, Georg Sauthoff 13 | 14 | # Assuming GNU versions of find, xargs, mktemp ... 15 | 16 | temp="" 17 | 18 | function cleanup() 19 | { 20 | rm -rf "$temp" 21 | } 22 | trap cleanup EXIT 23 | 24 | # make sure to use the default gonzofilter DB 25 | cd 26 | 27 | base=$HOME/maildir/.Spam 28 | 29 | temp=$(mktemp --directory --tmpdir="$base") 30 | 31 | find "$base"/{new,cur} -type f -print0 \ 32 | | xargs --no-run-if-empty -0 mv -t "$temp" 33 | 34 | if [ -z "$(find "$temp" -prune -empty)" ]; then 35 | find "$temp" -type f -print0 | xargs -0rl1 gonzofilter -spam -in 36 | fi 37 | -------------------------------------------------------------------------------- /unrpm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 2019, Georg Sauthoff 4 | # SPDX-License-Identifier: GPL-3.0-or-later 5 | 6 | set -eu 7 | 8 | verbose=0 9 | list=0 10 | filename=- 11 | dir=$PWD 12 | 13 | function usage 14 | { 15 | cat < 4 | # SPDX-License-Identifier: GPL-3.0-or-later 5 | 6 | import argparse 7 | import fcntl 8 | import logging 9 | import os 10 | import random 11 | import stat 12 | import struct 13 | import subprocess 14 | import sys 15 | 16 | 17 | log = logging.getLogger(__name__) 18 | 19 | def parse_args(*a): 20 | p = argparse.ArgumentParser( 21 | formatter_class=argparse.RawDescriptionHelpFormatter, 22 | description='Clear device data fast and thoroughly', 23 | epilog=''' 24 | Example: 25 | 26 | wipedev -av /dev/sdz 27 | 28 | ## How it Works 29 | 30 | The pre-wipe step just removes all filesystem, LUKS-encrypted 31 | volume, partition table (etc.) signatures and invokes the drive's 32 | DISCARD command. 33 | 34 | Although the main wipe step overwrites everything, the pre-wipe 35 | step gives some protection in case the main wipe step doesn't 36 | complete for some reason. Such as when your device finally dies 37 | or the computer is suddenly stopped. It thus can be seen as part 38 | of a defense-in-depth approach. 39 | 40 | The main wipe step overwrites everything with random data. We are 41 | using random data (instead of zeroes) because nowadays some 42 | storage devices (such as some SSDs) internally compress and/or 43 | deduplicate the written data. Since random data doesn't compress 44 | well (if at all) and can't be deduplicated those writes likely 45 | overwrite (almost all) disk blocks/storage cells. 46 | 47 | Note that modern devices often also contain some internal pool of 48 | extra cells to deal with failing cells and optimizing writes. 49 | Thus, even a full rewrite of a device (or even multiple full 50 | rewrites) doesn't guarantee that no data remains in some 51 | internal and usually inaccessible storage cell (which might be 52 | accessed by some proprietary and device specific method). But a 53 | DISCARD followed by a full device write followed by a DISCARD 54 | arguably minimizes the chances that an adversary might extract 55 | any useful from that device, anymore. 56 | 57 | The post-wipe step invokes DISCARD again which hides the fact 58 | that random garbage was written to the device, possibly discards 59 | some internal storage cells and possibly speeds up following 60 | writes. 61 | 62 | In general, also as part of an defense-in-depth approach, it's 63 | recommended to encrypt fileysstems which may contain any 64 | sensitive data, e.g. with LUKS. The primary goal of the wipe is 65 | then to make the key-slots inaccessible; in case the 66 | user-selected password turns out being not strong enough. 67 | 68 | There is some folklore around (magnetic) disk wiping which tells 69 | users to wipe disk drives multiple times in a row (with different 70 | patterns), because after one wipe the previous magnetization 71 | levels might still be recoverable with special equipment. This 72 | was probably never true; however since a few decades magnetic 73 | storage devices come with high areal storage density, thus it's 74 | certainly superfluous now. 75 | 76 | 2020, Georg Sauthoff , GPV3+ 77 | 78 | ''' 79 | ) 80 | p.add_argument('dev', metavar='DEVICE', nargs=1, help='device to wipe - e.g. /dev/sda') 81 | p.add_argument('--blocksize', '-b', type=int, default=8*1024*1024, help='write blocksize in bytes (default: %(default)s)') 82 | 83 | p.add_argument('--pre-wipe', '-x', action='store_true', 84 | help='quickly remove signatures of all partitions and discard everything before overwriting everything') 85 | p.add_argument('--wipe', '-w', action='store_true', 86 | help='main wipe, i.e. overwrite everything with random garbage') 87 | p.add_argument('--post-wipe', '-z', action='store_true', 88 | help='discard everything after the main wipe') 89 | p.add_argument('--all', '-a', action='store_true', 90 | help='apply pre/main/post wipe steps') 91 | p.add_argument('--verbose', '-v', action='store_true', 92 | help='verbose output') 93 | args = p.parse_args(*a) 94 | args.dev = args.dev[0] 95 | if args.all: 96 | args.pre_wipe = True 97 | args.wipe = True 98 | args.post_wipe = True 99 | if not (args.wipe or args.pre_wipe or args.post_wipe): 100 | raise RuntimeError('Specify one, more or all wipe steps') 101 | return args 102 | 103 | 104 | def setup_logging(verbose): 105 | log_format = '%(asctime)s - %(levelname)-8s - %(message)s [%(name)s]' 106 | log_date_format = '%Y-%m-%d %H:%M:%S' 107 | 108 | logging.basicConfig(format=log_format, datefmt=log_date_format, 109 | level=(logging.DEBUG if verbose else logging.INFO) ) 110 | 111 | def get_size(fd): 112 | st = os.fstat(fd) 113 | 114 | if stat.S_ISREG(st.st_mode): 115 | return st.st_size 116 | elif stat.S_ISBLK(st.st_mode): 117 | BLKGETSIZE64 = 0x80081272 118 | b = bytearray(8) 119 | r = fcntl.ioctl(fd, BLKGETSIZE64, b) 120 | if r != 0: 121 | raise RuntimeError('ioctl failed') 122 | return struct.unpack('Q', b)[0] 123 | else: 124 | raise RuntimeError('Unknown file mode') 125 | 126 | 127 | def get_parts(dev): 128 | xs = subprocess.check_output(['lsblk', '-o', 'path', '-l', '-n', dev], 129 | universal_newlines=True).splitlines() 130 | # make sure that partitions themselves are wiped before the 131 | # device's partition table itself ... 132 | xs.reverse() 133 | return xs 134 | 135 | def get_luks(devs): 136 | if not devs: 137 | raise RuntimeError('device list must not be empty') 138 | cmd = ['blkid', '--match-token', 'TYPE=crypto_LUKS', '-o', 'device'] + devs 139 | p = subprocess.run(cmd, stdout=subprocess.PIPE, universal_newlines=True) 140 | if p.returncode not in (0, 2): 141 | raise RuntimeError( 142 | f'Command {",".join(cmd)} failed with exit status {p.returncode}') 143 | xs = p.stdout.splitlines() 144 | return xs 145 | 146 | def erase_luks(devs): 147 | if not devs: 148 | raise RuntimeError('device list must not be empty') 149 | log.debug(f'LUKS-erasing {devs} ...') 150 | subprocess.run(['cryptsetup', '--batch-mode', 'luksErase'] + devs, check=True) 151 | 152 | def wipefs(devs): 153 | if not devs: 154 | raise RuntimeError('device list must not be empty') 155 | log.debug(f'Wipefsing {devs} ...') 156 | subprocess.check_output(['wipefs', '-a'] + devs) 157 | 158 | def discard(dev): 159 | log.debug(f'Discarding {dev} ...') 160 | subprocess.run(['blkdiscard', dev], check=True) 161 | 162 | def pre_wipe(dev): 163 | parts = get_parts(dev) 164 | luks = get_luks(parts) if parts else [] 165 | 166 | if luks: 167 | erase_luks(luks) 168 | wipefs(parts) 169 | 170 | discard(dev) 171 | 172 | 173 | def main(*a): 174 | args = parse_args(*a) 175 | setup_logging(args.verbose) 176 | if args.pre_wipe: 177 | pre_wipe(args.dev) 178 | if args.wipe: 179 | with open(args.dev, 'wb', buffering=0) as f: 180 | n = get_size(f.fileno()) 181 | log.debug(f'Writing {n/1024/1024/1024} GiB random data to {args.dev} ...') 182 | blocks = n // args.blocksize 183 | rest = n % args.blocksize 184 | for _ in range(blocks): 185 | # randbytes() churns buffers (i.e. python 186 | # objects) but this is (of course) still faster 187 | # than e.g. copying /dev/urandom to the device 188 | # ... 189 | f.write(random.randbytes(args.blocksize)) 190 | f.write(random.randbytes(rest)) 191 | if args.post_wipe: 192 | discard(args.dev) 193 | 194 | if __name__ == '__main__': 195 | sys.exit(main()) 196 | 197 | --------------------------------------------------------------------------------