aboutsummaryrefslogtreecommitdiff
path: root/src/fs/iso9660
diff options
context:
space:
mode:
authorXavier Del Campo Romero <xavi92@disroot.org>2025-07-07 13:22:53 +0200
committerXavier Del Campo Romero <xavi92@disroot.org>2025-11-11 00:08:15 +0100
commit7861a52adf92a083bb2aed4c35f98d8035dce032 (patch)
tree28cd3c40e4c878f730f5df3c1d93bdf91af490c3 /src/fs/iso9660
parent7fc48e9216ff809da5f8055a50b0be17628ef1df (diff)
downloadwnix-7861a52adf92a083bb2aed4c35f98d8035dce032.tar.gz
Setup project skeleton
Diffstat (limited to 'src/fs/iso9660')
-rw-r--r--src/fs/iso9660/CMakeLists.txt20
-rw-r--r--src/fs/iso9660/include/iso9660.h24
-rw-r--r--src/fs/iso9660/private_include/iso9660/ops.h39
-rw-r--r--src/fs/iso9660/private_include/iso9660/routines.h35
-rw-r--r--src/fs/iso9660/private_include/iso9660/types.h148
-rw-r--r--src/fs/iso9660/src/CMakeLists.txt35
-rw-r--r--src/fs/iso9660/src/check_header.c39
-rw-r--r--src/fs/iso9660/src/close.c26
-rw-r--r--src/fs/iso9660/src/eof.c33
-rw-r--r--src/fs/iso9660/src/etotimespec.c40
-rw-r--r--src/fs/iso9660/src/gmtime.c35
-rw-r--r--src/fs/iso9660/src/lmsb16.c31
-rw-r--r--src/fs/iso9660/src/lmsb32.c31
-rw-r--r--src/fs/iso9660/src/mkdir.c28
-rw-r--r--src/fs/iso9660/src/mount.c180
-rw-r--r--src/fs/iso9660/src/open.c107
-rw-r--r--src/fs/iso9660/src/read.c27
-rw-r--r--src/fs/iso9660/src/register.c44
-rw-r--r--src/fs/iso9660/src/search.c557
-rw-r--r--src/fs/iso9660/src/stat.c28
-rw-r--r--src/fs/iso9660/src/totimespec.c54
-rw-r--r--src/fs/iso9660/src/totm.c72
-rw-r--r--src/fs/iso9660/src/write.c28
23 files changed, 1661 insertions, 0 deletions
diff --git a/src/fs/iso9660/CMakeLists.txt b/src/fs/iso9660/CMakeLists.txt
new file mode 100644
index 0000000..736fc4e
--- /dev/null
+++ b/src/fs/iso9660/CMakeLists.txt
@@ -0,0 +1,20 @@
+# wnix, a Unix-like operating system for WebAssembly applications.
+# Copyright (C) 2025 Xavier Del Campo Romero
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+add_library(iso9660)
+add_subdirectory(src)
+target_include_directories(iso9660 PUBLIC include PRIVATE private_include)
+target_link_libraries(iso9660 PUBLIC c PRIVATE fs kprintf state)
diff --git a/src/fs/iso9660/include/iso9660.h b/src/fs/iso9660/include/iso9660.h
new file mode 100644
index 0000000..e7d1864
--- /dev/null
+++ b/src/fs/iso9660/include/iso9660.h
@@ -0,0 +1,24 @@
+/*
+ * wnix, a Unix-like operating system for WebAssembly applications.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef ISO9660_H
+#define ISO9660_H
+
+int iso9660_register(void);
+
+#endif
diff --git a/src/fs/iso9660/private_include/iso9660/ops.h b/src/fs/iso9660/private_include/iso9660/ops.h
new file mode 100644
index 0000000..b85d025
--- /dev/null
+++ b/src/fs/iso9660/private_include/iso9660/ops.h
@@ -0,0 +1,39 @@
+/*
+ * wnix, a Unix-like operating system for WebAssembly applications.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef ISO9660_OPS_H
+#define ISO9660_OPS_H
+
+#include <fs/fs.h>
+#include <fs/inode.h>
+
+int iso9660_mount(const struct fs_mount *m, struct fs_ret *r);
+int iso9660_mkdir(const struct fs_mkdir *m, const struct fs_mp *mp,
+ const union inode_result *i, struct fs_ret *r);
+int iso9660_open(const struct fs_open *o, const struct fs_mp *mp,
+ const union inode_result *i, struct fs_ret *r);
+int iso9660_read(const struct fs_read *r, struct fs_ret *ret);
+int iso9660_write(const struct fs_write *w, struct fs_ret *r);
+int iso9660_stat(const struct fs_stat *s, const struct fs_mp *mp,
+ const union inode_result *inode, struct fs_ret *r);
+int iso9660_close(struct fs_fd *fd);
+int iso9660_eof(const struct fs_fd *fd);
+int iso9660_search(const char *path, const struct fs_mp *prv,
+ union inode_result *inode, struct fs_ret *r);
+
+#endif
diff --git a/src/fs/iso9660/private_include/iso9660/routines.h b/src/fs/iso9660/private_include/iso9660/routines.h
new file mode 100644
index 0000000..467cce2
--- /dev/null
+++ b/src/fs/iso9660/private_include/iso9660/routines.h
@@ -0,0 +1,35 @@
+/*
+ * wnix, a Unix-like operating system for WebAssembly applications.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef ISO9660_ROUTINES_H
+#define ISO9660_ROUTINES_H
+
+#include <iso9660/types.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <time.h>
+
+int iso9660_check_header(const struct iso9660_magic *m);
+uint16_t iso9660_lmsb16(const struct iso9660_lmsb16 *p);
+uint32_t iso9660_lmsb32(const struct iso9660_lmsb32 *p);
+int iso9660_totm(const struct iso9660_dt *dt, struct tm *tm);
+int iso9660_totimespec(const struct iso9660_dt *dt, struct timespec *ts);
+int iso9660_etotimespec(const struct iso9660_entry_dt *dt, struct timespec *ts);
+struct tm iso9660_gmtime(const struct iso9660_entry_dt *dt);
+
+#endif
diff --git a/src/fs/iso9660/private_include/iso9660/types.h b/src/fs/iso9660/private_include/iso9660/types.h
new file mode 100644
index 0000000..55544fb
--- /dev/null
+++ b/src/fs/iso9660/private_include/iso9660/types.h
@@ -0,0 +1,148 @@
+/*
+ * wnix, a Unix-like operating system for WebAssembly applications.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef ISO9660_TYPES_H
+#define ISO9660_TYPES_H
+
+#include <fs/fs.h>
+#include <fs/inode.h>
+#include <stdint.h>
+
+#define ISO9660_LE ((const union {int a; char c;}){.a = 1}.c)
+
+struct fs_mp_prv
+{
+ int dummy;
+};
+
+enum
+{
+ ISO9660_TYPE_BOOT_RECORD,
+ ISO9660_TYPE_PRIMARY_VD,
+ ISO9660_TYPE_SUPPLEMENTARY_VD,
+ ISO9660_TYPE_VOLUME_PARTITION,
+ ISO9660_TYPE_TERMINATOR = 0xff,
+};
+
+struct inode_prv
+{
+ long offset;
+};
+
+struct iso9660_magic
+{
+ uint8_t id[sizeof "CD001" - 1];
+};
+
+struct iso9660_header
+{
+ uint8_t type;
+ struct iso9660_magic magic;
+ uint8_t version;
+};
+
+struct iso9660_lmsb16
+{
+ uint8_t le[sizeof (uint16_t)], be[sizeof (uint16_t)];
+};
+
+struct iso9660_lmsb32
+{
+ uint8_t le[sizeof (uint32_t)], be[sizeof (uint32_t)];
+};
+
+struct iso9660_dt
+{
+ char year[sizeof "9999" - 1],
+ month[sizeof "12" - 1],
+ day[sizeof "31" - 1],
+ hour[sizeof "23" - 1],
+ min[sizeof "59" - 1],
+ sec[sizeof "59" - 1],
+ hsec[sizeof "99" - 1];
+ int8_t zone;
+};
+
+struct iso9660_lsb32
+{
+ uint8_t data[sizeof (uint32_t)];
+};
+
+struct iso9660_msb32
+{
+ uint8_t data[sizeof (uint32_t)];
+};
+
+struct iso9660_entry_dt
+{
+ uint8_t year, month, day, hour, min, sec, offset;
+};
+
+enum
+{
+ ISO9660_FLAGS_HIDDEN = 1,
+ ISO9660_FLAGS_DIR = 1 << 1,
+ ISO9660_FLAGS_ASSOC = 1 << 2,
+ ISO9660_FLAGS_HAS_FORMAT = 1 << 3,
+ ISO9660_FLAGS_HAS_UID_GID = 1 << 4,
+ ISO9660_FLAGS_NOT_FINAL = 1 << 7
+};
+
+struct iso9660_entry
+{
+ uint8_t len, attr;
+ struct iso9660_lmsb32 extent_loc, data_len;
+ struct iso9660_entry_dt dt;
+ uint8_t flags;
+ uint8_t funit_sz, interleave_sz;
+ struct iso9660_lmsb16 seq_num;
+ uint8_t id_len;
+};
+
+struct iso9660_pvd
+{
+ uint8_t :8, sysid[32], vid[32], unused[8];
+ struct iso9660_lmsb32 space_size;
+ uint8_t unused_2[32];
+ struct iso9660_lmsb16 set_size, seq_num, block_size;
+ struct iso9660_lmsb32 pathtable_size;
+ struct iso9660_lsb32 lpath_loc, optlpath_loc;
+ struct iso9660_msb32 mpath_loc, optmpath_loc;
+ struct iso9660_entry root_dir;
+ uint8_t padding, set_id[128], pub_id[128], dprep_id[128], app_id[128],
+ copyright_id[37], abstract_id[37], bibl_id[37];
+ struct iso9660_dt creat, mod, exp, eff;
+ uint8_t fs_version, :8, reserved[512 + 653];
+};
+
+
+struct iso9660_vd
+{
+ struct iso9660_header header;
+
+ union
+ {
+ struct iso9660_pvd pvd;
+ } u;
+};
+
+enum {ISO9660_SECTOR_SZ = 2048};
+
+extern const struct fs iso9660;
+
+#endif
diff --git a/src/fs/iso9660/src/CMakeLists.txt b/src/fs/iso9660/src/CMakeLists.txt
new file mode 100644
index 0000000..740e404
--- /dev/null
+++ b/src/fs/iso9660/src/CMakeLists.txt
@@ -0,0 +1,35 @@
+# wnix, a Unix-like operating system for WebAssembly applications.
+# Copyright (C) 2025 Xavier Del Campo Romero
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+target_sources(iso9660 PRIVATE
+ check_header.c
+ close.c
+ eof.c
+ etotimespec.c
+ gmtime.c
+ lmsb16.c
+ lmsb32.c
+ mount.c
+ mkdir.c
+ open.c
+ stat.c
+ read.c
+ register.c
+ search.c
+ totimespec.c
+ totm.c
+ write.c
+)
diff --git a/src/fs/iso9660/src/check_header.c b/src/fs/iso9660/src/check_header.c
new file mode 100644
index 0000000..f504187
--- /dev/null
+++ b/src/fs/iso9660/src/check_header.c
@@ -0,0 +1,39 @@
+/*
+ * wnix, a Unix-like operating system for WebAssembly applications.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <iso9660.h>
+#include <iso9660/routines.h>
+#include <kprintf.h>
+#include <stdint.h>
+#include <string.h>
+
+int iso9660_check_header(const struct iso9660_magic *const m)
+{
+ static const struct iso9660_magic em = {{'C', 'D', '0', '0', '1'}};
+
+ if (memcmp(m, &em, sizeof *m))
+ {
+ const uint8_t *const b = m->id;
+
+ kprintf("Invalid ISO9660 header: "
+ "{%hhx, %hhx, %hhx, %hhx, %hhx}\n", b[0], b[1], b[2], b[3], b[4]);
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/src/fs/iso9660/src/close.c b/src/fs/iso9660/src/close.c
new file mode 100644
index 0000000..d43da29
--- /dev/null
+++ b/src/fs/iso9660/src/close.c
@@ -0,0 +1,26 @@
+/*
+ * wnix, a Unix-like operating system for WebAssembly applications.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <iso9660.h>
+#include <iso9660/ops.h>
+#include <fs/fs.h>
+
+int iso9660_close(struct fs_fd *const fd)
+{
+ return -1;
+}
diff --git a/src/fs/iso9660/src/eof.c b/src/fs/iso9660/src/eof.c
new file mode 100644
index 0000000..1c36b63
--- /dev/null
+++ b/src/fs/iso9660/src/eof.c
@@ -0,0 +1,33 @@
+/*
+ * wnix, a Unix-like operating system for WebAssembly applications.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <iso9660.h>
+#include <iso9660/ops.h>
+#include <iso9660/types.h>
+#include <fs/inode.h>
+
+int iso9660_eof(const struct fs_fd *const fd)
+{
+ const union inode_result *const i = &fd->tgt_inode;
+ const off_t sz = i->cachei.size;
+
+ if (!sz)
+ return 1;
+
+ return fd->offset >= sz - 1;
+}
diff --git a/src/fs/iso9660/src/etotimespec.c b/src/fs/iso9660/src/etotimespec.c
new file mode 100644
index 0000000..21debf7
--- /dev/null
+++ b/src/fs/iso9660/src/etotimespec.c
@@ -0,0 +1,40 @@
+/*
+ * wnix, a Unix-like operating system for WebAssembly applications.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <iso9660.h>
+#include <iso9660/routines.h>
+#include <iso9660/types.h>
+#include <time.h>
+
+int iso9660_etotimespec(const struct iso9660_entry_dt *const dt,
+ struct timespec *const ts)
+{
+ struct tm tm = iso9660_gmtime(dt);
+ const time_t t = mktime(&tm);
+
+ if (t == (time_t)-1)
+ return -1;
+
+ *ts = (const struct timespec)
+ {
+ .tv_sec = t,
+ .tv_nsec = 0
+ };
+
+ return 0;
+}
diff --git a/src/fs/iso9660/src/gmtime.c b/src/fs/iso9660/src/gmtime.c
new file mode 100644
index 0000000..2649688
--- /dev/null
+++ b/src/fs/iso9660/src/gmtime.c
@@ -0,0 +1,35 @@
+/*
+ * wnix, a Unix-like operating system for WebAssembly applications.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <iso9660.h>
+#include <iso9660/routines.h>
+#include <iso9660/types.h>
+#include <time.h>
+
+struct tm iso9660_gmtime(const struct iso9660_entry_dt *const dt)
+{
+ return (const struct tm)
+ {
+ .tm_year = dt->year + 1900,
+ .tm_mon = dt->month,
+ .tm_mday = dt->day,
+ .tm_hour = dt->hour,
+ .tm_min = dt->min,
+ .tm_sec = dt->sec
+ };
+}
diff --git a/src/fs/iso9660/src/lmsb16.c b/src/fs/iso9660/src/lmsb16.c
new file mode 100644
index 0000000..d404b2c
--- /dev/null
+++ b/src/fs/iso9660/src/lmsb16.c
@@ -0,0 +1,31 @@
+/*
+ * wnix, a Unix-like operating system for WebAssembly applications.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <iso9660/routines.h>
+#include <iso9660/types.h>
+#include <stdint.h>
+#include <string.h>
+
+uint16_t iso9660_lmsb16(const struct iso9660_lmsb16 *const p)
+{
+ const uint16_t *const d = (const void *)(ISO9660_LE ? p->le : p->be);
+ uint16_t ret;
+
+ memcpy(&ret, d, sizeof ret);
+ return ret;
+}
diff --git a/src/fs/iso9660/src/lmsb32.c b/src/fs/iso9660/src/lmsb32.c
new file mode 100644
index 0000000..2811226
--- /dev/null
+++ b/src/fs/iso9660/src/lmsb32.c
@@ -0,0 +1,31 @@
+/*
+ * wnix, a Unix-like operating system for WebAssembly applications.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <iso9660/routines.h>
+#include <iso9660/types.h>
+#include <stdint.h>
+#include <string.h>
+
+uint32_t iso9660_lmsb32(const struct iso9660_lmsb32 *const p)
+{
+ const uint32_t *const d = (const void *)(ISO9660_LE ? p->le : p->be);
+ uint32_t ret;
+
+ memcpy(&ret, d, sizeof ret);
+ return ret;
+}
diff --git a/src/fs/iso9660/src/mkdir.c b/src/fs/iso9660/src/mkdir.c
new file mode 100644
index 0000000..9d30659
--- /dev/null
+++ b/src/fs/iso9660/src/mkdir.c
@@ -0,0 +1,28 @@
+/*
+ * wnix, a Unix-like operating system for WebAssembly applications.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <iso9660.h>
+#include <iso9660/ops.h>
+#include <fs/fs.h>
+#include <fs/inode.h>
+
+int iso9660_mkdir(const struct fs_mkdir *const m, const struct fs_mp *const mp,
+ const union inode_result *const inode, struct fs_ret *const r)
+{
+ return -1;
+}
diff --git a/src/fs/iso9660/src/mount.c b/src/fs/iso9660/src/mount.c
new file mode 100644
index 0000000..83e7a6d
--- /dev/null
+++ b/src/fs/iso9660/src/mount.c
@@ -0,0 +1,180 @@
+/*
+ * wnix, a Unix-like operating system for WebAssembly applications.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <iso9660.h>
+#include <iso9660/ops.h>
+#include <iso9660/routines.h>
+#include <iso9660/types.h>
+#include <fs/fs.h>
+#include <kprintf.h>
+#include <state.h>
+#include <errno.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+struct mount
+{
+ char *src, *tgt;
+ struct fs_fd fd;
+ struct fs_ret *r;
+ struct fs_mount mount;
+ struct iso9660_header header;
+ const struct fs_mp *mp;
+};
+
+static void free_mount(struct mount *const m)
+{
+ if (!m)
+ return;
+
+ m->mp->fs->close(&m->fd);
+ free(m->src);
+ free(m->tgt);
+ free(m);
+}
+
+static enum state check_header(void *const args)
+{
+ enum state ret = STATE_OK;
+ struct mount *const m = args;
+
+ if (iso9660_check_header(&m->header.magic))
+ ret = STATE_FATAL;
+ else if (fs_mountpoint(m->src, m->tgt, &iso9660, NULL, NULL))
+ {
+ kprintf("Failed to allocate mountpoint {source=%s, target=%s}\n",
+ m->src, m->tgt);
+ ret = STATE_FATAL;
+ }
+
+ free_mount(m);
+ return ret;
+}
+
+static enum state read_header(void *const args)
+{
+ struct mount *const m = args;
+ struct fs_fd *const fd = &m->fd;
+ const struct fs *const fs = m->mp->fs;
+ const struct fs_read r =
+ {
+ .buf = &m->header,
+ .n = sizeof m->header,
+ .fd = fd
+ };
+
+ if (fd->error)
+ {
+ errno = fd->error;
+ goto failure;
+ }
+
+ *m->r = (const struct fs_ret)
+ {
+ .f = check_header,
+ .args = m
+ };
+
+ fd->offset = 16 * ISO9660_SECTOR_SZ;
+
+ if (fs->read(&r, m->r))
+ goto failure;
+
+ return STATE_AGAIN;
+
+failure:
+ free_mount(m);
+ return STATE_FATAL;
+}
+
+static int search_done(const enum state state, const char *const relpath,
+ const struct fs_mp *const mp, const union inode_result *const inode,
+ void *const args)
+{
+ struct mount *const m = args;
+ const struct fs_mount *const fm = &m->mount;
+
+ if (!inode)
+ {
+ errno = ENOENT;
+ goto failure;
+ }
+
+ const struct fs_open o =
+ {
+ .fd = &m->fd,
+ .gid = fm->gid,
+ .uid = fm->uid,
+ .mode = fm->mode,
+ .path = relpath
+ };
+
+ *m->r = (const struct fs_ret)
+ {
+ .f = read_header,
+ .args = m
+ };
+
+ if (mp->fs->open(&o, mp, inode, m->r))
+ goto failure;
+
+ m->mp = mp;
+ return 0;
+
+failure:
+ free_mount(m);
+ return -1;
+}
+
+int iso9660_mount(const struct fs_mount *const fm, struct fs_ret *const r)
+{
+ char *srcdup = NULL, *tgtdup = NULL;
+ struct mount *const m = malloc(sizeof *m);
+
+ if (!m
+ || !(srcdup = strdup(fm->src))
+ || !(tgtdup = strdup(fm->tgt)))
+ goto failure;
+
+ *m = (const struct mount)
+ {
+ .src = srcdup,
+ .tgt = tgtdup,
+ .mount = *fm,
+ .r = r
+ };
+
+ const struct inode_search s =
+ {
+ .args = m,
+ .path = fm->src,
+ .done = search_done
+ };
+
+ if (inode_search(&s, r))
+ goto failure;
+
+ return 0;
+
+failure:
+ free(srcdup);
+ free(tgtdup);
+ free(m);
+ return -1;
+}
diff --git a/src/fs/iso9660/src/open.c b/src/fs/iso9660/src/open.c
new file mode 100644
index 0000000..1ea6ad7
--- /dev/null
+++ b/src/fs/iso9660/src/open.c
@@ -0,0 +1,107 @@
+/*
+ * wnix, a Unix-like operating system for WebAssembly applications.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <iso9660.h>
+#include <iso9660/ops.h>
+#include <iso9660/routines.h>
+#include <iso9660/types.h>
+#include <fs/fs.h>
+#include <fs/inode.h>
+#include <errno.h>
+#include <stdlib.h>
+
+struct open
+{
+ struct inode inode;
+ struct fs_fd *fd;
+ struct fs_ret *pr, r;
+ const struct fs_mp *mp;
+};
+
+static int search_done(const enum state state, const char *const relpath,
+ const struct fs_mp *const mp, const union inode_result *const inode,
+ void *const args)
+{
+ int ret = 0;
+ struct open *const op = args;
+ const struct inode *const mpinode = &op->inode;
+ struct fs_fd *const fd = op->fd;
+
+ if (mpinode->flags & INODE_DIR)
+ {
+ fd->error = EISDIR;
+ ret = -1;
+ }
+ else if (state)
+ {
+ fd->error = ENODEV;
+ ret = -1;
+ }
+ else
+ {
+ *fd = (const struct fs_fd)
+ {
+ .start = mpinode->prv->offset,
+ .tgt_inode.cachei = op->inode,
+ .size = mpinode->size,
+ .tgt_mp = op->mp,
+ .inode = *inode,
+ .mp = mp
+ };
+
+ *op->pr = op->r;
+ }
+
+ free(op);
+ return ret;
+}
+
+int iso9660_open(const struct fs_open *const o, const struct fs_mp *const mp,
+ const union inode_result *const inode, struct fs_ret *const r)
+{
+ const struct inode *const i = &inode->cachei;
+ struct open *const op = malloc(sizeof *op);
+
+ if (!op)
+ return -1;
+
+ const struct inode_search s =
+ {
+ .args = op,
+ .path = mp->src,
+ .done = search_done
+ };
+
+ *op = (const struct open)
+ {
+ .fd = o->fd,
+ .inode = *i,
+ .mp = mp,
+ .pr = r,
+ .r = *r
+ };
+
+ if (inode_search(&s, r))
+ goto failure;
+
+ return 0;
+
+failure:
+ free(op);
+ return -1;
+}
diff --git a/src/fs/iso9660/src/read.c b/src/fs/iso9660/src/read.c
new file mode 100644
index 0000000..6e14845
--- /dev/null
+++ b/src/fs/iso9660/src/read.c
@@ -0,0 +1,27 @@
+/*
+ * wnix, a Unix-like operating system for WebAssembly applications.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <iso9660.h>
+#include <iso9660/ops.h>
+#include <fs/fs.h>
+#include <stddef.h>
+
+int iso9660_read(const struct fs_read *const fr, struct fs_ret *const ret)
+{
+ return -1;
+}
diff --git a/src/fs/iso9660/src/register.c b/src/fs/iso9660/src/register.c
new file mode 100644
index 0000000..70a33f9
--- /dev/null
+++ b/src/fs/iso9660/src/register.c
@@ -0,0 +1,44 @@
+/*
+ * wnix, a Unix-like operating system for WebAssembly applications.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <iso9660.h>
+#include <iso9660/ops.h>
+#include <fs/fs.h>
+
+const struct fs iso9660 =
+{
+ .type = "iso9660",
+ .flags = FS_DEV_REQUIRED,
+ .mount = iso9660_mount,
+ .stat = iso9660_stat,
+ .mkdir = iso9660_mkdir,
+ .open = iso9660_open,
+ .read = iso9660_read,
+ .write = iso9660_write,
+ .close = iso9660_close,
+ .eof = iso9660_eof,
+ .iops =
+ {
+ .search = iso9660_search
+ }
+};
+
+int iso9660_register(void)
+{
+ return fs_register(&iso9660);
+}
diff --git a/src/fs/iso9660/src/search.c b/src/fs/iso9660/src/search.c
new file mode 100644
index 0000000..a7a5150
--- /dev/null
+++ b/src/fs/iso9660/src/search.c
@@ -0,0 +1,557 @@
+/*
+ * wnix, a Unix-like operating system for WebAssembly applications.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <iso9660.h>
+#include <iso9660/ops.h>
+#include <iso9660/routines.h>
+#include <iso9660/types.h>
+#include <fs/fs.h>
+#include <kprintf.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+struct id
+{
+ char buf[UCHAR_MAX];
+};
+
+struct search
+{
+ union inode_result *inode;
+ const struct fs_mp *src_mp;
+ struct iso9660_entry entry;
+ const char *src, *path;
+ struct fs_ret r, *pr;
+ struct fs_fd fd;
+ off_t offset;
+ size_t token_i;
+ char *token;
+ void *buf;
+ struct id id;
+};
+
+static int prepare_entry(struct search *);
+static int next_entry(struct search *);
+
+static void free_search(struct search *const s)
+{
+ if (!s)
+ return;
+ else if (s->src_mp)
+ s->src_mp->fs->close(&s->fd);
+
+ free(s->buf);
+ free(s->token);
+ free(s);
+}
+
+static int set_inode(struct search *const s,
+ const struct iso9660_entry *const e, const struct id *const id)
+{
+ struct inode *const inode = &s->inode->cachei;
+ struct inode_prv *p = NULL;
+ char *namedup = NULL;
+ struct timespec ts, atim;
+
+ if (clock_gettime(CLOCK_REALTIME, &atim))
+ goto failure;
+ else if (iso9660_etotimespec(&e->dt, &ts))
+ {
+ kprintf("Invalid datetime, path=%s\n", s->path);
+ goto failure;
+ }
+ else if (!(namedup = strdup(id->buf)) || !(p = malloc(sizeof *p)))
+ goto failure;
+
+ *p = (const struct inode_prv)
+ {
+ .offset = iso9660_lmsb32(&e->extent_loc) * ISO9660_SECTOR_SZ
+ };
+
+ *inode = (const struct inode)
+ {
+ .size = iso9660_lmsb32(&e->data_len),
+ .flags = INODE_REGFILE,
+ .name = namedup,
+ .mode = 0555,
+ .atim = atim,
+ .free = free,
+ .ctim = ts,
+ .mtim = ts,
+ .prv = p
+ };
+
+ return 0;
+
+failure:
+ free(p);
+ free(namedup);
+ return -1;
+}
+
+static int next_token(struct search *const s)
+{
+ free(s->token);
+ s->token = NULL;
+
+ if (fs_next(s->path, &s->token, &s->token_i))
+ {
+ kprintf("Failed to extract next token, path=%s, i=%zu\n", s->path,
+ s->token_i);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int check_version(const char *const version)
+{
+ char *end;
+
+ errno = 0;
+ strtoul(version, &end, 10);
+
+ return errno || *end;
+}
+
+static void extract_id(const char *id, struct id *const out,
+ const char *const ext, const char *const version)
+{
+ char *p = out->buf;
+
+ if (ext)
+ {
+ const ptrdiff_t n = ext - id;
+ const char *const e = ext + 1;
+
+ memcpy(p, id, n);
+ p += n;
+ id += n;
+
+ if (*e)
+ {
+ const ptrdiff_t n = version ? version - e : strlen(e);
+
+ memcpy(p, id, n);
+ p += n;
+ }
+ }
+ else if (version)
+ {
+ memcpy(p, id, version - id);
+ p += version - id;
+ }
+ else
+ {
+ const ptrdiff_t n = strlen(id);
+
+ memcpy(p, id, n);
+ p += n;
+ }
+
+ *p = '\0';
+ p = out->buf;
+
+ while (*p)
+ {
+ *p = tolower(*p);
+ p++;
+ }
+}
+
+static int parse_id(const char *const id, struct id *const out)
+{
+ static const char accept[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
+ const char *const ext = strchr(id, '.'), *const version = strchr(id, ';');
+ const size_t n = strspn(id, accept), len = strlen(id);
+
+ /* Ensure only valid characters are met if no version or extension. */
+ if (!version && !ext && n != len)
+ return -1;
+ /* Ensure file version appears only after file extension,
+ * if both are present. */
+ else if (version && ext && ext > version)
+ return -1;
+ /* Ensure one and only one file version and/or extension appear. */
+ else if ((version && strchr(version + 1, ';'))
+ || (ext && strchr(ext + 1, '.')))
+ return -1;
+ else if (version && check_version(version + 1))
+ return -1;
+ /* When present, ensure valid characters only until the file extension
+ * is found. */
+ else if (ext && n != ext - id)
+ return -1;
+
+ extract_id(id, out, ext, version);
+ return 0;
+}
+
+static enum state id_read(void *const args)
+{
+ struct search *volatile const s = args;
+ const struct iso9660_entry *const e = &s->entry;
+ const char *const id = s->id.buf;
+ struct id outid;
+
+ if (!*id || !strcmp(id, "\1"))
+ {
+ if (next_entry(s))
+ goto failure;
+ }
+ else if (parse_id(id, &outid))
+ {
+ kprintf("Invalid file or directory identifier=%s\n", id);
+ goto failure;
+ }
+ else if (!strcmp(outid.buf, s->token))
+ {
+ char *token;
+
+ if (fs_next(s->path, &token, &s->token_i))
+ {
+ kprintf("Failed to extract next token, path=%s, i=%zu\n",
+ s->path, s->token_i);
+ goto failure;
+ }
+ else if (!token)
+ {
+ if (set_inode(s, e, &outid))
+ goto failure;
+
+ *s->pr = s->r;
+ free_search(s);
+ return STATE_AGAIN;
+ }
+ else if (!(e->flags & ISO9660_FLAGS_DIR))
+ {
+ kprintf("Intermediate node %s not a directory, path=%s\n",
+ id, s->path);
+ goto failure;
+ }
+ else
+ {
+ s->fd.offset = iso9660_lmsb32(&e->extent_loc) * ISO9660_SECTOR_SZ;
+
+ if (prepare_entry(s))
+ goto failure;
+ }
+
+ free(s->token);
+ free(s->buf);
+ s->token = token;
+ s->buf = NULL;
+ }
+ else if (next_entry(s))
+ goto failure;
+
+ return STATE_AGAIN;
+
+failure:
+ free_search(s);
+ return STATE_FATAL;
+}
+
+static enum state entry_read(void *const args)
+{
+ struct search *const s = args;
+ const struct iso9660_entry *const e = &s->entry;
+
+ if (e->len < sizeof *e)
+ {
+ kprintf("Unexpected entry length %hhu, expected %zu\n", e->len,
+ sizeof *e);
+ goto failure;
+ }
+
+ char *const buf = s->id.buf;
+ const struct fs_mp *const mp = s->fd.mp;
+ const struct fs_read r =
+ {
+ .buf = buf,
+ .n = e->id_len,
+ .fd = &s->fd
+ };
+
+ *(buf + e->id_len) = '\0';
+ *s->pr = (const struct fs_ret)
+ {
+ .f = id_read,
+ .args = s
+ };
+
+ if (mp->fs->read(&r, s->pr))
+ goto failure;
+
+ return STATE_AGAIN;
+
+failure:
+ free_search(s);
+ return STATE_FATAL;
+}
+
+static int prepare_entry(struct search *const s)
+{
+ const struct fs_read r =
+ {
+ .buf = &s->entry,
+ .fd = &s->fd,
+ .n = sizeof s->entry
+ };
+
+ *s->pr = (const struct fs_ret)
+ {
+ .f = entry_read,
+ .args = s
+ };
+
+ s->offset = s->fd.offset;
+ return s->src_mp->fs->read(&r, s->pr);
+}
+
+static int next_entry(struct search *const s)
+{
+ const struct iso9660_entry *const e = &s->entry;
+ struct fs_fd *const fd = &s->fd;
+
+ fd->offset = s->offset + e->len;
+ return prepare_entry(s);
+}
+
+static int set_root_inode(const struct iso9660_pvd *const pvd,
+ struct search *const s)
+{
+ struct inode *const inode = &s->inode->cachei;
+ struct inode_prv *p = NULL;
+ char *namedup = NULL;
+
+ if (clock_gettime(CLOCK_REALTIME, &inode->atim))
+ goto failure;
+ else if (iso9660_totimespec(&pvd->creat, &inode->ctim))
+ {
+ kprintf("Invalid volume creation time\n");
+ goto failure;
+ }
+ else if (iso9660_totimespec(&pvd->mod, &inode->mtim))
+ {
+ kprintf("Invalid volume modification time\n");
+ goto failure;
+ }
+ else if (!(namedup = strdup("/")) || !(p = malloc(sizeof *p)))
+ goto failure;
+
+ *p = (const struct inode_prv)
+ {
+ .offset = offsetof(struct iso9660_pvd, root_dir)
+ };
+
+ inode->flags = INODE_DIR;
+ inode->mode = 0555;
+ inode->name = namedup;
+ inode->prv = p;
+ *s->pr = s->r;
+ free_search(s);
+ return 0;
+
+failure:
+ free(p);
+ free(namedup);
+ return -1;
+}
+
+static int check_pvd(const struct iso9660_vd *const vd)
+{
+ const struct iso9660_pvd *const pvd = &vd->u.pvd;
+ const struct iso9660_entry *const e = &pvd->root_dir;
+ const struct iso9660_header *const h = &vd->header;
+ const uint16_t block_size = iso9660_lmsb16(&pvd->block_size);
+ const size_t exp_sz = sizeof (struct iso9660_entry) + sizeof pvd->padding;
+
+ if (iso9660_check_header(&h->magic))
+ return -1;
+ else if (h->version != 1)
+ {
+ kprintf("Unsupported ISO9660 version %#hhx, expected 1\n", h->version);
+ return -1;
+ }
+ else if (h->type != ISO9660_TYPE_PRIMARY_VD)
+ {
+ kprintf("Unexpected volume descriptor type %#hhx, expected=%#x\n",
+ h->type, ISO9660_TYPE_PRIMARY_VD);
+ return -1;
+ }
+ else if (pvd->fs_version != 1)
+ {
+ kprintf("Unexpected file structure version %#hhx, expected 1\n",
+ pvd->fs_version);
+ return -1;
+ }
+ else if (block_size != ISO9660_SECTOR_SZ)
+ {
+ kprintf("Unsupported block size %" PRIu16 ", expected %d\n",
+ block_size, ISO9660_SECTOR_SZ);
+ return -1;
+ }
+ else if (e->len != exp_sz)
+ {
+ kprintf("Unexpected root directory length %hhd, expected %zu\n",
+ e->len, exp_sz);
+ return -1;
+ }
+ else if (!(e->flags & ISO9660_FLAGS_DIR))
+ {
+ kprintf("Directory flag (%#hx) not found in file flags (%#hhx)\n",
+ ISO9660_FLAGS_DIR, e->flags);
+ return -1;
+ }
+
+ return 0;
+}
+
+static enum state read_pvd_done(void *const args)
+{
+ struct search *const s = args;
+ struct fs_fd *const fd = &s->fd;
+ const struct iso9660_vd *const vd = s->buf;
+ const struct iso9660_pvd *const pvd = &vd->u.pvd;
+ const struct iso9660_entry *const e = &pvd->root_dir;
+ const uint32_t lba = iso9660_lmsb32(&e->extent_loc);
+
+ fd->offset = lba * ISO9660_SECTOR_SZ;
+
+ if (check_pvd(vd))
+ goto failure;
+ else if (!strcmp(s->src, "/"))
+ {
+ if (set_root_inode(pvd, s))
+ goto failure;
+
+ return STATE_AGAIN;
+ }
+ else if (next_token(s) || prepare_entry(s))
+ goto failure;
+
+ return STATE_AGAIN;
+
+failure:
+ free_search(s);
+ return STATE_FATAL;
+}
+
+static enum state read_pvd(void *const args)
+{
+ struct search *const s = args;
+ struct fs_fd *const fd = &s->fd;
+
+ if (!(s->buf = malloc(ISO9660_SECTOR_SZ)) || next_token(s))
+ goto failure;
+
+ const struct fs_read r =
+ {
+ .buf = s->buf,
+ .fd = fd,
+ .n = ISO9660_SECTOR_SZ
+ };
+
+ *s->pr = (const struct fs_ret)
+ {
+ .f = read_pvd_done,
+ .args = s
+ };
+
+ fd->offset = 16 * ISO9660_SECTOR_SZ;
+
+ if (s->src_mp->fs->read(&r, s->pr))
+ goto failure;
+
+ return STATE_AGAIN;
+
+failure:
+ free_search(s);
+ return STATE_FATAL;
+}
+
+static int src_done(const enum state state, const char *const relpath,
+ const struct fs_mp *const mp, const union inode_result *const inode,
+ void *const args)
+{
+ struct search *const s = args;
+
+ if (state)
+ {
+ kprintf("Failed to find source inode for %s\n", s->src);
+ return -1;
+ }
+
+ s->src_mp = mp;
+ *s->pr = (const struct fs_ret)
+ {
+ .f = read_pvd,
+ .args = s
+ };
+
+ const struct fs_open o =
+ {
+ .fd = &s->fd,
+ .path = mp->src,
+ .flags = O_RDONLY
+ };
+
+ return mp->fs->open(&o, mp, inode, s->pr);
+}
+
+int iso9660_search(const char *const path, const struct fs_mp *const mp,
+ union inode_result *const inode, struct fs_ret *const r)
+{
+ struct search *const s = malloc(sizeof *s);
+
+ if (!s)
+ goto failure;
+
+ *s = (const struct search)
+ {
+ .inode = inode,
+ .src = mp->src,
+ .path = path,
+ .r = *r,
+ .pr = r
+ };
+
+ const struct inode_search is =
+ {
+ .path = mp->src,
+ .done = src_done,
+ .args = s,
+ };
+
+ if (inode_search(&is, r))
+ goto failure;
+
+ return 0;
+
+failure:
+ free(s);
+ return -1;
+}
diff --git a/src/fs/iso9660/src/stat.c b/src/fs/iso9660/src/stat.c
new file mode 100644
index 0000000..53732fe
--- /dev/null
+++ b/src/fs/iso9660/src/stat.c
@@ -0,0 +1,28 @@
+/*
+ * wnix, a Unix-like operating system for WebAssembly applications.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <iso9660.h>
+#include <iso9660/ops.h>
+#include <fs/fs.h>
+#include <sys/stat.h>
+
+int iso9660_stat(const struct fs_stat *const s, const struct fs_mp *const mp,
+ const union inode_result *const inode, struct fs_ret *const r)
+{
+ return -1;
+}
diff --git a/src/fs/iso9660/src/totimespec.c b/src/fs/iso9660/src/totimespec.c
new file mode 100644
index 0000000..735deb7
--- /dev/null
+++ b/src/fs/iso9660/src/totimespec.c
@@ -0,0 +1,54 @@
+/*
+ * wnix, a Unix-like operating system for WebAssembly applications.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <iso9660.h>
+#include <iso9660/routines.h>
+#include <iso9660/types.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+int iso9660_totimespec(const struct iso9660_dt *const dt,
+ struct timespec *const ts)
+{
+ char hsec[sizeof dt->hsec + 1], *end;
+ unsigned long v;
+ struct tm tm;
+ time_t t;
+
+ if (iso9660_totm(dt, &tm)
+ || ((t = mktime(&tm)) == (time_t)-1))
+ return -1;
+
+ errno = 0;
+ memcpy(hsec, dt->hsec, sizeof dt->hsec);
+ hsec[sizeof dt->hsec] = '\0';
+ v = strtoul(hsec, &end, 10);
+
+ if (errno || *end || v >= 100)
+ return -1;
+
+ *ts = (const struct timespec)
+ {
+ .tv_sec = t,
+ .tv_nsec = v * 100000ul
+ };
+
+ return 0;
+}
diff --git a/src/fs/iso9660/src/totm.c b/src/fs/iso9660/src/totm.c
new file mode 100644
index 0000000..1e94359
--- /dev/null
+++ b/src/fs/iso9660/src/totm.c
@@ -0,0 +1,72 @@
+/*
+ * wnix, a Unix-like operating system for WebAssembly applications.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <iso9660.h>
+#include <iso9660/routines.h>
+#include <iso9660/types.h>
+#include <errno.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+int iso9660_totm(const struct iso9660_dt *const dt, struct tm *const tm)
+{
+ char year[sizeof dt->year + 1],
+ month[sizeof dt->month + 1],
+ day[sizeof dt->day + 1],
+ hour[sizeof dt->hour + 1],
+ min[sizeof dt->min + 1],
+ sec[sizeof dt->sec + 1];
+
+ struct buf
+ {
+ const char *src;
+ char *s;
+ int *v;
+ size_t n;
+ } bufs[] =
+ {
+ {.src = dt->year, .s = year, .n = sizeof dt->year, .v = &tm->tm_year},
+ {.src = dt->month, .s = month, .n = sizeof dt->month, .v = &tm->tm_mon},
+ {.src = dt->day, .s = day, .n = sizeof dt->day, .v = &tm->tm_yday},
+ {.src = dt->hour, .s = hour, .n = sizeof dt->hour, .v = &tm->tm_hour},
+ {.src = dt->min, .s = min, .n = sizeof dt->min, .v = &tm->tm_min},
+ {.src = dt->sec, .s = sec, .n = sizeof dt->sec, .v = &tm->tm_sec}
+ };
+
+ for (size_t i = 0; i < sizeof bufs / sizeof *bufs; i++)
+ {
+ struct buf *const b = &bufs[i];
+ unsigned long v;
+ char *end;
+
+ memcpy(b->s, b->src, b->n - 1);
+ b->s[b->n] = '\0';
+ errno = 0;
+ v = strtoul(b->s, &end, 10);
+
+ if (errno || *end || v > INT_MAX)
+ return -1;
+
+ *b->v = v;
+ }
+
+ return 0;
+}
diff --git a/src/fs/iso9660/src/write.c b/src/fs/iso9660/src/write.c
new file mode 100644
index 0000000..2ce3823
--- /dev/null
+++ b/src/fs/iso9660/src/write.c
@@ -0,0 +1,28 @@
+/*
+ * wnix, a Unix-like operating system for WebAssembly applications.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <iso9660.h>
+#include <iso9660/ops.h>
+#include <fs/fs.h>
+#include <errno.h>
+
+int iso9660_write(const struct fs_write *const w, struct fs_ret *const r)
+{
+ errno = EROFS;
+ return -1;
+}