cubefs

Форк
0
413 строк · 11.6 Кб
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
         --recursive-unlink (recursively delete existing directory if path
92
                             collides with file or directory to extract)
93

94
Example: Change to sysfs directory, create ttar file from fixtures directory
95
         $bname -C sysfs -c -f sysfs/fixtures.ttar fixtures/
96
USAGE
97
exit "$1"
98
}
99

100
function vecho {
101
    if [ "${VERBOSE:-}" == "yes" ]; then
102
        echo >&7 "$@"
103
    fi
104
}
105

106
function set_cmd {
107
    if [ -n "$CMD" ]; then
108
        echo "ERROR: more than one command given"
109
        echo
110
        usage 2
111
    fi
112
    CMD=$1
113
}
114

115
unset VERBOSE
116
unset RECURSIVE_UNLINK
117

118
while getopts :cf:-:htxvC: opt; do
119
    case $opt in
120
        c)
121
            set_cmd "create"
122
            ;;
123
        f)
124
            ARCHIVE=$OPTARG
125
            ;;
126
        h)
127
            usage 0
128
            ;;
129
        t)
130
            set_cmd "list"
131
            ;;
132
        x)
133
            set_cmd "extract"
134
            ;;
135
        v)
136
            VERBOSE=yes
137
            exec 7>&1
138
            ;;
139
        C)
140
            CDIR=$OPTARG
141
            ;;
142
        -)
143
            case $OPTARG in
144
                recursive-unlink)
145
                    RECURSIVE_UNLINK="yes"
146
                    ;;
147
                *)
148
                    echo -e "Error: invalid option -$OPTARG"
149
                    echo
150
                    usage 1
151
                    ;;
152
            esac
153
            ;;
154
        *)
155
            echo >&2 "ERROR: invalid option -$OPTARG"
156
            echo
157
            usage 1
158
            ;;
159
    esac
160
done
161

162
# Remove processed options from arguments
163
shift $(( OPTIND - 1 ));
164

165
if [ "${CMD:-}" == "" ]; then
166
    echo >&2 "ERROR: no command given"
167
    echo
168
    usage 1
169
elif [ "${ARCHIVE:-}" == "" ]; then
170
    echo >&2 "ERROR: no archive name given"
171
    echo
172
    usage 1
173
fi
174

175
function list {
176
    local path=""
177
    local size=0
178
    local line_no=0
179
    local ttar_file=$1
180
    if [ -n "${2:-}" ]; then
181
        echo >&2 "ERROR: too many arguments."
182
        echo
183
        usage 1
184
    fi
185
    if [ ! -e "$ttar_file" ]; then
186
        echo >&2 "ERROR: file not found ($ttar_file)"
187
        echo
188
        usage 1
189
    fi
190
    while read -r line; do
191
        line_no=$(( line_no + 1 ))
192
        if [ $size -gt 0 ]; then
193
            size=$(( size - 1 ))
194
            continue
195
        fi
196
        if [[ $line =~ ^Path:\ (.*)$ ]]; then
197
            path=${BASH_REMATCH[1]}
198
        elif [[ $line =~ ^Lines:\ (.*)$ ]]; then
199
            size=${BASH_REMATCH[1]}
200
            echo "$path"
201
        elif [[ $line =~ ^Directory:\ (.*)$ ]]; then
202
            path=${BASH_REMATCH[1]}
203
            echo "$path/"
204
        elif [[ $line =~ ^SymlinkTo:\ (.*)$ ]]; then
205
            echo  "$path -> ${BASH_REMATCH[1]}"
206
        fi
207
    done < "$ttar_file"
208
}
209

210
function extract {
211
    local path=""
212
    local size=0
213
    local line_no=0
214
    local ttar_file=$1
215
    if [ -n "${2:-}" ]; then
216
        echo >&2 "ERROR: too many arguments."
217
        echo
218
        usage 1
219
    fi
220
    if [ ! -e "$ttar_file" ]; then
221
        echo >&2 "ERROR: file not found ($ttar_file)"
222
        echo
223
        usage 1
224
    fi
225
    while IFS= read -r line; do
226
        line_no=$(( line_no + 1 ))
227
        local eof_without_newline
228
        if [ "$size" -gt 0 ]; then
229
            if [[ "$line" =~ [^\\]EOF ]]; then
230
                # An EOF not preceded by a backslash indicates that the line
231
                # does not end with a newline
232
                eof_without_newline=1
233
            else
234
                eof_without_newline=0
235
            fi
236
            # Replace NULLBYTE with null byte if at beginning of line
237
            # Replace NULLBYTE with null byte unless preceded by backslash
238
            # Remove one backslash in front of NULLBYTE (if any)
239
            # Remove EOF unless preceded by backslash
240
            # Remove one backslash in front of EOF
241
            if [ $USE_PYTHON -eq 1 ]; then
242
                echo -n "$line" | python -c "$PYTHON_EXTRACT_FILTER" >> "$path"
243
            else
244
                # The repeated pattern makes up for sed's lack of negative
245
                # lookbehind assertions (for consecutive null bytes).
246
                echo -n "$line" | \
247
                    sed -e 's/^NULLBYTE/\x0/g;
248
                            s/\([^\\]\)NULLBYTE/\1\x0/g;
249
                            s/\([^\\]\)NULLBYTE/\1\x0/g;
250
                            s/\\NULLBYTE/NULLBYTE/g;
251
                            s/\([^\\]\)EOF/\1/g;
252
                            s/\\EOF/EOF/g;
253
                    ' >> "$path"
254
            fi
255
            if [[ "$eof_without_newline" -eq 0 ]]; then
256
                echo >> "$path"
257
            fi
258
            size=$(( size - 1 ))
259
            continue
260
        fi
261
        if [[ $line =~ ^Path:\ (.*)$ ]]; then
262
            path=${BASH_REMATCH[1]}
263
            if [ -L "$path" ]; then
264
                rm "$path"
265
            elif [ -d "$path" ]; then
266
                if [ "${RECURSIVE_UNLINK:-}" == "yes" ]; then
267
                    rm -r "$path"
268
                else
269
                    # Safe because symlinks to directories are dealt with above
270
                    rmdir "$path"
271
                fi
272
            elif [ -e "$path" ]; then
273
                rm "$path"
274
            fi
275
        elif [[ $line =~ ^Lines:\ (.*)$ ]]; then
276
            size=${BASH_REMATCH[1]}
277
            # Create file even if it is zero-length.
278
            touch "$path"
279
            vecho "    $path"
280
        elif [[ $line =~ ^Mode:\ (.*)$ ]]; then
281
            mode=${BASH_REMATCH[1]}
282
            chmod "$mode" "$path"
283
            vecho "$mode"
284
        elif [[ $line =~ ^Directory:\ (.*)$ ]]; then
285
            path=${BASH_REMATCH[1]}
286
            mkdir -p "$path"
287
            vecho "    $path/"
288
        elif [[ $line =~ ^SymlinkTo:\ (.*)$ ]]; then
289
            ln -s "${BASH_REMATCH[1]}" "$path"
290
            vecho "    $path -> ${BASH_REMATCH[1]}"
291
        elif [[ $line =~ ^# ]]; then
292
            # Ignore comments between files
293
            continue
294
        else
295
            echo >&2 "ERROR: Unknown keyword on line $line_no: $line"
296
            exit 1
297
        fi
298
    done < "$ttar_file"
299
}
300

301
function div {
302
    echo "# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -" \
303
         "- - - - - -"
304
}
305

306
function get_mode {
307
    local mfile=$1
308
    if [ -z "${STAT_OPTION:-}" ]; then
309
        if stat -c '%a' "$mfile" >/dev/null 2>&1; then
310
            # GNU stat
311
            STAT_OPTION='-c'
312
            STAT_FORMAT='%a'
313
        else
314
            # BSD stat
315
            STAT_OPTION='-f'
316
            # Octal output, user/group/other (omit file type, sticky bit)
317
            STAT_FORMAT='%OLp'
318
        fi
319
    fi
320
    stat "${STAT_OPTION}" "${STAT_FORMAT}" "$mfile"
321
}
322

323
function _create {
324
    shopt -s nullglob
325
    local mode
326
    local eof_without_newline
327
    while (( "$#" )); do
328
        file=$1
329
        if [ -L "$file" ]; then
330
            echo "Path: $file"
331
            symlinkTo=$(readlink "$file")
332
            echo "SymlinkTo: $symlinkTo"
333
            vecho "    $file -> $symlinkTo"
334
            div
335
        elif [ -d "$file" ]; then
336
            # Strip trailing slash (if there is one)
337
            file=${file%/}
338
            echo "Directory: $file"
339
            mode=$(get_mode "$file")
340
            echo "Mode: $mode"
341
            vecho "$mode $file/"
342
            div
343
            # Find all files and dirs, including hidden/dot files
344
            for x in "$file/"{*,.[^.]*}; do
345
                _create "$x"
346
            done
347
        elif [ -f "$file" ]; then
348
            echo "Path: $file"
349
            lines=$(wc -l "$file"|awk '{print $1}')
350
            eof_without_newline=0
351
            if [[ "$(wc -c "$file"|awk '{print $1}')" -gt 0 ]] && \
352
                    [[ "$(tail -c 1 "$file" | wc -l)" -eq 0 ]]; then
353
                eof_without_newline=1
354
                lines=$((lines+1))
355
            fi
356
            echo "Lines: $lines"
357
            # Add backslash in front of EOF
358
            # Add backslash in front of NULLBYTE
359
            # Replace null byte with NULLBYTE
360
            if [ $USE_PYTHON -eq 1 ]; then
361
                < "$file" python -c "$PYTHON_CREATE_FILTER"
362
            else
363
                < "$file" \
364
                    sed 's/EOF/\\EOF/g;
365
                            s/NULLBYTE/\\NULLBYTE/g;
366
                            s/\x0/NULLBYTE/g;
367
                    '
368
            fi
369
            if [[ "$eof_without_newline" -eq 1 ]]; then
370
                # Finish line with EOF to indicate that the original line did
371
                # not end with a linefeed
372
                echo "EOF"
373
            fi
374
            mode=$(get_mode "$file")
375
            echo "Mode: $mode"
376
            vecho "$mode $file"
377
            div
378
        else
379
            echo >&2 "ERROR: file not found ($file in $(pwd))"
380
            exit 2
381
        fi
382
        shift
383
    done
384
}
385

386
function create {
387
    ttar_file=$1
388
    shift
389
    if [ -z "${1:-}" ]; then
390
        echo >&2 "ERROR: missing arguments."
391
        echo
392
        usage 1
393
    fi
394
    if [ -e "$ttar_file" ]; then
395
        rm "$ttar_file"
396
    fi
397
    exec > "$ttar_file"
398
    echo "# Archive created by ttar $ARG_STRING"
399
    _create "$@"
400
}
401

402
test_environment
403

404
if [ -n "${CDIR:-}" ]; then
405
    if [[ "$ARCHIVE" != /* ]]; then
406
        # Relative path: preserve the archive's location before changing
407
        # directory
408
        ARCHIVE="$(pwd)/$ARCHIVE"
409
    fi
410
    cd "$CDIR"
411
fi
412

413
"$CMD" "$ARCHIVE" "$@"
414

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.