1/*
  2 * Copyright (c) 2017 rxi
  3 *
  4 * Permission is hereby granted, free of charge, to any person obtaining a copy
  5 * of this software and associated documentation files (the "Software"), to
  6 * deal in the Software without restriction, including without limitation the
  7 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  8 * sell copies of the Software, and to permit persons to whom the Software is
  9 * furnished to do so, subject to the following conditions:
 10 *
 11 * The above copyright notice and this permission notice shall be included in
 12 * all copies or substantial portions of the Software.
 13 *
 14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 19 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 20 * IN THE SOFTWARE.
 21 */
 22
 23#include <stdio.h>
 24#include <stdlib.h>
 25#include <stddef.h>
 26#include <string.h>
 27
 28#include "microtar.h"
 29
 30typedef struct {
 31  char name[100];
 32  char mode[8];
 33  char owner[8];
 34  char group[8];
 35  char size[12];
 36  char mtime[12];
 37  char checksum[8];
 38  char type;
 39  char linkname[100];
 40  char _padding[255];
 41} mtar_raw_header_t;
 42
 43
 44static unsigned round_up(unsigned n, unsigned incr) {
 45  return n + (incr - n % incr) % incr;
 46}
 47
 48
 49static unsigned checksum(const mtar_raw_header_t* rh) {
 50  unsigned i;
 51  unsigned char *p = (unsigned char*) rh;
 52  unsigned res = 256;
 53  for (i = 0; i < offsetof(mtar_raw_header_t, checksum); i++) {
 54    res += p[i];
 55  }
 56  for (i = offsetof(mtar_raw_header_t, type); i < sizeof(*rh); i++) {
 57    res += p[i];
 58  }
 59  return res;
 60}
 61
 62
 63static int tread(mtar_t *tar, void *data, unsigned size) {
 64  int err = tar->read(tar, data, size);
 65  tar->pos += size;
 66  return err;
 67}
 68
 69
 70static int twrite(mtar_t *tar, const void *data, unsigned size) {
 71  int err = tar->write(tar, data, size);
 72  tar->pos += size;
 73  return err;
 74}
 75
 76
 77static int write_null_bytes(mtar_t *tar, int n) {
 78  int i, err;
 79  char nul = '\0';
 80  for (i = 0; i < n; i++) {
 81    err = twrite(tar, &nul, 1);
 82    if (err) {
 83      return err;
 84    }
 85  }
 86  return MTAR_ESUCCESS;
 87}
 88
 89
 90static int raw_to_header(mtar_header_t *h, const mtar_raw_header_t *rh) {
 91  unsigned chksum1, chksum2;
 92
 93  /* If the checksum starts with a null byte we assume the record is NULL */
 94  if (*rh->checksum == '\0') {
 95    return MTAR_ENULLRECORD;
 96  }
 97
 98  /* Build and compare checksum */
 99  chksum1 = checksum(rh);
100  sscanf(rh->checksum, "%o", &chksum2);
101  if (chksum1 != chksum2) {
102    return MTAR_EBADCHKSUM;
103  }
104
105  /* Load raw header into header */
106  sscanf(rh->mode, "%o", &h->mode);
107  sscanf(rh->owner, "%o", &h->owner);
108  sscanf(rh->size, "%o", &h->size);
109  sscanf(rh->mtime, "%o", &h->mtime);
110  h->type = rh->type;
111  strcpy(h->name, rh->name);
112  strcpy(h->linkname, rh->linkname);
113
114  return MTAR_ESUCCESS;
115}
116
117
118static int header_to_raw(mtar_raw_header_t *rh, const mtar_header_t *h) {
119  unsigned chksum;
120
121  /* Load header into raw header */
122  memset(rh, 0, sizeof(*rh));
123  sprintf(rh->mode, "%o", h->mode);
124  sprintf(rh->owner, "%o", h->owner);
125  sprintf(rh->size, "%o", h->size);
126  sprintf(rh->mtime, "%o", h->mtime);
127  rh->type = h->type ? h->type : MTAR_TREG;
128  strcpy(rh->name, h->name);
129  strcpy(rh->linkname, h->linkname);
130
131  /* Calculate and write checksum */
132  chksum = checksum(rh);
133  sprintf(rh->checksum, "%06o", chksum);
134  rh->checksum[7] = ' ';
135
136  return MTAR_ESUCCESS;
137}
138
139
140const char* mtar_strerror(int err) {
141  switch (err) {
142    case MTAR_ESUCCESS     : return "success";
143    case MTAR_EFAILURE     : return "failure";
144    case MTAR_EOPENFAIL    : return "could not open";
145    case MTAR_EREADFAIL    : return "could not read";
146    case MTAR_EWRITEFAIL   : return "could not write";
147    case MTAR_ESEEKFAIL    : return "could not seek";
148    case MTAR_EBADCHKSUM   : return "bad checksum";
149    case MTAR_ENULLRECORD  : return "null record";
150    case MTAR_ENOTFOUND    : return "file not found";
151  }
152  return "unknown error";
153}
154
155
156static int file_write(mtar_t *tar, const void *data, unsigned size) {
157  unsigned res = fwrite(data, 1, size, tar->stream);
158  return (res == size) ? MTAR_ESUCCESS : MTAR_EWRITEFAIL;
159}
160
161static int file_read(mtar_t *tar, void *data, unsigned size) {
162  unsigned res = fread(data, 1, size, tar->stream);
163  return (res == size) ? MTAR_ESUCCESS : MTAR_EREADFAIL;
164}
165
166static int file_seek(mtar_t *tar, unsigned offset) {
167  int res = fseek(tar->stream, offset, SEEK_SET);
168  return (res == 0) ? MTAR_ESUCCESS : MTAR_ESEEKFAIL;
169}
170
171static int file_close(mtar_t *tar) {
172  fclose(tar->stream);
173  return MTAR_ESUCCESS;
174}
175
176
177int mtar_open(mtar_t *tar, const char *filename, const char *mode) {
178  int err;
179  mtar_header_t h;
180
181  /* Init tar struct and functions */
182  memset(tar, 0, sizeof(*tar));
183  tar->write = file_write;
184  tar->read = file_read;
185  tar->seek = file_seek;
186  tar->close = file_close;
187
188  /* Assure mode is always binary */
189  if ( strchr(mode, 'r') ) mode = "rb";
190  if ( strchr(mode, 'w') ) mode = "wb";
191  if ( strchr(mode, 'a') ) mode = "ab";
192  /* Open file */
193  tar->stream = fopen(filename, mode);
194  if (!tar->stream) {
195    return MTAR_EOPENFAIL;
196  }
197  /* Read first header to check it is valid if mode is `r` */
198  if (*mode == 'r') {
199    err = mtar_read_header(tar, &h);
200    if (err != MTAR_ESUCCESS) {
201      mtar_close(tar);
202      return err;
203    }
204  }
205
206  /* Return ok */
207  return MTAR_ESUCCESS;
208}
209
210
211int mtar_close(mtar_t *tar) {
212  return tar->close(tar);
213}
214
215
216int mtar_seek(mtar_t *tar, unsigned pos) {
217  int err = tar->seek(tar, pos);
218  tar->pos = pos;
219  return err;
220}
221
222
223int mtar_rewind(mtar_t *tar) {
224  tar->remaining_data = 0;
225  tar->last_header = 0;
226  return mtar_seek(tar, 0);
227}
228
229
230int mtar_next(mtar_t *tar) {
231  int err, n;
232  mtar_header_t h;
233  /* Load header */
234  err = mtar_read_header(tar, &h);
235  if (err) {
236    return err;
237  }
238  /* Seek to next record */
239  n = round_up(h.size, 512) + sizeof(mtar_raw_header_t);
240  return mtar_seek(tar, tar->pos + n);
241}
242
243
244int mtar_find(mtar_t *tar, const char *name, mtar_header_t *h) {
245  int err;
246  mtar_header_t header;
247  /* Start at beginning */
248  err = mtar_rewind(tar);
249  if (err) {
250    return err;
251  }
252  /* Iterate all files until we hit an error or find the file */
253  while ( (err = mtar_read_header(tar, &header)) == MTAR_ESUCCESS ) {
254    if ( !strcmp(header.name, name) ) {
255      if (h) {
256        *h = header;
257      }
258      return MTAR_ESUCCESS;
259    }
260    mtar_next(tar);
261  }
262  /* Return error */
263  if (err == MTAR_ENULLRECORD) {
264    err = MTAR_ENOTFOUND;
265  }
266  return err;
267}
268
269
270int mtar_read_header(mtar_t *tar, mtar_header_t *h) {
271  int err;
272  mtar_raw_header_t rh;
273  /* Save header position */
274  tar->last_header = tar->pos;
275  /* Read raw header */
276  err = tread(tar, &rh, sizeof(rh));
277  if (err) {
278    return err;
279  }
280  /* Seek back to start of header */
281  err = mtar_seek(tar, tar->last_header);
282  if (err) {
283    return err;
284  }
285  /* Load raw header into header struct and return */
286  return raw_to_header(h, &rh);
287}
288
289
290int mtar_read_data(mtar_t *tar, void *ptr, unsigned size) {
291  int err;
292  /* If we have no remaining data then this is the first read, we get the size,
293   * set the remaining data and seek to the beginning of the data */
294  if (tar->remaining_data == 0) {
295    mtar_header_t h;
296    /* Read header */
297    err = mtar_read_header(tar, &h);
298    if (err) {
299      return err;
300    }
301    /* Seek past header and init remaining data */
302    err = mtar_seek(tar, tar->pos + sizeof(mtar_raw_header_t));
303    if (err) {
304      return err;
305    }
306    tar->remaining_data = h.size;
307  }
308  /* Read data */
309  err = tread(tar, ptr, size);
310  if (err) {
311    return err;
312  }
313  tar->remaining_data -= size;
314  /* If there is no remaining data we've finished reading and seek back to the
315   * header */
316  if (tar->remaining_data == 0) {
317    return mtar_seek(tar, tar->last_header);
318  }
319  return MTAR_ESUCCESS;
320}
321
322
323int mtar_write_header(mtar_t *tar, const mtar_header_t *h) {
324  mtar_raw_header_t rh;
325  /* Build raw header and write */
326  header_to_raw(&rh, h);
327  tar->remaining_data = h->size;
328  return twrite(tar, &rh, sizeof(rh));
329}
330
331
332int mtar_write_file_header(mtar_t *tar, const char *name, unsigned size) {
333  mtar_header_t h;
334  /* Build header */
335  memset(&h, 0, sizeof(h));
336  strcpy(h.name, name);
337  h.size = size;
338  h.type = MTAR_TREG;
339  h.mode = 0664;
340  /* Write header */
341  return mtar_write_header(tar, &h);
342}
343
344
345int mtar_write_dir_header(mtar_t *tar, const char *name) {
346  mtar_header_t h;
347  /* Build header */
348  memset(&h, 0, sizeof(h));
349  strcpy(h.name, name);
350  h.type = MTAR_TDIR;
351  h.mode = 0775;
352  /* Write header */
353  return mtar_write_header(tar, &h);
354}
355
356
357int mtar_write_data(mtar_t *tar, const void *data, unsigned size) {
358  int err;
359  /* Write data */
360  err = twrite(tar, data, size);
361  if (err) {
362    return err;
363  }
364  tar->remaining_data -= size;
365  /* Write padding if we've written all the data for this file */
366  if (tar->remaining_data == 0) {
367    return write_null_bytes(tar, round_up(tar->pos, 512) - tar->pos);
368  }
369  return MTAR_ESUCCESS;
370}
371
372
373int mtar_finalize(mtar_t *tar) {
374  /* Write two NULL records */
375  return write_null_bytes(tar, sizeof(mtar_raw_header_t) * 2);
376}