1/* 2 * "untar" is an extremely simple tar extractor: 3 * * A single C source file, so it should be easy to compile 4 * and run on any system with a C compiler. 5 * * Extremely portable standard C. The only non-ANSI function 6 * used is mkdir(). 7 * * Reads basic ustar tar archives. 8 * * Does not require libarchive or any other special library. 9 * 10 * To compile: cc -o untar untar.c 11 * 12 * Usage: untar <archive> 13 * 14 * In particular, this program should be sufficient to extract the 15 * distribution for libarchive, allowing people to bootstrap 16 * libarchive on systems that do not already have a tar program. 17 * 18 * To unpack libarchive-x.y.z.tar.gz: 19 * * gunzip libarchive-x.y.z.tar.gz 20 * * untar libarchive-x.y.z.tar 21 * 22 * Written by Tim Kientzle, March 2009. 23 * 24 * Released into the public domain. 25 */ 26 27/* These are all highly standard and portable headers. */ 28#include <stdio.h> 29#include <stdlib.h> 30#include <string.h> 31 32/* This is for mkdir(); this may need to be changed for some platforms. */ 33#include <sys/stat.h> /* For mkdir() */ 34 35/* Parse an octal number, ignoring leading and trailing nonsense. */ 36static int 37parseoct(const char *p, size_t n) 38{ 39 int i = 0; 40 41 while (*p < '0' || *p > '7') { 42 ++p; 43 --n; 44 } 45 while (*p >= '0' && *p <= '7' && n > 0) { 46 i *= 8; 47 i += *p - '0'; 48 ++p; 49 --n; 50 } 51 return (i); 52} 53 54/* Returns true if this is 512 zero bytes. */ 55static int 56is_end_of_archive(const char *p) 57{ 58 int n; 59 for (n = 511; n >= 0; --n) 60 if (p[n] != '\0') 61 return (0); 62 return (1); 63} 64 65/* Create a directory, including parent directories as necessary. */ 66static void 67create_dir(char *pathname, int mode) 68{ 69 char *p; 70 int r; 71 72 /* Strip trailing '/' */ 73 if (pathname[strlen(pathname) - 1] == '/') 74 pathname[strlen(pathname) - 1] = '\0'; 75 76 /* Try creating the directory. */ 77 r = mkdir(pathname, mode); 78 79 if (r != 0) { 80 /* On failure, try creating parent directory. */ 81 p = strrchr(pathname, '/'); 82 if (p != NULL) { 83 *p = '\0'; 84 create_dir(pathname, 0755); 85 *p = '/'; 86 r = mkdir(pathname, mode); 87 } 88 } 89 if (r != 0) 90 fprintf(stderr, "Could not create directory %s\n", pathname); 91} 92 93/* Create a file, including parent directory as necessary. */ 94static FILE * 95create_file(char *pathname, int mode) 96{ 97 FILE *f; 98 f = fopen(pathname, "w+"); 99 if (f == NULL) { 100 /* Try creating parent dir and then creating file. */ 101 char *p = strrchr(pathname, '/'); 102 if (p != NULL) { 103 *p = '\0'; 104 create_dir(pathname, 0755); 105 *p = '/'; 106 f = fopen(pathname, "w+"); 107 } 108 } 109 return (f); 110} 111 112/* Verify the tar checksum. */ 113static int 114verify_checksum(const char *p) 115{ 116 int n, u = 0; 117 for (n = 0; n < 512; ++n) { 118 if (n < 148 || n > 155) 119 /* Standard tar checksum adds unsigned bytes. */ 120 u += ((unsigned char *)p)[n]; 121 else 122 u += 0x20; 123 124 } 125 return (u == parseoct(p + 148, 8)); 126} 127 128/* Extract a tar archive. */ 129static void 130untar(FILE *a, const char *path) 131{ 132 char buff[512]; 133 FILE *f = NULL; 134 size_t bytes_read; 135 int filesize; 136 137 printf("Extracting from %s\n", path); 138 for (;;) { 139 bytes_read = fread(buff, 1, 512, a); 140 if (bytes_read < 512) { 141 fprintf(stderr, 142 "Short read on %s: expected 512, got %d\n", 143 path, bytes_read); 144 return; 145 } 146 if (is_end_of_archive(buff)) { 147 printf("End of %s\n", path); 148 return; 149 } 150 if (!verify_checksum(buff)) { 151 fprintf(stderr, "Checksum failure\n"); 152 return; 153 } 154 filesize = parseoct(buff + 124, 12); 155 switch (buff[156]) { 156 case '1': 157 printf(" Ignoring hardlink %s\n", buff); 158 break; 159 case '2': 160 printf(" Ignoring symlink %s\n", buff); 161 break; 162 case '3': 163 printf(" Ignoring character device %s\n", buff); 164 break; 165 case '4': 166 printf(" Ignoring block device %s\n", buff); 167 break; 168 case '5': 169 printf(" Extracting dir %s\n", buff); 170 create_dir(buff, parseoct(buff + 100, 8)); 171 filesize = 0; 172 break; 173 case '6': 174 printf(" Ignoring FIFO %s\n", buff); 175 break; 176 default: 177 printf(" Extracting file %s\n", buff); 178 f = create_file(buff, parseoct(buff + 100, 8)); 179 break; 180 } 181 while (filesize > 0) { 182 bytes_read = fread(buff, 1, 512, a); 183 if (bytes_read < 512) { 184 fprintf(stderr, 185 "Short read on %s: Expected 512, got %d\n", 186 path, bytes_read); 187 return; 188 } 189 if (filesize < 512) 190 bytes_read = filesize; 191 if (f != NULL) { 192 if (fwrite(buff, 1, bytes_read, f) 193 != bytes_read) 194 { 195 fprintf(stderr, "Failed write\n"); 196 fclose(f); 197 f = NULL; 198 } 199 } 200 filesize -= bytes_read; 201 } 202 if (f != NULL) { 203 fclose(f); 204 f = NULL; 205 } 206 } 207} 208 209int 210main(int argc, char **argv) 211{ 212 FILE *a; 213 214 ++argv; /* Skip program name */ 215 for ( ;*argv != NULL; ++argv) { 216 a = fopen(*argv, "r"); 217 if (a == NULL) 218 fprintf(stderr, "Unable to open %s\n", *argv); 219 else { 220 untar(a, *argv); 221 fclose(a); 222 } 223 } 224 return (0); 225} 226