/* lib9660: a simple ISO9660 reader library especially suited to embedded * systems * * SPDX-License-Identifier: LicenseRef-ISC1 * SPDX-FileCopyrightText: © 2014 Erin Shepherd * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice appears in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #define SEEK_END L9660_SEEK_END #define SEEK_SET L9660_SEEK_SET #define SEEK_CUR L9660_SEEK_CUR #define DENT_EXISTS (1 << 0) #define DENT_ISDIR (1 << 1) #define DENT_ASSOCIATED (1 << 2) #define DENT_RECORD (1 << 3) #define DENT_PROTECTION (1 << 4) #define DENT_MULTIEXTENT (1 << 5) #define PVD(vdesc) ((l9660_vdesc_primary*)(vdesc)) #ifdef L9660_BIG_ENDIAN #define READ16(v) (((v).be[1]) | ((v).be[0] << 8)) #define READ32(v) (((v).be[3]) | ((v).be[2] << 8) | ((v).be[1]) << 16 | ((v).be[0] << 24)) #else #define READ16(v) (((v).le[0]) | ((v).le[1] << 8)) #define READ32(v) (((v).le[0]) | ((v).le[1] << 8) | ((v).le[2]) << 16 | ((v).le[3] << 24)) #endif #define HAVEBUFFER(f) (true) #define BUF(f) ((f)->buf) #define ROCKRIDGE_FILENAME_MAX 0xFF #define NM_CONTINUE 0x01 #define NM_CURRENT 0x02 #define NM_PARENT 0x04 static char* strchrnul(const char* s, int c) { while (*s && *s != c) s++; return (char*)s; } static inline uint16_t fsectoff(l9660_file* f) { return f->position % 2048; } static inline uint32_t fsector(l9660_file* f) { return f->position / 2048; } static inline uint32_t fnextsectpos(l9660_file* f) { return (f->position + 2047) & ~2047; } const char* l9660_get_rockridge_name(l9660_dirent* dirent, char* out, size_t* out_len) { uint8_t* ptr = (uint8_t*)dirent + sizeof(*dirent) + dirent->name_len; if (!(dirent->name_len & 1)) ptr++; uint8_t* end = (uint8_t*)dirent + dirent->length; size_t total = 0; bool found = false; while (ptr + sizeof(l9660_su_field) <= end) { l9660_su_field* sufield = (l9660_su_field*)ptr; if (sufield->len < 5 || ptr + sufield->len > end) break; if (sufield->sign[0] == 'N' && sufield->sign[1] == 'M') { uint8_t flags = sufield->data[0]; if ((flags & NM_CURRENT)) { out[0] = '.'; *out_len = 1; return out; } if ((flags & NM_PARENT)) { out[0] = '.'; out[1] = '.'; *out_len = 2; return out; } size_t part_len = sufield->len - 5; if (total + part_len > ROCKRIDGE_FILENAME_MAX) part_len = ROCKRIDGE_FILENAME_MAX - total; memcpy(out + total, &sufield->data[1], part_len); total += part_len; found = true; if (total < ROCKRIDGE_FILENAME_MAX) out[total] = '\0'; else out[ROCKRIDGE_FILENAME_MAX - 1] = '\0'; if (!(flags & NM_CONTINUE)) break; } ptr += sufield->len; } if (!found) return NULL; *out_len = total; return out; } l9660_status l9660_openfs(l9660_fs* fs, bool (*read_sector)(l9660_fs* fs, void* buf, uint32_t sector)) { fs->read_sector = read_sector; #ifndef L9660_SINGLEBUFFER l9660_vdesc_primary* pvd = PVD(&fs->pvd); #else last_file = NULL; l9660_vdesc_primary* pvd = PVD(gbuf); #endif uint32_t idx = 0x10; for (;;) { // Read next sector if (!read_sector(fs, pvd, idx)) return L9660_EIO; // Validate magic if (memcmp(pvd->hdr.magic, "CD001", 5) != 0) return L9660_EBADFS; if (pvd->hdr.type == 1) break; // Found PVD else if (pvd->hdr.type == 255) return L9660_EBADFS; } #ifdef L9660_SINGLEBUFFER memcpy(&fs->root_dir_ent, &pvd->root_dir_ent, pvd->root_dir_ent.length); #endif return L9660_OK; } l9660_status l9660_fs_open_root(l9660_dir* dir, l9660_fs* fs) { l9660_file* f = &dir->file; #ifndef L9660_SINGLEBUFFER l9660_dirent* dirent = &PVD(&fs->pvd)->rde.root_dir_ent; #else l9660_dirent* dirent = &fs->root_dir_ent; #endif f->fs = fs; f->first_sector = READ32(dirent->sector); f->length = READ32(dirent->size); f->position = 0; return L9660_OK; } static l9660_status buffer(l9660_file* f) { #ifdef L9660_SINGLEBUFFER last_file = f; #endif if (!f->fs->read_sector(f->fs, BUF(f), f->first_sector + f->position / 2048)) return L9660_EIO; else return L9660_OK; } static l9660_status prebuffer(l9660_file* f) { if (!HAVEBUFFER(f) || (f->position % 2048) == 0) return buffer(f); else return L9660_OK; } static l9660_status openat_raw(l9660_file* child, l9660_dir* parent, const char* name, bool isdir) { char rr_namebuf[ROCKRIDGE_FILENAME_MAX]; l9660_status rv; l9660_dirent* dent = NULL; size_t dent_rr_name_len = 0; if ((rv = l9660_seekdir(parent, 0))) return rv; do { const char* seg = name; name = strchrnul(name, '/'); size_t seglen = name - seg; /* ISO9660 stores '.' as '\0' */ if (seglen == 1 && *seg == '.') seg = "\0"; /* ISO9660 stores ".." as '\1' */ if (seglen == 2 && seg[0] == '.' && seg[1] == '.') { seg = "\1"; seglen = 1; } for (;;) { if ((rv = l9660_readdir(parent, &dent))) return rv; /* EOD */ if (!dent) return L9660_ENOENT; const char* rockridge_name = l9660_get_rockridge_name(dent, rr_namebuf, &dent_rr_name_len); if (rockridge_name == NULL) { /* wrong length */ if (seglen != dent->name_len) continue; /* check name */ if (memcmp(seg, dent->name, seglen) != 0) continue; /* check for a revision tag */ if (dent->name_len > seglen && dent->name[seglen] != ';') continue; /* all tests pass */ break; } else { if (seglen == dent_rr_name_len && memcmp(seg, rockridge_name, seglen) == 0) break; continue; } } child->fs = parent->file.fs; child->first_sector = READ32(dent->sector); child->length = READ32(dent->size); child->position = 0; if (*name && (dent->flags & DENT_ISDIR) == 0) return L9660_ENOTDIR; parent = (l9660_dir*)child; if (*name == '/') name++; } while (*name); if (isdir) { if ((dent->flags & DENT_ISDIR) == 0) return L9660_ENOTDIR; } else { if ((dent->flags & DENT_ISDIR) != 0) return L9660_ENOTFILE; } return L9660_OK; } l9660_status l9660_opendirat(l9660_dir* dir, l9660_dir* parent, const char* path) { return openat_raw(&dir->file, parent, path, true); } static inline unsigned aligneven(unsigned v) { return v + (v & 1); } l9660_status l9660_readdir(l9660_dir* dir, l9660_dirent** pdirent) { l9660_status rv; l9660_file* f = &dir->file; rebuffer: if (f->position >= f->length) { *pdirent = NULL; return L9660_OK; } if ((rv = prebuffer(f))) return rv; char* off = BUF(f) + fsectoff(f); if (*off == 0) { // Padded end of sector f->position = fnextsectpos(f); goto rebuffer; } l9660_dirent* dirent = (l9660_dirent*)off; f->position += aligneven(dirent->length); *pdirent = dirent; return L9660_OK; } l9660_status l9660_openat(l9660_file* child, l9660_dir* parent, const char* name) { return openat_raw(child, parent, name, false); } /*! Seek the file to \p offset from \p whence */ l9660_status l9660_seek(l9660_file* f, int whence, int32_t offset) { l9660_status rv; uint32_t cursect = fsector(f); switch (whence) { case SEEK_SET: f->position = offset; break; case SEEK_CUR: f->position = f->position + offset; break; case SEEK_END: f->position = f->length - offset; break; } if (fsector(f) != cursect && fsectoff(f) != 0) { if ((rv = buffer(f))) return rv; } return L9660_OK; } uint32_t l9660_tell(l9660_file* f) { return f->position; } l9660_status l9660_read(l9660_file* f, void* buf, size_t size, size_t* read) { l9660_status rv; if ((rv = prebuffer(f))) return rv; uint16_t rem = 2048 - fsectoff(f); if (rem > f->length - f->position) rem = f->length - f->position; if (rem < size) size = rem; memcpy(buf, BUF(f) + fsectoff(f), size); *read = size; f->position += size; return L9660_OK; }