github.com/hellobchain/third_party@v0.0.0-20230331131523-deb0478a2e52/prometheus/procfs/ttar (about) 1 #!/usr/bin/env bash 2 3 # Purpose: plain text tar format 4 # Limitations: - only suitable for text files, directories, and symlinks 5 # - stores only filename, content, and mode 6 # - not designed for untrusted input 7 # 8 # Note: must work with bash version 3.2 (macOS) 9 10 # Copyright 2017 Roger Luethi 11 # 12 # Licensed under the Apache License, Version 2.0 (the "License"); 13 # you may not use this file except in compliance with the License. 14 # You may obtain a copy of the License at 15 # 16 # http://www.apache.org/licenses/LICENSE-2.0 17 # 18 # Unless required by applicable law or agreed to in writing, software 19 # distributed under the License is distributed on an "AS IS" BASIS, 20 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 21 # See the License for the specific language governing permissions and 22 # limitations under the License. 23 24 set -o errexit -o nounset 25 26 # Sanitize environment (for instance, standard sorting of glob matches) 27 export LC_ALL=C 28 29 path="" 30 CMD="" 31 ARG_STRING="$*" 32 33 #------------------------------------------------------------------------------ 34 # Not all sed implementations can work on null bytes. In order to make ttar 35 # work out of the box on macOS, use Python as a stream editor. 36 37 USE_PYTHON=0 38 39 PYTHON_CREATE_FILTER=$(cat << 'PCF' 40 #!/usr/bin/env python 41 42 import re 43 import sys 44 45 for line in sys.stdin: 46 line = re.sub(r'EOF', r'\EOF', line) 47 line = re.sub(r'NULLBYTE', r'\NULLBYTE', line) 48 line = re.sub('\x00', r'NULLBYTE', line) 49 sys.stdout.write(line) 50 PCF 51 ) 52 53 PYTHON_EXTRACT_FILTER=$(cat << 'PEF' 54 #!/usr/bin/env python 55 56 import re 57 import sys 58 59 for line in sys.stdin: 60 line = re.sub(r'(?<!\\)NULLBYTE', '\x00', line) 61 line = re.sub(r'\\NULLBYTE', 'NULLBYTE', line) 62 line = re.sub(r'([^\\])EOF', r'\1', line) 63 line = re.sub(r'\\EOF', 'EOF', line) 64 sys.stdout.write(line) 65 PEF 66 ) 67 68 function test_environment { 69 if [[ "$(echo "a" | sed 's/a/\x0/' | wc -c)" -ne 2 ]]; then 70 echo "WARNING sed unable to handle null bytes, using Python (slow)." 71 if ! which python >/dev/null; then 72 echo "ERROR Python not found. Aborting." 73 exit 2 74 fi 75 USE_PYTHON=1 76 fi 77 } 78 79 #------------------------------------------------------------------------------ 80 81 function usage { 82 bname=$(basename "$0") 83 cat << USAGE 84 Usage: $bname [-C <DIR>] -c -f <ARCHIVE> <FILE...> (create archive) 85 $bname -t -f <ARCHIVE> (list archive contents) 86 $bname [-C <DIR>] -x -f <ARCHIVE> (extract archive) 87 88 Options: 89 -C <DIR> (change directory) 90 -v (verbose) 91 92 Example: Change to sysfs directory, create ttar file from fixtures directory 93 $bname -C sysfs -c -f sysfs/fixtures.ttar fixtures/ 94 USAGE 95 exit "$1" 96 } 97 98 function vecho { 99 if [ "${VERBOSE:-}" == "yes" ]; then 100 echo >&7 "$@" 101 fi 102 } 103 104 function set_cmd { 105 if [ -n "$CMD" ]; then 106 echo "ERROR: more than one command given" 107 echo 108 usage 2 109 fi 110 CMD=$1 111 } 112 113 unset VERBOSE 114 115 while getopts :cf:htxvC: opt; do 116 case $opt in 117 c) 118 set_cmd "create" 119 ;; 120 f) 121 ARCHIVE=$OPTARG 122 ;; 123 h) 124 usage 0 125 ;; 126 t) 127 set_cmd "list" 128 ;; 129 x) 130 set_cmd "extract" 131 ;; 132 v) 133 VERBOSE=yes 134 exec 7>&1 135 ;; 136 C) 137 CDIR=$OPTARG 138 ;; 139 *) 140 echo >&2 "ERROR: invalid option -$OPTARG" 141 echo 142 usage 1 143 ;; 144 esac 145 done 146 147 # Remove processed options from arguments 148 shift $(( OPTIND - 1 )); 149 150 if [ "${CMD:-}" == "" ]; then 151 echo >&2 "ERROR: no command given" 152 echo 153 usage 1 154 elif [ "${ARCHIVE:-}" == "" ]; then 155 echo >&2 "ERROR: no archive name given" 156 echo 157 usage 1 158 fi 159 160 function list { 161 local path="" 162 local size=0 163 local line_no=0 164 local ttar_file=$1 165 if [ -n "${2:-}" ]; then 166 echo >&2 "ERROR: too many arguments." 167 echo 168 usage 1 169 fi 170 if [ ! -e "$ttar_file" ]; then 171 echo >&2 "ERROR: file not found ($ttar_file)" 172 echo 173 usage 1 174 fi 175 while read -r line; do 176 line_no=$(( line_no + 1 )) 177 if [ $size -gt 0 ]; then 178 size=$(( size - 1 )) 179 continue 180 fi 181 if [[ $line =~ ^Path:\ (.*)$ ]]; then 182 path=${BASH_REMATCH[1]} 183 elif [[ $line =~ ^Lines:\ (.*)$ ]]; then 184 size=${BASH_REMATCH[1]} 185 echo "$path" 186 elif [[ $line =~ ^Directory:\ (.*)$ ]]; then 187 path=${BASH_REMATCH[1]} 188 echo "$path/" 189 elif [[ $line =~ ^SymlinkTo:\ (.*)$ ]]; then 190 echo "$path -> ${BASH_REMATCH[1]}" 191 fi 192 done < "$ttar_file" 193 } 194 195 function extract { 196 local path="" 197 local size=0 198 local line_no=0 199 local ttar_file=$1 200 if [ -n "${2:-}" ]; then 201 echo >&2 "ERROR: too many arguments." 202 echo 203 usage 1 204 fi 205 if [ ! -e "$ttar_file" ]; then 206 echo >&2 "ERROR: file not found ($ttar_file)" 207 echo 208 usage 1 209 fi 210 while IFS= read -r line; do 211 line_no=$(( line_no + 1 )) 212 local eof_without_newline 213 if [ "$size" -gt 0 ]; then 214 if [[ "$line" =~ [^\\]EOF ]]; then 215 # An EOF not preceeded by a backslash indicates that the line 216 # does not end with a newline 217 eof_without_newline=1 218 else 219 eof_without_newline=0 220 fi 221 # Replace NULLBYTE with null byte if at beginning of line 222 # Replace NULLBYTE with null byte unless preceeded by backslash 223 # Remove one backslash in front of NULLBYTE (if any) 224 # Remove EOF unless preceeded by backslash 225 # Remove one backslash in front of EOF 226 if [ $USE_PYTHON -eq 1 ]; then 227 echo -n "$line" | python -c "$PYTHON_EXTRACT_FILTER" >> "$path" 228 else 229 # The repeated pattern makes up for sed's lack of negative 230 # lookbehind assertions (for consecutive null bytes). 231 echo -n "$line" | \ 232 sed -e 's/^NULLBYTE/\x0/g; 233 s/\([^\\]\)NULLBYTE/\1\x0/g; 234 s/\([^\\]\)NULLBYTE/\1\x0/g; 235 s/\\NULLBYTE/NULLBYTE/g; 236 s/\([^\\]\)EOF/\1/g; 237 s/\\EOF/EOF/g; 238 ' >> "$path" 239 fi 240 if [[ "$eof_without_newline" -eq 0 ]]; then 241 echo >> "$path" 242 fi 243 size=$(( size - 1 )) 244 continue 245 fi 246 if [[ $line =~ ^Path:\ (.*)$ ]]; then 247 path=${BASH_REMATCH[1]} 248 if [ -e "$path" ] || [ -L "$path" ]; then 249 rm "$path" 250 fi 251 elif [[ $line =~ ^Lines:\ (.*)$ ]]; then 252 size=${BASH_REMATCH[1]} 253 # Create file even if it is zero-length. 254 touch "$path" 255 vecho " $path" 256 elif [[ $line =~ ^Mode:\ (.*)$ ]]; then 257 mode=${BASH_REMATCH[1]} 258 chmod "$mode" "$path" 259 vecho "$mode" 260 elif [[ $line =~ ^Directory:\ (.*)$ ]]; then 261 path=${BASH_REMATCH[1]} 262 mkdir -p "$path" 263 vecho " $path/" 264 elif [[ $line =~ ^SymlinkTo:\ (.*)$ ]]; then 265 ln -s "${BASH_REMATCH[1]}" "$path" 266 vecho " $path -> ${BASH_REMATCH[1]}" 267 elif [[ $line =~ ^# ]]; then 268 # Ignore comments between files 269 continue 270 else 271 echo >&2 "ERROR: Unknown keyword on line $line_no: $line" 272 exit 1 273 fi 274 done < "$ttar_file" 275 } 276 277 function div { 278 echo "# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -" \ 279 "- - - - - -" 280 } 281 282 function get_mode { 283 local mfile=$1 284 if [ -z "${STAT_OPTION:-}" ]; then 285 if stat -c '%a' "$mfile" >/dev/null 2>&1; then 286 # GNU stat 287 STAT_OPTION='-c' 288 STAT_FORMAT='%a' 289 else 290 # BSD stat 291 STAT_OPTION='-f' 292 # Octal output, user/group/other (omit file type, sticky bit) 293 STAT_FORMAT='%OLp' 294 fi 295 fi 296 stat "${STAT_OPTION}" "${STAT_FORMAT}" "$mfile" 297 } 298 299 function _create { 300 shopt -s nullglob 301 local mode 302 local eof_without_newline 303 while (( "$#" )); do 304 file=$1 305 if [ -L "$file" ]; then 306 echo "Path: $file" 307 symlinkTo=$(readlink "$file") 308 echo "SymlinkTo: $symlinkTo" 309 vecho " $file -> $symlinkTo" 310 div 311 elif [ -d "$file" ]; then 312 # Strip trailing slash (if there is one) 313 file=${file%/} 314 echo "Directory: $file" 315 mode=$(get_mode "$file") 316 echo "Mode: $mode" 317 vecho "$mode $file/" 318 div 319 # Find all files and dirs, including hidden/dot files 320 for x in "$file/"{*,.[^.]*}; do 321 _create "$x" 322 done 323 elif [ -f "$file" ]; then 324 echo "Path: $file" 325 lines=$(wc -l "$file"|awk '{print $1}') 326 eof_without_newline=0 327 if [[ "$(wc -c "$file"|awk '{print $1}')" -gt 0 ]] && \ 328 [[ "$(tail -c 1 "$file" | wc -l)" -eq 0 ]]; then 329 eof_without_newline=1 330 lines=$((lines+1)) 331 fi 332 echo "Lines: $lines" 333 # Add backslash in front of EOF 334 # Add backslash in front of NULLBYTE 335 # Replace null byte with NULLBYTE 336 if [ $USE_PYTHON -eq 1 ]; then 337 < "$file" python -c "$PYTHON_CREATE_FILTER" 338 else 339 < "$file" \ 340 sed 's/EOF/\\EOF/g; 341 s/NULLBYTE/\\NULLBYTE/g; 342 s/\x0/NULLBYTE/g; 343 ' 344 fi 345 if [[ "$eof_without_newline" -eq 1 ]]; then 346 # Finish line with EOF to indicate that the original line did 347 # not end with a linefeed 348 echo "EOF" 349 fi 350 mode=$(get_mode "$file") 351 echo "Mode: $mode" 352 vecho "$mode $file" 353 div 354 else 355 echo >&2 "ERROR: file not found ($file in $(pwd))" 356 exit 2 357 fi 358 shift 359 done 360 } 361 362 function create { 363 ttar_file=$1 364 shift 365 if [ -z "${1:-}" ]; then 366 echo >&2 "ERROR: missing arguments." 367 echo 368 usage 1 369 fi 370 if [ -e "$ttar_file" ]; then 371 rm "$ttar_file" 372 fi 373 exec > "$ttar_file" 374 echo "# Archive created by ttar $ARG_STRING" 375 _create "$@" 376 } 377 378 test_environment 379 380 if [ -n "${CDIR:-}" ]; then 381 if [[ "$ARCHIVE" != /* ]]; then 382 # Relative path: preserve the archive's location before changing 383 # directory 384 ARCHIVE="$(pwd)/$ARCHIVE" 385 fi 386 cd "$CDIR" 387 fi 388 389 "$CMD" "$ARCHIVE" "$@"