-
Notifications
You must be signed in to change notification settings - Fork 0
/
pack
executable file
·227 lines (206 loc) · 7.75 KB
/
pack
1
2
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
#!/bin/bash
# encrypts all files in the specified directory 'indir'
# writes keys into file 'keyfile'
# writes encrypted binary files with the same names into tarred 'outfile'
PROGN=${0##*/}
COMPRESSOR=lzma # the default general compression program
NOCOMPRESS=( 'jpg' 'jpeg' 'mp4' 'zip' '7z' 'lz' 'zst' 'gz' 'tgz' 'bz2' )
# function to compress one file
# takes one argument: FILENAME
# writes into global TEMPFILE
# creates and cleans up to two temporary files
function packfile {
local FILENAME="$1"
local EX="${1##*.}" # this file's last extension
local CH='u' # default uncompressed marker
local EX=${EX,,} # convert extension to lower case, to match capitals too
# if EX is on excluded list, return
[ -n "$EXCLUDE" ] && {
for EXT in "$EXCLUDE"; do
[ "$EX" = "$EXT" ] && return
done
}
# if EX is on NOCOMPRESS list, then skip compression
for KEEPEXT in "${NOCOMPRESS[@]}"; do
if [ "$EX" = "$KEEPEXT" ]; then
printf "u$FILENAME,$2," >> "$INDEXFILE" # write u+filename,filesize
cat "$FILENAME" >> "$OUTFILE" # write file contents
return
fi
done
local INFILE="$FILENAME" # the initial name of the file to be compressed
if [ $HEXTEST ] && hexcheck < "$FILENAME" > _"$FILENAME".tmp 2>/dev/null; then
CH='h' # hexadecimal compression enabled and detected
local INFILE=_"$FILENAME".tmp
else if [ $B64TEST ] && base64 -d -w 0 "$FILENAME" > _"$FILENAME".tmp 2>/dev/null; then
CH='b' # base64 compression enabled and detected
local INFILE=_"$FILENAME".tmp
fi
fi
# regardless whether hexcheck or base64 tests above succeeded or not,
# apply general compression test
$COMPRESSOR $FLAGS "$INFILE" > _"$FILENAME".cmp
local CMPS=$( stat -c%s _"$FILENAME".cmp ) # size after compression
local ORGS=$( stat -c%s "$INFILE" ) # size before compression
# use compressed version only if it is actually smaller
[ "$CMPS" -lt "$ORGS" ] && { # successful general compression
local INFILE=_"$FILENAME".cmp # use the final compressed file
if [ "$COMPRESSOR" = 'lzma' ]; then
case "$CH" in
u ) CH='i';;
h ) CH='j';;
b ) CH='k';;
* ) printf "$PROGN packfile: unrecognised ch\n"; exit 2;;
esac
else # compressor program is 'zstd'
case "$CH" in
u ) CH='l';;
h ) CH='m';;
b ) CH='n';;
* ) printf "$PROGN processfile: unrecognised ch\n"; exit 2;;
esac
fi
} # else no general compression gain, keep the original INFILE and codes: u,h,b
printf "$CH$FILENAME,%s," `stat -c%s "$INFILE"` >> "$INDEXFILE"
cat "$INFILE" >> "$OUTFILE" # write possibly compressed file contents
# clean up
[ -e _"$FILENAME".tmp ] && rm _"$FILENAME".tmp;
[ -e _"$FILENAME".cmp ] && rm _"$FILENAME".cmp;
} # end of packfile
# writes to global TEMPFILE
function packdir {
BN=`basename $1`
# do not process when INDIR is on IGNORE list
for D in "$IGNORE"; do
[ "$BN" = "$D" ] && return
done
cd "$1" || { # must be quoted or fails on windozy dirs with spaces!
printf "$PROGN packdir: failed to change to directory $1!\n" >&2
exit 2
}
# if recursing, process the subdirectories first (depth first descent)
[ $RECURSE ] && {
for D in */; do
[[ "$D" == '*/' ]] && break; # no more dirs
D=${D%/} # strip off the trailing slash
# D="${D//[[:blank:]]/}"; # remove windozy spaces
# if [[ "$D" =~ [[:blank:]] ]]; then
printf "$D," >> "$INDEXFILE" # write subdir name
packdir "$D" # recurse
done
}
# mark the end of subdirectories (with ,,) for tree parsing by unpack
printf "," >> $INDEXFILE
# process the files
for FILE in *; do
[[ "$FILE" == '*' ]] && break; # no more
[ -f "$FILE" ] || continue; # skip non files
# [ -n "$WINDOZE" ] && FILE="${FILE//[[:blank:]]/}"; # remove windozy spaces?
local FSIZE=`stat -c%s "$FILE"`
[ -z "$FSIZE" ] && continue; # clean up zero sized files
packfile "$FILE" "$FSIZE" # process this file
done
# mark the end of files and this dir (with ,,) for tree parsing by unpack
printf "," >> $INDEXFILE
cd ..
} # end of packdir
function usage { printf "Usage: $PROGN [-b][-h][-i][-q][-v][-x][-z] indir indexfile outfile keyfile\n" >&2; }
function helpmsg {
usage
printf "\t%s\n" \
'-b | --b64 : test for base64 encoded files' \
'-e | --exclude : (globally) listed file extensions' \
'-h | --help : print this text' \
'-i | --ignore : (globally) listed directories' \
'-q | --quiet : turn off the final summary' \
'-r | --recurse : into subdirectories' \
'-v | --verbose : detailed reporting' \
'-x | --hex : test for hexadecimal files' \
'-z | --zstd : use zstd compression instead of lzma' >&2
exit 1
}
# Main starts here ###########################################################
# get options, including long options, extracted as arg to the last option '-'
while getopts be:hi:qrvxz-: OPT; do # do not forget to list new options here!
if [ "$OPT" = "-" ]; then # long option: reset OPT and OPTARG
OPT="${OPTARG%%=*}" # extract long option name
OPTARG="${OPTARG#$OPT}" # extract long option argument (here empty)
OPTARG="${OPTARG#=}" # remove any assignments `=` from long options
fi
case "$OPT" in
b | b64 ) B64TEST=0;;
e | exclude ) EXCLUDE=$OPTARG;;
h | help ) helpmsg;;
i | ignore ) IGNORE=$OPTARG;;
q | quiet ) QUIET=0;;
r | recurse ) RECURSE=0;;
v | verbose ) VERBOSE=0;;
x | hex ) HEXTEST=0;;
z | zstd ) COMPRESSOR=zstd;; #alternative compressor and its extension
??* ) printf "$PROGN quitting, invalid long option: $OPT\n" >&2; usage; exit 2;;
*) exit 2;; # invalid short option (error will be reported via getopts)
esac
done
shift $((OPTIND-1)) # remove parsed options and args from $@ list
[ $RECURSE ] && {
printf "Recursing "
if [ -n "$IGNORE" ]; then
printf "and ignoring directories: $IGNORE\n"
else
printf "fully\n"
fi
}
[ -n "$EXCLUDE" ] && printf "Excluding extensions: $EXCLUDE\n"
# Globally validate the program arguments
[ "$#" -ne 4 ] && {
printf "$PROGN expected four arguments, "$#" given!\n" >&2
exit 2
}
[ -z "$1" ] && {
printf "$PROGN: the first argument (input directory) is missing!\n" >&2
exit 2
}
[ -d "$1" ] || {
printf "$PROGN: input directory '$1' not found!\n" >&2
exit 2
}
[ -e "$2" ] && {
printf "$PROGN: the second argument '$2' (indexfile) already exists!\n" >&2
exit 2
}
[ -e "$3" ] && {
printf "$PROGN: the third argument '$3' (outfile) already exists!\n" >&2
exit 2
}
[ -e "$4" ] && {
printf "$PROGN: the fourth argument '$4' (keyfile) already exists!\n" >&2
exit 2
}
# Globally validate the compressor. It has been set either to lzma or zstd
[ -z $( which "$COMPRESSOR" ) ] && {
printf "$PROGN quitting, compressor utility $COMPRESSOR not found\n" >&2
exit 2
}
# Global options for the compressor
FLAGS='-c -z -q'
if [ $VERBOSE ]; then
if [ "$COMPRESSOR" = 'lzma' ]; then FLAGS='-c -z -v'; else FLAGS='-c'; fi
fi
# name the verified global parameters
INDEXFILE=`pwd`/_idx.tmp
OUTFILE=`pwd`/_out.tmp
INDIR=${1%/} # strip any trailing /
printf "$PROGN: compressing directory '$INDIR'\n"
printf "$INDIR," > "$INDEXFILE" # record the indir path/name
packdir "$INDIR" # pack the directory into INDEXFILE and OUTFILE
# compress and encrypt indexfile
lzma -c -z -q "$INDEXFILE" | xorfork "$2" > "$4"
# encrypt output file append to keys
xorfork < "$OUTFILE" "$3" >> "$4"
rm "$INDEXFILE" "$OUTFILE" # remove the temporary files
# optional final report
if ! [ $QUIET ]; then
SIZES=( `du -hbs "$4" "$INDIR"` ) # array of size,dir,size,dir
printf "$PROGN: ${SIZES[3]} ${SIZES[2]} (100%%) => ${SIZES[1]} ${SIZES[0]} (%s%%)\n" \
$(( 100*${SIZES[0]}/${SIZES[2]} )) # calculate percentage
fi