[FREELDR] Diverse enhancements.
[reactos.git] / boot / freeldr / freeldr / lib / fs / iso.c
1 /*
2 * FreeLoader
3 * Copyright (C) 1998-2003 Brian Palmer <brianp@sginet.com>
4 * Copyright (C) 2009 Hervé Poussineau <hpoussin@reactos.org>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20
21 #ifndef _M_ARM
22 #include <freeldr.h>
23 #include <debug.h>
24
25 #define SECTORSIZE 2048
26 #define TAG_ISO_BUFFER 'BosI'
27 #define TAG_ISO_FILE 'FosI'
28
29 DBG_DEFAULT_CHANNEL(FILESYSTEM);
30
31 static BOOLEAN IsoSearchDirectoryBufferForFile(PVOID DirectoryBuffer, ULONG DirectoryLength, PCHAR FileName, PISO_FILE_INFO IsoFileInfoPointer)
32 {
33 PDIR_RECORD Record;
34 ULONG Offset;
35 ULONG i;
36 CHAR Name[32];
37
38 TRACE("IsoSearchDirectoryBufferForFile() DirectoryBuffer = 0x%x DirectoryLength = %d FileName = %s\n", DirectoryBuffer, DirectoryLength, FileName);
39
40 RtlZeroMemory(Name, 32 * sizeof(UCHAR));
41
42 Offset = 0;
43 Record = (PDIR_RECORD)DirectoryBuffer;
44 while (TRUE)
45 {
46 Offset = Offset + Record->RecordLength;
47 Record = (PDIR_RECORD)((ULONG_PTR)DirectoryBuffer + Offset);
48
49 if (Record->RecordLength == 0)
50 {
51 Offset = ROUND_UP(Offset, SECTORSIZE);
52 Record = (PDIR_RECORD)((ULONG_PTR)DirectoryBuffer + Offset);
53 }
54
55 if (Offset >= DirectoryLength)
56 return FALSE;
57
58 if (Record->FileIdLength == 1 && Record->FileId[0] == 0)
59 {
60 TRACE("Name '.'\n");
61 }
62 else if (Record->FileIdLength == 1 && Record->FileId[0] == 1)
63 {
64 TRACE("Name '..'\n");
65 }
66 else
67 {
68 for (i = 0; i < Record->FileIdLength && Record->FileId[i] != ';'; i++)
69 Name[i] = Record->FileId[i];
70 Name[i] = 0;
71 TRACE("Name '%s'\n", Name);
72
73 if (strlen(FileName) == strlen(Name) && _stricmp(FileName, Name) == 0)
74 {
75 IsoFileInfoPointer->FileStart = Record->ExtentLocationL;
76 IsoFileInfoPointer->FileSize = Record->DataLengthL;
77 IsoFileInfoPointer->FilePointer = 0;
78 IsoFileInfoPointer->Directory = (Record->FileFlags & 0x02)?TRUE:FALSE;
79
80 return TRUE;
81 }
82
83 }
84
85 RtlZeroMemory(Name, 32 * sizeof(UCHAR));
86 }
87
88 return FALSE;
89 }
90
91
92 /*
93 * IsoBufferDirectory()
94 * This function allocates a buffer, reads the specified directory
95 * and returns a pointer to that buffer into pDirectoryBuffer. The
96 * function returns an ARC error code. The directory is specified
97 * by its starting sector and length.
98 */
99 static ARC_STATUS IsoBufferDirectory(ULONG DeviceId, ULONG DirectoryStartSector, ULONG DirectoryLength,
100 PVOID* pDirectoryBuffer)
101 {
102 PVOID DirectoryBuffer;
103 ULONG SectorCount;
104 LARGE_INTEGER Position;
105 ULONG Count;
106 ARC_STATUS Status;
107
108 TRACE("IsoBufferDirectory() DirectoryStartSector = %d DirectoryLength = %d\n", DirectoryStartSector, DirectoryLength);
109
110 SectorCount = ROUND_UP(DirectoryLength, SECTORSIZE) / SECTORSIZE;
111 TRACE("Trying to read (DirectoryCount) %d sectors.\n", SectorCount);
112
113 //
114 // Attempt to allocate memory for directory buffer
115 //
116 TRACE("Trying to allocate (DirectoryLength) %d bytes.\n", DirectoryLength);
117 DirectoryBuffer = FrLdrTempAlloc(DirectoryLength, TAG_ISO_BUFFER);
118 if (!DirectoryBuffer)
119 return ENOMEM;
120
121 //
122 // Now read directory contents into DirectoryBuffer
123 //
124 Position.HighPart = 0;
125 Position.LowPart = DirectoryStartSector * SECTORSIZE;
126 Status = ArcSeek(DeviceId, &Position, SeekAbsolute);
127 if (Status != ESUCCESS)
128 {
129 FrLdrTempFree(DirectoryBuffer, TAG_ISO_BUFFER);
130 return Status;
131 }
132 Status = ArcRead(DeviceId, DirectoryBuffer, SectorCount * SECTORSIZE, &Count);
133 if (Status != ESUCCESS || Count != SectorCount * SECTORSIZE)
134 {
135 FrLdrTempFree(DirectoryBuffer, TAG_ISO_BUFFER);
136 return EIO;
137 }
138
139 *pDirectoryBuffer = DirectoryBuffer;
140 return ESUCCESS;
141 }
142
143
144 /*
145 * IsoLookupFile()
146 * This function searches the file system for the
147 * specified filename and fills in an ISO_FILE_INFO structure
148 * with info describing the file, etc. returns ARC error code
149 */
150 static ARC_STATUS IsoLookupFile(PCSTR FileName, ULONG DeviceId, PISO_FILE_INFO IsoFileInfoPointer)
151 {
152 UCHAR Buffer[SECTORSIZE];
153 PPVD Pvd = (PPVD)Buffer;
154 UINT32 i;
155 ULONG NumberOfPathParts;
156 CHAR PathPart[261];
157 PVOID DirectoryBuffer;
158 ULONG DirectorySector;
159 ULONG DirectoryLength;
160 ISO_FILE_INFO IsoFileInfo;
161 LARGE_INTEGER Position;
162 ULONG Count;
163 ARC_STATUS Status;
164
165 TRACE("IsoLookupFile() FileName = %s\n", FileName);
166
167 RtlZeroMemory(IsoFileInfoPointer, sizeof(ISO_FILE_INFO));
168 RtlZeroMemory(&IsoFileInfo, sizeof(ISO_FILE_INFO));
169
170 //
171 // Read The Primary Volume Descriptor
172 //
173 Position.HighPart = 0;
174 Position.LowPart = 16 * SECTORSIZE;
175 Status = ArcSeek(DeviceId, &Position, SeekAbsolute);
176 if (Status != ESUCCESS)
177 return Status;
178 Status = ArcRead(DeviceId, Pvd, SECTORSIZE, &Count);
179 if (Status != ESUCCESS || Count < sizeof(PVD))
180 return EIO;
181
182 DirectorySector = Pvd->RootDirRecord.ExtentLocationL;
183 DirectoryLength = Pvd->RootDirRecord.DataLengthL;
184
185 //
186 // Figure out how many sub-directories we are nested in
187 //
188 NumberOfPathParts = FsGetNumPathParts(FileName);
189
190 //
191 // Loop once for each part
192 //
193 for (i=0; i<NumberOfPathParts; i++)
194 {
195 //
196 // Get first path part
197 //
198 FsGetFirstNameFromPath(PathPart, FileName);
199
200 //
201 // Advance to the next part of the path
202 //
203 for (; (*FileName != '\\') && (*FileName != '/') && (*FileName != '\0'); FileName++)
204 {
205 }
206 FileName++;
207
208 //
209 // Buffer the directory contents
210 //
211 Status = IsoBufferDirectory(DeviceId, DirectorySector, DirectoryLength, &DirectoryBuffer);
212 if (Status != ESUCCESS)
213 return Status;
214
215 //
216 // Search for file name in directory
217 //
218 if (!IsoSearchDirectoryBufferForFile(DirectoryBuffer, DirectoryLength, PathPart, &IsoFileInfo))
219 {
220 FrLdrTempFree(DirectoryBuffer, TAG_ISO_BUFFER);
221 return ENOENT;
222 }
223
224 FrLdrTempFree(DirectoryBuffer, TAG_ISO_BUFFER);
225
226 //
227 // If we have another sub-directory to go then
228 // grab the start sector and file size
229 //
230 if ((i+1) < NumberOfPathParts)
231 {
232 DirectorySector = IsoFileInfo.FileStart;
233 DirectoryLength = IsoFileInfo.FileSize;
234 }
235
236 }
237
238 RtlCopyMemory(IsoFileInfoPointer, &IsoFileInfo, sizeof(ISO_FILE_INFO));
239
240 return ESUCCESS;
241 }
242
243 ARC_STATUS IsoClose(ULONG FileId)
244 {
245 PISO_FILE_INFO FileHandle = FsGetDeviceSpecific(FileId);
246
247 FrLdrTempFree(FileHandle, TAG_ISO_FILE);
248
249 return ESUCCESS;
250 }
251
252 ARC_STATUS IsoGetFileInformation(ULONG FileId, FILEINFORMATION* Information)
253 {
254 PISO_FILE_INFO FileHandle = FsGetDeviceSpecific(FileId);
255
256 RtlZeroMemory(Information, sizeof(*Information));
257 Information->EndingAddress.LowPart = FileHandle->FileSize;
258 Information->CurrentAddress.LowPart = FileHandle->FilePointer;
259
260 TRACE("IsoGetFileInformation(%lu) -> FileSize = %lu, FilePointer = 0x%lx\n",
261 FileId, Information->EndingAddress.LowPart, Information->CurrentAddress.LowPart);
262
263 return ESUCCESS;
264 }
265
266 ARC_STATUS IsoOpen(CHAR* Path, OPENMODE OpenMode, ULONG* FileId)
267 {
268 ISO_FILE_INFO TempFileInfo;
269 PISO_FILE_INFO FileHandle;
270 ULONG DeviceId;
271 ARC_STATUS Status;
272
273 if (OpenMode != OpenReadOnly)
274 return EACCES;
275
276 DeviceId = FsGetDeviceId(*FileId);
277
278 TRACE("IsoOpen() FileName = %s\n", Path);
279
280 RtlZeroMemory(&TempFileInfo, sizeof(TempFileInfo));
281 Status = IsoLookupFile(Path, DeviceId, &TempFileInfo);
282 if (Status != ESUCCESS)
283 return ENOENT;
284
285 FileHandle = FrLdrTempAlloc(sizeof(ISO_FILE_INFO), TAG_ISO_FILE);
286 if (!FileHandle)
287 return ENOMEM;
288
289 RtlCopyMemory(FileHandle, &TempFileInfo, sizeof(ISO_FILE_INFO));
290
291 FsSetDeviceSpecific(*FileId, FileHandle);
292 return ESUCCESS;
293 }
294
295 ARC_STATUS IsoRead(ULONG FileId, VOID* Buffer, ULONG N, ULONG* Count)
296 {
297 PISO_FILE_INFO FileHandle = FsGetDeviceSpecific(FileId);
298 UCHAR SectorBuffer[SECTORSIZE];
299 LARGE_INTEGER Position;
300 ULONG DeviceId;
301 ULONG FilePointer;
302 ULONG SectorNumber;
303 ULONG OffsetInSector;
304 ULONG LengthInSector;
305 ULONG NumberOfSectors;
306 ULONG BytesRead;
307 ARC_STATUS Status;
308
309 TRACE("IsoRead() Buffer = %p, N = %lu\n", Buffer, N);
310
311 DeviceId = FsGetDeviceId(FileId);
312 *Count = 0;
313
314 //
315 // If they are trying to read past the
316 // end of the file then return success
317 // with Count == 0
318 //
319 FilePointer = FileHandle->FilePointer;
320 if (FilePointer >= FileHandle->FileSize)
321 {
322 return ESUCCESS;
323 }
324
325 //
326 // If they are trying to read more than there is to read
327 // then adjust the amount to read
328 //
329 if (FilePointer + N > FileHandle->FileSize)
330 {
331 N = FileHandle->FileSize - FilePointer;
332 }
333
334 //
335 // Ok, now we have to perform at most 3 calculations
336 // I'll draw you a picture (using nifty ASCII art):
337 //
338 // CurrentFilePointer -+
339 // |
340 // +----------------+
341 // |
342 // +-----------+-----------+-----------+-----------+
343 // | Sector 1 | Sector 2 | Sector 3 | Sector 4 |
344 // +-----------+-----------+-----------+-----------+
345 // | |
346 // +---------------+--------------------+
347 // |
348 // N -----------------+
349 //
350 // 1 - The first calculation (and read) will align
351 // the file pointer with the next sector
352 // boundary (if we are supposed to read that much)
353 // 2 - The next calculation (and read) will read
354 // in all the full sectors that the requested
355 // amount of data would cover (in this case
356 // sectors 2 & 3).
357 // 3 - The last calculation (and read) would read
358 // in the remainder of the data requested out of
359 // the last sector.
360 //
361
362
363 //
364 // Only do the first read if we
365 // aren't aligned on a cluster boundary
366 //
367 if (FilePointer % SECTORSIZE)
368 {
369 //
370 // Do the math for our first read
371 //
372 SectorNumber = FileHandle->FileStart + (FilePointer / SECTORSIZE);
373 OffsetInSector = FilePointer % SECTORSIZE;
374 LengthInSector = (N > (SECTORSIZE - OffsetInSector)) ? (SECTORSIZE - OffsetInSector) : N;
375
376 //
377 // Now do the read and update Count, N, FilePointer, & Buffer
378 //
379 Position.HighPart = 0;
380 Position.LowPart = SectorNumber * SECTORSIZE;
381 Status = ArcSeek(DeviceId, &Position, SeekAbsolute);
382 if (Status != ESUCCESS)
383 {
384 return Status;
385 }
386 Status = ArcRead(DeviceId, SectorBuffer, SECTORSIZE, &BytesRead);
387 if (Status != ESUCCESS || BytesRead != SECTORSIZE)
388 {
389 return EIO;
390 }
391 RtlCopyMemory(Buffer, SectorBuffer + OffsetInSector, LengthInSector);
392 *Count += LengthInSector;
393 N -= LengthInSector;
394 FilePointer += LengthInSector;
395 Buffer = (PVOID)((ULONG_PTR)Buffer + LengthInSector);
396 }
397
398 //
399 // Do the math for our second read (if any data left)
400 //
401 if (N > 0)
402 {
403 //
404 // Determine how many full clusters we need to read
405 //
406 NumberOfSectors = (N / SECTORSIZE);
407
408 SectorNumber = FileHandle->FileStart + (FilePointer / SECTORSIZE);
409
410 //
411 // Now do the read and update Count, N, FilePointer, & Buffer
412 //
413 Position.HighPart = 0;
414 Position.LowPart = SectorNumber * SECTORSIZE;
415 Status = ArcSeek(DeviceId, &Position, SeekAbsolute);
416 if (Status != ESUCCESS)
417 {
418 return Status;
419 }
420 Status = ArcRead(DeviceId, Buffer, NumberOfSectors * SECTORSIZE, &BytesRead);
421 if (Status != ESUCCESS || BytesRead != NumberOfSectors * SECTORSIZE)
422 {
423 return EIO;
424 }
425
426 *Count += NumberOfSectors * SECTORSIZE;
427 N -= NumberOfSectors * SECTORSIZE;
428 FilePointer += NumberOfSectors * SECTORSIZE;
429 Buffer = (PVOID)((ULONG_PTR)Buffer + NumberOfSectors * SECTORSIZE);
430 }
431
432 //
433 // Do the math for our third read (if any data left)
434 //
435 if (N > 0)
436 {
437 SectorNumber = FileHandle->FileStart + (FilePointer / SECTORSIZE);
438
439 //
440 // Now do the read and update Count, N, FilePointer, & Buffer
441 //
442 Position.HighPart = 0;
443 Position.LowPart = SectorNumber * SECTORSIZE;
444 Status = ArcSeek(DeviceId, &Position, SeekAbsolute);
445 if (Status != ESUCCESS)
446 {
447 return Status;
448 }
449 Status = ArcRead(DeviceId, SectorBuffer, SECTORSIZE, &BytesRead);
450 if (Status != ESUCCESS || BytesRead != SECTORSIZE)
451 {
452 return EIO;
453 }
454 RtlCopyMemory(Buffer, SectorBuffer, N);
455 *Count += N;
456 FilePointer += N;
457 }
458
459 TRACE("IsoRead() done\n");
460
461 return ESUCCESS;
462 }
463
464 ARC_STATUS IsoSeek(ULONG FileId, LARGE_INTEGER* Position, SEEKMODE SeekMode)
465 {
466 PISO_FILE_INFO FileHandle = FsGetDeviceSpecific(FileId);
467
468 TRACE("IsoSeek() NewFilePointer = %lu\n", Position->LowPart);
469
470 if (SeekMode != SeekAbsolute)
471 return EINVAL;
472 if (Position->HighPart != 0)
473 return EINVAL;
474 if (Position->LowPart >= FileHandle->FileSize)
475 return EINVAL;
476
477 FileHandle->FilePointer = Position->LowPart;
478 return ESUCCESS;
479 }
480
481 const DEVVTBL Iso9660FuncTable =
482 {
483 IsoClose,
484 IsoGetFileInformation,
485 IsoOpen,
486 IsoRead,
487 IsoSeek,
488 L"cdfs",
489 };
490
491 const DEVVTBL* IsoMount(ULONG DeviceId)
492 {
493 UCHAR Buffer[SECTORSIZE];
494 PPVD Pvd = (PPVD)Buffer;
495 LARGE_INTEGER Position;
496 ULONG Count;
497 ARC_STATUS Status;
498
499 TRACE("Enter IsoMount(%lu)\n", DeviceId);
500
501 //
502 // Read The Primary Volume Descriptor
503 //
504 Position.HighPart = 0;
505 Position.LowPart = 16 * SECTORSIZE;
506 Status = ArcSeek(DeviceId, &Position, SeekAbsolute);
507 if (Status != ESUCCESS)
508 return NULL;
509 Status = ArcRead(DeviceId, Pvd, SECTORSIZE, &Count);
510 if (Status != ESUCCESS || Count < sizeof(PVD))
511 return NULL;
512
513 //
514 // Check if PVD is valid. If yes, return ISO9660 function table
515 //
516 if (Pvd->VdType == 1 && RtlEqualMemory(Pvd->StandardId, "CD001", 5))
517 {
518 TRACE("IsoMount(%lu) success\n", DeviceId);
519 return &Iso9660FuncTable;
520 }
521
522 return NULL;
523 }
524
525 #endif