zip-fuse

A (really bad) FUSE filesystem that mounts zip files. For fun exploring FUSE.

git clone https://code.pdelong.com/zip-fuse.git

  1#define FUSE_USE_VERSION 31
  2
  3#include <assert.h>
  4#include <fcntl.h>
  5#include <stdint.h>
  6#include <stdio.h>
  7#include <stdlib.h>
  8#include <string.h>
  9#include <sys/mman.h>
 10#include <sys/stat.h>
 11#include <unistd.h>
 12#include <stdbool.h>
 13
 14#include <zlib.h>
 15#include <fuse3/fuse.h>
 16
 17struct EOCD {
 18  int16_t disk_number;
 19  int16_t dir_disk;
 20  int16_t dir_records_this_disk;
 21  int16_t dir_records;
 22  int32_t dir_size;
 23  int32_t dir_offset;
 24  int32_t comment_len;
 25
 26  char* comment;
 27};
 28
 29struct ExtraField{
 30  int16_t id;
 31  int16_t length;
 32  int8_t* data;
 33};
 34
 35struct CD {
 36  int16_t made_version;
 37  int16_t needed_version;
 38  int16_t flags;
 39  int16_t compress_method;
 40  int16_t mod_time;
 41  int16_t mod_date;
 42  int32_t uncompress_checksum;
 43  int32_t compress_size;
 44  int32_t uncompress_size;
 45  int16_t name_len;
 46  int16_t extra_field_len;
 47  int16_t comment_len;
 48  int16_t file_disk;
 49  int16_t internal_attrs;
 50  int32_t external_attrs;
 51  int32_t file_offset;
 52  int32_t num_extra_fields;
 53
 54  char* filename;
 55  char* comment;
 56
 57  struct ExtraField* extra_fields;
 58};
 59
 60struct File {
 61  int16_t needed_version;
 62  int16_t flags;
 63  int16_t compress_method;
 64  int16_t mod_time;
 65  int16_t mod_date;
 66  int32_t uncompress_checksum;
 67  int32_t compress_size;
 68  int32_t uncompress_size;
 69  int16_t name_len;
 70  int16_t extra_field_len;
 71  int32_t num_extra_fields;
 72
 73  char* filename;
 74  uint8_t* data;
 75
 76  struct ExtraField* extra_fields;
 77};
 78
 79uint16_t parse_le16(uint8_t* data) {
 80  return (data[0] << 0) | (data[1] << 8);
 81}
 82
 83uint32_t parse_le32(uint8_t* data) {
 84  return (data[0] << 0) | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);
 85}
 86
 87void eocd_free(struct EOCD* eocd) {
 88  free(eocd->comment);
 89}
 90
 91void extra_field_free(struct ExtraField ef) {
 92  free(ef.data);
 93}
 94
 95void cd_free(struct CD cd) {
 96  free(cd.filename);
 97  free(cd.comment);
 98
 99  for (int i = 0; i < cd.num_extra_fields; i++) {
100    extra_field_free(cd.extra_fields[i]);
101  }
102}
103
104void free_cds(struct CD* cds, size_t num_cds) {
105  for (int i = 0; i < num_cds; i++) {
106    cd_free(cds[i]);
107  }
108
109  free(cds);
110}
111
112void file_free(struct File f) {
113  free(f.filename);
114  free(f.data);
115
116  for (int i = 0; i < f.num_extra_fields; i++) {
117    extra_field_free(f.extra_fields[i]);
118  }
119}
120
121bool parse_eocd(uint8_t* file, size_t filelen, struct EOCD* eocd) {
122  uint8_t* eocd_start = NULL;
123  for (int i = filelen-4; i > 0; i--) {
124    if (parse_le32(&file[i]) == 0x6054B50) {
125      eocd_start = &file[i];
126      break;
127    }
128  }
129
130  if (eocd_start == NULL) {
131    return false;
132  }
133
134  eocd->disk_number = parse_le16(&eocd_start[4]);
135  eocd->dir_disk = parse_le16(&eocd_start[6]);
136  eocd->dir_records_this_disk = parse_le16(&eocd_start[8]);
137  eocd->dir_records = parse_le16(&eocd_start[10]);
138  eocd->dir_size = parse_le32(&eocd_start[12]);
139  eocd->dir_offset = parse_le32(&eocd_start[16]);
140  eocd->comment_len = parse_le16(&eocd_start[20]);
141
142  eocd->comment = malloc(eocd->comment_len+1);
143  memcpy(eocd->comment, &eocd_start[22], eocd->comment_len);
144  eocd->comment[eocd->comment_len] = '\0';
145
146  return true;
147}
148
149off_t parse_extra_field(uint8_t* data, struct ExtraField* ef) {
150  ef->id = parse_le16(&data[0]);
151  ef->length = parse_le16(&data[2]);
152  ef->data = malloc(ef->length);
153
154  memcpy(ef->data, &data[4], ef->length);
155
156  return ef->length + 4;
157}
158
159// Populates `cd` if possible and returns the size of the header, so the caller
160// can skip to the next one.
161//
162// If unable to populate, returns 01
163off_t parse_cd(uint8_t* data, struct CD* cd) {
164  if (parse_le32(&data[0]) != 0x02014b50) {
165    return -1;
166  }
167
168  cd->made_version = parse_le16(&data[4]);
169  cd->needed_version = parse_le16(&data[6]);
170  cd->flags = parse_le16(&data[8]);
171  cd->compress_method = parse_le16(&data[10]);
172  cd->mod_time = parse_le16(&data[12]);
173  cd->mod_date = parse_le16(&data[14]);
174  cd->uncompress_checksum = parse_le32(&data[16]);
175  cd->compress_size = parse_le32(&data[20]);
176  cd->uncompress_size = parse_le32(&data[24]);
177  cd->name_len = parse_le16(&data[28]);
178  cd->extra_field_len = parse_le16(&data[30]);
179  cd->comment_len = parse_le16(&data[32]);
180  cd->file_disk = parse_le16(&data[34]);
181  cd->internal_attrs = parse_le16(&data[36]);
182  cd->external_attrs = parse_le32(&data[38]);
183  cd->file_offset = parse_le32(&data[42]);
184  cd->num_extra_fields = 0;
185
186  cd->filename = malloc(cd->name_len+1);
187  memcpy(cd->filename, &data[46], cd->name_len);
188  cd->filename[cd->name_len] = '\0';
189
190  if (cd->extra_field_len > 0) {
191    cd->extra_fields = malloc(sizeof(struct ExtraField));
192    uint8_t* end = &data[46+cd->name_len+cd->extra_field_len];
193    uint8_t* curr = &data[46+cd->name_len];
194    int num_fields = 0;
195    int fields_cap = 1;
196    while (curr < end) {
197      if (num_fields >= fields_cap) {
198        fields_cap *= 2;
199        cd->extra_fields = realloc(cd->extra_fields, sizeof(struct ExtraField) * fields_cap);
200      }
201
202      off_t length = parse_extra_field(curr, &cd->extra_fields[num_fields++]);
203      if (length == -1) {
204        return -1;
205      }
206
207      cd->num_extra_fields++;
208      curr += length;
209    }
210  }
211
212  cd->comment = malloc(cd->comment_len+1);
213  memcpy(cd->comment, &data[46+cd->name_len+cd->extra_field_len], cd->comment_len);
214  cd->comment[cd->name_len] = '\0';
215
216
217  return 46+cd->name_len+cd->extra_field_len+cd->comment_len;
218}
219
220bool parse_cds(uint8_t *file, off_t cds_offset, size_t num_files,
221               struct CD **out_cds) {
222  struct CD *cds = calloc(num_files, sizeof(struct CD));
223
224  off_t offset = cds_offset;
225  for (int i = 0; i < num_files; i++) {
226    off_t new_off = parse_cd(&file[offset], &cds[i]);
227    if (new_off == -1) {
228      return false;
229    }
230    offset += new_off;
231  }
232
233  *out_cds = cds;
234  return true;
235}
236
237bool parse_file(uint8_t* data, struct File* f) {
238  if (parse_le32(&data[0]) != 0x04034b50) {
239    return false;
240  }
241
242  f->needed_version = parse_le16(&data[4]);
243  f->flags = parse_le16(&data[6]);
244  f->compress_method = parse_le16(&data[8]);
245  f->mod_time = parse_le16(&data[10]);
246  f->mod_date = parse_le16(&data[12]);
247  f->uncompress_checksum = parse_le32(&data[14]);
248  f->compress_size = parse_le32(&data[18]);
249  f->uncompress_size = parse_le32(&data[22]);
250  f->name_len = parse_le16(&data[26]);
251  f->extra_field_len = parse_le16(&data[28]);
252  f->num_extra_fields = 0;
253
254  f->filename = malloc(f->name_len+1);
255  memcpy(f->filename, &data[30], f->name_len);
256  f->filename[f->name_len] = '\0';
257
258  if (f->extra_field_len > 0) {
259    f->extra_fields = malloc(sizeof(struct ExtraField));
260    uint8_t* end = &data[30+f->name_len+f->extra_field_len];
261    uint8_t* curr = &data[30+f->name_len];
262    int num_fields = 0;
263    int fields_cap = 1;
264    while (curr < end) {
265      if (num_fields >= fields_cap) {
266        fields_cap *= 2;
267        f->extra_fields = realloc(f->extra_fields, sizeof(struct ExtraField) * fields_cap);
268      }
269
270      off_t length = parse_extra_field(curr, &f->extra_fields[num_fields++]);
271      if (length == -1) {
272        return -1;
273      }
274
275      f->num_extra_fields++;
276      curr += length;
277    }
278  }
279
280  f->data = malloc(f->compress_size);
281  memcpy(f->data, &data[30+f->name_len+f->extra_field_len], f->compress_size);
282
283  return true;
284}
285
286int parse_zip(char* filename, struct File** out_files, size_t* num_files) {
287  int retval = false;
288  int fd = open(filename, O_RDONLY);
289  if (fd == -1) {
290    perror("Opening file:");
291    goto error;
292  }
293
294  struct stat buf;
295  if (fstat(fd, &buf) == -1) {
296    perror("Statting file");
297    goto close;
298  }
299
300  uint8_t *file =
301      mmap(NULL, buf.st_size, PROT_READ, MAP_FILE | MAP_SHARED, fd, 0);
302  if (file == MAP_FAILED) {
303    perror("Mapping file");
304    goto close;
305  }
306
307  struct EOCD eocd;
308  if (!parse_eocd(file, buf.st_size, &eocd)) {
309    goto munmap;
310  }
311
312  struct CD *cds;
313  if (!parse_cds(file, eocd.dir_offset, eocd.dir_records, &cds)) {
314    goto eocd_free;
315  }
316
317  struct File* files = calloc(sizeof(struct File), eocd.dir_records);
318  for (int i = 0; i < eocd.dir_records; i++) {
319    if (!parse_file(&file[cds[i].file_offset], &files[i])) {
320      fprintf(stderr, "failed to parse file");
321      goto free_cds;
322    }
323  }
324
325  retval = true;
326  *out_files = files;
327  *num_files = eocd.dir_records;
328
329free_cds:
330  free_cds(cds, eocd.dir_records);
331eocd_free:
332  eocd_free(&eocd);
333munmap:
334  munmap(file, buf.st_size);
335close:
336  close(fd);
337error:
338
339  return true;
340}
341
342uint8_t* inflate_file(struct File f, size_t* out_length) {
343  uint8_t* buf = NULL;
344
345  if (f.compress_method == 0) {
346    buf = malloc(f.uncompress_size);
347    *out_length = f.compress_size;
348    memcpy(buf, f.data, f.compress_size);
349  } else if (f.compress_method == 8) {
350    buf = malloc(f.uncompress_size);
351    *out_length = f.uncompress_size;
352    z_stream strm;
353    strm.next_in = f.data;
354    strm.avail_in = f.compress_size;
355    strm.next_out = buf;
356    strm.avail_out = f.uncompress_size;
357
358    strm.zalloc = NULL;
359    strm.zfree = NULL;
360    strm.opaque = NULL;
361
362    int ret = inflateInit2(&strm, -MAX_WBITS);
363    if (ret < 0) {
364      fputs("Error encountered\n", stderr);
365      return NULL;
366    }
367
368    ret = inflate(&strm, Z_SYNC_FLUSH);
369    if (ret != Z_STREAM_END) {
370      fprintf(stderr, "Stream not finished: %d\n", ret);
371      return NULL;
372    }
373  } else {
374    fprintf(stderr, "Unknown compression: %d\n", f.compress_method);
375    return NULL;
376  }
377
378  return buf;
379}
380
381int main(int argc, char **argv) {
382  bool bad = false;
383
384  if (argc != 2) {
385    puts("usage: zip <FILE>");
386    return 1;
387  }
388
389  struct File *files;
390  size_t num_files;
391  if (!parse_zip(argv[1], &files, &num_files)) {
392    fprintf(stderr, "failed to parse zip\n");
393    return 1;
394  }
395
396  for (int i = 0; i < num_files; i++) {
397    struct File f = files[i];
398    size_t length;
399    uint8_t* buf = inflate_file(f, &length);
400
401    fwrite(buf, sizeof(uint8_t), length, stdout);
402  }
403
404  return 0;
405}