diff options
| author | manuel <manuel@mausz.at> | 2012-03-27 11:51:08 +0200 |
|---|---|---|
| committer | manuel <manuel@mausz.at> | 2012-03-27 11:51:08 +0200 |
| commit | 4f670845ff9ab6c48bcb5f7bf4d4ef6dc3c3064b (patch) | |
| tree | 868c52e06f207b5ec8a3cc141f4b8b2bdfcc165c /lib/ustar.c | |
| parent | eae0bd57f0a26314a94785061888d193d186944a (diff) | |
| download | progos-4f670845ff9ab6c48bcb5f7bf4d4ef6dc3c3064b.tar.gz progos-4f670845ff9ab6c48bcb5f7bf4d4ef6dc3c3064b.tar.bz2 progos-4f670845ff9ab6c48bcb5f7bf4d4ef6dc3c3064b.zip | |
reorganize file structure to match the upstream requirements
Diffstat (limited to 'lib/ustar.c')
| -rw-r--r-- | lib/ustar.c | 228 |
1 files changed, 228 insertions, 0 deletions
diff --git a/lib/ustar.c b/lib/ustar.c new file mode 100644 index 0000000..49af69a --- /dev/null +++ b/lib/ustar.c | |||
| @@ -0,0 +1,228 @@ | |||
| 1 | #include <ustar.h> | ||
| 2 | #include <limits.h> | ||
| 3 | #include <packed.h> | ||
| 4 | #include <stdio.h> | ||
| 5 | #include <string.h> | ||
| 6 | |||
| 7 | /* Header for ustar-format tar archive. See the documentation of | ||
| 8 | the "pax" utility in [SUSv3] for the the "ustar" format | ||
| 9 | specification. */ | ||
| 10 | struct ustar_header | ||
| 11 | { | ||
| 12 | char name[100]; /* File name. Null-terminated if room. */ | ||
| 13 | char mode[8]; /* Permissions as octal string. */ | ||
| 14 | char uid[8]; /* User ID as octal string. */ | ||
| 15 | char gid[8]; /* Group ID as octal string. */ | ||
| 16 | char size[12]; /* File size in bytes as octal string. */ | ||
| 17 | char mtime[12]; /* Modification time in seconds | ||
| 18 | from Jan 1, 1970, as octal string. */ | ||
| 19 | char chksum[8]; /* Sum of octets in header as octal string. */ | ||
| 20 | char typeflag; /* An enum ustar_type value. */ | ||
| 21 | char linkname[100]; /* Name of link target. | ||
| 22 | Null-terminated if room. */ | ||
| 23 | char magic[6]; /* "ustar\0" */ | ||
| 24 | char version[2]; /* "00" */ | ||
| 25 | char uname[32]; /* User name, always null-terminated. */ | ||
| 26 | char gname[32]; /* Group name, always null-terminated. */ | ||
| 27 | char devmajor[8]; /* Device major number as octal string. */ | ||
| 28 | char devminor[8]; /* Device minor number as octal string. */ | ||
| 29 | char prefix[155]; /* Prefix to file name. | ||
| 30 | Null-terminated if room. */ | ||
| 31 | char padding[12]; /* Pad to 512 bytes. */ | ||
| 32 | } | ||
| 33 | PACKED; | ||
| 34 | |||
| 35 | /* Returns the checksum for the given ustar format HEADER. */ | ||
| 36 | static unsigned int | ||
| 37 | calculate_chksum (const struct ustar_header *h) | ||
| 38 | { | ||
| 39 | const uint8_t *header = (const uint8_t *) h; | ||
| 40 | unsigned int chksum; | ||
| 41 | size_t i; | ||
| 42 | |||
| 43 | chksum = 0; | ||
| 44 | for (i = 0; i < USTAR_HEADER_SIZE; i++) | ||
| 45 | { | ||
| 46 | /* The ustar checksum is calculated as if the chksum field | ||
| 47 | were all spaces. */ | ||
| 48 | const size_t chksum_start = offsetof (struct ustar_header, chksum); | ||
| 49 | const size_t chksum_end = chksum_start + sizeof h->chksum; | ||
| 50 | bool in_chksum_field = i >= chksum_start && i < chksum_end; | ||
| 51 | chksum += in_chksum_field ? ' ' : header[i]; | ||
| 52 | } | ||
| 53 | return chksum; | ||
| 54 | } | ||
| 55 | |||
| 56 | /* Drop possibly dangerous prefixes from FILE_NAME and return the | ||
| 57 | stripped name. An archive with file names that start with "/" | ||
| 58 | or "../" could cause a naive tar extractor to write to | ||
| 59 | arbitrary parts of the file system, not just the destination | ||
| 60 | directory. We don't want to create such archives or be such a | ||
| 61 | naive extractor. | ||
| 62 | |||
| 63 | The return value can be a suffix of FILE_NAME or a string | ||
| 64 | literal. */ | ||
| 65 | static const char * | ||
| 66 | strip_antisocial_prefixes (const char *file_name) | ||
| 67 | { | ||
| 68 | while (*file_name == '/' | ||
| 69 | || !memcmp (file_name, "./", 2) | ||
| 70 | || !memcmp (file_name, "../", 3)) | ||
| 71 | file_name = strchr (file_name, '/') + 1; | ||
| 72 | return *file_name == '\0' || !strcmp (file_name, "..") ? "." : file_name; | ||
| 73 | } | ||
| 74 | |||
| 75 | /* Composes HEADER as a USTAR_HEADER_SIZE (512)-byte archive | ||
| 76 | header in ustar format for a SIZE-byte file named FILE_NAME of | ||
| 77 | the given TYPE. The caller is responsible for writing the | ||
| 78 | header to a file or device. | ||
| 79 | |||
| 80 | If successful, returns true. On failure (due to an | ||
| 81 | excessively long file name), returns false. */ | ||
| 82 | bool | ||
| 83 | ustar_make_header (const char *file_name, enum ustar_type type, | ||
| 84 | int size, char header[USTAR_HEADER_SIZE]) | ||
| 85 | { | ||
| 86 | struct ustar_header *h = (struct ustar_header *) header; | ||
| 87 | |||
| 88 | ASSERT (sizeof (struct ustar_header) == USTAR_HEADER_SIZE); | ||
| 89 | ASSERT (type == USTAR_REGULAR || type == USTAR_DIRECTORY); | ||
| 90 | |||
| 91 | /* Check file name. */ | ||
| 92 | file_name = strip_antisocial_prefixes (file_name); | ||
| 93 | if (strlen (file_name) > 99) | ||
| 94 | { | ||
| 95 | printf ("%s: file name too long\n", file_name); | ||
| 96 | return false; | ||
| 97 | } | ||
| 98 | |||
| 99 | /* Fill in header except for final checksum. */ | ||
| 100 | memset (h, 0, sizeof *h); | ||
| 101 | strlcpy (h->name, file_name, sizeof h->name); | ||
| 102 | snprintf (h->mode, sizeof h->mode, "%07o", | ||
| 103 | type == USTAR_REGULAR ? 0644 : 0755); | ||
| 104 | strlcpy (h->uid, "0000000", sizeof h->uid); | ||
| 105 | strlcpy (h->gid, "0000000", sizeof h->gid); | ||
| 106 | snprintf (h->size, sizeof h->size, "%011o", size); | ||
| 107 | snprintf (h->mtime, sizeof h->size, "%011o", 1136102400); | ||
| 108 | h->typeflag = type; | ||
| 109 | strlcpy (h->magic, "ustar", sizeof h->magic); | ||
| 110 | h->version[0] = h->version[1] = '0'; | ||
| 111 | strlcpy (h->gname, "root", sizeof h->gname); | ||
| 112 | strlcpy (h->uname, "root", sizeof h->uname); | ||
| 113 | |||
| 114 | /* Compute and fill in final checksum. */ | ||
| 115 | snprintf (h->chksum, sizeof h->chksum, "%07o", calculate_chksum (h)); | ||
| 116 | |||
| 117 | return true; | ||
| 118 | } | ||
| 119 | |||
| 120 | /* Parses a SIZE-byte octal field in S in the format used by | ||
| 121 | ustar format. If successful, stores the field's value in | ||
| 122 | *VALUE and returns true; on failure, returns false. | ||
| 123 | |||
| 124 | ustar octal fields consist of a sequence of octal digits | ||
| 125 | terminated by a space or a null byte. The ustar specification | ||
| 126 | seems ambiguous as to whether these fields must be padded on | ||
| 127 | the left with '0's, so we accept any field that fits in the | ||
| 128 | available space, regardless of whether it fills the space. */ | ||
| 129 | static bool | ||
| 130 | parse_octal_field (const char *s, size_t size, unsigned long int *value) | ||
| 131 | { | ||
| 132 | size_t ofs; | ||
| 133 | |||
| 134 | *value = 0; | ||
| 135 | for (ofs = 0; ofs < size; ofs++) | ||
| 136 | { | ||
| 137 | char c = s[ofs]; | ||
| 138 | if (c >= '0' && c <= '7') | ||
| 139 | { | ||
| 140 | if (*value > ULONG_MAX / 8) | ||
| 141 | { | ||
| 142 | /* Overflow. */ | ||
| 143 | return false; | ||
| 144 | } | ||
| 145 | *value = c - '0' + *value * 8; | ||
| 146 | } | ||
| 147 | else if (c == ' ' || c == '\0') | ||
| 148 | { | ||
| 149 | /* End of field, but disallow completely empty | ||
| 150 | fields. */ | ||
| 151 | return ofs > 0; | ||
| 152 | } | ||
| 153 | else | ||
| 154 | { | ||
| 155 | /* Bad character. */ | ||
| 156 | return false; | ||
| 157 | } | ||
| 158 | } | ||
| 159 | |||
| 160 | /* Field did not end in space or null byte. */ | ||
| 161 | return false; | ||
| 162 | } | ||
| 163 | |||
| 164 | /* Returns true if the CNT bytes starting at BLOCK are all zero, | ||
| 165 | false otherwise. */ | ||
| 166 | static bool | ||
| 167 | is_all_zeros (const char *block, size_t cnt) | ||
| 168 | { | ||
| 169 | while (cnt-- > 0) | ||
| 170 | if (*block++ != 0) | ||
| 171 | return false; | ||
| 172 | return true; | ||
| 173 | } | ||
| 174 | |||
| 175 | /* Parses HEADER as a ustar-format archive header for a regular | ||
| 176 | file or directory. If successful, stores the archived file's | ||
| 177 | name in *FILE_NAME (as a pointer into HEADER or a string | ||
| 178 | literal), its type in *TYPE, and its size in bytes in *SIZE, | ||
| 179 | and returns a null pointer. On failure, returns a | ||
| 180 | human-readable error message. */ | ||
| 181 | const char * | ||
| 182 | ustar_parse_header (const char header[USTAR_HEADER_SIZE], | ||
| 183 | const char **file_name, enum ustar_type *type, int *size) | ||
| 184 | { | ||
| 185 | const struct ustar_header *h = (const struct ustar_header *) header; | ||
| 186 | unsigned long int chksum, size_ul; | ||
| 187 | |||
| 188 | ASSERT (sizeof (struct ustar_header) == USTAR_HEADER_SIZE); | ||
| 189 | |||
| 190 | /* Detect end of archive. */ | ||
| 191 | if (is_all_zeros (header, USTAR_HEADER_SIZE)) | ||
| 192 | { | ||
| 193 | *file_name = NULL; | ||
| 194 | *type = USTAR_EOF; | ||
| 195 | *size = 0; | ||
| 196 | return NULL; | ||
| 197 | } | ||
| 198 | |||
| 199 | /* Validate ustar header. */ | ||
| 200 | if (memcmp (h->magic, "ustar", 6)) | ||
| 201 | return "not a ustar archive"; | ||
| 202 | else if (h->version[0] != '0' || h->version[1] != '0') | ||
| 203 | return "invalid ustar version"; | ||
| 204 | else if (!parse_octal_field (h->chksum, sizeof h->chksum, &chksum)) | ||
| 205 | return "corrupt chksum field"; | ||
| 206 | else if (chksum != calculate_chksum (h)) | ||
| 207 | return "checksum mismatch"; | ||
| 208 | else if (h->name[sizeof h->name - 1] != '\0' || h->prefix[0] != '\0') | ||
| 209 | return "file name too long"; | ||
| 210 | else if (h->typeflag != USTAR_REGULAR && h->typeflag != USTAR_DIRECTORY) | ||
| 211 | return "unimplemented file type"; | ||
| 212 | if (h->typeflag == USTAR_REGULAR) | ||
| 213 | { | ||
| 214 | if (!parse_octal_field (h->size, sizeof h->size, &size_ul)) | ||
| 215 | return "corrupt file size field"; | ||
| 216 | else if (size_ul > INT_MAX) | ||
| 217 | return "file too large"; | ||
| 218 | } | ||
| 219 | else | ||
| 220 | size_ul = 0; | ||
| 221 | |||
| 222 | /* Success. */ | ||
| 223 | *file_name = strip_antisocial_prefixes (h->name); | ||
| 224 | *type = h->typeflag; | ||
| 225 | *size = size_ul; | ||
| 226 | return NULL; | ||
| 227 | } | ||
| 228 | |||
