1
/* $Id: isofs.cpp 34406 2010-11-26 16:45:34Z vboxsync $ */
3
* IPRT - ISO 9660 file system handling.
7
* Copyright (C) 2010 Oracle Corporation
9
* This file is part of VirtualBox Open Source Edition (OSE), as
10
* available from http://www.virtualbox.org. This file is free software;
11
* you can redistribute it and/or modify it under the terms of the GNU
12
* General Public License (GPL) as published by the Free Software
13
* Foundation, in version 2 as it comes in the "COPYING" file of the
14
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
17
* The contents of this file may alternatively be used under the terms
18
* of the Common Development and Distribution License Version 1.0
19
* (CDDL) only, as it comes in the "COPYING.CDDL" file of the
20
* VirtualBox OSE distribution, in which case the provisions of the
21
* CDDL are applicable instead of those of the GPL.
23
* You may elect to license modified versions of this file under the
24
* terms and conditions of either the GPL or the CDDL or both.
28
/*******************************************************************************
30
*******************************************************************************/
31
#include <iprt/isofs.h>
33
#include <iprt/file.h>
36
#include <iprt/path.h>
37
#include <iprt/string.h>
41
* Destroys the path cache.
43
* @param pFile ISO handle.
45
static void rtIsoFsDestroyPathCache(PRTISOFSFILE pFile)
47
PRTISOFSPATHTABLEENTRY pNode = RTListGetFirst(&pFile->listPaths, RTISOFSPATHTABLEENTRY, Node);
50
PRTISOFSPATHTABLEENTRY pNext = RTListNodeGetNext(&pNode->Node, RTISOFSPATHTABLEENTRY, Node);
51
bool fLast = RTListNodeIsLast(&pFile->listPaths, &pNode->Node);
54
RTStrFree(pNode->path);
56
RTStrFree(pNode->path_full);
57
RTListNodeRemove(&pNode->Node);
69
* Adds a path entry to the path table list.
71
* @return IPRT status code.
72
* @param pList Path table list to add the path entry to.
73
* @param pszPath Path to add.
74
* @param pHeader Path header information to add.
76
static int rtIsoFsAddToPathCache(PRTLISTNODE pList, const char *pszPath,
77
RTISOFSPATHTABLEHEADER *pHeader)
79
AssertPtrReturn(pList, VERR_INVALID_PARAMETER);
80
AssertPtrReturn(pszPath, VERR_INVALID_PARAMETER);
81
AssertPtrReturn(pHeader, VERR_INVALID_PARAMETER);
83
PRTISOFSPATHTABLEENTRY pNode = (PRTISOFSPATHTABLEENTRY)RTMemAlloc(sizeof(RTISOFSPATHTABLEENTRY));
85
return VERR_NO_MEMORY;
88
if (RT_SUCCESS(RTStrAAppend(&pNode->path, pszPath)))
90
memcpy((RTISOFSPATHTABLEHEADER*)&pNode->header,
91
(RTISOFSPATHTABLEHEADER*)pHeader, sizeof(pNode->header));
93
pNode->path_full = NULL;
94
pNode->Node.pPrev = NULL;
95
pNode->Node.pNext = NULL;
96
RTListAppend(pList, &pNode->Node);
99
return VERR_NO_MEMORY;
104
* Retrieves the parent path of a given node, assuming that the path table
105
* (still) is in sync with the node's index.
107
* @return IPRT status code.
108
* @param pList Path table list to use.
109
* @param pNode Node of path table entry to lookup the full path for.
110
* @param pszPathNode Current (partial) parent path; needed for recursion.
111
* @param ppszPath Pointer to a pointer to store the retrieved full path to.
113
static int rtIsoFsGetParentPathSub(PRTLISTNODE pList, PRTISOFSPATHTABLEENTRY pNode,
114
char *pszPathNode, char **ppszPath)
116
int rc = VINF_SUCCESS;
117
/* Do we have a parent? */
118
if (pNode->header.parent_index > 1)
121
/* Get the parent of our current node (pNode) */
122
PRTISOFSPATHTABLEENTRY pNodeParent = RTListGetFirst(pList, RTISOFSPATHTABLEENTRY, Node);
123
while (idx++ < pNode->header.parent_index)
124
pNodeParent = RTListNodeGetNext(&pNodeParent->Node, RTISOFSPATHTABLEENTRY, Node);
125
/* Construct intermediate path (parent + current path). */
126
char *pszPath = RTPathJoinA(pNodeParent->path, pszPathNode);
129
/* ... and do the same with the parent's parent until we reached the root. */
130
rc = rtIsoFsGetParentPathSub(pList, pNodeParent, pszPath, ppszPath);
134
rc = VERR_NO_STR_MEMORY;
136
else /* No parent (left), this must be the root path then. */
137
*ppszPath = RTStrDup(pszPathNode);
143
* Updates the path table cache of an ISO file.
145
* @return IPRT status code.
146
* @param pFile ISO handle.
148
static int rtIsoFsUpdatePathCache(PRTISOFSFILE pFile)
150
AssertPtrReturn(pFile, VERR_INVALID_PARAMETER);
151
rtIsoFsDestroyPathCache(pFile);
153
RTListInit(&pFile->listPaths);
155
/* Seek to path tables. */
156
int rc = VINF_SUCCESS;
157
Assert(pFile->pvd.path_table_start_first > 16);
158
uint64_t uTableStart = (pFile->pvd.path_table_start_first * RTISOFS_SECTOR_SIZE);
159
Assert(uTableStart % RTISOFS_SECTOR_SIZE == 0); /* Make sure it's aligned. */
160
if (RTFileTell(pFile->file) != uTableStart)
161
rc = RTFileSeek(pFile->file, uTableStart, RTFILE_SEEK_BEGIN, &uTableStart);
164
* Since this is a sequential format, for performance it's best to read the
165
* complete path table (every entry can have its own level (directory depth) first
166
* and the actual directories of the path table afterwards.
169
/* Read in the path table ... */
170
size_t cbLeft = pFile->pvd.path_table_size;
171
RTISOFSPATHTABLEHEADER header;
172
while ((cbLeft > 0) && RT_SUCCESS(rc))
175
rc = RTFileRead(pFile->file, (RTISOFSPATHTABLEHEADER*)&header, sizeof(RTISOFSPATHTABLEHEADER), &cbRead);
181
Assert(cbLeft >= header.length);
182
Assert(header.length <= 31);
183
/* Allocate and read in the actual path name. */
184
char *pszName = RTStrAlloc(header.length + 1);
185
rc = RTFileRead(pFile->file, (char*)pszName, header.length, &cbRead);
189
pszName[cbRead] = '\0'; /* Terminate string. */
190
/* Add entry to cache ... */
191
rc = rtIsoFsAddToPathCache(&pFile->listPaths, pszName, &header);
194
/* Read padding if required ... */
195
if ((header.length % 2) != 0) /* If we have an odd length, read/skip the padding byte. */
197
rc = RTFileSeek(pFile->file, 1, RTFILE_SEEK_CURRENT, NULL);
203
/* Transform path names into full paths. This is a bit ugly right now. */
204
PRTISOFSPATHTABLEENTRY pNode = RTListGetLast(&pFile->listPaths, RTISOFSPATHTABLEENTRY, Node);
206
&& !RTListNodeIsFirst(&pFile->listPaths, &pNode->Node)
209
rc = rtIsoFsGetParentPathSub(&pFile->listPaths, pNode,
210
pNode->path, &pNode->path_full);
212
pNode = RTListNodeGetPrev(&pNode->Node, RTISOFSPATHTABLEENTRY, Node);
219
RTR3DECL(int) RTIsoFsOpen(PRTISOFSFILE pFile, const char *pszFileName)
221
AssertPtrReturn(pFile, VERR_INVALID_PARAMETER);
222
AssertPtrReturn(pszFileName, VERR_INVALID_PARAMETER);
224
RTListInit(&pFile->listPaths);
226
Assert(sizeof(RTISOFSDATESHORT) == 7);
227
Assert(sizeof(RTISOFSDATELONG) == 17);
228
int l = sizeof(RTISOFSDIRRECORD);
229
RTPrintf("RTISOFSDIRRECORD=%ld\n", l);
231
/* Each volume descriptor exactly occupies one sector. */
232
l = sizeof(RTISOFSPRIVOLDESC);
233
RTPrintf("RTISOFSPRIVOLDESC=%ld\n", l);
234
Assert(l == RTISOFS_SECTOR_SIZE);
236
int rc = RTFileOpen(&pFile->file, pszFileName, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE);
240
rc = RTFileGetSize(pFile->file, &cbSize);
242
&& cbSize > 16 * RTISOFS_SECTOR_SIZE)
244
uint64_t cbOffset = 16 * RTISOFS_SECTOR_SIZE; /* Start reading at 32k. */
246
RTISOFSPRIVOLDESC pvd;
247
bool fFoundPrimary = false;
248
bool fIsValid = false;
249
while (cbOffset < _1M)
251
/* Get primary descriptor. */
252
rc = RTFileRead(pFile->file, (PRTISOFSPRIVOLDESC)&pvd, sizeof(RTISOFSPRIVOLDESC), &cbRead);
253
if (RT_FAILURE(rc) || cbRead < sizeof(RTISOFSPRIVOLDESC))
255
if ( RTStrStr((char*)pvd.name_id, RTISOFS_STANDARD_ID)
256
&& pvd.type == 0x1 /* Primary Volume Descriptor */)
258
memcpy((PRTISOFSPRIVOLDESC)&pFile->pvd,
259
(PRTISOFSPRIVOLDESC)&pvd, sizeof(RTISOFSPRIVOLDESC));
260
fFoundPrimary = true;
262
else if(pvd.type == 0xff /* Termination Volume Descriptor */)
268
cbOffset += sizeof(RTISOFSPRIVOLDESC);
272
rc = rtIsoFsUpdatePathCache(pFile);
274
rc = VERR_INVALID_PARAMETER;
283
RTR3DECL(void) RTIsoFsClose(PRTISOFSFILE pFile)
287
rtIsoFsDestroyPathCache(pFile);
288
RTFileClose(pFile->file);
294
* Parses an extent given at the specified sector + size and
295
* searches for a file name to return an allocated directory record.
297
* @return IPRT status code.
298
* @param pFile ISO handle.
299
* @param pszFileName Absolute file name to search for.
300
* @param uExtentSector Sector of extent.
301
* @param cbExtent Size (in bytes) of extent.
302
* @param ppRec Pointer to a pointer to return the
303
* directory record. Must be free'd with
304
* rtIsoFsFreeDirectoryRecord().
306
static int rtIsoFsFindEntry(PRTISOFSFILE pFile, const char *pszFileName,
307
uint32_t uExtentSector, uint32_t cbExtent /* Bytes */,
308
PRTISOFSDIRRECORD *ppRec)
310
AssertPtrReturn(pFile, VERR_INVALID_PARAMETER);
311
Assert(uExtentSector > 16);
313
int rc = RTFileSeek(pFile->file, uExtentSector * RTISOFS_SECTOR_SIZE,
314
RTFILE_SEEK_BEGIN, NULL);
317
rc = VERR_FILE_NOT_FOUND;
319
uint8_t uBuffer[RTISOFS_SECTOR_SIZE];
320
size_t cbLeft = cbExtent;
321
while (!RT_SUCCESS(rc) && cbLeft > 0)
324
int rc2 = RTFileRead(pFile->file, (void*)&uBuffer, sizeof(uBuffer), &cbRead);
325
Assert(RT_SUCCESS(rc2) && cbRead == RTISOFS_SECTOR_SIZE);
331
PRTISOFSDIRRECORD pCurRecord = (PRTISOFSDIRRECORD)&uBuffer[idx];
332
if (pCurRecord->record_length == 0)
335
char *pszName = RTStrAlloc(pCurRecord->name_len + 1);
337
Assert(idx + sizeof(RTISOFSDIRRECORD) < cbRead);
338
memcpy(pszName, &uBuffer[idx + sizeof(RTISOFSDIRRECORD)], pCurRecord->name_len);
339
pszName[pCurRecord->name_len] = '\0'; /* Force string termination. */
341
if ( pCurRecord->name_len == 1
342
&& pszName[0] == 0x0)
344
/* This is a "." directory (self). */
346
else if ( pCurRecord->name_len == 1
347
&& pszName[0] == 0x1)
349
/* This is a ".." directory (parent). */
351
else /* Regular directory or file */
353
if (pCurRecord->flags & RT_BIT(1)) /* Directory */
355
/* We don't recursively go into directories
356
* because we already have the cached path table. */
357
pszName[pCurRecord->name_len] = 0;
358
/*rc = rtIsoFsParseDir(pFile, pszFileName,
359
pDirHdr->extent_location, pDirHdr->extent_data_length);*/
363
/* Get last occurrence of ";" and cut it off. */
364
char *pTerm = strrchr(pszName, ';');
366
pszName[pTerm - pszName] = 0;
368
/* Don't use case sensitive comparison here, in IS0 9660 all
369
* file / directory names are UPPERCASE. */
370
if (!RTStrICmp(pszName, pszFileName))
372
PRTISOFSDIRRECORD pRec = (PRTISOFSDIRRECORD)RTMemAlloc(sizeof(RTISOFSDIRRECORD));
375
memcpy(pRec, pCurRecord, sizeof(RTISOFSDIRRECORD));
385
idx += pCurRecord->record_length;
394
* Retrieves the sector of a file extent given by the
395
* full file path within the ISO.
397
* @return IPRT status code.
398
* @param pFile ISO handle.
399
* @param pszPath File path to resolve.
400
* @param puSector Pointer where to store the found sector to.
402
static int rtIsoFsResolvePath(PRTISOFSFILE pFile, const char *pszPath, uint32_t *puSector)
404
AssertPtrReturn(pFile, VERR_INVALID_PARAMETER);
405
AssertPtrReturn(pszPath, VERR_INVALID_PARAMETER);
406
AssertPtrReturn(puSector, VERR_INVALID_PARAMETER);
408
int rc = VERR_FILE_NOT_FOUND;
409
char *pszTemp = RTStrDup(pszPath);
412
RTPathStripFilename(pszTemp);
415
PRTISOFSPATHTABLEENTRY pNode;
416
if (!RTStrCmp(pszTemp, ".")) /* Root directory? Use first node! */
418
pNode = RTListGetFirst(&pFile->listPaths, RTISOFSPATHTABLEENTRY, Node);
424
RTListForEach(&pFile->listPaths, pNode, RTISOFSPATHTABLEENTRY, Node)
426
if ( pNode->path_full != NULL /* Root does not have a path! */
427
&& !RTStrICmp(pNode->path_full, pszTemp))
437
*puSector = pNode->header.sector_dir_table;
441
rc = VERR_FILE_NOT_FOUND;
451
* Allocates a new directory record.
453
* @return Pointer to the newly allocated directory record.
455
static PRTISOFSDIRRECORD rtIsoFsCreateDirectoryRecord(void)
457
PRTISOFSDIRRECORD pRecord = (PRTISOFSDIRRECORD)RTMemAlloc(sizeof(RTISOFSDIRRECORD));
463
* Frees a previously allocated directory record.
465
* @return IPRT status code.
467
static void rtIsoFsFreeDirectoryRecord(PRTISOFSDIRRECORD pRecord)
474
* Returns an allocated directory record for a given file.
476
* @return IPRT status code.
477
* @param pFile ISO handle.
478
* @param pszPath File path to resolve.
479
* @param ppRecord Pointer to a pointer to return the
480
* directory record. Must be free'd with
481
* rtIsoFsFreeDirectoryRecord().
483
static int rtIsoFsGetDirectoryRecord(PRTISOFSFILE pFile, const char *pszPath,
484
PRTISOFSDIRRECORD *ppRecord)
486
AssertPtrReturn(pFile, VERR_INVALID_PARAMETER);
487
AssertPtrReturn(pszPath, VERR_INVALID_PARAMETER);
488
AssertPtrReturn(ppRecord, VERR_INVALID_PARAMETER);
491
int rc = rtIsoFsResolvePath(pFile, pszPath, &uSector);
494
/* Seek and read the directory record of given file. */
495
rc = RTFileSeek(pFile->file, uSector * RTISOFS_SECTOR_SIZE,
496
RTFILE_SEEK_BEGIN, NULL);
500
PRTISOFSDIRRECORD pRecord = rtIsoFsCreateDirectoryRecord();
503
rc = RTFileRead(pFile->file, (PRTISOFSDIRRECORD)pRecord, sizeof(RTISOFSDIRRECORD), &cbRead);
506
Assert(cbRead == sizeof(RTISOFSDIRRECORD));
510
rtIsoFsFreeDirectoryRecord(pRecord);
520
RTR3DECL(int) RTIsoFsGetFileInfo(PRTISOFSFILE pFile, const char *pszPath,
521
uint32_t *pcbOffset, size_t *pcbLength)
523
AssertPtrReturn(pFile, VERR_INVALID_PARAMETER);
524
AssertPtrReturn(pszPath, VERR_INVALID_PARAMETER);
525
AssertPtrReturn(pcbOffset, VERR_INVALID_PARAMETER);
527
PRTISOFSDIRRECORD pDirRecord;
528
int rc = rtIsoFsGetDirectoryRecord(pFile, pszPath, &pDirRecord);
531
/* Get actual file record. */
532
PRTISOFSDIRRECORD pFileRecord = NULL; /* shut up gcc*/
533
rc = rtIsoFsFindEntry(pFile,
534
RTPathFilename(pszPath),
535
pDirRecord->extent_location,
536
pDirRecord->extent_data_length,
540
*pcbOffset = pFileRecord->extent_location * RTISOFS_SECTOR_SIZE;
541
*pcbLength = pFileRecord->extent_data_length;
542
rtIsoFsFreeDirectoryRecord(pFileRecord);
544
rtIsoFsFreeDirectoryRecord(pDirRecord);
550
RTR3DECL(int) RTIsoFsExtractFile(PRTISOFSFILE pFile, const char *pszSource,
553
AssertPtrReturn(pFile, VERR_INVALID_PARAMETER);
554
AssertPtrReturn(pszSource, VERR_INVALID_PARAMETER);
555
AssertPtrReturn(pszDest, VERR_INVALID_PARAMETER);
559
int rc = RTIsoFsGetFileInfo(pFile, pszSource, &cbOffset, &cbLength);
562
rc = RTFileSeek(pFile->file, cbOffset, RTFILE_SEEK_BEGIN, NULL);
566
rc = RTFileOpen(&fileDest, pszDest, RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE);
569
size_t cbToRead, cbRead, cbWritten;
570
uint8_t byBuffer[_64K];
574
cbToRead = RT_MIN(cbLength, _64K);
575
rc = RTFileRead(pFile->file, (uint8_t*)byBuffer, cbToRead, &cbRead);
578
rc = RTFileWrite(fileDest, (uint8_t*)byBuffer, cbRead, &cbWritten);
583
RTFileClose(fileDest);