pathinfo improvements - ctime, atime, mtime support added
[libogc.git] / libdi / di_iso9660.c
blob15d19a366146fe75ed40956470a24c68f7a10c29
1 /*
3 di_iso9660.c -- an ISO9660 DVD devoptab library for the Wii
5 Copyright (C) 2008 Joseph Jordan <joe.ftpii@psychlaw.com.au>
7 This software is provided 'as-is', without any express or implied warranty.
8 In no event will the authors be held liable for any damages arising from
9 the use of this software.
11 Permission is granted to anyone to use this software for any purpose,
12 including commercial applications, and to alter it and redistribute it
13 freely, subject to the following restrictions:
15 1.The origin of this software must not be misrepresented; you must not
16 claim that you wrote the original software. If you use this software in a
17 product, an acknowledgment in the product documentation would be
18 appreciated but is not required.
20 2.Altered source versions must be plainly marked as such, and must not be
21 misrepresented as being the original software.
23 3.This notice may not be removed or altered from any source distribution.
27 #include <errno.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <sys/dir.h>
32 #include <sys/iosupport.h>
34 #include <ogcsys.h>
35 #include <ogc/lwp_watchdog.h>
36 #include <ogc/mutex.h>
38 #include <di/di.h>
39 #include <di/di_iso9660.h>
41 #define DEVICE_NAME "dvd"
43 #define FLAG_DIR 2
44 #define DIR_SEPARATOR '/'
45 #define SECTOR_SIZE 0x800
46 #define BUFFER_SIZE 0x8000
48 typedef struct {
49 u8 name_length;
50 u8 extended_sectors;
51 u32 sector;
52 u16 parent;
53 char name[ISO_MAXPATHLEN];
54 } __attribute__((packed)) PATHTABLE_ENTRY;
56 typedef struct PATH_ENTRY_STRUCT {
57 PATHTABLE_ENTRY table_entry;
58 u16 index;
59 u32 childCount;
60 struct PATH_ENTRY_STRUCT *children;
61 } PATH_ENTRY;
63 typedef struct DIR_ENTRY_STRUCT {
64 char name[ISO_MAXPATHLEN];
65 u32 sector;
66 u32 size;
67 u8 flags;
68 u32 fileCount;
69 PATH_ENTRY *path_entry;
70 struct DIR_ENTRY_STRUCT *children;
71 } DIR_ENTRY;
73 typedef struct {
74 DIR_ENTRY entry;
75 u32 offset;
76 bool inUse;
77 } FILE_STRUCT;
79 typedef struct {
80 DIR_ENTRY entry;
81 u32 index;
82 bool inUse;
83 } DIR_STATE_STRUCT;
85 static mutex_t _mutex = 0;
86 static u8 read_buffer[BUFFER_SIZE] __attribute__((aligned(32)));
87 static u8 cluster_buffer[BUFFER_SIZE] __attribute__((aligned(32)));
88 static u32 cache_start = 0;
89 static u32 cache_sectors = 0;
91 static PATH_ENTRY *root = NULL;
92 static PATH_ENTRY *current = NULL;
93 static bool unicode = false;
94 static u64 last_access = 0;
95 static s32 dotab_device = -1;
97 static bool is_dir(DIR_ENTRY *entry) {
98 return entry->flags & FLAG_DIR;
101 #define OFFSET_EXTENDED 1
102 #define OFFSET_SECTOR 6
103 #define OFFSET_SIZE 14
104 #define OFFSET_FLAGS 25
105 #define OFFSET_NAMELEN 32
106 #define OFFSET_NAME 33
108 // lock mutex before calling
109 static int _read(void *ptr, u64 offset, size_t len) {
110 u32 sector = offset / SECTOR_SIZE;
111 u32 end_sector = (offset + len - 1) / SECTOR_SIZE;
112 u32 sectors = MIN(BUFFER_SIZE / SECTOR_SIZE, end_sector - sector + 1);
113 u32 sector_offset = offset % SECTOR_SIZE;
114 len = MIN(BUFFER_SIZE - sector_offset, len);
116 if (cache_sectors && sector >= cache_start && (sector + sectors) <= (cache_start + cache_sectors)) {
117 memcpy(ptr, read_buffer + (sector - cache_start) * SECTOR_SIZE + sector_offset, len);
118 return len;
121 if (DI_ReadDVD(read_buffer, BUFFER_SIZE / SECTOR_SIZE, sector)) {
122 last_access = gettime();
123 cache_sectors = 0;
124 return -1;
127 last_access = gettime();
128 cache_start = sector;
129 cache_sectors = BUFFER_SIZE / SECTOR_SIZE;
130 memcpy(ptr, read_buffer + sector_offset, len);
131 return len;
134 static int read_entry(DIR_ENTRY *entry, u8 *buf) {
135 u8 extended_sectors = buf[OFFSET_EXTENDED];
136 u32 sector = *(u32 *)(buf + OFFSET_SECTOR) + extended_sectors;
137 u32 size = *(u32 *)(buf + OFFSET_SIZE);
138 u8 flags = buf[OFFSET_FLAGS];
139 u8 namelen = buf[OFFSET_NAMELEN];
141 if (namelen == 1 && buf[OFFSET_NAME] == 1) {
142 // ..
143 } else if (namelen == 1 && !buf[OFFSET_NAME]) {
144 entry->sector = sector;
145 entry->size = size;
146 entry->flags = flags;
147 } else {
148 DIR_ENTRY *newChildren = realloc(entry->children, sizeof(DIR_ENTRY) * (entry->fileCount + 1));
149 if (!newChildren) return -1;
150 bzero(newChildren + entry->fileCount, sizeof(DIR_ENTRY));
151 entry->children = newChildren;
152 DIR_ENTRY *child = &entry->children[entry->fileCount++];
153 child->sector = sector;
154 child->size = size;
155 child->flags = flags;
156 char *name = child->name;
157 if (unicode) {
158 u32 i;
159 for (i = 0; i < (namelen / 2); i++) name[i] = buf[OFFSET_NAME + i * 2 + 1];
160 name[i] = '\x00';
161 namelen = i;
162 } else {
163 memcpy(name, buf + OFFSET_NAME, namelen);
164 name[namelen] = '\x00';
166 if (!(flags & FLAG_DIR) && namelen >= 2 && name[namelen - 2] == ';') name[namelen - 2] = '\x00';
169 return *buf;
172 static bool read_directory(DIR_ENTRY *dir_entry, PATH_ENTRY *path_entry) {
173 u32 sector = path_entry->table_entry.sector;
174 u32 remaining = 0;
175 u32 sector_offset = 0;
177 LWP_MutexLock(_mutex);
178 do {
179 if (_read(cluster_buffer, (u64)sector * SECTOR_SIZE + sector_offset, (SECTOR_SIZE - sector_offset)) != (SECTOR_SIZE - sector_offset)) {
180 LWP_MutexUnlock(_mutex);
181 return false;
184 int offset = read_entry(dir_entry, cluster_buffer);
185 if (offset == -1) {
186 LWP_MutexUnlock(_mutex);
187 return false;
190 if (!remaining) {
191 remaining = dir_entry->size;
192 dir_entry->path_entry = path_entry;
195 sector_offset += offset;
196 if (sector_offset >= SECTOR_SIZE || !cluster_buffer[offset]) {
197 remaining -= SECTOR_SIZE;
198 sector_offset = 0;
199 sector++;
201 } while (remaining > 0);
202 LWP_MutexUnlock(_mutex);
204 return true;
207 static bool path_entry_from_path(PATH_ENTRY *path_entry, const char *path) {
208 bool found = false;
209 bool notFound = false;
210 const char *pathPosition = path;
211 const char *pathEnd = strchr(path, '\0');
212 PATH_ENTRY *entry = root;
213 while (pathPosition[0] == DIR_SEPARATOR) pathPosition++;
214 if (pathPosition >= pathEnd) found = true;
215 PATH_ENTRY *dir = entry;
216 while (!found && !notFound) {
217 const char *nextPathPosition = strchr(pathPosition, DIR_SEPARATOR);
218 size_t dirnameLength;
219 if (nextPathPosition != NULL) dirnameLength = nextPathPosition - pathPosition;
220 else dirnameLength = strlen(pathPosition);
221 if (dirnameLength >= ISO_MAXPATHLEN) return false;
223 u32 childIndex = 0;
224 while (childIndex < dir->childCount && !found && !notFound) {
225 entry = &dir->children[childIndex];
226 if (dirnameLength == strnlen(entry->table_entry.name, ISO_MAXPATHLEN - 1) && !strncasecmp(pathPosition, entry->table_entry.name, dirnameLength)) found = true;
227 if (!found) childIndex++;
230 if (childIndex >= dir->childCount) {
231 notFound = true;
232 found = false;
233 } else if (!nextPathPosition || nextPathPosition >= pathEnd) {
234 found = true;
235 } else {
236 dir = entry;
237 pathPosition = nextPathPosition;
238 while (pathPosition[0] == DIR_SEPARATOR) pathPosition++;
239 if (pathPosition >= pathEnd) found = true;
240 else found = false;
244 if (found) memcpy(path_entry, entry, sizeof(PATH_ENTRY));
245 return found;
248 static bool find_in_directory(DIR_ENTRY *dir_entry, PATH_ENTRY *parent, const char *base) {
249 u32 nl = strlen(base);
251 // there will be no basename if we are looking for root
252 if (!nl) {
253 return read_directory(dir_entry, parent);
256 // check directories i know about first
257 u32 childIndex;
258 for (childIndex = 0; childIndex < parent->childCount; childIndex++) {
259 PATH_ENTRY *child = parent->children + childIndex;
260 if (nl == strnlen(child->table_entry.name, ISO_MAXPATHLEN - 1) && !strncasecmp(base, child->table_entry.name, nl)) {
261 // found the thing we're after and it is a directory
262 // read it into dir_entry, and return true
263 return read_directory(dir_entry, child);
267 // read ourselves into a DIR_ENTRY, look into children for matching file
268 if (!read_directory(dir_entry, parent)) return false;
269 for (childIndex = 0; childIndex < dir_entry->fileCount; childIndex++) {
270 DIR_ENTRY *child = dir_entry->children + childIndex;
271 if (nl == strnlen(child->name, ISO_MAXPATHLEN - 1) && !strncasecmp(base, child->name, nl)) {
272 // found the thing we're after and it is a file
273 // stick it in dir_entry, and return true
274 memcpy(dir_entry, child, sizeof(DIR_ENTRY));
275 return true;
279 return false;
282 // result gets alloced, dont forget to free it
283 static char *dirname(char *path) {
284 size_t i, j;
285 char *result = strdup(path);
287 j = strlen(result) - 1;
289 if (j < 0)
290 return result;
292 for (i = j; i >= 0; i--)
293 if (result[i] == DIR_SEPARATOR) {
294 result[i] = 0;
295 return result;
298 result[0] = 0;
299 return result;
302 static char *basename(char *path) {
303 s32 i;
304 for (i = strlen(path) - 1; i >= 0; i--) {
305 if (path[i] == DIR_SEPARATOR) {
306 return path + i + 1;
309 return path;
312 static bool invalid_drive_specifier(const char *path) {
313 if (strchr(path, ':') == NULL) return false;
314 int namelen = strlen(DEVICE_NAME);
315 if (!strncmp(DEVICE_NAME, path, namelen) && path[namelen] == ':') return false;
316 return true;
319 static bool entry_from_path(DIR_ENTRY *dir_entry, const char *const_path) {
320 bzero(dir_entry, sizeof(DIR_ENTRY));
322 if (invalid_drive_specifier(const_path)) return false;
324 // get rid of drive specifier
325 if (strchr(const_path, ':') != NULL) const_path = strchr(const_path, ':') + 1;
327 char path[strlen(const_path) + 1];
328 strcpy(path, const_path);
330 // strip trailing slashes except for root
331 u32 len = strlen(path);
332 while (len > 1 && path[len - 1] == DIR_SEPARATOR) {
333 path[--len] = '\x00';
337 char *dir = dirname(path);
338 char *base = basename(path);
340 PATH_ENTRY parent_entry;
341 if (!path_entry_from_path(&parent_entry, dir)) {
342 free(dir);
343 return false;
345 free(dir);
347 bool found = find_in_directory(dir_entry, &parent_entry, base);
348 if (!found && dir_entry->children) free(dir_entry->children);
349 return found;
352 static int _ISO9660_open_r(struct _reent *r, void *fileStruct, const char *path, int flags, int mode) {
353 FILE_STRUCT *file = (FILE_STRUCT *)fileStruct;
354 DIR_ENTRY entry;
355 if (!entry_from_path(&entry, path)) {
356 r->_errno = ENOENT;
357 return -1;
358 } else if (is_dir(&entry)) {
359 if (entry.children) free(entry.children);
360 r->_errno = EISDIR;
361 return -1;
364 memcpy(&file->entry, &entry, sizeof(DIR_ENTRY));
365 file->offset = 0;
366 file->inUse = true;
368 return (int)file;
371 static int _ISO9660_close_r(struct _reent *r, int fd) {
372 FILE_STRUCT *file = (FILE_STRUCT *)fd;
373 if (!file->inUse) {
374 r->_errno = EBADF;
375 return -1;
377 file->inUse = false;
378 return 0;
381 static int _ISO9660_read_r(struct _reent *r, int fd, char *ptr, size_t len) {
382 FILE_STRUCT *file = (FILE_STRUCT *)fd;
383 if (!file->inUse) {
384 r->_errno = EBADF;
385 return -1;
387 if (file->offset >= file->entry.size) {
388 r->_errno = EOVERFLOW;
389 return 0;
391 if (len + file->offset > file->entry.size) {
392 r->_errno = EOVERFLOW;
393 len = file->entry.size - file->offset;
395 if (len <= 0) {
396 return 0;
399 u64 offset = file->entry.sector * SECTOR_SIZE + file->offset;
401 LWP_MutexLock(_mutex);
402 if ((len = _read(ptr, offset, len)) < 0) {
403 LWP_MutexUnlock(_mutex);
404 r->_errno = EIO;
405 return -1;
407 LWP_MutexUnlock(_mutex);
409 file->offset += len;
411 return len;
414 static off_t _ISO9660_seek_r(struct _reent *r, int fd, off_t pos, int dir) {
415 FILE_STRUCT *file = (FILE_STRUCT *)fd;
416 if (!file->inUse) {
417 r->_errno = EBADF;
418 return -1;
421 int position;
423 switch (dir) {
424 case SEEK_SET:
425 position = pos;
426 break;
427 case SEEK_CUR:
428 position = file->offset + pos;
429 break;
430 case SEEK_END:
431 position = file->entry.size + pos;
432 break;
433 default:
434 r->_errno = EINVAL;
435 return -1;
438 if (pos > 0 && position < 0) {
439 r->_errno = EOVERFLOW;
440 return -1;
443 if (position < 0 || position > file->entry.size) {
444 r->_errno = EINVAL;
445 return -1;
448 file->offset = position;
450 return position;
453 static void stat_entry(DIR_ENTRY *entry, struct stat *st) {
454 st->st_dev = 69;
455 st->st_ino = (ino_t)entry->sector;
456 st->st_mode = (is_dir(entry) ? S_IFDIR : S_IFREG) | (S_IRUSR | S_IRGRP | S_IROTH);
457 st->st_nlink = 1;
458 st->st_uid = 1;
459 st->st_gid = 2;
460 st->st_rdev = st->st_dev;
461 st->st_size = entry->size;
462 st->st_atime = 0;
463 st->st_spare1 = 0;
464 st->st_mtime = 0;
465 st->st_spare2 = 0;
466 st->st_ctime = 0;
467 st->st_spare3 = 0;
468 st->st_blksize = SECTOR_SIZE;
469 st->st_blocks = (entry->size + SECTOR_SIZE - 1) / SECTOR_SIZE;
470 st->st_spare4[0] = 0;
471 st->st_spare4[1] = 0;
474 static int _ISO9660_fstat_r(struct _reent *r, int fd, struct stat *st) {
475 FILE_STRUCT *file = (FILE_STRUCT *)fd;
476 if (!file->inUse) {
477 r->_errno = EBADF;
478 return -1;
480 stat_entry(&file->entry, st);
481 return 0;
484 static int _ISO9660_stat_r(struct _reent *r, const char *path, struct stat *st) {
485 DIR_ENTRY entry;
486 if (!entry_from_path(&entry, path)) {
487 r->_errno = ENOENT;
488 return -1;
490 stat_entry(&entry, st);
491 if (entry.children) free(entry.children);
492 return 0;
495 static int _ISO9660_chdir_r(struct _reent *r, const char *path) {
496 DIR_ENTRY entry;
497 if (!entry_from_path(&entry, path)) {
498 r->_errno = ENOENT;
499 return -1;
500 } else if (!is_dir(&entry)) {
501 r->_errno = ENOTDIR;
502 return -1;
504 current = entry.path_entry;
505 if (entry.children) free(entry.children);
506 return 0;
509 static DIR_ITER *_ISO9660_diropen_r(struct _reent *r, DIR_ITER *dirState, const char *path) {
510 DIR_STATE_STRUCT *state = (DIR_STATE_STRUCT *)(dirState->dirStruct);
511 if (!entry_from_path(&state->entry, path)) {
512 r->_errno = ENOENT;
513 return NULL;
514 } else if (!is_dir(&state->entry)) {
515 r->_errno = ENOTDIR;
516 return NULL;
518 state->index = 0;
519 state->inUse = true;
520 return dirState;
523 static int _ISO9660_dirreset_r(struct _reent *r, DIR_ITER *dirState) {
524 DIR_STATE_STRUCT *state = (DIR_STATE_STRUCT *)(dirState->dirStruct);
525 if (!state->inUse) {
526 r->_errno = EBADF;
527 return -1;
529 state->index = 0;
530 return 0;
533 static int _ISO9660_dirnext_r(struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *st) {
534 DIR_STATE_STRUCT *state = (DIR_STATE_STRUCT *)(dirState->dirStruct);
535 if (!state->inUse) {
536 r->_errno = EBADF;
537 return -1;
539 if (state->index >= state->entry.fileCount) {
540 r->_errno = ENOENT;
541 return -1;
543 DIR_ENTRY *entry = &state->entry.children[state->index++];
544 strncpy(filename, entry->name, ISO_MAXPATHLEN - 1);
545 stat_entry(entry, st);
546 return 0;
549 static int _ISO9660_dirclose_r(struct _reent *r, DIR_ITER *dirState) {
550 DIR_STATE_STRUCT *state = (DIR_STATE_STRUCT *)(dirState->dirStruct);
551 if (!state->inUse) {
552 r->_errno = EBADF;
553 return -1;
555 state->inUse = false;
556 if (state->entry.children) free(state->entry.children);
557 return 0;
560 static const devoptab_t dotab_iso9660 = {
561 DEVICE_NAME,
562 sizeof(FILE_STRUCT),
563 _ISO9660_open_r,
564 _ISO9660_close_r,
565 NULL,
566 _ISO9660_read_r,
567 _ISO9660_seek_r,
568 _ISO9660_fstat_r,
569 _ISO9660_stat_r,
570 NULL,
571 NULL,
572 _ISO9660_chdir_r,
573 NULL,
574 NULL,
575 sizeof(DIR_STATE_STRUCT),
576 _ISO9660_diropen_r,
577 _ISO9660_dirreset_r,
578 _ISO9660_dirnext_r,
579 _ISO9660_dirclose_r,
580 NULL
583 typedef struct {
584 char id[8];
585 char system_id[32];
586 char volume_id[32];
587 char zero[8];
588 unsigned long total_sector_le, total_sect_be;
589 char zero2[32];
590 unsigned long volume_set_size, volume_seq_nr;
591 unsigned short sector_size_le, sector_size_be;
592 unsigned long path_table_len_le, path_table_len_be;
593 unsigned long path_table_le, path_table_2nd_le;
594 unsigned long path_table_be, path_table_2nd_be;
595 u8 root[34];
596 char volume_set_id[128], publisher_id[128], data_preparer_id[128], application_id[128];
597 char copyright_file_id[37], abstract_file_id[37], bibliographical_file_id[37];
598 } __attribute__((packed)) VOLUME_DESCRIPTOR;
600 static VOLUME_DESCRIPTOR *read_volume_descriptor(u8 descriptor) {
601 u8 sector;
603 LWP_MutexLock(_mutex);
604 for (sector = 16; sector < 32; sector++) {
605 if (DI_ReadDVD(read_buffer, 1, sector)) {
606 LWP_MutexUnlock(_mutex);
607 return NULL;
610 if (!memcmp(read_buffer + 1, "CD001\1", 6)) {
611 if (*read_buffer == descriptor) {
612 LWP_MutexUnlock(_mutex);
613 return (VOLUME_DESCRIPTOR *)read_buffer;
614 } else {
615 if (*read_buffer == 0xff) {
616 LWP_MutexUnlock(_mutex);
617 return NULL;
623 LWP_MutexUnlock(_mutex);
625 return NULL;
628 static PATH_ENTRY *entry_from_index(PATH_ENTRY *entry, u16 index) {
629 if (entry->index == index) return entry;
630 u32 i;
631 for (i = 0; i < entry->childCount; i++) {
632 PATH_ENTRY *match = entry_from_index(&entry->children[i], index);
633 if (match) return match;
635 return NULL;
638 static PATH_ENTRY *add_child_entry(PATH_ENTRY *dir) {
639 PATH_ENTRY *newChildren = realloc(dir->children, (dir->childCount + 1) * sizeof(PATH_ENTRY));
640 if (!newChildren) return NULL;
641 bzero(newChildren + dir->childCount, sizeof(PATH_ENTRY));
642 dir->children = newChildren;
643 PATH_ENTRY *child = &dir->children[dir->childCount++];
644 return child;
647 static bool read_directories() {
648 VOLUME_DESCRIPTOR *volume = read_volume_descriptor(2);
649 if (volume) unicode = true;
650 else if (!(volume = read_volume_descriptor(1))) return false;
652 if (!(root = malloc(sizeof(PATH_ENTRY)))) return false;
653 bzero(root, sizeof(PATH_ENTRY));
654 root->table_entry.name_length = 1;
655 root->table_entry.extended_sectors = volume->root[OFFSET_EXTENDED];
656 root->table_entry.sector = *(u32 *)(volume->root + OFFSET_SECTOR);
657 root->table_entry.parent = 0;
658 root->table_entry.name[0] = '\x00';
659 root->index = 1;
660 current = root;
662 u32 path_table = volume->path_table_be;
663 u32 path_table_len = volume->path_table_len_be;
664 u16 i = 1;
665 u64 offset = sizeof(PATHTABLE_ENTRY) - ISO_MAXPATHLEN + 2;
666 PATH_ENTRY *parent = root;
667 while (i < 0xffff && offset < path_table_len) {
668 PATHTABLE_ENTRY entry;
670 LWP_MutexLock(_mutex);
671 if (_read(&entry, (u64)path_table * SECTOR_SIZE + offset, sizeof(PATHTABLE_ENTRY)) != sizeof(PATHTABLE_ENTRY)) {
672 LWP_MutexUnlock(_mutex);
673 return false; // kinda dodgy - could be reading too far
675 LWP_MutexUnlock(_mutex);
677 if (parent->index != entry.parent)
678 parent = entry_from_index(root, entry.parent);
679 if (!parent)
680 return false;
682 PATH_ENTRY *child = add_child_entry(parent);
683 if (!child)
684 return false;
686 memcpy(&child->table_entry, &entry, sizeof(PATHTABLE_ENTRY));
687 offset += sizeof(PATHTABLE_ENTRY) - ISO_MAXPATHLEN + child->table_entry.name_length;
688 if (child->table_entry.name_length % 2) offset++;
689 child->index = ++i;
691 if (unicode) {
692 u32 i;
693 for (i = 0; i < (child->table_entry.name_length / 2); i++)
694 child->table_entry.name[i] = entry.name[i * 2 + 1];
695 child->table_entry.name[i] = '\x00';
696 child->table_entry.name_length = i;
697 } else {
698 child->table_entry.name[child->table_entry.name_length] = '\x00';
702 return true;
705 static void cleanup_recursive(PATH_ENTRY *entry) {
706 u32 i;
707 for (i = 0; i < entry->childCount; i++)
708 cleanup_recursive(&entry->children[i]);
709 if (entry->children) free(entry->children);
712 bool ISO9660_Mount() {
713 ISO9660_Unmount();
714 bool success = read_directories() && (dotab_device = AddDevice(&dotab_iso9660)) >= 0;
715 if (success) last_access = gettime();
716 else ISO9660_Unmount();
718 LWP_MutexInit(&_mutex, false);
720 return success;
723 bool ISO9660_Unmount() {
724 if (root) {
725 cleanup_recursive(root);
726 free(root);
727 root = NULL;
730 current = root;
731 unicode = false;
732 cache_sectors = 0;
733 last_access = 0;
735 if (dotab_device >= 0) {
736 dotab_device = -1;
737 return !RemoveDevice(DEVICE_NAME ":");
740 if (_mutex != 0) {
741 LWP_MutexDestroy(_mutex);
742 _mutex = 0;
745 return true;
748 u64 ISO9660_LastAccess() {
749 return last_access;