Subversion Repositories Games.Descent

Rev

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