b.liu | e958203 | 2025-04-17 19:18:16 +0800 | [diff] [blame^] | 1 | # SPDX-License-Identifier: GPL-2.0-or-later OR MIT |
| 2 | |
| 3 | # Example usage: |
| 4 | # |
| 5 | # { |
| 6 | # tar_print_member "date.txt" "It's $(date +"%Y")" |
| 7 | # tar_print_trailer |
| 8 | # } > test.tar |
| 9 | |
| 10 | __tar_print_padding() { |
| 11 | dd if=/dev/zero bs=1 count=$1 2>/dev/null |
| 12 | } |
| 13 | |
| 14 | tar_print_member() { |
| 15 | local name="$1" |
| 16 | local content="$2" |
| 17 | local mtime="${3:-$(date +%s)}" |
| 18 | local mode=644 |
| 19 | local uid=0 |
| 20 | local gid=0 |
| 21 | local size=${#content} |
| 22 | local type=0 |
| 23 | local link="" |
| 24 | local username="root" |
| 25 | local groupname="root" |
| 26 | |
| 27 | # 100 byte of padding bytes, using 0x01 since the shell does not tolerate null bytes in strings |
| 28 | local pad=$'\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1' |
| 29 | |
| 30 | # validate name (strip leading slash if present) |
| 31 | name=${name#/} |
| 32 | |
| 33 | # truncate string header values to their maximum length |
| 34 | name=${name:0:100} |
| 35 | link=${link:0:100} |
| 36 | username=${username:0:32} |
| 37 | groupname=${groupname:0:32} |
| 38 | |
| 39 | # construct header part before checksum field |
| 40 | local header1="${name}${pad:0:$((100 - ${#name}))}" |
| 41 | header1="${header1}$(printf '%07d\1' $mode)" |
| 42 | header1="${header1}$(printf '%07o\1' $uid)" |
| 43 | header1="${header1}$(printf '%07o\1' $gid)" |
| 44 | header1="${header1}$(printf '%011o\1' $size)" |
| 45 | header1="${header1}$(printf '%011o\1' $mtime)" |
| 46 | |
| 47 | # construct header part after checksum field |
| 48 | local header2="$(printf '%d' $type)" |
| 49 | header2="${header2}${link}${pad:0:$((100 - ${#link}))}" |
| 50 | header2="${header2}ustar ${pad:0:1}" |
| 51 | header2="${header2}${username}${pad:0:$((32 - ${#username}))}" |
| 52 | header2="${header2}${groupname}${pad:0:$((32 - ${#groupname}))}" |
| 53 | |
| 54 | # calculate checksum over header fields |
| 55 | local checksum=0 |
| 56 | for byte in $(printf '%s%8s%s' "$header1" "" "$header2" | tr '\1' '\0' | hexdump -ve '1/1 "%u "'); do |
| 57 | checksum=$((checksum + byte)) |
| 58 | done |
| 59 | |
| 60 | # print member header, padded to 512 byte |
| 61 | printf '%s%06o\0 %s' "$header1" $checksum "$header2" | tr '\1' '\0' |
| 62 | __tar_print_padding 183 |
| 63 | |
| 64 | # print content data, padded to multiple of 512 byte |
| 65 | printf "%s" "$content" |
| 66 | __tar_print_padding $((512 - (size % 512))) |
| 67 | } |
| 68 | |
| 69 | tar_print_trailer() { |
| 70 | __tar_print_padding 1024 |
| 71 | } |