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" "$@"