#include #include #include #include #include #include #include "dosfs.h" #include "debug.h" static unsigned int *read_fat_chain(dosfs_t *, unsigned int, int *); /* * Opens an image a la open(2). Returns 0 on success, or * some error number on failure. */ int open_image(const char *path, int flags, dosfs_t *fsd) { int res, ifd; if (fsd == NULL) return EINVAL; ifd = open(path, flags); if (ifd == -1) return errno; memset(&fsd->ib, 0, sizeof(struct bpb)); res = read(ifd, &fsd->ib, sizeof(struct bpb)); if (res == -1) { close(ifd); return errno; } if ((fsd->ib.jmp[0] != 0xEB || fsd->ib.jmp[2] != 0x90) && /* JMP rel8 */ fsd->ib.jmp[0] != 0xE9) { /* JMP rel16 */ DPRINTF(("hm... doesn't look like a FAT image?\n")); close(ifd); return EOPNOTSUPP; } if (fsd->ib.sect_ct == 0 || fsd->ib.fat_size == 0) { DPRINTF(("sect_ct or fat_size == 0, likely fat>32 (unsupported)\n")); close(ifd); return EOPNOTSUPP; } fsd->root_size = ((fsd->ib.dirent_ct * 32) + (fsd->ib.sect_size - 1)) / fsd->ib.sect_size; fsd->data_size = fsd->ib.sect_ct - (fsd->ib.resv_sect + (fsd->ib.fat_ct * fsd->ib.fat_size) + fsd->root_size); if (fsd->data_size < FAT12_MAX_CLUSTERS) { fsd->fs_ver = 12; } else if (fsd->data_size < FAT16_MAX_CLUSTERS) { fsd->fs_ver = 16; } else { DPRINTF(("data_size >= FAT12_MAX_CLUSTERS, likely fat>32 (unsupported)\n")); close(ifd); return EOPNOTSUPP; } fsd->ifd = ifd; fsd->root_loc = (fsd->ib.resv_sect + (fsd->ib.fat_ct * fsd->ib.fat_size)) * fsd->ib.sect_size; fsd->data_loc = fsd->root_loc + (fsd->root_size * fsd->ib.sect_size); return 0; } /* * Frees the file descriptor for a given image. */ void close_image(dosfs_t *fsd) { close(fsd->ifd); fsd->ifd = -1; } /* * Reads directory listing at given offset. Returns a pointer * to a dosfile_t on success, or NULL on failure (errno set). */ dosfile_t * dos_listdir(dosfs_t *fsd, unsigned int offset) { dosfile_t *start, *cur; struct dos_dirent *dirs = NULL; int i, j, res, root_ents; start = malloc(sizeof(dosfile_t)); if (start == NULL) { DPRINTF(("malloc failed\n")); return NULL; } /* * Not sure if we should be trusting the bpb for anything, * but relying on it here will be better than reading every * single entry one at a time. */ root_ents = (fsd->root_size * fsd->ib.sect_size) / sizeof(struct dos_dirent); res = reallocarr(&dirs, root_ents, sizeof(struct dos_dirent)); if (res == -1) { DPRINTF(("reallocarr failed\n")); return NULL; } res = pread(fsd->ifd, dirs, (root_ents * sizeof(struct dos_dirent)), offset); if (res == -1) { DPRINTF(("pread failed\n")); return NULL; } /* iterate through every dir entry */ cur = start; for (i = 0; i < root_ents; i++) { if (dirs[i].filename[0] == 0) { break; } if (i > 0) { cur->next = calloc(1, sizeof(dosfile_t)); if (cur->next == NULL) { DPRINTF(("inner malloc failed!?\n")); res = -1; break; } cur = cur->next; } for (j = 0; j < 8; j++) { if (dirs[i].filename[j] == ' ') { cur->fname[j] = 0; break; } else { cur->fname[j] = dirs[i].filename[j]; } } cur->fname[8] = 0; for (j = 0; j < 3; j++) { if (dirs[i].filename[j+8] == ' ') { cur->fext[j] = 0; break; } else { cur->fext[j] = dirs[i].filename[j+8]; } } cur->fext[3] = 0; (void)memcpy(&cur->ent, &dirs[i], sizeof(struct dos_dirent)); /* useful for get_byte_offset() */ cur->fat_chain = read_fat_chain(fsd, cur->ent.low_loc, &cur->fc_len); res = 0; } free(dirs); /* cleanup if something went wrong */ if (res != 0) { dos_freedir(start); return NULL; } return start; } /* * Frees a directory listing. */ void dos_freedir(dosfile_t *dirs) { dosfile_t *cur, *prev; cur = dirs; while (cur != NULL) { prev = cur; cur = cur->next; free(prev); } } static unsigned int * read_fat_chain(dosfs_t *fsd, unsigned int first, int *length) { unsigned int foff, byte_off, *chain = NULL; uint16_t next, cur; uint8_t nbuf[3]; int len = 0, res; cur = first; for (;;) { len++; res = reallocarr(&chain, len, sizeof(unsigned int)); if (res == -1) { DPRINTF(("reallocarr failed\n")); free(chain); return NULL; } /* get byte offset of FAT entry */ foff = (fsd->fs_ver == 12) ? ((cur / 2)*3) : /* fat12 */ (cur * 2); /* fat16 */ byte_off = (fsd->ib.resv_sect * fsd->ib.sect_size) + foff; res = pread(fsd->ifd, &nbuf, 3, byte_off); if (res == -1) { DPRINTF(("pread failed\n")); free(chain); return NULL; } if (fsd->fs_ver == 12) { if (cur % 2) { next = (nbuf[1] >> 4) | (uint16_t)(nbuf[2] << 4); } else { next = (uint16_t)(nbuf[1] & 0x0f) << 8 | nbuf[0]; } if (next == 0x0FFF) { break; } else if (next < 2 || next > 0xFF7) { DPRINTF(("got weird sector %x from %x, bailing...\n", next, cur)); free(chain); return NULL; } } else { next = nbuf[0] | (uint16_t)nbuf[1] << 8; if (next == 0xFFFF) { break; } } chain[len-1] = next; cur = next; } *length = len; return chain; } /* * Converts the byte offset of a file into the "real" * offset, i.e. one that can be used with lseek()/pread(). */ int get_byte_offset(dosfs_t *fsd, dosfile_t *file, unsigned int f_offset) { int coff, csz; if (f_offset > file->ent.size) { DPRINTF(("offset requested is out of bounds!\n")); return -ERANGE; } /* size in bytes of a cluster */ csz = fsd->ib.sect_per_cluster * fsd->ib.sect_size; /* get the offset in the fat chain */ coff = f_offset / csz; if (coff > file->fc_len) { DPRINTF(("offset is within filesize, but not enough clusters??\n")); return -EINVAL; } return (file->fat_chain[coff] * csz) + (f_offset % csz); }