[FREELDR] Advance the file pointers every time a read operation is performed, in...
[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
24 #include <debug.h>
25 DBG_DEFAULT_CHANNEL(FILESYSTEM);
26
27 #define SECTORSIZE 2048
28 #define TAG_ISO_BUFFER 'BosI'
29 #define TAG_ISO_FILE 'FosI'
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);
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 FrLdrTempFree(FileHandle, TAG_ISO_FILE);
247 return ESUCCESS;
248 }
249
250 ARC_STATUS IsoGetFileInformation(ULONG FileId, FILEINFORMATION* Information)
251 {
252 PISO_FILE_INFO FileHandle = FsGetDeviceSpecific(FileId);
253
254 RtlZeroMemory(Information, sizeof(*Information));
255 Information->EndingAddress.LowPart = FileHandle->FileSize;
256 Information->CurrentAddress.LowPart = FileHandle->FilePointer;
257
258 TRACE("IsoGetFileInformation(%lu) -> FileSize = %lu, FilePointer = 0x%lx\n",
259 FileId, Information->EndingAddress.LowPart, Information->CurrentAddress.LowPart);
260
261 return ESUCCESS;
262 }
263
264 ARC_STATUS IsoOpen(CHAR* Path, OPENMODE OpenMode, ULONG* FileId)
265 {
266 ISO_FILE_INFO TempFileInfo;
267 PISO_FILE_INFO FileHandle;
268 ULONG DeviceId;
269 ARC_STATUS Status;
270
271 if (OpenMode != OpenReadOnly)
272 return EACCES;
273
274 DeviceId = FsGetDeviceId(*FileId);
275
276 TRACE("IsoOpen() FileName = %s\n", Path);
277
278 RtlZeroMemory(&TempFileInfo, sizeof(TempFileInfo));
279 Status = IsoLookupFile(Path, DeviceId, &TempFileInfo);
280 if (Status != ESUCCESS)
281 return ENOENT;
282
283 FileHandle = FrLdrTempAlloc(sizeof(ISO_FILE_INFO), TAG_ISO_FILE);
284 if (!FileHandle)
285 return ENOMEM;
286
287 RtlCopyMemory(FileHandle, &TempFileInfo, sizeof(ISO_FILE_INFO));
288
289 FsSetDeviceSpecific(*FileId, FileHandle);
290 return ESUCCESS;
291 }
292
293 ARC_STATUS IsoRead(ULONG FileId, VOID* Buffer, ULONG N, ULONG* Count)
294 {
295 ARC_STATUS Status;
296 PISO_FILE_INFO FileHandle = FsGetDeviceSpecific(FileId);
297 UCHAR SectorBuffer[SECTORSIZE];
298 LARGE_INTEGER Position;
299 ULONG DeviceId;
300 ULONG SectorNumber;
301 ULONG OffsetInSector;
302 ULONG LengthInSector;
303 ULONG NumberOfSectors;
304 ULONG BytesRead;
305
306 TRACE("IsoRead() Buffer = %p, N = %lu\n", Buffer, N);
307
308 DeviceId = FsGetDeviceId(FileId);
309 *Count = 0;
310
311 //
312 // If the user is trying to read past the end of
313 // the file then return success with Count == 0.
314 //
315 if (FileHandle->FilePointer >= FileHandle->FileSize)
316 {
317 return ESUCCESS;
318 }
319
320 //
321 // If the user is trying to read more than there is to read
322 // then adjust the amount to read.
323 //
324 if (FileHandle->FilePointer + N > FileHandle->FileSize)
325 {
326 N = FileHandle->FileSize - FileHandle->FilePointer;
327 }
328
329 //
330 // Ok, now we have to perform at most 3 calculations
331 // I'll draw you a picture (using nifty ASCII art):
332 //
333 // CurrentFilePointer -+
334 // |
335 // +----------------+
336 // |
337 // +-----------+-----------+-----------+-----------+
338 // | Sector 1 | Sector 2 | Sector 3 | Sector 4 |
339 // +-----------+-----------+-----------+-----------+
340 // | |
341 // +---------------+--------------------+
342 // |
343 // N -----------------+
344 //
345 // 1 - The first calculation (and read) will align
346 // the file pointer with the next sector
347 // boundary (if we are supposed to read that much)
348 // 2 - The next calculation (and read) will read
349 // in all the full sectors that the requested
350 // amount of data would cover (in this case
351 // sectors 2 & 3).
352 // 3 - The last calculation (and read) would read
353 // in the remainder of the data requested out of
354 // the last sector.
355 //
356
357
358 //
359 // Only do the first read if we
360 // aren't aligned on a cluster boundary
361 //
362 if (FileHandle->FilePointer % SECTORSIZE)
363 {
364 //
365 // Do the math for our first read
366 //
367 SectorNumber = FileHandle->FileStart + (FileHandle->FilePointer / SECTORSIZE);
368 OffsetInSector = FileHandle->FilePointer % SECTORSIZE;
369 LengthInSector = min(N, SECTORSIZE - OffsetInSector);
370
371 //
372 // Now do the read and update Count, N, FilePointer, & Buffer
373 //
374 Position.HighPart = 0;
375 Position.LowPart = SectorNumber * SECTORSIZE;
376 Status = ArcSeek(DeviceId, &Position, SeekAbsolute);
377 if (Status != ESUCCESS)
378 {
379 return Status;
380 }
381 Status = ArcRead(DeviceId, SectorBuffer, SECTORSIZE, &BytesRead);
382 if (Status != ESUCCESS || BytesRead != SECTORSIZE)
383 {
384 return EIO;
385 }
386 RtlCopyMemory(Buffer, SectorBuffer + OffsetInSector, LengthInSector);
387 *Count += LengthInSector;
388 N -= LengthInSector;
389 FileHandle->FilePointer += LengthInSector;
390 Buffer = (PVOID)((ULONG_PTR)Buffer + LengthInSector);
391 }
392
393 //
394 // Do the math for our second read (if any data left)
395 //
396 if (N > 0)
397 {
398 //
399 // Determine how many full clusters we need to read
400 //
401 NumberOfSectors = (N / SECTORSIZE);
402
403 SectorNumber = FileHandle->FileStart + (FileHandle->FilePointer / SECTORSIZE);
404
405 //
406 // Now do the read and update Count, N, FilePointer, & Buffer
407 //
408 Position.HighPart = 0;
409 Position.LowPart = SectorNumber * SECTORSIZE;
410 Status = ArcSeek(DeviceId, &Position, SeekAbsolute);
411 if (Status != ESUCCESS)
412 {
413 return Status;
414 }
415 Status = ArcRead(DeviceId, Buffer, NumberOfSectors * SECTORSIZE, &BytesRead);
416 if (Status != ESUCCESS || BytesRead != NumberOfSectors * SECTORSIZE)
417 {
418 return EIO;
419 }
420
421 *Count += NumberOfSectors * SECTORSIZE;
422 N -= NumberOfSectors * SECTORSIZE;
423 FileHandle->FilePointer += NumberOfSectors * SECTORSIZE;
424 Buffer = (PVOID)((ULONG_PTR)Buffer + NumberOfSectors * SECTORSIZE);
425 }
426
427 //
428 // Do the math for our third read (if any data left)
429 //
430 if (N > 0)
431 {
432 SectorNumber = FileHandle->FileStart + (FileHandle->FilePointer / SECTORSIZE);
433
434 //
435 // Now do the read and update Count, N, FilePointer, & Buffer
436 //
437 Position.HighPart = 0;
438 Position.LowPart = SectorNumber * SECTORSIZE;
439 Status = ArcSeek(DeviceId, &Position, SeekAbsolute);
440 if (Status != ESUCCESS)
441 {
442 return Status;
443 }
444 Status = ArcRead(DeviceId, SectorBuffer, SECTORSIZE, &BytesRead);
445 if (Status != ESUCCESS || BytesRead != SECTORSIZE)
446 {
447 return EIO;
448 }
449 RtlCopyMemory(Buffer, SectorBuffer, N);
450 *Count += N;
451 FileHandle->FilePointer += N;
452 }
453
454 TRACE("IsoRead() done\n");
455
456 return ESUCCESS;
457 }
458
459 ARC_STATUS IsoSeek(ULONG FileId, LARGE_INTEGER* Position, SEEKMODE SeekMode)
460 {
461 PISO_FILE_INFO FileHandle = FsGetDeviceSpecific(FileId);
462 LARGE_INTEGER NewPosition = *Position;
463
464 switch (SeekMode)
465 {
466 case SeekAbsolute:
467 break;
468 case SeekRelative:
469 NewPosition.QuadPart += (ULONGLONG)FileHandle->FilePointer;
470 break;
471 default:
472 ASSERT(FALSE);
473 return EINVAL;
474 }
475
476 if (NewPosition.HighPart != 0)
477 return EINVAL;
478 if (NewPosition.LowPart >= FileHandle->FileSize)
479 return EINVAL;
480
481 FileHandle->FilePointer = NewPosition.LowPart;
482 return ESUCCESS;
483 }
484
485 const DEVVTBL Iso9660FuncTable =
486 {
487 IsoClose,
488 IsoGetFileInformation,
489 IsoOpen,
490 IsoRead,
491 IsoSeek,
492 L"cdfs",
493 };
494
495 const DEVVTBL* IsoMount(ULONG DeviceId)
496 {
497 UCHAR Buffer[SECTORSIZE];
498 PPVD Pvd = (PPVD)Buffer;
499 LARGE_INTEGER Position;
500 ULONG Count;
501 ARC_STATUS Status;
502
503 TRACE("Enter IsoMount(%lu)\n", DeviceId);
504
505 //
506 // Read The Primary Volume Descriptor
507 //
508 Position.HighPart = 0;
509 Position.LowPart = 16 * SECTORSIZE;
510 Status = ArcSeek(DeviceId, &Position, SeekAbsolute);
511 if (Status != ESUCCESS)
512 return NULL;
513 Status = ArcRead(DeviceId, Pvd, SECTORSIZE, &Count);
514 if (Status != ESUCCESS || Count < sizeof(PVD))
515 return NULL;
516
517 //
518 // Check if PVD is valid. If yes, return ISO9660 function table
519 //
520 if (Pvd->VdType == 1 && RtlEqualMemory(Pvd->StandardId, "CD001", 5))
521 {
522 TRACE("IsoMount(%lu) success\n", DeviceId);
523 return &Iso9660FuncTable;
524 }
525
526 return NULL;
527 }
528
529 #endif