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