fk98/src/dosfs.c
2020-11-13 21:43:35 -08:00

273 lines
6.1 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"
/*
* 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));
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;
}
/*
* TODO: reads a file to a given buffer
*/
int
dos_fread(dosfs_t *fsd, dosfile_t *file, void *buf, int max_size)
{
unsigned int *chain = NULL;
int clen = 0, i, left, chunk, cbytes, off, res;
chain = read_fat_chain(fsd, file->ent.low_loc, &clen);
if (chain == NULL) {
return -1; /* XXX */
}
cbytes = fsd->ib.sect_size * fsd->ib.sect_per_cluster;
left = max_size;
for (i = 0; i < clen; i++) {
/* only copy as much as we can hold */
chunk = (left < cbytes) ? left : cbytes;
res = pread(fsd->ifd, buf + (i * cbytes),
chunk, fsd->data_loc + (chain[i] * cbytes));
if (res == -1) {
DPRINTF("pread failed\n");
free(chain);
return -1;
}
left -= chunk;
if (left <= 0)
break;
}
return 0;
}