UNIXworkcode

1 /* 2 * DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 3 * Version 2, December 2004 4 * 5 * Copyright (C) 2017 Olaf Wintermann <olaf.wintermann@gmail.com> 6 * 7 * Everyone is permitted to copy and distribute verbatim or modified 8 * copies of this license document, and changing it is allowed as long 9 * as the name is changed. 10 * 11 * DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 12 * TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 13 * 14 * 0. You just DO WHAT THE FUCK YOU WANT TO. 15 */ 16 17 #include <stdio.h> 18 #include <stdlib.h> 19 #include <string.h> 20 #include <inttypes.h> 21 #include <time.h> 22 #include <sys/types.h> 23 #include <sys/stat.h> 24 #include <unistd.h> 25 26 #define TAR_TYPE_FILE '0' 27 #define TAR_TYPE_DIRECTORY '5' 28 29 enum TarError { 30 TAR_OK = 0, 31 TAR_PATH_TOO_LONG, 32 TAR_FILE_TOO_LARGE, 33 TAR_CONTENT_TOO_LARGE, 34 TAR_UNFINISHED_FILE, 35 TAR_CONTENT_BROKEN, 36 TAR_ERROR 37 }; 38 39 typedef enum TarError TarError; 40 41 typedef struct TarHeader { 42 char name[100]; 43 char mode[8]; 44 char uid[8]; 45 char gid[8]; 46 char size[12]; 47 char mtime[12]; 48 char chksum[8]; 49 char typeflag; 50 char linkname[100]; 51 char magic[6]; 52 char version[2]; 53 char uname[32]; 54 char gname[32]; 55 char devmajor[8]; 56 char devminor[8]; 57 char prefix[155]; 58 char padding[12]; 59 } TarHeader; 60 61 typedef struct TarOutputStream { 62 FILE *file; 63 uint64_t cur_filesize; 64 uint64_t cur_written; 65 TarError error; 66 } TarOutputStream; 67 68 typedef struct TarEntry { 69 char *path; 70 uint64_t size; 71 int type; 72 } TarEntry; 73 74 typedef struct TarInputStream { 75 FILE *file; 76 TarEntry cur_entry; 77 uint64_t cur_read; 78 TarError error; 79 } TarInputStream; 80 81 TarOutputStream* tar_open(FILE *f) { 82 TarOutputStream *tar = malloc(sizeof(TarOutputStream)); 83 tar->file= f; 84 tar->cur_filesize = 0; 85 tar->cur_written = 0; 86 tar->error = 0; 87 return tar; 88 } 89 90 91 static int add_header(TarOutputStream *tar, char *path, uint32_t mode, uint64_t size, time_t mtime, int type) { 92 // split path in prefix and name 93 char *prefix = path; 94 size_t prefixlen = 0; 95 char *name = path; 96 size_t namelen = 0; 97 98 int pathsep = 0; 99 char *p = path; 100 char c; 101 size_t i = 0; 102 while((c = *p) != '\0') { 103 if(c == '/') { 104 pathsep = 1; 105 } else { 106 if(pathsep) { 107 name = p; 108 prefixlen = i; 109 } 110 pathsep = 0; 111 } 112 113 i++; 114 p++; 115 } 116 namelen = i - prefixlen; 117 118 // check prefix/name length 119 if(prefixlen > 154) { 120 tar->error = TAR_PATH_TOO_LONG; 121 return -1; 122 } 123 if(namelen > 99) { 124 tar->error = TAR_PATH_TOO_LONG; 125 return -1; 126 } 127 128 // check file length 129 if(size >= 077777777777 ) { 130 tar->error = TAR_FILE_TOO_LARGE; 131 return -1; 132 } 133 134 // set header fields 135 TarHeader h; 136 memset(&h, 0, sizeof(TarHeader)); 137 138 // name 139 memcpy(h.name, name, namelen); 140 // mode 141 snprintf(h.mode, 8, "%07o", mode); 142 h.mode[7] = ' '; 143 // uid/gid 144 memset(h.uid, '0', 16); 145 h.uid[7] = ' '; 146 h.gid[7] = ' '; 147 // size 148 snprintf(h.size, 12, "%011lo", size); 149 h.size[11] = ' '; 150 // mtime 151 uint64_t t = (uint64_t)mtime; 152 snprintf(h.mtime, 12, "%011lo", mtime); 153 h.mtime[11] = ' '; 154 // chksum 155 memset(h.chksum, ' ', 8); 156 // typeflag 157 h.typeflag = type; 158 // linkname - zeros 159 // magic 160 snprintf(h.magic, 6, "ustar"); 161 // version 162 h.version[0] = '0'; 163 h.version[1] = '0'; 164 // uname/gname - zero 165 // devmajor/devminor 166 char *devbuf = (char*)h.devmajor; 167 snprintf(devbuf, 16, "%015o", 0); 168 h.devmajor[7] = ' '; 169 h.devminor[7] = ' '; 170 // prefix 171 memcpy(h.prefix, prefix, prefixlen); 172 173 // compute checksum 174 uint8_t *header = (uint8_t*)&h; 175 uint32_t chksum = 0; 176 for(int i=0;i<512;i++) { 177 chksum += header[i]; 178 } 179 snprintf(h.chksum, 8, "%07o", chksum); 180 181 fwrite(&h, 1, 512, tar->file); 182 183 return 0; 184 } 185 186 187 int tar_add_dir(TarOutputStream *tar, char *path, uint32_t mode, time_t mtime) { 188 return add_header(tar, path, mode, 0, mtime, TAR_TYPE_DIRECTORY); 189 } 190 191 int tar_begin_file( 192 TarOutputStream *tar, 193 char *path, 194 uint32_t mode, 195 uint64_t size, 196 time_t mtime) 197 { 198 if(add_header(tar, path, mode, size, mtime, TAR_TYPE_FILE)) { 199 return -1; 200 } 201 202 tar->cur_filesize = size; 203 tar->cur_written = 0; 204 205 return 0; 206 } 207 208 size_t tar_fwrite(const void *ptr, size_t s, size_t n, TarOutputStream *stream) { 209 size_t w = fwrite(ptr, s, n, stream->file); 210 stream->cur_written += w; 211 if(stream->cur_written > stream->cur_filesize) { 212 stream->error = TAR_CONTENT_TOO_LARGE; 213 return 0; 214 } 215 return w; 216 } 217 218 int tar_end_file(TarOutputStream *tar) { 219 // the file content must have an alignment of 512 bytes 220 // this functions writes some padding bytes to the stream 221 size_t pad = 512 - tar->cur_written % 512; 222 char buf[512]; 223 memset(buf, 0, 512); 224 fwrite(buf, 1, pad, tar->file); 225 226 tar->cur_filesize = 0; 227 tar->cur_written = 0; 228 return 0; 229 } 230 231 int tar_close(TarOutputStream *tar) { 232 char buf[1024]; 233 memset(buf, 0, 1024); 234 fwrite(buf, 1, 1024, tar->file); 235 return 0; 236 } 237 238 239 #define IO_BUF_SIZE 0x4000 240 int main(int argc, char **argv) { 241 if(argc <= 1) { 242 fprintf(stderr, "Usage: %s <file> ...\n", argv[0]); 243 } 244 245 TarOutputStream *tar = tar_open(stdout); 246 247 for(int i=1;i<argc;i++) { 248 char *file = argv[i]; 249 250 struct stat s; 251 if(stat(file, &s)) { 252 perror("stat"); 253 continue; 254 } 255 256 // we wan't just the permission part 257 uint32_t tmode = s.st_mode & 07777; 258 259 // add directories or regular files to the tape archive 260 if(S_ISDIR(s.st_mode)) { 261 fprintf(stderr, "add: %s\n", file); 262 263 // this adds just a header 264 if(tar_add_dir(tar, file, tmode, s.st_mtime)) { 265 fprintf(stderr, "tar_add_dir failed: %d\n", tar->error); 266 } 267 } else if(S_ISREG(s.st_mode)) { 268 fprintf(stderr, "add: %s\n", file); 269 270 FILE *f = fopen(file, "r"); 271 if(!f) { 272 perror("open"); 273 continue; 274 } 275 276 // add tar header for this file 277 if(tar_begin_file(tar, file, tmode, s.st_size, s.st_mtime)) { 278 fprintf(stderr, "tar_begin_file failed: %d\n", tar->error); 279 } else { 280 // header successfully written 281 // now we can copy the file content 282 char buf[IO_BUF_SIZE]; 283 size_t r; 284 while((r = fread(buf, 1, IO_BUF_SIZE, f)) > 0) { 285 if(tar_fwrite(buf, 1, r, tar) == 0) { 286 fprintf(stderr, "tar_fwrite failed\n"); 287 break; 288 } 289 } 290 291 // this writes padding some padding bytes 292 tar_end_file(tar); 293 } 294 295 fclose(f); 296 } 297 } 298 299 // write last 512 bytes block with just zeros 300 tar_close(tar); 301 302 return 0; 303 } 304 305