Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
1 | pmbaty | 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 | |||
42 | static int iso9660LoadEntries(PHYSFS_Io *io, const int joliet, |
||
43 | const char *base, const PHYSFS_uint64 dirstart, |
||
44 | const PHYSFS_uint64 dirend, void *unpkarc); |
||
45 | |||
46 | static 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 | |||
128 | static 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 | |||
228 | static 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 | |||
334 | static 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 | unpkarc = UNPK_openArchive(io); |
||
350 | BAIL_IF_ERRPASS(!unpkarc, NULL); |
||
351 | |||
352 | if (!iso9660LoadEntries(io, joliet, "", rootpos, rootpos + len, unpkarc)) |
||
353 | { |
||
354 | UNPK_abandonArchive(unpkarc); |
||
355 | return NULL; |
||
356 | } /* if */ |
||
357 | |||
358 | return unpkarc; |
||
359 | } /* ISO9660_openArchive */ |
||
360 | |||
361 | |||
362 | const PHYSFS_Archiver __PHYSFS_Archiver_ISO9660 = |
||
363 | { |
||
364 | CURRENT_PHYSFS_ARCHIVER_API_VERSION, |
||
365 | { |
||
366 | "ISO", |
||
367 | "ISO9660 image file", |
||
368 | "Ryan C. Gordon <icculus@icculus.org>", |
||
369 | "https://icculus.org/physfs/", |
||
370 | 0, /* supportsSymlinks */ |
||
371 | }, |
||
372 | ISO9660_openArchive, |
||
373 | UNPK_enumerate, |
||
374 | UNPK_openRead, |
||
375 | UNPK_openWrite, |
||
376 | UNPK_openAppend, |
||
377 | UNPK_remove, |
||
378 | UNPK_mkdir, |
||
379 | UNPK_stat, |
||
380 | UNPK_closeArchive |
||
381 | }; |
||
382 | |||
383 | #endif /* defined PHYSFS_SUPPORTS_ISO9660 */ |
||
384 | |||
385 | /* end of physfs_archiver_iso9660.c ... */ |
||
386 |