268 lines
6.2 KiB
C
268 lines
6.2 KiB
C
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#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) {
|
|
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 = malloc(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++) {
|
|
cur->fext[j] = dirs[i].filename[j+8];
|
|
}
|
|
cur->fext[4] = 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 cur, foff, byte_off, *chain = NULL;
|
|
uint16_t next;
|
|
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;
|
|
}
|
|
|
|
foff = (fsd->fs_ver == 12) ? (cur + (cur / 2)) : /* fat12 */
|
|
(cur * 2); /* fat16 */
|
|
byte_off = (fsd->ib.resv_sect * fsd->ib.sect_size) + foff;
|
|
|
|
/* XXX: endianness */
|
|
res = pread(fsd->ifd, &next, 2, byte_off);
|
|
if (res == -1) {
|
|
DPRINTF(("pread failed\n"));
|
|
free(chain);
|
|
return NULL;
|
|
}
|
|
|
|
if (fsd->fs_ver == 12) {
|
|
if (cur & 0x0001) {
|
|
next = next >> 4;
|
|
} else {
|
|
next = next & 0x0FFF;
|
|
}
|
|
if (next == 0x0FFF) {
|
|
break;
|
|
}
|
|
} else {
|
|
if (next == 0xFFFF) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
chain[len-1] = 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 -EINVAL;
|
|
}
|
|
|
|
/* 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);
|
|
}
|