├── run_test ├── end │ ├── end_ftp │ ├── end_sh │ ├── end_csh │ ├── end_telnet │ ├── end_bash │ ├── end_dash │ ├── end_tcsh │ ├── end_zsh │ ├── end_htop │ ├── end_top │ ├── end_less │ ├── end_more │ ├── end_vim │ └── end_ex ├── test_MacOS │ ├── pty │ └── regular ├── test_FreeBSD │ ├── pty │ └── regular ├── test_Linux │ ├── pty │ ├── coreutil_rust │ ├── regular │ └── .__afsD2C2 ├── README └── run.py ├── generate_test ├── size and parameters of datasets.xlsx ├── README ├── generate_small3.py ├── generate_large3.py ├── generate_medium3.py ├── generate_huge3.py ├── generate_small1.py ├── generate_large1.py ├── generate_huge1.py └── generate_medium1.py ├── src ├── Makefile ├── README ├── fuzz.c └── ptyjig.c ├── doc ├── README ├── fuzz.man ├── ptyjig.man ├── fuzz.1 ├── run.py.1 └── ptyjig.1 └── README.md /run_test/end/end_ftp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | exit 7 | 8 | 9 | 10 | 11 | exit 12 | 13 | 14 | 15 | 16 | exit 17 | -------------------------------------------------------------------------------- /run_test/end/end_sh: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | exit 7 | 8 | 9 | exit 10 | 11 | 12 | 13 | exit 14 | 15 | 16 | 17 | exit 18 | -------------------------------------------------------------------------------- /run_test/end/end_csh: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | exit 7 | 8 | 9 | exit 10 | 11 | 12 | 13 | exit 14 | 15 | 16 | 17 | exit 18 | -------------------------------------------------------------------------------- /run_test/end/end_telnet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | q 7 | 8 | 9 | q 10 | 11 | 12 | 13 | q 14 | 15 | 16 | 17 | q 18 | 19 | 20 | -------------------------------------------------------------------------------- /generate_test/size and parameters of datasets.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dyninst/fuzz/HEAD/generate_test/size and parameters of datasets.xlsx -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | all: fuzz ptyjig 2 | 3 | fuzz: fuzz.c 4 | gcc fuzz.c -o fuzz 5 | 6 | ptyjig: ptyjig.c 7 | gcc ptyjig.c -o ptyjig 8 | 9 | clean: 10 | rm ./fuzz ./ptyjig 11 | -------------------------------------------------------------------------------- /src/README: -------------------------------------------------------------------------------- 1 | In this directory: 2 | Makefile 3 | Compile fuzz.c and ptyjig.c. 4 | fuzz.c 5 | A random string generator. 6 | ptyjig.c 7 | A tool to provide input to utilities that read input from the terminal, it is used to test interactive utilities such as vim and top. 8 | -------------------------------------------------------------------------------- /run_test/end/end_bash: -------------------------------------------------------------------------------- 1 | nnn 2 | nnn 3 | nnn 4 | nnn 5 | 6 |  7 |  8 | exit 9 | 10 |  11 |  12 | exit 13 | 14 |  15 |  16 | exit 17 | 18 |  19 |  20 | exit 21 | 22 |  23 |  24 | exit 25 | 26 |  27 |  28 | exit 29 | 30 |  31 |  32 | exit 33 | 34 | -------------------------------------------------------------------------------- /doc/README: -------------------------------------------------------------------------------- 1 | TODO: add run.py.man, modify description 2 | This directory contains manual pages for fuzz and ptyjig. fuzz.1 3 | and ptyjig.1 are pre-processed (used cat or more to view) man pages. 4 | To print the manual to printer, use the following troff command: 5 | 6 | ditroff -man -Pprinter *.man 7 | 8 | 9 | -------------------------------------------------------------------------------- /run_test/end/end_dash: -------------------------------------------------------------------------------- 1 | \\ 2 | 3 | ' 4 | exit 5 | 6 | " 7 | exit 8 | 9 | ` 10 | exit 11 | 12 | } 13 | exit 14 | 15 | ) 16 | exit 17 | 18 | ' 19 | exit 20 | 21 | " 22 | exit 23 | 24 | ` 25 | exit 26 | 27 | } 28 | exit 29 | 30 | ) 31 | exit 32 | 33 | ' 34 | exit 35 | 36 | " 37 | exit 38 | 39 | ` 40 | exit 41 | 42 | } 43 | exit 44 | 45 | ) 46 | exit 47 | 48 | -------------------------------------------------------------------------------- /run_test/end/end_tcsh: -------------------------------------------------------------------------------- 1 |  2 | exit 3 |  4 | exit 5 |  6 | exit 7 |  8 | exit 9 |  10 | exit 11 |  12 | exit 13 |  14 | exit 15 |  16 | exit 17 |  18 | exit 19 |  20 | exit 21 |  22 | exit 23 |  24 | exit 25 |  26 | exit 27 |  28 | exit 29 |  30 | exit 31 |  32 | -------------------------------------------------------------------------------- /run_test/end/end_zsh: -------------------------------------------------------------------------------- 1 |  2 | exit 3 |  4 | exit 5 |  6 | exit 7 |  8 | exit 9 |  10 | exit 11 |  12 | exit 13 |  14 | exit 15 |  16 | exit 17 |  18 | exit 19 |  20 | exit 21 |  22 | exit 23 |  24 | exit 25 |  26 | exit 27 |  28 | exit 29 |  30 | exit 31 |  32 | -------------------------------------------------------------------------------- /run_test/test_MacOS/pty: -------------------------------------------------------------------------------- 1 | pty bash [-i -l -r -s] 2 | pty dash 3 | pty ex [-A -b -C] 4 | pty htop [-C] 5 | pty less [-a -A -B -c -C -d -e -E -f -F -g -G -i -I -J -K -L -m -M -n -N -q -Q -r -R -s -S -u -U -w -W -X -~ -# --follow-name --no-keypad --use-backslash] 6 | pty tcsh [-c -e] 7 | pty telnet [-4 -6 -8 -E -L -a -d -r] 8 | pty top [-a -d -e -F] 9 | pty vim [-A -b] 10 | pty zsh 11 | -------------------------------------------------------------------------------- /run_test/end/end_htop: -------------------------------------------------------------------------------- 1 | 2 | qqq 3 | qqq 4 |  5 | 6 | 7 | 8 | qqq 9 | qqq 10 |  11 | 12 | 13 | 14 | qqq 15 | qqq 16 |  17 | 18 | 19 | 20 | qqq 21 | qqq 22 |  23 | 24 | 25 | 26 | qqq 27 | qqq 28 |  29 | 30 | 31 | 32 | qqq 33 | qqq 34 |  35 | 36 | 37 | 38 | qqq 39 | qqq 40 |  41 | 42 | 43 | 44 | qqq 45 | qqq 46 |  47 | 48 | 49 | 50 | qqq 51 | qqq 52 |  53 | 54 | 55 | -------------------------------------------------------------------------------- /run_test/end/end_top: -------------------------------------------------------------------------------- 1 | 2 | qqq 3 | qqq 4 |  5 | 6 | 7 | 8 | qqq 9 | qqq 10 |  11 | 12 | 13 | 14 | qqq 15 | qqq 16 |  17 | 18 | 19 | 20 | qqq 21 | qqq 22 |  23 | 24 | 25 | 26 | qqq 27 | qqq 28 |  29 | 30 | 31 | 32 | qqq 33 | qqq 34 |  35 | 36 | 37 | 38 | qqq 39 | qqq 40 |  41 | 42 | 43 | 44 | qqq 45 | qqq 46 |  47 | 48 | 49 | 50 | qqq 51 | qqq 52 |  53 | 54 | 55 | -------------------------------------------------------------------------------- /run_test/test_FreeBSD/pty: -------------------------------------------------------------------------------- 1 | pty bash [-i -l -r -s] 2 | pty dash 3 | pty ex [-F -R -r] 4 | pty ftp [-4 -6 -a -f] 5 | pty htop [-C -s -t] 6 | pty less [-a -A -B -c -C -d -e -E -f -F -g -G -i -I -J -K -L -m -M -n -N -q -Q -r -R -s -S -u -U -w -W -X -~ -# --follow-name --no-keypad --use-backslash --rscroll] 7 | pty sh [-a -b -C -p] 8 | pty tcsh [-e -f] 9 | pty telnet [-4 -6 -8 -E -N] 10 | pty top [-C -S -I] 11 | pty vim [-A -b -C] 12 | pty zsh 13 | -------------------------------------------------------------------------------- /run_test/test_Linux/pty: -------------------------------------------------------------------------------- 1 | pty bash [-i -l -r -s -v -x] 2 | pty csh [-c -e] 3 | pty dash 4 | pty ex [-A -b -C] 5 | pty ftp [-4 -6 -p -n -e -g -v -d] 6 | pty htop [-C] 7 | pty less [-a -A -B -c -C -d -e -E -f -F -g -G -i -I -J -K -L -m -M -n -N -q -Q -r -R -s -S -u -U -w -W -X --follow-name --mouse --MOUSE --no-keypad --no-histdups --rscroll --save-marks --use-backslash] 8 | pty more [-d -l -f -p -s -u] 9 | pty telnet [-4 -6 -8 -E -L -a -d -r] 10 | pty top [-a -d -e -F] 11 | pty vim [-A -b] 12 | pty zsh 13 | -------------------------------------------------------------------------------- /run_test/README: -------------------------------------------------------------------------------- 1 | In this directory: 2 | ./test_Linux/ 3 | Name and option pools of each cmd to be tested on Linux 4 | ./test_MacOS/ 5 | Name and option pools of each cmd to be tested on MacOS 6 | ./test_FreeBSD/ 7 | Name and option pools of each cmd to be tested on FreeBSD 8 | ./end/ 9 | The designed string for each interactive utility to be appended to the random input. In this way we can distinguish if the program is waiting for more input after the end of random input from a program that is hung. 10 | 11 | run.py 12 | Automatic script to test all utilities listed in a file(see ./test_Linux/ or ./test_MacOS/ or ./test_FreeBSD/). To see how to use run.py, run: 13 | python3 run.py 14 | -------------------------------------------------------------------------------- /run_test/end/end_less: -------------------------------------------------------------------------------- 1 | N 2 | N 3 | N 4 | 5 |  6 | qqqqqqqqqqqqqqq 7 | qqqqqqqqqqqqqqq 8 | qqqqqqqqqqqqqqq 9 | qqqqqqqqqqqqqqq 10 | qqqqqqqqqqqqqqq 11 | qqqqqqqqqqqqqqq 12 | qqqqqqqqqqqqqqq 13 | qqqqqqqqqqqqqqq 14 | qqqqqqqqqqqqqqq 15 | :q! 16 | :q! 17 | :q! 18 | :q! 19 | :q! 20 | :q! 21 | :q! 22 | :q! 23 |  24 | qqqqqqqqqqqqqqq 25 | qqqqqqqqqqqqqqq 26 | qqqqqqqqqqqqqqq 27 | qqqqqqqqqqqqqqq 28 | qqqqqqqqqqqqqqq 29 | qqqqqqqqqqqqqqq 30 | qqqqqqqqqqqqqqq 31 | qqqqqqqqqqqqqqq 32 | qqqqqqqqqqqqqqq 33 | :q! 34 | :q! 35 | :q! 36 | :q! 37 | :q! 38 | :q! 39 | :q! 40 | :q! 41 |  42 | qqqqqqqqqqqqqqq 43 | qqqqqqqqqqqqqqq 44 | qqqqqqqqqqqqqqq 45 | qqqqqqqqqqqqqqq 46 | qqqqqqqqqqqqqqq 47 | -------------------------------------------------------------------------------- /generate_test/README: -------------------------------------------------------------------------------- 1 | In this directory: 2 | size and parameters of datasets.xlsx: 3 | A xlsx file with the parameters to generate dataset. 4 | generate_small1.py: 5 | python script to generate dateset Small1 and Small2. 6 | generate_medium1.py: 7 | python script to generate dateset Medium1 and Medium2. 8 | generate_large1.py: 9 | python script to generate dateset Large1 and Large2. 10 | generate_huge1.py: 11 | python script to generate dateset Huge1 and Huge2. 12 | generate_small3.py: 13 | python script to generate dateset Small3. 14 | generate_medium3.py: 15 | python script to generate dateset Medium3. 16 | generate_large3.py: 17 | python script to generate dateset Large3. 18 | generate_huge3.py: 19 | python script to generate dateset Huge3. 20 | -------------------------------------------------------------------------------- /run_test/test_Linux/coreutil_rust: -------------------------------------------------------------------------------- 1 | file ./coreutils_rust/target/debug/cat [-A -b -E -n -s -t -T -v] 2 | two_files ./coreutils_rust/target/debug/comm [-1 -2 -3] 3 | file ./coreutils_rust/target/debug/cut [--complement -z] -b 1 4 | file ./coreutils_rust/target/debug/expand [-i -U] 5 | file ./coreutils_rust/target/debug/fmt [-c -t -m -s -u -x -q] 6 | file ./coreutils_rust/target/debug/fold [-b -s] 7 | file ./coreutils_rust/target/debug/head [-q] 8 | file ./coreutils_rust/target/debug/ptx [-A -G -R -f -r] 9 | file ./coreutils_rust/target/debug/sum [-r -s] 10 | stdin ./coreutils_rust/target/debug/tee [-a] 11 | stdin ./coreutils_rust/target/debug/tr a b 12 | file ./coreutils_rust/target/debug/tsort 13 | file ./coreutils_rust/target/debug/uniq [-c -d -i -u -z] 14 | file ./coreutils_rust/target/debug/wc [-c -m -l -L -w] 15 | -------------------------------------------------------------------------------- /run_test/end/end_more: -------------------------------------------------------------------------------- 1 | qqqqqqqqqqqqqqq 2 | qqqqqqqqqqqqqqq 3 | qqqqqqqqqqqqqqq 4 | qqqqqqqqqqqqqqq 5 | qqqqqqqqqqqqqqq 6 | qqqqqqqqqqqqqqq 7 | qqqqqqqqqqqqqqq 8 | qqqqqqqqqqqqqqq 9 | qqqqqqqqqqqqqqq 10 | :q! 11 | :q! 12 | :q! 13 | :q! 14 | :q! 15 | :q! 16 | :q! 17 | :q! 18 | qqqqqqqqqqqqqqq 19 | qqqqqqqqqqqqqqq 20 | qqqqqqqqqqqqqqq 21 | qqqqqqqqqqqqqqq 22 | qqqqqqqqqqqqqqq 23 | qqqqqqqqqqqqqqq 24 | qqqqqqqqqqqqqqq 25 | qqqqqqqqqqqqqqq 26 | qqqqqqqqqqqqqqq 27 | :q! 28 | :q! 29 | :q! 30 | :q! 31 | :q! 32 | :q! 33 | :q! 34 | :q! 35 | qqqqqqqqqqqqqqq 36 | qqqqqqqqqqqqqqq 37 | qqqqqqqqqqqqqqq 38 | qqqqqqqqqqqqqqq 39 | qqqqqqqqqqqqqqq 40 | qqqqqqqqqqqqqqq 41 | qqqqqqqqqqqqqqq 42 | qqqqqqqqqqqqqqq 43 | qqqqqqqqqqqqqqq 44 | :q! 45 | :q! 46 | :q! 47 | :q! 48 | :q! 49 | :q! 50 | :q! 51 | :q! 52 | -------------------------------------------------------------------------------- /run_test/end/end_vim: -------------------------------------------------------------------------------- 1 | :q! 2 | :q! 3 | :q! 4 | :q! 5 | :q! 6 | :q! 7 | :q! 8 | :q! 9 | :q! 10 | :q! 11 | :q! 12 | :q! 13 | :q! 14 | :q! 15 | :q! 16 | :q! 17 | :q! 18 | :q! 19 | :q! 20 | :q! 21 | :q! 22 | :q! 23 | :q! 24 | :q! 25 | :q! 26 | :q! 27 | :q! 28 | -------------------------------------------------------------------------------- /run_test/end/end_ex: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | :q! 7 | 8 | :q! 9 | 10 | :q! 11 | 12 | :q! 13 | 14 | :q! 15 | 16 | 17 | :q! 18 | :q! 19 | :q! 20 | :q! 21 | :q! 22 | :q! 23 | 24 | 25 | 26 | 27 | 28 | 29 | :q! 30 | 31 | :q! 32 | 33 | :q! 34 | 35 | :q! 36 | 37 | :q! 38 | 39 | 40 | :q! 41 | :q! 42 | :q! 43 | :q! 44 | :q! 45 | :q! 46 | 47 | 48 | 49 | 50 | 51 | :q! 52 | 53 | :q! 54 | 55 | :q! 56 | 57 | :q! 58 | 59 | :q! 60 | 61 | 62 | :q! 63 | :q! 64 | :q! 65 | :q! 66 | :q! 67 | :q! 68 | -------------------------------------------------------------------------------- /doc/fuzz.man: -------------------------------------------------------------------------------- 1 | .\" Copyright (c) 1989 Lars Fredriksen, Bryan So and Barton Miller 2 | .\" All rights reserved. 3 | .\" 4 | .\" @(#)fuzz.1 5 | .\" 6 | .TH FUZZ 1 "December 1, 1988" 7 | .UC 4 8 | .SH NAME 9 | fuzz \- random character generator 10 | .SH SYNOPSIS 11 | .B fuzz 12 | length [ option ] ... 13 | .SH DESCRIPTION 14 | The main purpose of \fIfuzz\fP is to test the robustness of system 15 | utilities. We use \fIfuzz\fP 16 | to generate random characters. These are then piped to a 17 | system utility (using \fIptyjig(1)\fP if necessary.) 18 | If the utility crashes, the saved 19 | input and output streams can then be analyzed to decide what sorts 20 | of input cause problems. 21 | .PP 22 | .I Length 23 | is taken to be the 24 | length of the output stream, usually in bytes, 25 | When \fB\-l\fP is selected it 26 | the length is in number of strings. 27 | .PP 28 | The following options can be specified. 29 | .TP 30 | .B \-0 31 | Include NULL (ASCII 0) characters 32 | .TP 33 | .B \-a 34 | Include all ASCII characters except NULL (default) 35 | .TP 36 | .BI \-d " delay" 37 | Specify a delay in seconds between each character. 38 | .TP 39 | .BI \-e " string" 40 | Send \fIstring\fP after all the characters. This feature can be used 41 | to send termination strings to the test programs. Standard C escape 42 | sequences can be used. 43 | .TP 44 | .BI \-l " [len]" 45 | Generate random length strings. 46 | If \fIlen\fP is specified, it is taken to be the 47 | maximum length of each string (default = 255). 48 | Strings are terminated with the ASCII newline character. 49 | .TP 50 | .BI \-o " file" 51 | Store the output stream to \fIfile\fP as well as sending them to 52 | \fIstdout\fP. 53 | .TP 54 | .B \-p 55 | Generate printable ASCII characters only 56 | .TP 57 | .BI \-r " file" 58 | Replay characters stored in \fIfile\fP. 59 | .TP 60 | .BI \-s " seed" 61 | Use \fIseed\fP as the seed to the random number generator. 62 | .TP 63 | .BI \-m " modulus" 64 | Use \fImodulus\fP to restrict the range of random seed to 0 to \fImodulus-1\fP. 65 | .TP 66 | .B \-x 67 | Print the seed as the first line of stdout. 68 | .SH AUTHORS 69 | Lars Fredriksen, Bryan So. 70 | .SH "SEE ALSO" 71 | ptyjig(1) 72 | -------------------------------------------------------------------------------- /run_test/test_FreeBSD/regular: -------------------------------------------------------------------------------- 1 | file as [-a -D -g -J -L -R -w -Z] 2 | file awk '{print}' 3 | stdin bc [-c -l] 4 | file bison [-u -y -t --location -l -k] 5 | file calendar [-a -d] -f 6 | file cat [-b -e -l -n -s -t -u -v] 7 | file checknr [-a -c -f -s] 8 | cp t.c clang [-E -S -c -ansi -ObjC -trigraphs -fmath-errno -fblocks -fwritable-strings -g -ftrapv] 9 | two_files cmp [-h -l -x -z] 10 | stdin col [-b -f -h -p -x] 11 | stdin colcrt [- -2] 12 | stdin colrm 13 | two_files comm [-1 -2 -3 -i] 14 | cp t compress [-c -f -v] 15 | file ctags [-B -F -T -a -d -f -u -w -x] 16 | file cut -f 1 [-s -w] 17 | stdin dc [-x] 18 | stdin dd 19 | two_files diff [-i -E -b -w -B --strip-trailing-cr -a -p -q -e --normal -n -y --left-column] 20 | stdin ed [-s] 21 | stdin eqn [-C -N -r -R] 22 | stdin expand 23 | stdin flex 24 | stdin fmt [-c -m -n -p -s] 25 | stdin fold [-b -s] 26 | cp t.c gcc 27 | stdin gdb [-n -q -batch -f] 28 | stdin grep a [-a -b -c -F -P -G -H -h -I -i -L -l] 29 | stdin grn [-C] 30 | file groff [-e -g -G -l -L -N -p -R -s -S -t -U -V -X -z -Z -a -b -c -C -e] 31 | stdin head 32 | stdin indent [-bacc -bad -bap -bbb -bc -bl -ncdb -nce -dj -nei -eei -nfbs -st -troff] 33 | two_files join 34 | stdin lldb [-w -d -e -x] 35 | file look [-d -f] a 36 | stdin m4 [-g -P -s] 37 | file mail -s "subject" avergerzmx@outlook.com < 38 | file make [-B -e -i -k -n -N -q -r -s -t -W -w -X] -f 39 | file md5 [-q -r -t] 40 | stdin neqn 41 | file nm [--debug-syms --defined-only --line-numbers --no-demangle --no-sort] 42 | stdin pdftex 43 | stdin pic [-C -S -U -n -t -c -z -D] 44 | stdin pr [--d -F -r -t] 45 | file refer [-b -e -n -C -P -S -k -l -B -R] 46 | stdin rev 47 | two_files sdiff [-l -s -a -b -d -i -t] 48 | stdin sed '-s/a/b/' [-E -a -l -n -r -u] 49 | stdin soelim [-C] 50 | file sort [-c -m -u -s -b -d -f -g -h -i] 51 | cp t split [-d] -n 10 52 | stdin strings [-a -f] 53 | cp t strip [-g -p -s -w -x -X] 54 | stdin sum 55 | stdin tail [-F -q] 56 | stdin tbl [-C] 57 | stdin tee [-a] 58 | stdin tex 59 | stdin tr a b 60 | stdin troff [-a -b -c -C -E -R -U -z] 61 | stdin tsort [-d] 62 | stdin ul [-i] 63 | stdin uniq [-c -d -u -i] 64 | stdin units [-e -q -t] 65 | stdin wc [-L -c -l -m -w] 66 | stdin xargs [-0 -r -t] 67 | file zic [-D -v -s] 68 | -------------------------------------------------------------------------------- /run_test/test_MacOS/regular: -------------------------------------------------------------------------------- 1 | file as [-dynamic -static -g -W -q] 2 | file awk '{print}' 3 | stdin bc [-l -w -s -q] 4 | file bison [-d -g -k -l -n -t] 5 | file calendar -f 6 | file cat [-b -e -n -s -t -u -v] 7 | file checknr [-a -c -f -s] 8 | cp t.c clang [-E -fsyntax-only -S -c -ansi] 9 | two_files cmp [-b -l] 10 | stdin col [-b -f -h -p -x] 11 | file colcrt [- -2] 12 | stdin colrm 1 13 | two_files comm [-1 -2 -3 -i] 14 | cp t compress [-c -f -v] 15 | file ctags [-B -e -F -R -u -w -x] 16 | file cut [-n] -b 10 17 | stdin dc 18 | stdin dd 19 | two_files diff [-i -E -b -a -p] 20 | stdin ed [-s -x] 21 | stdin eqn [-C -N -r -R] 22 | stdin expand 23 | file flex [-Ca -Ce -Cf -Cm -f -d -b -p -s -T -7 -8 -B -X -L -R --bison-bridge --bison-locations --stdinit] 24 | file fmt [-c -m -n -p -s] 25 | file fold [-b -s] 26 | stdin gdb [-write -nh -nx -quiet -batch -fullname] 27 | file grep [-a -b -c -E --exclude --exclude-dir -i -J -L -l -n -O -p -q -R -S -s -U -v -w -x] "e" -f 28 | stdin grn [-C] 29 | file groff [-e -l -N -p -R -S -s -t -U] 30 | file head 31 | stdin indent [-bacc -bad -bap -bbb -bc -ncdb -nce -dj -br -lp -nut] -st 32 | two_files join 33 | stdin lldb [-w -d -e -x] 34 | file look a [-d -f] 35 | stdin m4 [-E -e -P -Q -s -G] 36 | stdin mail -s "subject" avengerzmx@outlook.com < 37 | file make [-b -B -d -e -i -k -L -p -q -r -R -s -S -t -w] -f 38 | stdin md5 [-p -q -r] 39 | file mig [-v -L -K -s -MD] 40 | stdin neqn 41 | file nm [-a -g -n -p -r -u] 42 | stdin pdftex 43 | file pic [-C -S -U -n -t -c -z -D] 44 | file pr 45 | file refer [-b -e -n -C -P -R -B -S -k -l] 46 | file rev 47 | two_files sdiff [-i -b -W -B -a -l -s -t -d -H] 48 | cp tmp sed [-E -a -l -n] 's/a/b/' 49 | file soelim [-C -r -t] 50 | file sort [-c -u -s -b] 51 | file split 52 | file strings [- -- -a -o] 53 | file strip [-u -r -i -A -n -S -X -T -N -x -c] 54 | file sum 55 | file tail [-q -r] 56 | file tbl [-C] 57 | stdin tee [-a] 58 | stdin tex [-enc -file-line-error -file-line-error-style -ini -ipc -ipc-start -mltex -parse-first-line -recorder -shell-escape -src-specials] 59 | stdin tr a b 60 | file troff [-a -b -c -C -E -R -U] 61 | file tsort [-l -q] 62 | file ul [-i] 63 | file uniq [-d -i] 64 | file units [-q] 65 | file wc [-c -l -m -w] 66 | stdin xargs [-0] 67 | file zic [-D -v -s] -------------------------------------------------------------------------------- /doc/ptyjig.man: -------------------------------------------------------------------------------- 1 | .\" Copyright (c) 1989 Lars Fredriksen, Bryan So and Barton Miller 2 | .\" All rights reserved. 3 | .\" 4 | .\" @(#)ptyjig.1 5 | .\" 6 | .TH PTYJIG 1 "December 1, 1988" 7 | .UC 4 8 | .SH NAME 9 | ptyjig \- pseudo-terminal pipe 10 | .SH SYNOPSIS 11 | .B ptyjig 12 | [ option ] ... 13 | command 14 | [ args ] ... 15 | .SH DESCRIPTION 16 | .I Ptyjig 17 | executes the Unix 18 | .I command 19 | with 20 | .I args 21 | as its arguments if supplied. 22 | The standard input of ptyjig is piped to 23 | .I command 24 | as if typed at a terminal. 25 | Ptyjig is expected to be used with \fIfuzz(1)\fP to test interactive 26 | (terminal based) programs. If the test program is stopped by a 27 | Control-Z, it is resumed immediately. 28 | Ptyjig has been modified to run on Linux, MacOS and freeBSD. It captures the abnormal termination of a utility and returns it to operating system. 29 | .PP 30 | The following options can be specified. 31 | .TP 32 | .B \-e 33 | Do not send EOF character after \fIstdin\fP has exhausted. 34 | .TP 35 | .B \-s 36 | Do not process interrupt signals, such as SIGINT, SIGQUIT and SIGSTOP. 37 | .TP 38 | .B \-x 39 | Do not write output from \fIcommand\fP to \fIstdout\fP. 40 | .TP 41 | .BI \-i " file" 42 | Save the input stream sent to \fIcommand\fP into \fIfile\fP. 43 | .TP 44 | .BI \-o " file" 45 | Save the output produced by \fIcommand\fP into \fIfile\fP. 46 | .TP 47 | .BI \-d " delay" 48 | Wait \fIdelay\fP seconds after sending each character. 49 | .TP 50 | .BI \-t " interval" 51 | If input has exhausted but \fIcommand\fP has 52 | neither exited nor sent any output, exit after \fIinterval\fP 53 | seconds. Default is 2.0 seconds. 54 | .TP 55 | \fIDelay\fP and \fIinterval\fP can have fractions. 56 | .SH EXAMPLE 57 | ptyjig -o out -d 0.2 -t 10 vi text1 = 126 or 20 | < 0, an error is recorded in the result file as well as the test file that 21 | causes the error. If the test does not finish in 5 minutes, a hang is 22 | recorded together with the test file that causes the hang. 23 | 24 | config_file 25 | 26 | Specify the type of input fed into each utility using the following 27 | syntax: 28 | 29 | [stdin|file|cp|two_files|pty] [utility_name] [options] 30 | 31 | stdin Extract the content from the test file and feed into the 32 | utility. 33 | 34 | file Feed the file name directly to the utility. 35 | 36 | cp Copy the content of test file to a temporary file with 37 | the correct appendix in the file name. 38 | 39 | two_files Feed two files into the utility. 40 | 41 | pty Test the utility using ptyjig. To use ptyjig, a temporary 42 | file is created for each test file and then fed into the 43 | utility. In the temporary file, we filter out characters 44 | that will suspend the process and append the corresponding 45 | end to ensure proper quit. 46 | 47 | options Add the options required for the utility to take input. 48 | 49 | -i [test_dir] 50 | Specify the directory which contains test files to be fed into the 51 | utilities. 52 | 53 | -o [result_dir] 54 | Specify the directory to store the testing results. 55 | 56 | The following options can be specified. 57 | 58 | -p [prefix] 59 | Specify the prefix of test files to be used. 60 | 61 | -t [time_out] 62 | Specify the timeout for each test. The default time out is 5 minutes. 63 | 64 | 65 | Sun Release 4.0 Last change: April 1, 2020 1 66 | 67 | 68 | 69 | 70 | 71 | 72 | RUN.PY(1) USER COMMANDS RUN.PY(1) 73 | 74 | 75 | 76 | AUTHORS 77 | Emma He, Mengxiao Zhang 78 | 79 | SEE ALSO 80 | fuzz(1), ptyjig(1) 81 | 82 | -------------------------------------------------------------------------------- /generate_test/generate_small1.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Emma He, Mengxiao Zhang, Barton Miller 2 | # 3 | # This program is distributed in the hope that it will be useful, but 4 | # WITHOUT ANY WARRANTY; without even the implied warranty of 5 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 6 | 7 | # this script is used to generate small test cases. 8 | 9 | import os, sys, re 10 | import subprocess 11 | import random 12 | import time 13 | 14 | fnull = open(os.devnull, 'w') 15 | path = "./Small1" 16 | 17 | if not os.path.exists(path): 18 | os.mkdir(path) 19 | 20 | inc = 200 21 | start = 0 22 | 23 | # -0 24 | for i in range(start, start+inc): 25 | print(i) 26 | if os.path.isfile(os.path.join(path, "t%d" % i)): 27 | continue 28 | # sleep more than 1 second to prevent fuzz from using the same random seed as the previous one. 29 | time.sleep(1.1) 30 | n = random.randint(0, 1e3) 31 | subprocess.call(["fuzz", "%d" % n, "-0", "-o", os.path.join(path, "t%d" % i)], stdout=fnull, stderr=subprocess.STDOUT) 32 | start = start + inc 33 | 34 | # -a 35 | for i in range(start, start+inc): 36 | print(i) 37 | if os.path.isfile(os.path.join(path, "t%d" % i)): 38 | continue 39 | time.sleep(1.1) 40 | n = random.randint(0, 1e3) 41 | subprocess.call(["fuzz", "%d" % n, "-a", "-o", os.path.join(path, "t%d" % i)], stdout=fnull, stderr=subprocess.STDOUT) 42 | start = start + inc 43 | 44 | # -p 45 | for i in range(start, start+inc): 46 | print(i) 47 | if os.path.isfile(os.path.join(path, "t%d" % i)): 48 | continue 49 | time.sleep(1.1) 50 | n = random.randint(0, 1e3) 51 | subprocess.call(["fuzz", "%d" % n, "-p", "-o", os.path.join(path, "t%d" % i)], stdout=fnull, stderr=subprocess.STDOUT) 52 | start = start + inc 53 | 54 | # -0 + -l 55 | for i in range(start, start+inc): 56 | print(i) 57 | if os.path.isfile(os.path.join(path, "t%d" % i)): 58 | continue 59 | time.sleep(1.1) 60 | n = random.randint(0, 100) 61 | l = 255 62 | subprocess.call(["fuzz", "%d" % n, "-l", "%d" % l, "-0", "-o", os.path.join(path, "t%d" % i)], stdout=fnull, stderr=subprocess.STDOUT) 63 | start = start + inc 64 | 65 | # -a + -l 66 | for i in range(start, start+inc): 67 | print(i) 68 | if os.path.isfile(os.path.join(path, "t%d" % i)): 69 | continue 70 | time.sleep(1.1) 71 | n = random.randint(0, 100) 72 | l = 255 73 | subprocess.call(["fuzz", "%d" % n, "-l", "%d" % l, "-a", "-o", os.path.join(path, "t%d" % i)], stdout=fnull, stderr=subprocess.STDOUT) 74 | start = start + inc 75 | 76 | # -p + -l 77 | for i in range(start, start+inc): 78 | print(i) 79 | if os.path.isfile(os.path.join(path, "t%d" % i)): 80 | continue 81 | time.sleep(1.1) 82 | n = random.randint(0, 100) 83 | l = 255 84 | subprocess.call(["fuzz", "%d" % n, "-l", "%d" % l, "-p", "-o", os.path.join(path, "t%d" % i)], stdout=fnull, stderr=subprocess.STDOUT) 85 | 86 | -------------------------------------------------------------------------------- /generate_test/generate_large1.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Emma He, Mengxiao Zhang, Barton Miller 2 | # 3 | # This program is distributed in the hope that it will be useful, but 4 | # WITHOUT ANY WARRANTY; without even the implied warranty of 5 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 6 | 7 | # this script is used to generate large test cases. 8 | 9 | import os, sys, re 10 | import subprocess 11 | import random 12 | import time 13 | 14 | fnull = open(os.devnull, 'w') 15 | path = "./Large1" 16 | 17 | if not os.path.exists(path): 18 | os.mkdir(path) 19 | 20 | inc = 30 21 | start = 0 22 | 23 | # -0 24 | for i in range(start, start+inc): 25 | print(i) 26 | if os.path.isfile(os.path.join(path, "t%d" % i)): 27 | continue 28 | # sleep more than 1 second to prevent fuzz from using the same random seed as the previous one. 29 | time.sleep(1.1) 30 | n = random.randint(1e6, 1e7) 31 | subprocess.call(["fuzz", "%d" % n, "-0", "-o", os.path.join(path, "t%d" % i)], stdout=fnull, stderr=subprocess.STDOUT) 32 | start = start + inc 33 | 34 | # -a 35 | for i in range(start, start+inc): 36 | print(i) 37 | if os.path.isfile(os.path.join(path, "t%d" % i)): 38 | continue 39 | time.sleep(1.1) 40 | n = random.randint(1e6, 1e7) 41 | subprocess.call(["fuzz", "%d" % n, "-a", "-o", os.path.join(path, "t%d" % i)], stdout=fnull, stderr=subprocess.STDOUT) 42 | start = start + inc 43 | 44 | # -p 45 | for i in range(start, start+inc): 46 | print(i) 47 | if os.path.isfile(os.path.join(path, "t%d" % i)): 48 | continue 49 | time.sleep(1.1) 50 | n = random.randint(1e6, 1e7) 51 | subprocess.call(["fuzz", "%d" % n, "-p", "-o", os.path.join(path, "t%d" % i)], stdout=fnull, stderr=subprocess.STDOUT) 52 | start = start + inc 53 | 54 | # -0 + -l 55 | for i in range(start, start+inc): 56 | print(i) 57 | if os.path.isfile(os.path.join(path, "t%d" % i)): 58 | continue 59 | time.sleep(1.1) 60 | n = random.randint(1e4, 1e5) 61 | l = 255 62 | subprocess.call(["fuzz", "%d" % n, "-l", "%d" % l, "-0", "-o", os.path.join(path, "t%d" % i)], stdout=fnull, stderr=subprocess.STDOUT) 63 | start = start + inc 64 | 65 | # -a + -l 66 | for i in range(start, start+inc): 67 | print(i) 68 | if os.path.isfile(os.path.join(path, "t%d" % i)): 69 | continue 70 | time.sleep(1.1) 71 | n = random.randint(1e4, 1e5) 72 | l = 255 73 | subprocess.call(["fuzz", "%d" % n, "-l", "%d" % l, "-a", "-o", os.path.join(path, "t%d" % i)], stdout=fnull, stderr=subprocess.STDOUT) 74 | start = start + inc 75 | 76 | # -p + -l 77 | for i in range(start, start+inc): 78 | print(i) 79 | if os.path.isfile(os.path.join(path, "t%d" % i)): 80 | continue 81 | time.sleep(1.1) 82 | n = random.randint(1e4, 1e5) 83 | l = 255 84 | subprocess.call(["fuzz", "%d" % n, "-l", "%d" % l, "-p", "-o", os.path.join(path, "t%d" % i)], stdout=fnull, stderr=subprocess.STDOUT) 85 | 86 | -------------------------------------------------------------------------------- /generate_test/generate_huge1.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Emma He, Mengxiao Zhang, Barton Miller 2 | # 3 | # This program is distributed in the hope that it will be useful, but 4 | # WITHOUT ANY WARRANTY; without even the implied warranty of 5 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 6 | 7 | # this script is used to generate huge test cases. 8 | 9 | import os, sys, re 10 | import subprocess 11 | import random 12 | import time 13 | 14 | fnull = open(os.devnull, 'w') 15 | path = "./Huge1" 16 | 17 | if not os.path.exists(path): 18 | os.mkdir(path) 19 | 20 | inc = 10 21 | start = 0 22 | waitTime = 1.1 23 | 24 | # -0 25 | for i in range(start, start+inc): 26 | print(i) 27 | if os.path.isfile(os.path.join(path, "t%d" % i)): 28 | continue 29 | # sleep more than 1 second to prevent fuzz from using the same random seed as the previous one. 30 | time.sleep(waitTime) 31 | n = random.randint(1e7, 1e8) 32 | subprocess.call(["fuzz", "%d" % n, "-0", "-o", os.path.join(path, "t%d" % i)], stdout=fnull, stderr=subprocess.STDOUT) 33 | start = start + inc 34 | 35 | # -a 36 | for i in range(start, start+inc): 37 | print(i) 38 | if os.path.isfile(os.path.join(path, "t%d" % i)): 39 | continue 40 | time.sleep(waitTime) 41 | n = random.randint(1e7, 1e8) 42 | subprocess.call(["fuzz", "%d" % n, "-a", "-o", os.path.join(path, "t%d" % i)], stdout=fnull, stderr=subprocess.STDOUT) 43 | start = start + inc 44 | 45 | # -p 46 | for i in range(start, start+inc): 47 | print(i) 48 | if os.path.isfile(os.path.join(path, "t%d" % i)): 49 | continue 50 | time.sleep(waitTime) 51 | n = random.randint(1e7, 1e8) 52 | subprocess.call(["fuzz", "%d" % n, "-p", "-o", os.path.join(path, "t%d" % i)], stdout=fnull, stderr=subprocess.STDOUT) 53 | start = start + inc 54 | 55 | # -0 + -l 56 | for i in range(start, start+inc): 57 | print(i) 58 | if os.path.isfile(os.path.join(path, "t%d" % i)): 59 | continue 60 | time.sleep(waitTime) 61 | n = random.randint(1e5, 1e6) 62 | l = 255 63 | subprocess.call(["fuzz", "%d" % n, "-l", "%d" % l, "-0", "-o", os.path.join(path, "t%d" % i)], stdout=fnull, stderr=subprocess.STDOUT) 64 | start = start + inc 65 | 66 | # -a + -l 67 | for i in range(start, start+inc): 68 | print(i) 69 | if os.path.isfile(os.path.join(path, "t%d" % i)): 70 | continue 71 | time.sleep(waitTime) 72 | n = random.randint(1e5, 1e6) 73 | l = 255 74 | subprocess.call(["fuzz", "%d" % n, "-l", "%d" % l, "-a", "-o", os.path.join(path, "t%d" % i)], stdout=fnull, stderr=subprocess.STDOUT) 75 | start = start + inc 76 | 77 | # -p + -l 78 | for i in range(start, start+inc): 79 | print(i) 80 | if os.path.isfile(os.path.join(path, "t%d" % i)): 81 | continue 82 | time.sleep(waitTime) 83 | n = random.randint(1e5, 1e6) 84 | l = 255 85 | subprocess.call(["fuzz", "%d" % n, "-l", "%d" % l, "-p", "-o", os.path.join(path, "t%d" % i)], stdout=fnull, stderr=subprocess.STDOUT) 86 | 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The Relevance of Classic Fuzz Testing: Have We Solved This One? 2 | 3 | ### Description 4 | 5 | This work is a revisit to Prof. Miller's previous fuzzing works, i.e., [1990](https://dl.acm.org/doi/abs/10.1145/96267.96279), [1995](https://minds.wisconsin.edu/bitstream/handle/1793/59964/TR1268.pdf) and [2006](https://dl.acm.org/doi/abs/10.1145/1145735.1145743). We applied original fuzzing techniques to command-line utilities on multiple platforms and found that 9 crash or hang out of 74 utilities on Linux, 15 out of 78 utilities 6 | on FreeBSD, and 12 out of 76 utilities on MacOS. We found that the failure rates of command-line utilities are higher than those in previous studies. We also provided detailed bug analysis, including when a bug was introduced and when it was solved. Some of the errors that we found have been present in the codebase for many years. Plus, we fuzzed core utilities implemented in Rust. 7 | 8 | ### Code 9 | 10 | In this study, we updated the source code used in the [original fuzzing study](https://dl.acm.org/doi/abs/10.1145/96267.96279). Now it applies to Linux, OS X and FreeBSD. 11 | 12 | #### ./src 13 | 14 | In this directory: 15 | 16 | Makefile 17 | 18 | ​ To build fuzz.c and ptyjig.c, run: 19 | 20 | ​ ```cd ./src && make all``` 21 | 22 | fuzz.c 23 | 24 | ​ A random string generator. 25 | 26 | ​ For the usage, run: 27 | 28 | ​ ```man ./doc/fuzz.man```. 29 | 30 | ptyjig.c 31 | 32 | ​ A tool to provide input to utilities that read input from the terminal, it is used to test programs such as vim and bash. 33 | 34 | ​ For the usage, run: 35 | 36 | ​ ```man ./doc/ptyjig.man```. 37 | 38 | #### ./run_test 39 | 40 | In this directory: 41 | 42 | ./test_Linux/ 43 | 44 | ​ Name and option pools of each cmd to be tested on Linux. 45 | 46 | ./test_MacOS/ 47 | 48 | ​ Name and option pools of each cmd to be tested on MacOS. 49 | 50 | ./test_FreeBSD/ 51 | 52 | ​ Name and option pools of each cmd to be tested on FreeBSD. 53 | 54 | ./end/ 55 | 56 | ​ For each program tested with ptyjig, we specify a string to append to the random input to attempt to terminate the utility. For example, when testing vim, we append the sequence “ESC :q !”. In this way we can distinguish if the program is waiting for more input after the end of random input from a program that is hung. 57 | 58 | run.py 59 | 60 | ​ Automatic script to test all utilities listed in a configuration file(see ./test_Linux/ or ./test_MacOS/ or ./test_FreeBSD/). 61 | 62 | ​ For the usage, run: 63 | 64 | ​ ```man ./doc/run.py.1```. 65 | 66 | #### ./generate_test 67 | 68 | Python scripts to generate random files. 69 | 70 | #### ./doc 71 | 72 | Man pages for the above files. 73 | 74 | ### Reference 75 | 76 | If you find this implementation useful in your research, please consider citing: 77 | 78 | ``` 79 | @article{miller2020relevance, 80 | title={The Relevance of Classic Fuzz Testing: Have We Solved This One?}, 81 | author={Miller, Barton and Zhang, Mengxiao and Heymann, Elisa}, 82 | journal={IEEE Transactions on Software Engineering}, 83 | year={2020}, 84 | publisher={IEEE} 85 | } 86 | ``` -------------------------------------------------------------------------------- /doc/ptyjig.1: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | PTYJIG(1) USER COMMANDS PTYJIG(1) 5 | 6 | 7 | 8 | NAME 9 | ptyjig - pseudo-terminal pipe 10 | 11 | SYNOPSIS 12 | ptyjig [ option ] ... command [ args ] ... 13 | 14 | DESCRIPTION 15 | _P_t_y_j_i_g executes the Unix _c_o_m_m_a_n_d with _a_r_g_s as its arguments 16 | if supplied. The standard input of ptyjig is piped to _c_o_m_- 17 | _m_a_n_d as if typed at a terminal. Ptyjig is expected to be 18 | used with _f_u_z_z(_1) to test interactive (terminal based) pro- 19 | grams. If the test program is stopped by a Control-Z, it is 20 | resumed immediately. 21 | 22 | The following options can be specified. 23 | 24 | -e Do not send EOF character after _s_t_d_i_n has exhausted. 25 | 26 | -s Do not process interrupt signals, such as SIGINT, 27 | SIGQUIT and SIGSTOP. 28 | 29 | -x Do not write output from _c_o_m_m_a_n_d to _s_t_d_o_u_t. 30 | 31 | -i _f_i_l_e 32 | Save the input stream sent to _c_o_m_m_a_n_d into _f_i_l_e. 33 | 34 | -o _f_i_l_e 35 | Save the output produced by _c_o_m_m_a_n_d into _f_i_l_e. 36 | 37 | -d _d_e_l_a_y 38 | Wait _d_e_l_a_y seconds after sending each character. 39 | 40 | -t _i_n_t_e_r_v_a_l 41 | If input has exhausted but _c_o_m_m_a_n_d has neither exited 42 | nor sent any output, exit after _i_n_t_e_r_v_a_l seconds. 43 | Default is 2.0 seconds. 44 | 45 | _D_e_l_a_y and _i_n_t_e_r_v_a_l can have fractions. 46 | 47 | EXAMPLE 48 | ptyjig -o out -d 0.2 -t 10 vi text1 69 | #include 70 | #include 71 | #include 72 | #include 73 | #include 74 | #include 75 | 76 | #define SWITCH '-' 77 | 78 | /* 79 | * Global flags 80 | */ 81 | int flag0 = 0; 82 | int flaga = 1; /* 0 if flagp */ 83 | unsigned flagd = 0; 84 | int flagl = 0; 85 | int flags = 0; 86 | int flage = 0; 87 | int seed = 0; 88 | int flagn = 0; 89 | int flagx = 0; 90 | int flago = 0; 91 | int flagr = 0; 92 | int length = 0; 93 | int flagm = 0; 94 | int modulus = 0; 95 | char epilog[1024]; 96 | char *infile, *outfile; 97 | FILE *in, *out; 98 | 99 | void usage(); 100 | void init(); 101 | void replay(); 102 | void fuzz(); 103 | void putch(int i); 104 | void fuzzchar(int m, int h); 105 | void fuzzstr(int m, int h); 106 | void myputs(char *s); 107 | int oct2dec(int i); 108 | 109 | int main(int argc, char** argv) 110 | { 111 | float f; 112 | 113 | /* 114 | * Parse command line 115 | */ 116 | while (*(++argv) != NULL) 117 | if (**argv != SWITCH) { /* Not a switch, must be a length */ 118 | if (sscanf(*argv, "%d", &length) != 1) 119 | usage(); 120 | flagn = 1; 121 | } else { /* A switch */ 122 | switch ((*argv)[1]) { 123 | case '0': 124 | flag0 = 1; 125 | break; 126 | case 'a': 127 | flaga = 1; 128 | break; 129 | case 'd': 130 | argv++; 131 | if (sscanf(*argv, "%f", &f) != 1) 132 | usage(); 133 | flagd = (unsigned) (f * 1000000.0); 134 | break; 135 | case 'o': 136 | flago = 1; 137 | argv++; 138 | outfile = *argv; 139 | break; 140 | case 'r': 141 | flagr = 1; 142 | argv++; 143 | infile = *argv; 144 | break; 145 | case 'l': 146 | flagl = 255; 147 | if (argv[1] != NULL && argv[1][0] != SWITCH) { 148 | argv++; 149 | if (sscanf(*argv, "%d", &flagl) != 1 || flagl <= 0) 150 | usage(); 151 | } 152 | break; 153 | case 'p': 154 | flaga = 0; 155 | break; 156 | case 's': 157 | argv++; 158 | flags = 1; 159 | if (sscanf(*argv, "%d", &seed) != 1) 160 | usage(); 161 | break; 162 | case 'm': 163 | argv++; 164 | flagm = 1; 165 | if (sscanf(*argv, "%d", &modulus) != 1) 166 | usage(); 167 | break; 168 | case 'e': 169 | argv++; 170 | flage = 1; 171 | if (*argv == NULL) 172 | usage(); 173 | sprintf(epilog, "%s", *argv); 174 | break; 175 | case 'x': 176 | flagx = 1; 177 | break; 178 | default: 179 | usage(); 180 | } 181 | } 182 | 183 | init(); 184 | if (flagr) 185 | replay(); 186 | else 187 | fuzz(); 188 | myputs(epilog); 189 | 190 | if (flago) 191 | if (fclose(out) == EOF) { 192 | perror(outfile); 193 | exit(1); 194 | } 195 | if (flagr) 196 | if (fclose(in) == EOF) { 197 | perror(infile); 198 | exit(1); 199 | } 200 | return 0; 201 | } 202 | 203 | 204 | void usage() 205 | { 206 | puts("Usage: fuzz [-x] [-0] [-a] [-l [strlen]] [-p] [-o outfile]"); 207 | puts(" [-m [modulus] [-r infile] [-d delay] [-s seed]"); 208 | puts(" [-e \"epilog\"] [len]"); 209 | exit(1); 210 | } 211 | 212 | 213 | /* 214 | * Initialize random number generator and others 215 | */ 216 | void init() 217 | { 218 | long now; 219 | 220 | /* 221 | * Init random numbers 222 | */ 223 | if (!flags){ 224 | // if no seed specified, use current time for random seed 225 | seed = (int)time(&now); 226 | } 227 | 228 | // if -m is set, restrict the seed value 229 | if (flagm){ 230 | seed = seed % modulus; 231 | } 232 | 233 | srand(seed); 234 | 235 | /* 236 | * Random line length if necessary 237 | */ 238 | if (!flagn) 239 | length = rand() % 100000; 240 | 241 | /* 242 | * Open data files if necessary 243 | */ 244 | if (flago) 245 | if ((out = fopen(outfile, "wb")) == NULL) { 246 | perror(outfile); 247 | exit(1); 248 | } 249 | if (flagr) { 250 | if ((in = fopen(infile, "rb")) == NULL) { 251 | perror(infile); 252 | exit(1); 253 | } 254 | } else if (flagx) { 255 | printf("%d\n", seed); 256 | if (fflush(stdout) == EOF) { 257 | perror(progname); 258 | exit(1); 259 | } 260 | if (flago) { 261 | fprintf(out, "%d\n", seed); 262 | if (fflush(out) == EOF) { 263 | perror(outfile); 264 | exit(1); 265 | } 266 | } 267 | } 268 | } 269 | 270 | 271 | /* 272 | * Replay characters in "in" 273 | */ 274 | void replay() 275 | { 276 | int c; 277 | 278 | while ((c = getc(in)) != EOF) 279 | putch(c); 280 | } 281 | 282 | 283 | /* 284 | * Decide th effective range of the random characters 285 | */ 286 | void fuzz() 287 | { 288 | int m, h; 289 | 290 | /* 291 | * Every random character is of the form c = rand() % m + h 292 | */ 293 | h = 1; 294 | m = 255; /* Defaults, 1-255 */ 295 | if (flag0) { 296 | h = 0; 297 | m = 256; /* All ASCII, including 0, 0-255 */ 298 | } 299 | if (!flaga) { 300 | h = 32; 301 | m = 95 + (flag0!=0); /* Printables, 32-126 */ 302 | } 303 | if (flagl) 304 | fuzzstr(m, h); 305 | else 306 | fuzzchar(m, h); 307 | } 308 | 309 | 310 | /* 311 | * Output a character to standard out with delay 312 | */ 313 | void putch(int i) 314 | { 315 | char c; 316 | 317 | c = (char) i; 318 | if (write(1, &c, 1) != 1) { 319 | perror(progname); 320 | if (flagr) 321 | (void)fclose(in); 322 | if (flago) 323 | (void)fclose(out); 324 | exit(1); 325 | } 326 | if (flago) 327 | if (write(fileno(out), &c, 1) != 1) { 328 | perror(outfile); 329 | exit(1); 330 | } 331 | if (flagd) 332 | usleep(flagd); 333 | } 334 | 335 | 336 | /* 337 | * Make a random character 338 | */ 339 | void fuzzchar(int m, int h) 340 | { 341 | int i,c; 342 | 343 | for (i = 0; i < length; i++) { 344 | c = (int) (rand() % m) + h; 345 | if (flag0 && !flaga && c == 127) 346 | c = 0; 347 | putch(c); 348 | } 349 | } 350 | 351 | 352 | /* 353 | * make random strings 354 | */ 355 | void fuzzstr(int m, int h) 356 | { 357 | int i, j, l, c; 358 | 359 | for (i = 0; i < length; i++) { 360 | l = rand() % flagl; /* Line length */ 361 | for (j = 0; j < l; j++) { 362 | c = (int) (rand() % m) + h; 363 | if (flag0 && !flaga && c == 127) 364 | c = 0; 365 | putch(c); 366 | } 367 | putch('\n'); 368 | } 369 | } 370 | 371 | 372 | /* 373 | * Output the "epilog" with C escape sequences 374 | */ 375 | void myputs(char *s) 376 | { 377 | int c; 378 | 379 | while (strlen(s) != 0) { 380 | if (*s == '\\') { 381 | switch (*++s) { 382 | case 'b': 383 | putch('\b'); 384 | break; 385 | case 'f': 386 | putch('\f'); 387 | break; 388 | case 'n': 389 | putch('\n'); 390 | break; 391 | case 'r': 392 | putch('\r'); 393 | break; 394 | case 't': 395 | putch('\r'); 396 | break; 397 | case 'v': 398 | putch('\v'); 399 | break; 400 | case 'x': 401 | s++; 402 | (void) sscanf(s, "%2x", &c); 403 | putch(c); 404 | s++; 405 | break; 406 | default: 407 | if (isdigit(*s)) { 408 | (void) sscanf(s, "%3d", &c); 409 | putch(oct2dec(c)); 410 | for (c = 0; c < 3 && isdigit(*s); c++) 411 | s++; 412 | s--; 413 | } else 414 | putch(*s); 415 | break; 416 | } 417 | } else 418 | putch(*s); 419 | s++; 420 | } 421 | } 422 | 423 | 424 | /* 425 | * Octal to Decimal conversion, required by myputs() 426 | */ 427 | int oct2dec(int i) 428 | { 429 | char s[8]; 430 | int r = 0; 431 | 432 | sprintf(s, "%d", i); 433 | for (i = 0; i < strlen(s); i++) 434 | r = r * 8 + (s[i] - '0'); 435 | 436 | return r; 437 | } 438 | -------------------------------------------------------------------------------- /run_test/run.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Emma He, Mengxiao Zhang, Barton Miller 2 | # 3 | # This program is distributed in the hope that it will be useful, but 4 | # WITHOUT ANY WARRANTY; without even the implied warranty of 5 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 6 | 7 | # this script is used to generate big test cases. 8 | 9 | import os 10 | import subprocess 11 | import sys 12 | import random 13 | import getopt 14 | import re 15 | import datetime 16 | 17 | # define variables 18 | 19 | # return_value is the lowest return value with special meaning 20 | return_value = 126 21 | float_rand = 0.5 22 | arg_num = 6 23 | 24 | # redirect the output to /dev/null, otherwise the shell will be overwhelmed by outputs 25 | fnull = open(os.devnull, 'w') 26 | ptyjig_path = "../src/ptyjig" 27 | 28 | first_file_for_more = "/p/paradyn/papers/fuzz2020/testcases/Large3/t150" 29 | 30 | usage = "Usage: python3 run.py configuration_file [-i inputfile] [-p prefix] [-t timeout] [-o outputfile]" 31 | 32 | # return a random subset of s, each element has 0.5 probability 33 | def random_subset(s): 34 | out = "" 35 | for el in s: 36 | # random coin flip 37 | if random.random() > float_rand: 38 | out = out + el + " " 39 | return out 40 | 41 | def line_commented(line): 42 | return line.startswith("//") 43 | 44 | 45 | def line_syntax_valid(line): 46 | if(not line.startswith("//") \ 47 | and not line.startswith("file ") \ 48 | and not line.startswith("stdin ") \ 49 | and not line.startswith("cp ") \ 50 | and not line.startswith("two_files ") \ 51 | and not line.startswith("pty ") 52 | ): 53 | return False 54 | 55 | # if specify option pool, there should be a pair of [] 56 | if(line.count("[") > 0 or line.count("]") > 0): 57 | if(line.count("[") != 1 or line.count("]") != 1): 58 | return False 59 | # if [ is on the right of ] 60 | elif(line.find("[") > line.find("]")): 61 | return False 62 | 63 | return True 64 | 65 | def get_options_from_pool(option_part_of_line): 66 | # keep characters on the left of [ and characters on the right of ] 67 | idx_left = option_part_of_line.find("[") 68 | idx_right = option_part_of_line.find("]") 69 | if(idx_left >= 0): 70 | return option_part_of_line[idx_left+1: idx_right] 71 | else: 72 | return "" 73 | 74 | # leave a space for randomly selected options 75 | def get_other_options(option_part_of_line): 76 | # return string on the left of [ and string on the right of ] 77 | idx_left = option_part_of_line.find("[") 78 | idx_right = option_part_of_line.find("]") 79 | if(idx_left >= 0): 80 | return option_part_of_line[0: idx_left] + " %s " + option_part_of_line[idx_right+1: ] 81 | else: 82 | return option_part_of_line + " %s" 83 | 84 | def parse_a_line(line): 85 | 86 | # filter out redundant white space 87 | line = " ".join(line.split()) 88 | 89 | # type is "stdin", "file", "two_files", "cp" or "pty" 90 | test_type = line.split()[0] 91 | 92 | # get the name of temporary file if a utility needs to copy test case to it 93 | # if cp, file name is specified, e.g., t.c 94 | if(test_type == "cp"): 95 | new_file_name = line.split()[1] 96 | # if pty, just copy test cases as tmp 97 | elif(test_type == "pty"): 98 | new_file_name = "tmp" 99 | # other test_types don't need to copy test cases 100 | else: 101 | new_file_name = "" 102 | 103 | # get the name of utilities 104 | # if cp, utility name is the third element of line.split(), e.g., cc from "cp t.c cc" 105 | if(test_type == "cp"): 106 | utility_name = line.split()[2] 107 | # otherwise, utility name is the second element of line.split(), e.g., flex from "stdin flex" 108 | else: 109 | utility_name = line.split()[1] 110 | 111 | # option_part_of_line is the part behind utility name 112 | if(test_type == "cp"): 113 | option_part_of_line = (line+" ").split(" ", 3)[3] 114 | else: 115 | option_part_of_line = (line+" ").split(" ", 2)[2] 116 | 117 | # get the options in option pool 118 | all_options_from_pool = get_options_from_pool(option_part_of_line) 119 | other_options = get_other_options(option_part_of_line) 120 | 121 | 122 | if(test_type == "stdin"): 123 | # leave a space for testcase 124 | cmd = utility_name \ 125 | + " " + other_options \ 126 | + " < " + "%s" 127 | 128 | elif(test_type == "file"): 129 | # leave a space for testcase 130 | cmd = utility_name \ 131 | + " " + other_options \ 132 | + " " + "%s" 133 | 134 | elif(test_type == "cp"): 135 | cmd = utility_name \ 136 | + " " + other_options \ 137 | + " " + new_file_name 138 | 139 | elif(test_type == "two_files"): 140 | # leave two space for testcases 141 | cmd = utility_name \ 142 | + " " + other_options \ 143 | + " " + "%s" \ 144 | + " " + "%s" 145 | 146 | elif(test_type == "pty"): 147 | # -d delay will be set in run_pty 148 | 149 | # more and less are special, it requires two files. The first file is the file to operate on, the second file provides random control sequence. Before the testing, copy one big test case to /tmp/tmp as the first file. 150 | if(utility_name == "more" or utility_name == "less"): 151 | cmd = ptyjig_path \ 152 | + " " + "-d" + " " + "%g" \ 153 | + " " + "-x" \ 154 | + " " + utility_name \ 155 | + " " + other_options \ 156 | + " " + "/tmp/tmp" \ 157 | + " < " + new_file_name 158 | else: 159 | cmd = ptyjig_path \ 160 | + " " + "-d" + " " + "%g" \ 161 | + " " + "-x" \ 162 | + " " + utility_name \ 163 | + " " + other_options \ 164 | + " < " + new_file_name 165 | 166 | 167 | log_name = "%s.%s" % (os.path.basename(utility_name), test_type) 168 | 169 | return cmd, test_type, utility_name, new_file_name, all_options_from_pool, log_name 170 | 171 | # run "file" 172 | def run_file(cmd, utility_name, log_path, all_options_from_pool, testcase_list): 173 | 174 | # if the log exists and have been finished, go to test the next utility 175 | if os.path.exists(log_path) and os.stat(log_path).st_size != 0: 176 | with open(log_path, "r") as f: 177 | if f.readlines()[-1] == "finished\n": 178 | return 179 | 180 | # otherwise, create the log or overwrite it 181 | log_writer = open(log_path, "w") 182 | log_writer.write("start: %s\n" % line) 183 | 184 | for testcase in testcase_list: 185 | 186 | options_sampled_from_pool = random_subset(all_options_from_pool.split()) 187 | final_cmd = cmd % (options_sampled_from_pool, testcase) 188 | print("running: %s" % final_cmd) 189 | 190 | try: 191 | retcode = subprocess.call(final_cmd, shell=True, stdout=fnull, stderr=subprocess.STDOUT, timeout=timeout) 192 | 193 | except(subprocess.TimeoutExpired): 194 | log_writer.write("%s hung\n" % final_cmd) 195 | 196 | else: 197 | print("retcode is %d" % retcode) 198 | if(retcode == 127): 199 | log_writer.write("%s not found\n" % utility_name) 200 | break 201 | # check return value, record exit code with special meaning 202 | if retcode >= return_value or retcode < 0: 203 | log_writer.write("%s failed, error: %d\n" % (final_cmd, retcode)) 204 | 205 | log_writer.write("%s\n" % datetime.datetime.now()) 206 | log_writer.write("finished\n") 207 | log_writer.close() 208 | print("finished: %s" % line) 209 | 210 | # run "stdin", so far it's identical to run_file 211 | def run_stdin(cmd, utility_name, log_path, all_options_from_pool, testcase_list): 212 | 213 | # if the log exists and have been finished, go to test the next utility 214 | if os.path.exists(log_path) and os.stat(log_path).st_size != 0: 215 | with open(log_path, "r") as f: 216 | if f.readlines()[-1] == "finished\n": 217 | return 218 | 219 | # otherwise, create the log or overwrite it 220 | log_writer = open(log_path, "w") 221 | log_writer.write("start: %s\n" % line) 222 | 223 | for testcase in testcase_list: 224 | 225 | options_sampled_from_pool = random_subset(all_options_from_pool.split()) 226 | final_cmd = cmd % (options_sampled_from_pool, testcase) 227 | print("running: %s" % final_cmd) 228 | 229 | try: 230 | retcode = subprocess.call(final_cmd, shell=True, stdout=fnull, stderr=subprocess.STDOUT, timeout=timeout) 231 | 232 | except(subprocess.TimeoutExpired): 233 | log_writer.write("%s hung\n" % final_cmd) 234 | 235 | else: 236 | print("retcode is %d" % retcode) 237 | if(utility_name not in ("sh", "csh", "zsh") and retcode == 127): 238 | log_writer.write("%s not found\n" % utility_name) 239 | break 240 | # check return value, record exit code with special meaning 241 | if retcode >= return_value or retcode < 0: 242 | log_writer.write("%s failed, error: %d\n" % (final_cmd, retcode)) 243 | 244 | log_writer.write("%s\n" % datetime.datetime.now()) 245 | log_writer.write("finished\n") 246 | log_writer.close() 247 | print("finished: %s" % line) 248 | 249 | # run "cp", need to copy test case firstly 250 | def run_cp(cmd, utility_name, new_file_name, log_path, all_options_from_pool, testcase_list): 251 | 252 | # if the log exists and have been finished, go to test the next utility 253 | if os.path.exists(log_path) and os.stat(log_path).st_size != 0: 254 | with open(log_path, "r") as f: 255 | if f.readlines()[-1] == "finished\n": 256 | return 257 | 258 | # otherwise, create the log or overwrite it 259 | log_writer = open(log_path, "w") 260 | log_writer.write("start: %s\n" % line) 261 | 262 | for testcase in testcase_list: 263 | 264 | options_sampled_from_pool = random_subset(all_options_from_pool.split()) 265 | final_cmd = cmd % options_sampled_from_pool 266 | 267 | # copy test case to a new temporary file with a specified name 268 | subprocess.call(["cp %s %s" % (testcase, new_file_name)], shell=True) 269 | 270 | print("running: %s" % final_cmd) 271 | 272 | try: 273 | retcode = subprocess.call(final_cmd, shell=True, stdout=fnull, stderr=subprocess.STDOUT, timeout=timeout) 274 | 275 | except(subprocess.TimeoutExpired): 276 | log_writer.write("%s hung, testcase is %s\n" % (final_cmd, testcase)) 277 | 278 | else: 279 | print("retcode is %d" % retcode) 280 | if(retcode == 127): 281 | log_writer.write("%s not found\n" % utility_name) 282 | break 283 | # check return value, record exit code with special meaning 284 | if retcode >= return_value or retcode < 0: 285 | log_writer.write("%s failed, error: %d\n" % (final_cmd, retcode)) 286 | 287 | finally: 288 | subprocess.call("rm %s" % new_file_name, shell=True) 289 | 290 | log_writer.write("%s\n" % datetime.datetime.now()) 291 | log_writer.write("finished\n") 292 | log_writer.close() 293 | print("finished: %s" % line) 294 | 295 | 296 | # run "two_files", so far it's identical to run_file 297 | def run_two_files(cmd, utility_name, log_path, all_options_from_pool, testcase_list): 298 | 299 | # if the log exists and have been finished, go to test the next utility 300 | if os.path.exists(log_path) and os.stat(log_path).st_size != 0: 301 | with open(log_path, "r") as f: 302 | if f.readlines()[-1] == "finished\n": 303 | return 304 | 305 | # otherwise, create the log or overwrite it 306 | log_writer = open(log_path, "w") 307 | log_writer.write("start: %s\n" % line) 308 | 309 | for testcase in testcase_list: 310 | 311 | options_sampled_from_pool = random_subset(all_options_from_pool.split()) 312 | 313 | # randomly select two testcases each time 314 | testcase1 = random.choice(testcase_list) 315 | testcase2 = random.choice(testcase_list) 316 | final_cmd = cmd % (options_sampled_from_pool, testcase1, testcase2) 317 | print("running: %s" % final_cmd) 318 | 319 | try: 320 | retcode = subprocess.call(final_cmd, shell=True, stdout=fnull, stderr=subprocess.STDOUT, timeout=timeout) 321 | 322 | except(subprocess.TimeoutExpired): 323 | log_writer.write("%s hung\n" % final_cmd) 324 | 325 | else: 326 | print("retcode is %d" % retcode) 327 | if(retcode == 127): 328 | log_writer.write("%s not found\n" % utility_name) 329 | break 330 | # check return value, record exit code with special meaning 331 | if retcode >= return_value or retcode < 0: 332 | log_writer.write("%s failed, error: %d\n" % (final_cmd, retcode)) 333 | 334 | log_writer.write("%s\n" % datetime.datetime.now()) 335 | log_writer.write("finished\n") 336 | log_writer.close() 337 | print("finished: %s" % line) 338 | 339 | 340 | # run "pty" 341 | def run_pty(cmd, utility_name, log_path, all_options_from_pool, testcase_list): 342 | 343 | # if the log exists and have been finished, go to test the next utility 344 | if os.path.exists(log_path) and os.stat(log_path).st_size != 0: 345 | with open(log_path, "r") as f: 346 | if f.readlines()[-1] == "finished\n": 347 | return 348 | 349 | # otherwise, create the log or overwrite it 350 | log_writer = open(log_path, "w") 351 | log_writer.write("start: %s\n" % line) 352 | 353 | for testcase in testcase_list: 354 | 355 | options_sampled_from_pool = random_subset(all_options_from_pool.split()) 356 | 357 | # copy test case to tmp and append the designed end file 358 | subprocess.call("cat %s ./end/end_%s > tmp" % (testcase, utility_name), shell=True, stdout=fnull, stderr=subprocess.STDOUT) 359 | 360 | fr = open("tmp", "rb") 361 | s = fr.read() 362 | fr.close() 363 | # remove all ^z in tmp 364 | s = s.replace(b"\x1a", b"") 365 | # remove all ^c in tmp 366 | s = s.replace(b"\x03", b"") 367 | # remove all ^\ in tmp 368 | s = s.replace(b"\x1c", b"") 369 | # remove all \x9a in tmp 370 | s = s.replace(b"\x9a", b"") 371 | # remove all \xc0 in tmp 372 | s = s.replace(b"\xc0", b"") 373 | 374 | # Z or z will suspend telnet 375 | if(utility_name == "telnet"): 376 | s = s.replace(b"Z", b"") 377 | s = s.replace(b"z", b"") 378 | # command F will make less show the file updates in real time, it requires an interrupt to quit. 379 | if(utility_name == "less"): 380 | s = s.replace(b"F", b"") 381 | fw = open("tmp", "wb") 382 | fw.write(s) 383 | fw.close() 384 | 385 | # htop and top need to be fed input slowly, otherwise it can't quit 386 | if(utility_name == "htop"): 387 | final_cmd = cmd % (0.01, options_sampled_from_pool) 388 | elif(utility_name == "top"): 389 | final_cmd = cmd % (0.01, options_sampled_from_pool) 390 | else: 391 | final_cmd = cmd % (0.001, options_sampled_from_pool) 392 | 393 | # more needs two files, one file provides random control sequence, another file is the file to read 394 | if(utility_name == "more" or utility_name == "less"): 395 | subprocess.call(["cp %s /tmp/tmp" % first_file_for_more], shell=True) 396 | 397 | 398 | # print final_cmd to stdin 399 | print("running: %s, test case: %s" % (final_cmd, testcase)) 400 | 401 | # debug 402 | f = open("log", "w") 403 | f.write("running: %s, current test case: %s" % (final_cmd, testcase)) 404 | f.close() 405 | # debug 406 | 407 | try: 408 | retcode = subprocess.call(final_cmd, shell=True, stdout=fnull, stderr=subprocess.STDOUT, timeout=timeout) 409 | 410 | except(subprocess.TimeoutExpired): 411 | log_writer.write("%s hung, testcase is %s\n" % (final_cmd, testcase)) 412 | 413 | else: 414 | print("retcode is %d" % retcode) 415 | if(retcode == 127): 416 | log_writer.write("%s not found\n" % utility_name) 417 | break 418 | 419 | # killed by built-in timer of ptyjig because of timeout 420 | if(retcode == 137 or retcode == -9): 421 | log_writer.write("%s hung, testcase is %s\n" % (final_cmd, testcase)) 422 | # check return value, record exit code with special meaning 423 | elif retcode >= return_value or retcode < 0: 424 | log_writer.write("%s failed, testcase is %s, error: %d\n" % (final_cmd, testcase, retcode)) 425 | 426 | finally: 427 | subprocess.call("rm tmp", shell=True) 428 | 429 | log_writer.write("%s\n" % datetime.datetime.now()) 430 | log_writer.write("finished\n") 431 | log_writer.close() 432 | print("finished: %s" % line) 433 | 434 | 435 | 436 | 437 | # the script start here 438 | if __name__ == "__main__": 439 | 440 | # options 441 | 442 | # where are the test cases 443 | test_dir = "../../testcases" 444 | 445 | # the result will be saved in output_dir, each cmd corresponds to a result file 446 | result_dir = "./result" 447 | 448 | # the script will test all the files starting with a specified prefix. Prefix is empty string by default, 449 | # which means all the files in test_dir will be tested. 450 | prefix = "" 451 | 452 | # if the cmd does not finish in timeout(300 by default) seconds, the test result will be considered as a hang 453 | timeout = 300 454 | 455 | # too few arguments 456 | if(len(sys.argv) < 2): 457 | print("too few arguments") 458 | print(usage) 459 | sys.exit(1) 460 | # -h or --help 461 | elif(len(sys.argv) == 2 and (sys.argv[1] == "-h" or sys.argv[1] == "--help")): 462 | print(usage) 463 | sys.exit(1) 464 | 465 | # get configuration_file and check if it exists 466 | configuration_file = sys.argv[1] 467 | if(not os.path.isfile(configuration_file)): 468 | print("configuration_file does not exist") 469 | print(usage) 470 | sys.exit(1) 471 | 472 | try: 473 | opts, args = getopt.getopt(sys.argv[2:],"i:o:p:t:",["ifile=", "ofile=", "prefix=", "timeout="]) 474 | except getopt.GetoptError as err: 475 | print("error on arguments") 476 | print(usage) 477 | sys.exit(1) 478 | 479 | for opt, arg in opts: 480 | if(opt in ("-i", "--ifile")): 481 | test_dir = arg 482 | elif(opt in ("-o", "--ofile")): 483 | result_dir = arg 484 | elif(opt in ("-p", "--prefix")): 485 | prefix = arg 486 | elif(opt in ("-t", "--timeout")): 487 | timeout = int(arg) 488 | 489 | if not os.path.isdir(test_dir): 490 | print("%s is not a directory" % test_dir) 491 | print(usage) 492 | sys.exit(1) 493 | 494 | if result_dir == "": 495 | print("please provide valid directory for result") 496 | print(usage) 497 | sys.exit(1) 498 | 499 | 500 | # print out the parameters 501 | print("Input directory is %s" % test_dir) 502 | print("Output directory is %s" % result_dir) 503 | print("Configuration file is %s" % configuration_file) 504 | print("Prefix is %s" % "None" if(prefix == "") else prefix) 505 | print("Timeout is %d" % timeout) 506 | 507 | # make directory to save output 508 | if not os.path.exists(result_dir): 509 | os.makedirs(result_dir) 510 | 511 | # get path of all test cases 512 | testcase_list = [] 513 | testcase_list = os.listdir(test_dir) 514 | testcase_list = [file for file in testcase_list if file.startswith(prefix)] 515 | testcase_list = [os.path.join(test_dir, file) for file in testcase_list] 516 | testcase_list.sort() 517 | 518 | # open run.master and test every cmd 519 | with open(configuration_file, "r") as configuration_reader: 520 | utilities = configuration_reader.readlines() 521 | 522 | # process every line in configuration_file 523 | for line in utilities: 524 | line = line.strip() 525 | # skip empty line 526 | if(line == ""): 527 | continue 528 | 529 | # skip commented line 530 | if(line_commented(line)): 531 | print("%s" % line) 532 | continue 533 | 534 | # check syntax valid 535 | valid = line_syntax_valid(line) 536 | if(not valid): 537 | print("invalid syntax: %s" % line) 538 | with open(os.path.join(result_dir, "err"), "a") as err_writer: 539 | err_writer.write("invalid syntax: %s\n" % line) 540 | continue 541 | 542 | print("start testing: %s" % line) 543 | 544 | # parse the line 545 | cmd, test_type, utility_name, new_file_name, all_options_from_pool, log_name = parse_a_line(line) 546 | 547 | log_path = os.path.join(result_dir, log_name) 548 | 549 | if(test_type == "file"): 550 | run_file(cmd, utility_name, log_path, all_options_from_pool, testcase_list) 551 | elif(test_type == "stdin"): 552 | run_stdin(cmd, utility_name, log_path, all_options_from_pool, testcase_list) 553 | elif(test_type == "cp"): 554 | run_cp(cmd, utility_name, new_file_name, log_path, all_options_from_pool, testcase_list) 555 | elif(test_type == "two_files"): 556 | run_two_files(cmd, utility_name, log_path, all_options_from_pool, testcase_list) 557 | else: 558 | run_pty(cmd, utility_name, log_path, all_options_from_pool, testcase_list) 559 | 560 | fnull.close() 561 | 562 | 563 | 564 | 565 | -------------------------------------------------------------------------------- /src/ptyjig.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Lars Fredriksen, Bryan So, Barton Miller 3 | * 4 | * This program is distributed in the hope that it will be useful, but 5 | * WITHOUT ANY WARRANTY; without even the implied warranty of 6 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 7 | * 8 | */ 9 | 10 | /* 11 | * ptyjig -- Super pipe for piping output to Unix utilities. 12 | * 13 | * ptyjig [option(s)] cmd [args] 14 | * 15 | * Run Unix command "cmd" with arguments "args" in background, piping 16 | * standard input to "cmd" as its input and prints out "cmd"'s output 17 | * to stdout. This program sets up pseudo-terminal pairs, so that 18 | * it can be used to pipe input to programs that read directly from 19 | * tty. 20 | * 21 | * -e suppresses sending of EOF character after stdin exhausted 22 | * -s suppresses interrupts. 23 | * -x suppresses the standard output. 24 | * -i specifies a file to which the standard input is saved. 25 | * -o specifies a file to which the standard output is saved. 26 | * -d specifies a keystroke delay in seconds (floating point accepted.) 27 | * -t specifies a timeout interval. The program will exit if the 28 | * standard input is exhausted and "cmd" does not send output 29 | * for "ttt" seconds(takes only integers) 30 | * -w specifies another delay parameter. The program starts to send 31 | * input to "cmd" after "www" seconds. 32 | * 33 | * Defaults: 34 | * -i /dev/nul -o /dev/nul -d 0 -t 2 35 | * 36 | * Examples: 37 | * 38 | * pty -o out -d 0.2 -t 10 vi text1 < text2 39 | * 40 | * Starts "vi text1" in background, typing the characters in 41 | * "text2" into it with a delay of 0.2 sec between each char- 42 | * acter, and save the output by "vi" to "out". The program 43 | * ends when "vi" stops outputting for 10 seconds. 44 | * 45 | * pty -i in -o out csh 46 | * 47 | * Behaves like "script out" except the keystrokes typed by 48 | * a user are also saved into "in". 49 | * 50 | * Authors: 51 | * 52 | * Bryan So, Lars Fredriksen, Barton P. Miller 53 | * 54 | * Updated by: 55 | * 56 | * Mengxiao Zhang, Emma He (April 2020) 57 | * 58 | */ 59 | 60 | // For more debug info, define DEBUG, or define DEBUG_off to suppress it. 61 | #define DEBUG_off 62 | 63 | #include 64 | #include 65 | #include 66 | #include 67 | #include 68 | #include 69 | #include 70 | #include 71 | #include 72 | #include 73 | #include 74 | #include 75 | #include 76 | #include 77 | #include 78 | #include 79 | 80 | int posix_openpt(int flags); 81 | int grantpt(int fd); 82 | int unlockpt(int fd); 83 | char *ptsname(int fd); 84 | int ptsname_r(int fd, char *buf, size_t buflen); 85 | 86 | 87 | /* GLOBAL VARIABLES */ 88 | #define CHILD 0 89 | #define TRUE 1 90 | #define FALSE 0 91 | 92 | char flage = TRUE; 93 | int flags = FALSE; 94 | int flagx = FALSE; 95 | int flagi = FALSE; 96 | int flago = FALSE; 97 | // Timeout interval in seconds 98 | unsigned flagt = 2; 99 | // Starting wait in useconds 100 | unsigned flagw = FALSE; 101 | // Delay between keystrokes in useconds 102 | unsigned flagd = FALSE; 103 | 104 | char* namei; 105 | char* nameo; 106 | FILE* filei; 107 | FILE* fileo; 108 | 109 | // pids for the reader, writer and exec 110 | int readerPID = -1; 111 | int writerPID = -1; 112 | int execPID = -1; 113 | 114 | // tty and pty file descriptors 115 | int tty = -1; 116 | int pty = -1; 117 | 118 | char ttyNameUsed[40]; 119 | char* progname; 120 | 121 | // return value of functions 122 | int ret; 123 | 124 | // for more verbose output at the end of execution 125 | // makes reason for dying more obvious 126 | struct mesg { 127 | char *iname; 128 | char *pname; 129 | } mesg[] = { 130 | {0, 0}, 131 | {"HUP", "Hangup"}, 132 | {"INT", "Interrupt"}, 133 | {"QUIT", "Quit"}, 134 | {"ILL", "Illegal instruction"}, 135 | {"TRAP", "Trace/BPT trap"}, 136 | {"IOT", "IOT trap"}, 137 | {"EMT", "EMT trap"}, 138 | {"FPE", "Floating exception"}, 139 | {"KILL", "Killed"}, 140 | {"BUS", "Bus error"}, 141 | {"SEGV", "Segmentation fault"}, 142 | {"SYS", "Bad system call"}, 143 | {"PIPE", "Broken pipe"}, 144 | {"ALRM", "Alarm clock"}, 145 | {"TERM", "Terminated"}, 146 | {"URG", "Urgent I/O condition"}, 147 | {"STOP", "Stopped (signal)"}, 148 | {"TSTP", "Stopped"}, 149 | {"CONT", "Continued"}, 150 | {"CHLD", "Child exited"}, 151 | {"TTIN", "Stopped (tty input)"}, 152 | {"TTOU", "Stopped (tty output)"}, 153 | {"IO", "I/O possible"}, 154 | {"XCPU", "Cputime limit exceeded"}, 155 | {"XFSZ", "Filesize limit exceeded"}, 156 | {"VTALRM","Virtual timer expired"}, 157 | {"PROF", "Profiling timer expired"}, 158 | {"WINCH","Window size changed"}, 159 | {0, "Signal 29"}, 160 | {"USR1", "User defined signal 1"}, 161 | {"USR2", "User defined signal 2"}, 162 | {0, "Signal 32"} 163 | }; 164 | 165 | 166 | // A flag indicating status of the writer 167 | int writing = TRUE; 168 | 169 | // A flag indicating status of the executing program 170 | int executing = TRUE; 171 | 172 | // Unfix the above and exit when we are done. 173 | void done() { 174 | #ifdef DEBUG 175 | printf("ptyjig: in done()\n"); 176 | #endif 177 | 178 | // Close output files if opened 179 | if(flagi) { 180 | fclose(filei); 181 | flagi = FALSE; 182 | } 183 | 184 | if(flago) { 185 | fclose(fileo); 186 | flago = FALSE; 187 | } 188 | 189 | } 190 | 191 | // Signal handler for SIGCHLD 192 | void sigchld(int sig) { 193 | int pid; 194 | //union wait status; 195 | int status; 196 | 197 | #ifdef DEBUG 198 | printf("ptyjig: got signal SIGCHLD\n"); 199 | #endif 200 | 201 | // Guarantee to return since a child is dead 202 | pid = wait3((int*)&status, WUNTRACED, 0); 203 | #ifdef DEBUG 204 | printf("sigchld wait3: pid = %d\n",pid); 205 | #endif 206 | 207 | if(pid) { 208 | #ifdef DEBUG 209 | printf("ptyjig: pid = %d\n",pid); 210 | printf("ptyjig: status = %d %d %d %d %d\n", 211 | WTERMSIG(status),WCOREDUMP(status),WEXITSTATUS(status), 212 | WIFSTOPPED(status),WSTOPSIG(status)); 213 | 214 | #endif 215 | 216 | if(WSTOPSIG(status) == SIGTSTP) { 217 | kill(pid, SIGCONT); 218 | } 219 | else { 220 | signal(SIGINT, SIG_DFL); 221 | signal(SIGQUIT, SIG_DFL); 222 | signal(SIGTERM, SIG_DFL); 223 | signal(SIGWINCH, SIG_DFL); 224 | signal(SIGCHLD, SIG_IGN); 225 | 226 | done(); 227 | 228 | if(pid != execPID) { 229 | #ifdef DEBUG 230 | printf("ptyjig: somebody killed my child\n"); 231 | printf("ptyjig: killing execPID = %d\n", execPID); 232 | #endif 233 | // kill the exec too 234 | kill(-execPID, SIGKILL); 235 | // use the same method to suicide 236 | kill(readerPID, WTERMSIG(status)); 237 | } 238 | 239 | // Just to make sure it is killed 240 | kill(-execPID, SIGKILL); 241 | 242 | if(pid != writerPID && writerPID != -1) { 243 | #ifdef DEBUG 244 | printf("ptyjig: killing writerPID = %d\n", writerPID); 245 | #endif 246 | kill(writerPID, SIGKILL); 247 | } 248 | 249 | if(WTERMSIG(status)) { 250 | printf("ptyjig: %s: %s%s\n",progname, 251 | mesg[WTERMSIG(status)].pname, 252 | WCOREDUMP(status) ? " (core dumped)" : ""); 253 | } 254 | 255 | // If process terminates normally, return its retcode 256 | // If abnormally, return termsig. This is not exactly 257 | // the same as csh, since the csh method is not too obvious 258 | 259 | exit(WTERMSIG(status) ? WTERMSIG(status) : WEXITSTATUS(status)); 260 | } 261 | 262 | exit(0); 263 | } 264 | } 265 | 266 | // Clean up processes 267 | void clean() { 268 | #ifdef DEBUG 269 | printf("ptyjig: in clean()\n"); 270 | #endif 271 | 272 | // Not necessary for sigchld to take over 273 | signal(SIGCHLD, SIG_IGN); 274 | 275 | // must close files, and kill all running processes 276 | if(execPID != -1) { 277 | #ifdef DEBUG 278 | printf("ptyjig: killing execPID = %d\n", execPID); 279 | #endif 280 | kill(-execPID, SIGKILL); 281 | } 282 | 283 | if(writerPID != -1) { 284 | #ifdef DEBUG 285 | printf("ptyjig: killing writerPID = %d\n", writerPID); 286 | #endif 287 | kill(writerPID, SIGKILL); 288 | } 289 | 290 | done(); 291 | } 292 | 293 | // Handle window size change SIGWINCH 294 | void sigwinch(int sig) { 295 | struct winsize ws; 296 | 297 | ret = ioctl(0, TIOCGWINSZ, &ws); 298 | if (ret < 0) { 299 | fprintf(stderr, "Error %d on ioctl(0, TIOCGWINSZ, &ws)\n", errno); 300 | exit(1); 301 | } 302 | ret = ioctl(pty, TIOCSWINSZ, &ws); 303 | if (ret < 0) { 304 | fprintf(stderr, "Error %d on ioctl(pty, TIOCGWINSZ, &ws)\n", errno); 305 | exit(1); 306 | } 307 | 308 | kill(-execPID, SIGWINCH); 309 | } 310 | 311 | // Handle user interrupt SIGINT 312 | void clean_int(int sig) { 313 | #ifdef DEBUG 314 | printf("ptyjig: got signal SIGINT\n"); 315 | #endif 316 | 317 | signal(SIGINT, SIG_DFL); 318 | clean(); 319 | 320 | kill(readerPID, SIGINT); 321 | } 322 | 323 | // Handle quit signal SIGQUIT 324 | void clean_quit(int sig) { 325 | #ifdef DEBUG 326 | printf("ptyjig: got signal SIGQUIT\n"); 327 | #endif 328 | 329 | clean(); 330 | signal(SIGQUIT, SIG_DFL); 331 | kill(readerPID, SIGQUIT); 332 | } 333 | 334 | // Handle user terminate signal SIGTERM 335 | void clean_term(int sig) { 336 | #ifdef DEBUG 337 | printf("ptyjig: got signal SIGTERM\n"); 338 | #endif 339 | 340 | clean(); 341 | signal(SIGTERM, SIG_DFL); 342 | 343 | kill(readerPID, SIGTERM); 344 | } 345 | 346 | // Opens a master pseudo-tty device 347 | void setup_pty() { 348 | printf("enter setup_pty\n"); 349 | int rc; 350 | 351 | pty = posix_openpt(O_RDWR); 352 | if (pty < 0) { 353 | fprintf(stderr, "Error %d on posix_openpt()\n", errno); 354 | exit(1); 355 | } 356 | 357 | rc = grantpt(pty); 358 | if (rc != 0) { 359 | fprintf(stderr, "Error %d on grantpt()\n", errno); 360 | exit(1); 361 | } 362 | 363 | rc = unlockpt(pty); 364 | if (rc != 0) { 365 | fprintf(stderr, "Error %d on unlockpt()\n", errno); 366 | exit(1); 367 | } 368 | } 369 | 370 | // Opens the slave device. 371 | void setup_tty() { 372 | char* name_of_slave = ptsname(pty); 373 | if(name_of_slave == NULL) { 374 | perror("fail to open slave device\n"); 375 | exit(1); 376 | } 377 | tty = open(name_of_slave, O_RDWR); 378 | if(tty < 0) { 379 | perror(ttyNameUsed); 380 | exit(1); 381 | } 382 | } 383 | 384 | // Sets boolean to false to stop CMD's execution 385 | void execute_done(int sig) { 386 | executing = FALSE; 387 | } 388 | 389 | // Fork off a copy and execute "arg". Before executing, assign "tty" to 390 | // stdin, stdout and stderr, so that the output of the child program can be 391 | // recorded by the other end of "tty". 392 | void execute(char** cmd) { 393 | int fstdin, fstdout, fstderr; 394 | 395 | signal(SIGUSR1, execute_done); 396 | 397 | if((execPID = fork()) == -1) { 398 | fprintf(stderr, "Error %d on execPID = fork()\n", errno); 399 | exit(1); 400 | } 401 | 402 | if(execPID == CHILD) { 403 | // save copies in case exec fails 404 | fstdin = dup(0); 405 | if(fstdin < 0) { 406 | fprintf(stderr, "Error %d on fstdin = dup(0)\n", errno); 407 | exit(1); 408 | } 409 | fstdout = dup(1); 410 | if(fstdout < 0) { 411 | fprintf(stderr, "Error %d on fstdout = dup(1)\n", errno); 412 | exit(1); 413 | } 414 | fstderr = dup(2); 415 | if(fstderr < 0) { 416 | fprintf(stderr, "Error %d on fstderr = dup(2)\n", errno); 417 | exit(1); 418 | } 419 | 420 | ret = setsid(); 421 | if(ret < 0) { 422 | fprintf(stderr, "Error %d on ret = setsid()\n", errno); 423 | exit(1); 424 | } 425 | 426 | setup_tty(); 427 | // copy tty to stdin 428 | ret = dup2(tty, 0); 429 | if(ret < 0) { 430 | fprintf(stderr, "Error %d on ret = dup2(tty, 0)\n", errno); 431 | exit(1); 432 | } 433 | // copy tty to stdout 434 | ret = dup2(tty, 1); 435 | if(ret < 0) { 436 | fprintf(stderr, "Error %d on ret = dup2(tty, 1)\n", errno); 437 | exit(1); 438 | } 439 | // copy tty to stderr 440 | ret = dup2(tty, 2); 441 | if(ret < 0) { 442 | fprintf(stderr, "Error %d on ret = dup2(tty, 2)\n", errno); 443 | exit(1); 444 | } 445 | 446 | if(tty > 2) { 447 | close(tty); 448 | } 449 | 450 | // suppress signals if -s present 451 | if(flags) { 452 | signal(SIGINT, SIG_IGN); 453 | signal(SIGQUIT, SIG_IGN); 454 | signal(SIGTSTP, SIG_IGN); 455 | } 456 | 457 | // Better be setup to handle a SIGUSR1 458 | kill(getppid(), SIGUSR1); 459 | 460 | execvp(cmd[0], cmd); 461 | 462 | // IF IT EVER GETS HERE, error when executing "cmd" 463 | ret = dup2(fstdin, 0); 464 | if(ret < 0) { 465 | fprintf(stderr, "Error %d on dup2(fstdin, 0)\n", errno); 466 | exit(1); 467 | } 468 | ret = dup2(fstdout, 1); 469 | if(ret < 0) { 470 | fprintf(stderr, "Error %d on dup2(fstdout, 1)\n", errno); 471 | exit(1); 472 | } 473 | ret = dup2(fstderr, 2); 474 | if(ret < 0) { 475 | fprintf(stderr, "Error %d on dup2(fstderr, 2)\n", errno); 476 | exit(1); 477 | } 478 | 479 | perror(cmd[0]); 480 | 481 | exit(1); 482 | } 483 | #ifdef DEBUG 484 | printf("execPID: pid = %d\n",execPID); 485 | #endif 486 | 487 | // let child run until it gives signal 488 | while(executing); 489 | 490 | usleep(flagw); 491 | 492 | #ifdef DEBUG 493 | printf("ptyjig: execPID = %d\n", execPID); 494 | #endif 495 | } 496 | 497 | // Sleeps for 1 second, then KILLs PID execPID using SIGKILL 498 | void reader_done(int sig) { 499 | // Let execPID die naturally 500 | sleep(1); 501 | 502 | #ifdef DEBUG 503 | printf("ptyjig: killing execPID = %d\n", execPID); 504 | #endif 505 | 506 | // If it doesn't die on its own, kill it 507 | kill(-execPID, SIGKILL); 508 | } 509 | 510 | // Sets boolean to false to stop writer 511 | void writer_done(int sig) { 512 | writing = FALSE; 513 | alarm(flagt); 514 | } 515 | 516 | // Read from stdin and send everything character read to "pty". Record the 517 | // keystrokes in "filei" if -i flag is on. 518 | void writer() { 519 | char c; 520 | 521 | // Read from keyboard continuously and send it to "pty" 522 | while(read(0, &c, 1) == 1) { 523 | if(write(pty, &c, 1) != 1) { 524 | break; 525 | } 526 | 527 | if (flagi) { 528 | // Do not send '\r', send '\n' instead 529 | if(c == '\r') { 530 | c = '\n'; 531 | } 532 | 533 | if(write(fileno(filei), &c, 1) != 1) { 534 | perror(namei); 535 | break; 536 | } 537 | } 538 | 539 | // Delay writing to "pty" if flagged 540 | if(flagd) { 541 | usleep(flagd); 542 | } 543 | } 544 | 545 | if(flage) { 546 | (void)write(pty, &flage, 1); 547 | } 548 | 549 | #ifdef DEBUG 550 | printf("ptyjig: writer finished\n"); 551 | #endif 552 | 553 | // tell reader to quit if no more char from exec 554 | kill(readerPID, SIGUSR1); 555 | 556 | // XXX: INFINITE LOOP: Wait until parent kills me 557 | while(1); 558 | } 559 | 560 | // Read from "pty" and send it to stdout 561 | void reader() { 562 | char c[BUFSIZ]; 563 | int i; 564 | 565 | #ifdef DEBUG 566 | printf("reader(): pid = %d\n",getpid()); 567 | #endif 568 | 569 | // Continuously read from "pty" until exhausted. Write every character 570 | // to stdout if -x flag is not present, and to "fileo" if -o flag is on. 571 | signal(SIGALRM, reader_done); 572 | signal(SIGUSR1, writer_done); 573 | 574 | while ((i = read(pty, c, sizeof(c))) > 0) { 575 | if(!flagx) { 576 | if(write(1, c, i) != i) { 577 | exit(1); 578 | } 579 | } 580 | 581 | if(flago) { 582 | if(write(fileno(fileo), c, i) != i) { 583 | perror(nameo); 584 | exit(1); 585 | } 586 | } 587 | 588 | // The following "if" essentially means when "writer" is done, and 589 | // there is no more keystroke coming from "pty" wait for "flagt" 590 | // seconds and quit. If during this wait, a character comes from 591 | // "pty", then the wait is set back. 592 | if(!writing) { 593 | alarm(flagt); 594 | } 595 | } 596 | 597 | #ifdef DEBUG 598 | printf("ptyjig: reader finished\n"); 599 | #endif 600 | 601 | reader_done(0); 602 | } 603 | 604 | void doreader() { 605 | reader(); 606 | } 607 | 608 | // Fork writer 609 | void dowriter() { 610 | if((writerPID = fork()) == -1) { 611 | fprintf(stderr, "Error %d on writerPID = fork()\n", errno); 612 | exit(1); 613 | } 614 | 615 | if(writerPID == CHILD) { 616 | writerPID = getpid(); 617 | writer(); 618 | } 619 | #ifdef DEBUG 620 | printf("dowriter(): pid = %d\n",writerPID); 621 | #endif 622 | } 623 | 624 | // Display help screen 625 | void usage() { 626 | printf(" ptyjig -- Super pipe for piping output to Unix utilities.\n\n"); 627 | printf(" Usage:\n"); 628 | printf(" ptyjig [options] cmd \n\n"); 629 | printf(" Description:\n"); 630 | printf(" Run command \"cmd\" with arguments \"args\" in background, piping\n"); 631 | printf(" stdin to \"cmd\" as its input and prints out \"cmd\"'s output\n"); 632 | printf(" to stdout. This program sets up pseudo-terminal pairs, so that\n"); 633 | printf(" it can be used to pipe input to programs that read directly from\n"); 634 | printf(" a tty interface.\n\n"); 635 | printf(" Options:\n"); 636 | printf(" -e suppresses sending EOF char after stdin exhausted\n"); 637 | printf(" -s suppresses interrupts\n"); 638 | printf(" -x suppresses the standard output\n"); 639 | printf(" -i FILEIN standard input saved to file FILEIN\n"); 640 | printf(" -o FILEOUT standard output saved to file FILEOUT\n"); 641 | printf(" -d DELAY use a keystroke delay of DELAY seconds (accepts floating pt)\n"); 642 | printf(" -t TIMEOUT kill \"cmd\" if stdin exhausted and \"cmd\" doesn't send\n"); 643 | printf(" output for TIMEOUT seconds (takes only integer)\n"); 644 | printf(" -w WAIT wait WAIT seconds before streaming input to \"cmd\"\n\n"); 645 | printf(" Defaults:\n"); 646 | printf(" -i /dev/null -o /dev/null -d 0 -t 2\n\n"); 647 | printf(" Examples:\n\n"); 648 | printf(" ptyjig -o out -d 0.05 -t 10 vi text1 < text2\n\n"); 649 | printf(" Starts \"vi text1\" in background, typing the characters\n"); 650 | printf(" in \"text2\" into it with a delay of 0.05 sec between each\n"); 651 | printf(" character, and save the output of \"vi\" to \"out\".\n"); 652 | printf(" Program ends when \"vi\" stops outputting for 10 seconds.\n\n"); 653 | printf(" ptyjig -i in -o out csh\n\n"); 654 | printf(" Behaves like \"script out\" except the keystrokes typed by\n"); 655 | printf(" a user are also saved into \"in\".\n"); 656 | printf(" Authors: \n"); 657 | printf(" Lars Fredriksen, Bryan So, Barton Miller\n\n"); 658 | printf(" Updated by: \n"); 659 | printf(" Gregory Smethells, Brian Bowers, Karlen Lie\n"); 660 | 661 | exit(1); 662 | } 663 | 664 | // Parse the args, start reader and writer, fork the process to be "piped" to 665 | int main(int argc, char** argv) { 666 | int num; 667 | int cont; 668 | float f; 669 | unsigned int t; 670 | extern int optind; 671 | extern char* optarg; 672 | 673 | struct termios termIOSettings; 674 | 675 | #ifdef DEBUG 676 | printf("main: pid = %d\n",getpid()); 677 | #endif 678 | 679 | 680 | // Parse the arguments to ptyjig. A ":" after a letter means 681 | // that flag takes an argument value and isn't just a boolean 682 | while((argc > 1) && (argv[1][0] == '-')) { 683 | num = 1; 684 | cont = TRUE; 685 | 686 | while(cont) { 687 | switch(argv[1][num]) { 688 | case 'e': 689 | flage = FALSE; 690 | break; 691 | 692 | case 's': 693 | flags = TRUE; 694 | break; 695 | 696 | case 'x': 697 | flagx = TRUE; 698 | break; 699 | 700 | case 'i': 701 | flagi = TRUE; 702 | namei = argv[2]; 703 | argc--; 704 | argv++; 705 | cont = FALSE; 706 | break; 707 | 708 | case 'o': 709 | flago = TRUE; 710 | nameo = argv[2]; 711 | argc--; 712 | argv++; 713 | cont = FALSE; 714 | break; 715 | 716 | case 'd': 717 | if(sscanf(argv[2], "%f", &f) < 1) { 718 | usage(); 719 | } 720 | 721 | argc--; 722 | argv++; 723 | cont = FALSE; 724 | 725 | // Convert to microseconds 726 | flagd = (unsigned)(f * 1000000.0); 727 | break; 728 | 729 | case 't': 730 | if(sscanf(argv[2], "%u", &t) < 1) { 731 | usage(); 732 | } 733 | 734 | argc--; 735 | argv++; 736 | cont = FALSE; 737 | 738 | // Convert to microseconds 739 | flagt = t; 740 | break; 741 | 742 | case 'w': 743 | if(sscanf(argv[2], "%f", &f) < 1) { 744 | usage(); 745 | } 746 | 747 | argc--; 748 | argv++; 749 | cont = FALSE; 750 | 751 | // Convert to microseconds 752 | flagw = (unsigned)(f * 1000000.0); 753 | break; 754 | 755 | default: 756 | usage(); 757 | } 758 | 759 | num++; 760 | 761 | if(cont && !argv[1][num]) { 762 | cont = FALSE; 763 | } 764 | } 765 | 766 | argc--; 767 | argv++; 768 | } 769 | 770 | 771 | // Now, argv point to a command 772 | if(!argv[1]) { 773 | usage(); 774 | } 775 | 776 | // Possibly open a "save the input file" 777 | if(flagi) { 778 | filei = fopen(namei, "wb"); 779 | 780 | if(filei == NULL) { 781 | perror(namei); 782 | exit(1); 783 | } 784 | } 785 | 786 | // Possible open a "save the output file" 787 | if(flago) { 788 | fileo = fopen(nameo, "wb"); 789 | 790 | if(fileo == NULL) { 791 | perror(nameo); 792 | exit(1); 793 | } 794 | } 795 | 796 | 797 | // open an arbitrary pseudo-terminal pair 798 | setup_pty(); 799 | 800 | // Get Attributes for Master 801 | tcgetattr(pty, &termIOSettings); 802 | 803 | // Make output as RAW as possible 804 | cfmakeraw(&termIOSettings); 805 | 806 | termIOSettings.c_lflag |= ECHO; 807 | 808 | // Set Attributes for Master to RAWed version of "termIOSettings" 809 | (void) tcsetattr(pty, TCSANOW, &termIOSettings); 810 | 811 | // fork and execute test program with arguments 812 | progname = argv[1]; 813 | execute((char **) &argv[1]); 814 | 815 | 816 | 817 | signal(SIGWINCH, sigwinch); 818 | 819 | readerPID = getpid(); 820 | 821 | dowriter(); 822 | 823 | // put here instead of above to avoid invoking clean_XYZ twice 824 | signal(SIGQUIT, clean_quit); 825 | signal(SIGTERM, clean_term); 826 | signal(SIGINT, clean_int); 827 | 828 | doreader(); 829 | 830 | 831 | // process the return value of cmd 832 | int pid, status; 833 | 834 | #ifdef DEBUG 835 | printf("ptyjig: got signal SIGCHLD\n"); 836 | #endif 837 | 838 | // Guarantee to return since a child is dead 839 | pid = wait3((int*)&status, WUNTRACED, 0); 840 | #ifdef DEBUG 841 | printf("sigchld wait3: pid = %d\n",pid); 842 | #endif 843 | 844 | if(pid) { 845 | #ifdef DEBUG 846 | printf("ptyjig: pid = %d\n",pid); 847 | printf("ptyjig: status = %d %d %d %d %d\n", 848 | WTERMSIG(status),WCOREDUMP(status),WEXITSTATUS(status), 849 | WIFSTOPPED(status),WSTOPSIG(status)); 850 | 851 | #endif 852 | int ret = 0; 853 | if(WSTOPSIG(status) == SIGTSTP) { 854 | kill(pid, SIGCONT); 855 | } 856 | else { 857 | signal(SIGINT, SIG_DFL); 858 | signal(SIGQUIT, SIG_DFL); 859 | signal(SIGTERM, SIG_DFL); 860 | signal(SIGWINCH, SIG_DFL); 861 | signal(SIGCHLD, SIG_IGN); 862 | 863 | done(); 864 | 865 | if(pid != execPID) { 866 | #ifdef DEBUG 867 | printf("ptyjig: somebody killed my child\n"); 868 | printf("ptyjig: killing execPID = %d\n", execPID); 869 | #endif 870 | // kill the exec too 871 | kill(-execPID, SIGKILL); 872 | // use the same method to suicide 873 | kill(readerPID, WTERMSIG(status)); 874 | } 875 | 876 | // Just to make sure it is killed 877 | kill(-execPID, SIGKILL); 878 | 879 | if(pid != writerPID && writerPID != -1) { 880 | #ifdef DEBUG 881 | printf("ptyjig: killing writerPID = %d\n", writerPID); 882 | #endif 883 | kill(writerPID, SIGKILL); 884 | } 885 | 886 | if(WTERMSIG(status)) { 887 | printf("ptyjig: %s: %s%s\n",progname, 888 | mesg[WTERMSIG(status)].pname, 889 | WCOREDUMP(status) ? " (core dumped)" : ""); 890 | ret = 128 + WTERMSIG(status); 891 | } 892 | } 893 | return ret; 894 | } 895 | } 896 | 897 | 898 | --------------------------------------------------------------------------------