1/*
2 * ISO9660 support routines for PhysicsFS.
3 *
4 * Please see the file LICENSE.txt in the source's root directory.
5 *
6 * This file originally written by Christoph Nelles, but was largely
7 * rewritten by Ryan C. Gordon (so please harass Ryan about bugs and not
8 * Christoph).
9 */
10
11/*
12 * Handles CD-ROM disk images (and raw CD-ROM devices).
13 *
14 * Not supported:
15 * - Rock Ridge (needed for sparse files, device nodes and symlinks, etc).
16 * - Non 2048 Sectors
17 * - TRANS.TBL (maps 8.3 filenames on old discs to long filenames).
18 * - Multiextents (4gb max file size without it).
19 * - UDF
20 *
21 * Deviations from the standard
22 * - Ignores mandatory sort order
23 * - Allows various invalid file names
24 *
25 * Problems
26 * - Ambiguities in the standard
27 */
28
29#define __PHYSICSFS_INTERNAL__
30#include "physfs_internal.h"
31
32#if PHYSFS_SUPPORTS_ISO9660
33
34#include <time.h>
35
36/* ISO9660 often stores values in both big and little endian formats: little
37 first, followed by big. While technically there might be different values
38 in each, we just always use the littleendian ones and swap ourselves. The
39 fields aren't aligned anyhow, so you have to serialize them in any case
40 to avoid crashes on many CPU archs in any case. */
41
42static int iso9660LoadEntries(PHYSFS_Io *io, const int joliet,
43 const char *base, const PHYSFS_uint64 dirstart,
44 const PHYSFS_uint64 dirend, void *unpkarc);
45
46static int iso9660AddEntry(PHYSFS_Io *io, const int joliet, const int isdir,
47 const char *base, PHYSFS_uint8 *fname,
48 const int fnamelen, const PHYSFS_sint64 ts,
49 const PHYSFS_uint64 pos, const PHYSFS_uint64 len,
50 void *unpkarc)
51{
52 char *fullpath;
53 char *fnamecpy;
54 size_t baselen;
55 size_t fullpathlen;
56 void *entry;
57 int i;
58
59 if (fnamelen == 1 && ((fname[0] == 0) || (fname[0] == 1)))
60 return 1; /* Magic that represents "." and "..", ignore */
61
62 BAIL_IF(fnamelen == 0, PHYSFS_ERR_CORRUPT, 0);
63 assert(fnamelen > 0);
64 assert(fnamelen <= 255);
65 BAIL_IF(joliet && (fnamelen % 2), PHYSFS_ERR_CORRUPT, 0);
66
67 /* Joliet is UCS-2, so at most UTF-8 will double the byte size */
68 baselen = strlen(base);
69 fullpathlen = baselen + (fnamelen * (joliet ? 2 : 1)) + 2;
70 fullpath = (char *) __PHYSFS_smallAlloc(fullpathlen);
71 BAIL_IF(!fullpath, PHYSFS_ERR_OUT_OF_MEMORY, 0);
72 fnamecpy = fullpath;
73 if (baselen > 0)
74 {
75 snprintf(fullpath, fullpathlen, "%s/", base);
76 fnamecpy += baselen + 1;
77 fullpathlen -= baselen - 1;
78 } /* if */
79
80 if (joliet)
81 {
82 PHYSFS_uint16 *ucs2 = (PHYSFS_uint16 *) fname;
83 int total = fnamelen / 2;
84 for (i = 0; i < total; i++)
85 ucs2[i] = PHYSFS_swapUBE16(ucs2[i]);
86 ucs2[total] = '\0';
87 PHYSFS_utf8FromUcs2(ucs2, fnamecpy, fullpathlen);
88 } /* if */
89 else
90 {
91 for (i = 0; i < fnamelen; i++)
92 {
93 /* We assume the filenames are low-ASCII; consider the archive
94 corrupt if we see something above 127, since we don't know the
95 encoding. (We can change this later if we find out these exist
96 and are intended to be, say, latin-1 or UTF-8 encoding). */
97 BAIL_IF(fname[i] > 127, PHYSFS_ERR_CORRUPT, 0);
98 fnamecpy[i] = fname[i];
99 } /* for */
100 fnamecpy[fnamelen] = '\0';
101
102 if (!isdir)
103 {
104 /* find last SEPARATOR2 */
105 char *ptr = strrchr(fnamecpy, ';');
106 if (ptr && (ptr != fnamecpy))
107 *(ptr--) = '\0';
108 else
109 ptr = fnamecpy + (fnamelen - 1);
110
111 /* chop out any trailing '.', as done in all implementations */
112 if (*ptr == '.')
113 *ptr = '\0';
114 } /* if */
115 } /* else */
116
117 entry = UNPK_addEntry(unpkarc, fullpath, isdir, ts, ts, pos, len);
118 if ((entry) && (isdir))
119 {
120 if (!iso9660LoadEntries(io, joliet, fullpath, pos, pos + len, unpkarc))
121 entry = NULL; /* so we report a failure later. */
122 } /* if */
123
124 __PHYSFS_smallFree(fullpath);
125 return entry != NULL;
126} /* iso9660AddEntry */
127
128static int iso9660LoadEntries(PHYSFS_Io *io, const int joliet,
129 const char *base, const PHYSFS_uint64 dirstart,
130 const PHYSFS_uint64 dirend, void *unpkarc)
131{
132 PHYSFS_uint64 readpos = dirstart;
133
134 while (1)
135 {
136 PHYSFS_uint8 recordlen;
137 PHYSFS_uint8 extattrlen;
138 PHYSFS_uint32 extent;
139 PHYSFS_uint32 datalen;
140 PHYSFS_uint8 ignore[4];
141 PHYSFS_uint8 year, month, day, hour, minute, second, offset;
142 PHYSFS_uint8 flags;
143 PHYSFS_uint8 fnamelen;
144 PHYSFS_uint8 fname[256];
145 PHYSFS_sint64 timestamp;
146 struct tm t;
147 int isdir;
148 int multiextent;
149
150 BAIL_IF_ERRPASS(!io->seek(io, readpos), 0);
151
152 /* recordlen = 0 -> no more entries or fill entry */
153 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &recordlen, 1), 0);
154 if (recordlen > 0)
155 readpos += recordlen; /* ready to seek to next record. */
156 else
157 {
158 PHYSFS_uint64 nextpos;
159
160 /* if we are in the last sector of the directory & it's 0 -> end */
161 if ((dirend - 2048) <= (readpos - 1))
162 break; /* finished */
163
164 /* else skip to the next sector & continue; */
165 nextpos = (((readpos - 1) / 2048) + 1) * 2048;
166
167 /* whoops, can't make forward progress! */
168 BAIL_IF(nextpos == readpos, PHYSFS_ERR_CORRUPT, 0);
169
170 readpos = nextpos;
171 continue; /* start back at upper loop. */
172 } /* else */
173
174 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &extattrlen, 1), 0);
175 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &extent, 4), 0);
176 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* extent be */
177 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &datalen, 4), 0);
178 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* datalen be */
179
180 /* record timestamp */
181 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &year, 1), 0);
182 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &month, 1), 0);
183 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &day, 1), 0);
184 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &hour, 1), 0);
185 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &minute, 1), 0);
186 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &second, 1), 0);
187 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &offset, 1), 0);
188
189 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &flags, 1), 0);
190 isdir = (flags & (1 << 1)) != 0;
191 multiextent = (flags & (1 << 7)) != 0;
192 BAIL_IF(multiextent, PHYSFS_ERR_UNSUPPORTED, 0); /* !!! FIXME */
193
194 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 1), 0); /* unit size */
195 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 1), 0); /* interleave gap */
196 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 2), 0); /* seqnum le */
197 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 2), 0); /* seqnum be */
198 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &fnamelen, 1), 0);
199 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, fname, fnamelen), 0);
200
201 t.tm_sec = second;
202 t.tm_min = minute;
203 t.tm_hour = hour;
204 t.tm_mday = day;
205 t.tm_mon = month - 1;
206 t.tm_year = year;
207 t.tm_wday = 0;
208 t.tm_yday = 0;
209 t.tm_isdst = -1;
210 timestamp = (PHYSFS_sint64) mktime(&t);
211
212 extent += extattrlen; /* skip extended attribute record. */
213
214 /* infinite loop, corrupt file? */
215 BAIL_IF((extent * 2048) == dirstart, PHYSFS_ERR_CORRUPT, 0);
216
217 if (!iso9660AddEntry(io, joliet, isdir, base, fname, fnamelen,
218 timestamp, extent * 2048, datalen, unpkarc))
219 {
220 return 0;
221 } /* if */
222 } /* while */
223
224 return 1;
225} /* iso9660LoadEntries */
226
227
228static int parseVolumeDescriptor(PHYSFS_Io *io, PHYSFS_uint64 *_rootpos,
229 PHYSFS_uint64 *_rootlen, int *_joliet,
230 int *_claimed)
231{
232 PHYSFS_uint64 pos = 32768; /* start at the Primary Volume Descriptor */
233 int found = 0;
234 int done = 0;
235
236 *_joliet = 0;
237
238 while (!done)
239 {
240 PHYSFS_uint8 type;
241 PHYSFS_uint8 identifier[5];
242 PHYSFS_uint8 version;
243 PHYSFS_uint8 flags;
244 PHYSFS_uint8 escapeseqs[32];
245 PHYSFS_uint8 ignore[32];
246 PHYSFS_uint16 blocksize;
247 PHYSFS_uint32 extent;
248 PHYSFS_uint32 datalen;
249
250 BAIL_IF_ERRPASS(!io->seek(io, pos), 0);
251 pos += 2048; /* each volume descriptor is 2048 bytes */
252
253 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &type, 1), 0);
254 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, identifier, 5), 0);
255
256 if (memcmp(identifier, "CD001", 5) != 0) /* maybe not an iso? */
257 {
258 BAIL_IF(!*_claimed, PHYSFS_ERR_UNSUPPORTED, 0);
259 continue; /* just skip this one */
260 } /* if */
261
262 *_claimed = 1; /* okay, this is probably an iso. */
263
264 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &version, 1), 0); /* version */
265 BAIL_IF(version != 1, PHYSFS_ERR_UNSUPPORTED, 0);
266
267 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &flags, 1), 0);
268 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 32), 0); /* system id */
269 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 32), 0); /* volume id */
270 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 8), 0); /* reserved */
271 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* space le */
272 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* space be */
273 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, escapeseqs, 32), 0);
274 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 2), 0); /* setsize le */
275 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 2), 0); /* setsize be */
276 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 2), 0); /* seq num le */
277 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 2), 0); /* seq num be */
278 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &blocksize, 2), 0);
279 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 2), 0); /* blocklen be */
280 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* pthtablen le */
281 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* pthtablen be */
282 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* pthtabpos le */
283 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* optpthtabpos le */
284 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* pthtabpos be */
285 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* optpthtabpos be */
286
287 /* root directory record... */
288 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 1), 0); /* len */
289 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 1), 0); /* attr len */
290 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &extent, 4), 0);
291 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* extent be */
292 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &datalen, 4), 0);
293 BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* datalen be */
294
295 /* !!! FIXME: deal with this properly. */
296 blocksize = PHYSFS_swapULE32(blocksize);
297 BAIL_IF(blocksize && (blocksize != 2048), PHYSFS_ERR_UNSUPPORTED, 0);
298
299 switch (type)
300 {
301 case 1: /* Primary Volume Descriptor */
302 case 2: /* Supplementary Volume Descriptor */
303 if (found < type)
304 {
305 *_rootpos = PHYSFS_swapULE32(extent) * 2048;
306 *_rootlen = PHYSFS_swapULE32(datalen);
307 found = type;
308
309 if (found == 2) /* possible Joliet volume */
310 {
311 const PHYSFS_uint8 *s = escapeseqs;
312 *_joliet = !(flags & 1) &&
313 (s[0] == 0x25) && (s[1] == 0x2F) &&
314 ((s[2] == 0x40) || (s[2] == 0x43) || (s[2] == 0x45));
315 } /* if */
316 } /* if */
317 break;
318
319 case 255: /* type 255 terminates the volume descriptor list */
320 done = 1;
321 break;
322
323 default:
324 break; /* skip unknown types. */
325 } /* switch */
326 } /* while */
327
328 BAIL_IF(!found, PHYSFS_ERR_CORRUPT, 0);
329
330 return 1;
331} /* parseVolumeDescriptor */
332
333
334static void *ISO9660_openArchive(PHYSFS_Io *io, const char *filename,
335 int forWriting, int *claimed)
336{
337 PHYSFS_uint64 rootpos = 0;
338 PHYSFS_uint64 len = 0;
339 int joliet = 0;
340 void *unpkarc = NULL;
341
342 assert(io != NULL); /* shouldn't ever happen. */
343
344 BAIL_IF(forWriting, PHYSFS_ERR_READ_ONLY, NULL);
345
346 if (!parseVolumeDescriptor(io, &rootpos, &len, &joliet, claimed))
347 return NULL;
348
349 /* !!! FIXME: check case_sensitive and only_usascii params for this archive. */
350 unpkarc = UNPK_openArchive(io, 1, 0);
351 BAIL_IF_ERRPASS(!unpkarc, NULL);
352
353 if (!iso9660LoadEntries(io, joliet, "", rootpos, rootpos + len, unpkarc))
354 {
355 UNPK_abandonArchive(unpkarc);
356 return NULL;
357 } /* if */
358
359 return unpkarc;
360} /* ISO9660_openArchive */
361
362
363const PHYSFS_Archiver __PHYSFS_Archiver_ISO9660 =
364{
365 CURRENT_PHYSFS_ARCHIVER_API_VERSION,
366 {
367 "ISO",
368 "ISO9660 image file",
369 "Ryan C. Gordon <icculus@icculus.org>",
370 "https://icculus.org/physfs/",
371 0, /* supportsSymlinks */
372 },
373 ISO9660_openArchive,
374 UNPK_enumerate,
375 UNPK_openRead,
376 UNPK_openWrite,
377 UNPK_openAppend,
378 UNPK_remove,
379 UNPK_mkdir,
380 UNPK_stat,
381 UNPK_closeArchive
382};
383
384#endif /* defined PHYSFS_SUPPORTS_ISO9660 */
385
386/* end of physfs_archiver_iso9660.c ... */
387
388