blob: c542ee08815d3a7e7f0935ce8fb0a951713f9e73 [file] [log] [blame]
Nico Hubercc960f22024-01-29 01:13:45 +01001-- Copyright (C) 2024 secunet Security Networks AG
2--
3-- This program is free software; you can redistribute it and/or modify
4-- it under the terms of the GNU General Public License as published by
5-- the Free Software Foundation; either version 2 of the License, or
6-- (at your option) any later version.
7
8with System;
9with Interfaces;
10with Interfaces.C;
11
12with FILO.Blockdev;
13with FILO.FS.VFS;
14
15use Interfaces.C;
16
17package body FILO.FS.ISO9660 is
18
Nico Huber780b7f42024-02-08 19:11:37 +010019 subtype SUSP_Signature is String (1 .. 2);
20
21 --------------------------------------------------------------------------
22
Nico Hubercc960f22024-01-29 01:13:45 +010023 function Is_Mounted (State : T) return Boolean is (State.S >= Mounted);
24 function Is_Open (State : T) return Boolean is (State.S = File_Opened);
25
26 --------------------------------------------------------------------------
27
Nico Huber780b7f42024-02-08 19:11:37 +010028 function "+" (Rec : FS_Record; Off : FSBlock_Length) return FS_Record
29 is
30 (Block => Rec.Block, Offset => Rec.Offset + Off, Size => Rec.Size - Off)
31 with
32 Pre => Rec.Size >= Off;
33
Nico Hubercc960f22024-01-29 01:13:45 +010034 procedure Read
Nico Huber4210fcc2024-01-29 16:33:47 +010035 (Buf : out Buffer_Type;
Nico Huber666da672024-02-08 15:30:20 +010036 Rec : in FS_Record;
Nico Hubercc960f22024-01-29 01:13:45 +010037 Success : out Boolean)
38 with
Nico Huber666da672024-02-08 15:30:20 +010039 Pre => Buf'Length <= Rec.Size
Nico Hubercc960f22024-01-29 01:13:45 +010040 is
Nico Huber666da672024-02-08 15:30:20 +010041 Block_64 : constant Integer_64 := Integer_64 (Rec.Block);
42 Offset_64 : constant Integer_64 := Integer_64 (Rec.Offset);
Nico Hubercc960f22024-01-29 01:13:45 +010043 begin
Nico Hubercc960f22024-01-29 01:13:45 +010044 Blockdev.Read (Buf, Blockdev_Length (Block_64 * FSBlock_Size + Offset_64), Success);
45 end Read;
46
47 procedure Mount
48 (State : in out T;
49 Part_Len : in Partition_Length;
50 Success : out Boolean)
51 is
52 D_Type : constant Index_Type := 0;
53 D_Id : constant Index_Type := 1;
54 D_Ver : constant Index_Type := 6;
55
56 D_Type_Primary : constant := 1;
57 D_Type_Terminator : constant := 255;
58
59 CD001 : constant Buffer_Type :=
60 (Character'Pos ('C'), Character'Pos ('D'),
61 Character'Pos ('0'), Character'Pos ('0'), Character'Pos ('1'));
62
63 Static : Mount_State renames State.Static;
64
Nico Huber4210fcc2024-01-29 16:33:47 +010065 Volume_Descriptor : Buffer_Type (0 .. D_Ver);
Nico Huber65f2ea22024-02-06 17:37:35 +010066 Block : FSBlock := 16;
Nico Hubercc960f22024-01-29 01:13:45 +010067 begin
68 loop
69 pragma Loop_Invariant (Block < 1024);
Nico Huber4210fcc2024-01-29 16:33:47 +010070 pragma Loop_Invariant (not Is_Mounted (State));
Nico Hubercc960f22024-01-29 01:13:45 +010071
72 if Part_Len < Partition_Length (Block + 1) * FSBlock_Size then
73 Success := False;
74 return;
75 end if;
Nico Huber666da672024-02-08 15:30:20 +010076 Static.Part_Len := Part_Len;
Nico Hubercc960f22024-01-29 01:13:45 +010077
Nico Huber666da672024-02-08 15:30:20 +010078 Read (Volume_Descriptor, FS_Record'(Block, 0, FSBlock_Size), Success);
Nico Hubercc960f22024-01-29 01:13:45 +010079 if not Success then
80 return;
81 end if;
82
83 if Volume_Descriptor (D_Ver) /= 1 or
84 Volume_Descriptor (D_Id .. D_Ver - 1) /= CD001 or
85 Volume_Descriptor (D_Type) = D_Type_Terminator
86 then
Nico Huber4210fcc2024-01-29 16:33:47 +010087 Success := False;
Nico Hubercc960f22024-01-29 01:13:45 +010088 return;
89 end if;
90
91 exit when Volume_Descriptor (D_Type) = D_Type_Primary;
92
93 Block := Block + 1;
94 if Block >= 1024 then
Nico Huber4210fcc2024-01-29 16:33:47 +010095 Success := False;
Nico Hubercc960f22024-01-29 01:13:45 +010096 return;
97 end if;
98 end loop;
99
Nico Huber666da672024-02-08 15:30:20 +0100100 Static.Root_Inode := (Block, 156, Directory_Record'Length);
Nico Hubercc960f22024-01-29 01:13:45 +0100101
102 State.S := Mounted;
103 end Mount;
104
Nico Huber780b7f42024-02-08 19:11:37 +0100105 procedure Find_SUSP_Record
106 (State : in T;
107 Rec : out FS_Record;
108 Dir_Rec_Pos : in File_Offset;
109 Dir_Rec_Len : in FSBlock_Length;
110 Searched_Sig : in SUSP_Signature)
111 with
112 Pre => FSBlock_Index (Dir_Rec_Pos mod FSBlock_Size) <= FSBlock_Size - Dir_Rec_Len
113 is
114 subtype Signature_Buf is Buffer_Type (0 .. 1);
115 function Signature (Str : SUSP_Signature) return Signature_Buf
116 is
117 ((Character'Pos (Str (Str'First)), Character'Pos (Str (Str'First + 1))));
118
119 Cur : FS_Record :=
120 (Block => State.Inode.Extents (0).Start + FSBlock (Dir_Rec_Pos / FSBlock_Size),
121 Offset => FSBlock_Index (Dir_Rec_Pos mod FSBlock_Size),
122 Size => Dir_Rec_Len);
123
124 Continuation : FS_Record := (others => <>);
125
126 SUSP_Head : Buffer_Type (0 .. 3);
127
128 Success : Boolean;
129 begin
130 Rec := (others => <>);
131 if Cur.Size = 0 then
132 return;
133 end if;
134
135 if Cur.Offset mod 2 = 1 then
136 Cur := Cur + 1;
137 end if;
138
139 for I in 1 .. 2**16 loop -- arbitrary limit to avoid endless loop
140 if Cur.Size < SUSP_Head'Length then
141 if Continuation.Size = 0 then
142 return;
143 else
144 Cur := Continuation;
145 Continuation := (others => <>);
146 end if;
147 end if;
148
149 Read (SUSP_Head, Cur, Success);
150 declare
151 Rec_Len : constant Natural := Natural (SUSP_Head (2));
152 SUSP_CE : Buffer_Type (0 .. 27);
153 begin
154 if not Success or else Rec_Len not in 4 .. Cur.Size then
155 return;
156 end if;
157
158 if SUSP_Head (0 .. 1) = Signature (Searched_Sig) then
159 Rec :=
160 (Block => Cur.Block,
161 Offset => Cur.Offset,
162 Size => Rec_Len);
163 return;
164 elsif SUSP_Head (0 .. 1) = Signature ("CE") then
165 if Rec_Len < SUSP_CE'Length then
166 return;
167 end if;
168
169 Read (SUSP_CE, Cur, Success);
170 if not Success then
171 return;
172 end if;
173 declare
174 CE_Extent : constant Unsigned_32 := Read_LE32 (SUSP_CE, 4);
175 CE_Offset : constant Unsigned_32 := Read_LE32 (SUSP_CE, 12);
176 CE_Size : constant Unsigned_32 := Read_LE32 (SUSP_CE, 20);
177 begin
178 if (CE_Offset > FSBlock_Size or CE_Size > FSBlock_Size) or else
179 CE_Offset > FSBlock_Size - CE_Size
180 then
181 return;
182 end if;
183 Continuation :=
184 (Block => FSBlock (CE_Extent),
185 Offset => FSBlock_Index (CE_Offset),
186 Size => FSBlock_Length (CE_Size));
187 end;
188 end if;
189
190 Cur := Cur + Rec_Len;
191 end;
192 end loop;
193 end Find_SUSP_Record;
194
Nico Hubercc960f22024-01-29 01:13:45 +0100195 procedure Open
196 (State : in out T;
197 I : in Inode_Index;
198 Success : out Boolean)
199 with
200 Pre => Is_Mounted (State),
201 Post => Is_Mounted (State) and (Success = Is_Open (State))
202 is
Nico Hubercc960f22024-01-29 01:13:45 +0100203 Inode : Inode_Info renames State.Inode;
204
205 Dir_Rec : Directory_Record;
206 begin
Nico Huber4210fcc2024-01-29 16:33:47 +0100207 State.S := Mounted;
208
Nico Hubercc960f22024-01-29 01:13:45 +0100209 Inode := (I, others => <>);
210
Nico Huber666da672024-02-08 15:30:20 +0100211 Read (Dir_Rec, I, Success);
Nico Hubercc960f22024-01-29 01:13:45 +0100212 if not Success then
213 return;
214 end if;
215
216 declare
217 D_Flag_Dir : constant := 16#02#;
218 D_Flag_Cont : constant := 16#80#;
219 D_Flags : constant Unsigned_8 := Dir_Rec (25);
Nico Huber4210fcc2024-01-29 16:33:47 +0100220 D_Extent : constant Unsigned_32 := Read_LE32 (Dir_Rec, 2);
Nico Hubercc960f22024-01-29 01:13:45 +0100221 begin
Nico Huber4210fcc2024-01-29 16:33:47 +0100222 if D_Extent > Unsigned_32 (FSBlock'Last) then
223 Success := False;
224 return;
225 end if;
Nico Hubercc960f22024-01-29 01:13:45 +0100226 if (D_Flags and D_Flag_Cont) /= 0 then
227 Success := False;
228 return;
229 end if;
Nico Huber4210fcc2024-01-29 16:33:47 +0100230
Nico Hubercc960f22024-01-29 01:13:45 +0100231 if (D_Flags and D_Flag_Dir) /= 0 then
232 Inode.Mode := Dir;
233 else
234 Inode.Mode := Regular;
235 end if;
236 Inode.Size := Inode_Length (Read_LE32 (Dir_Rec, 10));
237 Inode.Extents (0) :=
Nico Huber4210fcc2024-01-29 16:33:47 +0100238 (Start => FSBlock (D_Extent),
Nico Hubercc960f22024-01-29 01:13:45 +0100239 Count => FSBlock_Count (Inode.Size / Inode_Length (FSBlock_Size)));
240 State.S := File_Opened;
241 end;
242 end Open;
243
244 procedure Open
245 (State : in out T;
246 File_Len : out File_Length;
247 File_Type : out FS.File_Type;
248 File_Name : in String;
249 In_Root : in Boolean;
250 Success : out Boolean)
251 is
252 File_Name_Max : constant := 255;
253
Nico Huberab933b82024-02-07 11:50:59 +0100254 procedure Match_ISO_Name
255 (ISO_Name_Length : in Natural;
256 Record_Dir_Pos : in File_Offset;
257 Success : in out Boolean)
Nico Hubercc960f22024-01-29 01:13:45 +0100258 with
Nico Huberab933b82024-02-07 11:50:59 +0100259 Post => State.Static = State.Static'Old
Nico Hubercc960f22024-01-29 01:13:45 +0100260 is
Nico Huberab933b82024-02-07 11:50:59 +0100261 function Upper_Case_Equal (Str : String; Buf : Buffer_Type) return Boolean
262 with
263 Pre => Str'Length <= Buf'Length
264 is
265 begin
266 for I in Str'Range loop
267 declare
268 Chr : constant Unsigned_8 :=
269 (if 'a' <= Str (I) and Str (I) <= 'z' then
270 Character'Pos (Str (I)) - Character'Pos ('a') + Character'Pos ('A')
271 else Character'Pos (Str (I)));
272 begin
273 if Chr /= Buf (Buf'First + (I - Str'First)) then
274 return False;
275 end if;
276 end;
277 end loop;
278 return True;
279 end Upper_Case_Equal;
280
281 Dir_Pos : File_Offset := Record_Dir_Pos;
282 ISO_Name : Buffer_Type (0 .. ISO_Name_Length - 1);
283 Name_Len : Natural;
Nico Hubercc960f22024-01-29 01:13:45 +0100284 begin
Nico Huberab933b82024-02-07 11:50:59 +0100285 -- Check if length could match.
286 -- First for the weird `.' and `..' encoding,
287 -- then for regular file names + `.' and file version `;1'.
288 if not (ISO_Name_Length = 1 and File_Name'Length <= 2) and
289 ISO_Name_Length not in File_Name'Length .. File_Name'Length + 3
290 then
291 return;
292 end if;
293
294 pragma Warnings
295 (GNATprove, Off, """Dir_Pos"" is set*but not used",
296 Reason => "This is the last part of the record we care about.");
297 Read
298 (State => State,
299 File_Pos => Dir_Pos,
300 Buf => ISO_Name,
301 Len => Name_Len);
302 if Name_Len /= ISO_Name_Length then
303 return;
304 end if;
305 pragma Warnings (GNATprove, On, """Dir_Pos"" is set*but not used");
306
307 if (Name_Len = 1 and ISO_Name (0) = 16#00# and File_Name = ".") or
308 (Name_Len = 1 and ISO_Name (0) = 16#01# and File_Name = "..")
309 then
310 Success := True;
311 return;
312 end if;
313
314 -- Ignore file version `;1'
315 if Name_Len >= File_Name'Length + 2 and
316 ISO_Name (Name_Len - 2) = Character'Pos (';') and
317 ISO_Name (Name_Len - 1) = Character'Pos ('1')
318 then
319 Name_Len := Name_Len - 2;
320 end if;
321
322 -- Ignore trailing `.'
323 if Name_Len >= File_Name'Length + 1 and
324 ISO_Name (Name_Len - 1) = Character'Pos ('.')
325 then
326 Name_Len := Name_Len - 1;
327 end if;
328
329 Success := Name_Len = File_Name'Length and then
330 Upper_Case_Equal (File_Name, ISO_Name);
331 end Match_ISO_Name;
Nico Hubercc960f22024-01-29 01:13:45 +0100332
Nico Huber780b7f42024-02-08 19:11:37 +0100333 procedure Match_Rock_Ridge_Name
334 (Pos : in File_Offset;
335 Len : in Natural;
336 Success : in out Boolean)
337 with
338 Post => State = State'Old
339 is
340 Name_Rec : FS_Record;
341 begin
342 Find_SUSP_Record (State, Name_Rec, Pos, Len, "NM");
343 if Name_Rec.Size /= File_Name'Length + 5 then
344 Success := False;
345 return;
346 end if;
347
348 declare
349 RR_Name : Buffer_Type (0 .. File_Name'Length - 1);
350 begin
351 Read (RR_Name, Name_Rec + 5, Success);
352 Success := Success and then Equal (File_Name, RR_Name);
353 end;
354 end Match_Rock_Ridge_Name;
355
Nico Hubercc960f22024-01-29 01:13:45 +0100356 Found_Inode : Inode_Index;
357 Dir_Pos : File_Length;
358 begin
359 File_Len := 0;
360 File_Type := FS.File_Type'First;
361
362 if File_Name'Length > File_Name_Max then
363 Success := False;
364 return;
365 end if;
366
367 -- Ensure dir is opened:
368 --
369 if State.S = File_Opened then
370 if State.Cur_Dir /= State.Inode.I then
371 Success := False;
372 return;
373 end if;
374 else
375 if In_Root then
376 State.Cur_Dir := State.Static.Root_Inode;
377 end if;
378 declare
379 Cur_Dir : constant Inode_Index := State.Cur_Dir;
380 begin
381 Open (State, Cur_Dir, Success);
382 if not Success then
383 return;
384 end if;
385 end;
386 end if;
387
388 -- Lookup file in opened dir:
389 --
390 Dir_Pos := 0;
391 Success := False;
392 loop
393 pragma Loop_Invariant (Is_Open (State) and not Success);
Nico Huber4210fcc2024-01-29 16:33:47 +0100394 pragma Loop_Invariant
395 (FSBlock_Index (Dir_Pos mod FSBlock_Size) <= Directory_Record_Offset'Last);
396
Nico Hubercc960f22024-01-29 01:13:45 +0100397 declare
Nico Huberab933b82024-02-07 11:50:59 +0100398 Dir_Rec_Head : Directory_Record;
Nico Hubercc960f22024-01-29 01:13:45 +0100399 Record_Dir_Pos : File_Offset := Dir_Pos;
Nico Hubercc960f22024-01-29 01:13:45 +0100400 Len : Natural;
401 begin
402 Read
403 (State => State,
404 File_Pos => Record_Dir_Pos,
Nico Huberab933b82024-02-07 11:50:59 +0100405 Buf => Dir_Rec_Head,
Nico Hubercc960f22024-01-29 01:13:45 +0100406 Len => Len);
Nico Huberab933b82024-02-07 11:50:59 +0100407 if Len /= Dir_Rec_Head'Length then
Nico Hubercc960f22024-01-29 01:13:45 +0100408 return;
409 end if;
410
Nico Huberb18691e2024-02-06 17:52:17 +0100411 declare
Nico Huberab933b82024-02-07 11:50:59 +0100412 Dir_Rec_Length : constant Natural := Natural (Dir_Rec_Head (0));
413 Dir_Rec_Name_Length : constant Natural := Natural (Dir_Rec_Head (32));
Nico Huberb18691e2024-02-06 17:52:17 +0100414 begin
Nico Huberab933b82024-02-07 11:50:59 +0100415 if Dir_Rec_Length /= 0 then
416
417 if Unsigned_64 (Dir_Pos) > Unsigned_64 (State.Inode.Size) - Unsigned_64 (Dir_Rec_Length) or
418 Dir_Rec_Name_Length > Dir_Rec_Length - Dir_Rec_Head'Length
419 then
Nico Huberb18691e2024-02-06 17:52:17 +0100420 return;
Nico Huberb18691e2024-02-06 17:52:17 +0100421 end if;
Nico Huberab933b82024-02-07 11:50:59 +0100422
Nico Huber780b7f42024-02-08 19:11:37 +0100423 -- First skip ISO name and try Rock Ridge extension:
424 Match_Rock_Ridge_Name
425 (Pos => Record_Dir_Pos + File_Offset (Dir_Rec_Name_Length),
426 Len => Dir_Rec_Length - Dir_Rec_Head'Length - Dir_Rec_Name_Length,
427 Success => Success);
428 if not Success then
429 Match_ISO_Name (Dir_Rec_Name_Length, Record_Dir_Pos, Success);
430 end if;
Nico Huberab933b82024-02-07 11:50:59 +0100431
432 if Success then
433 Found_Inode :=
Nico Huber666da672024-02-08 15:30:20 +0100434 (Block => FSBlock (Dir_Pos / FSBlock_Size) + State.Inode.Extents (0).Start,
435 Offset => FSBlock_Index (Dir_Pos mod FSBlock_Size),
436 Size => Dir_Rec_Length);
Nico Huberab933b82024-02-07 11:50:59 +0100437 exit;
438 end if;
439
440 Dir_Pos := Dir_Pos + File_Length (Dir_Rec_Length);
441
Nico Huberb18691e2024-02-06 17:52:17 +0100442 else
Nico Huberab933b82024-02-07 11:50:59 +0100443 -- `Dir_Rec_Length = 0` means we have to skip to the next fs block:
444 declare
445 Block_Gap : constant File_Length := FSBlock_Size - (Dir_Pos mod FSBlock_Size);
446 begin
447 if Unsigned_64 (Dir_Pos) >= Unsigned_64 (State.Inode.Size) - Unsigned_64 (Block_Gap) then
448 return;
449 else
450 Dir_Pos := Dir_Pos + Block_Gap;
451 end if;
452 end;
Nico Huberb18691e2024-02-06 17:52:17 +0100453 end if;
454 end;
Nico Hubercc960f22024-01-29 01:13:45 +0100455 end;
456 end loop;
457 pragma Assert_And_Cut (Success and Is_Mounted (State));
458
459 Open (State, Found_Inode, Success);
460 if not Success then
461 return;
462 end if;
463
464 if State.Inode.Mode = Dir then
465 State.Cur_Dir := Found_Inode;
466 end if;
467
Nico Huber4210fcc2024-01-29 16:33:47 +0100468 Success := State.Inode.Size <= Inode_Length (File_Length'Last);
Nico Hubercc960f22024-01-29 01:13:45 +0100469 if Success then
470 File_Len := File_Length (State.Inode.Size);
471 File_Type := State.Inode.Mode;
472 else
473 Close (State);
474 end if;
475 end Open;
476
477 procedure Close (State : in out T) is
478 begin
479 State.S := Mounted;
480 end Close;
481
482 procedure Read
483 (State : in out T;
484 File_Pos : in out File_Offset;
485 Buf : out Buffer_Type;
486 Len : out Natural)
487 is
488 Static : Mount_State renames State.Static;
489
490 Pos : Natural range Buf'First .. Buf'Last + 1;
491 begin
492 Len := 0;
493 Pos := Buf'First;
494 while Pos <= Buf'Last and
495 File_Pos < File_Offset'Last and
496 Inode_Length (File_Pos) < State.Inode.Size
497 loop
Nico Huber4210fcc2024-01-29 16:33:47 +0100498 pragma Loop_Invariant (for all I in Buf'First .. Pos - 1 => Buf (I)'Initialized);
Nico Hubercc960f22024-01-29 01:13:45 +0100499 pragma Loop_Invariant (Is_Open (State));
500 declare
501 In_Block : constant FSBlock_Index := Natural (File_Pos mod FSBlock_Size);
502 Logical : constant FSBlock_Logical := FSBlock_Logical (File_Pos / FSBlock_Size);
503 In_Block_Space : constant Positive := Index_Type (FSBlock_Size) - In_Block;
504 In_File_Space : constant Inode_Length := State.Inode.Size - Inode_Length (File_Pos);
505 In_Buf_Space : constant Positive := Buf'Last - Pos + 1;
506 Len_Here : Positive;
507 begin
508 exit when FSBlock (Logical) > FSBlock'Last - State.Inode.Extents (0).Start;
509
510 Len_Here := In_Block_Space;
511 if In_File_Space < Inode_Length (Len_Here) then
512 Len_Here := Positive (In_File_Space);
513 end if;
514 if In_Buf_Space < Len_Here then
515 Len_Here := In_Buf_Space;
516 end if;
517 if File_Offset'Last - File_Pos < File_Length (Len_Here) then
518 Len_Here := Positive (File_Offset'Last - File_Pos);
519 end if;
520
521 declare
522 Last : constant Index_Type := Pos + Len_Here - 1;
523 Physical : constant FSBlock := State.Inode.Extents (0).Start + FSBlock (Logical);
524 Success : Boolean;
525 begin
526 Read
527 (Buf => Buf (Pos .. Last),
Nico Huber666da672024-02-08 15:30:20 +0100528 Rec => FS_Record'(Physical, In_Block, In_Block_Space),
Nico Hubercc960f22024-01-29 01:13:45 +0100529 Success => Success);
530 exit when not Success;
531
532 File_Pos := File_Pos + File_Length (Len_Here);
533 Pos := Pos + Len_Here;
534 Len := Pos - Buf'First;
535 end;
536 end;
537 end loop;
538 Buf (Pos .. Buf'Last) := (others => 16#00#);
539 end Read;
540
541 --------------------------------------------------------------------------
542
543 package C is new VFS (T => T, Initial => (S => Unmounted, others => <>));
544
545 function C_Mount return int
546 with
547 Export,
548 Convention => C,
549 External_Name => "iso9660_mount";
550 function C_Mount return int
551 with
552 SPARK_Mode => Off
553 is
554 begin
555 return C.C_Mount;
556 end C_Mount;
557
558 function C_Open (File_Path : System.Address) return int
559 with
560 Export,
561 Convention => C,
562 External_Name => "iso9660_dir";
563 function C_Open (File_Path : System.Address) return int
564 with
565 SPARK_Mode => Off
566 is
567 begin
568 return C.C_Open (File_Path);
569 end C_Open;
570
571 function C_Read (Buf : System.Address; Len : int) return int
572 with
573 Export,
574 Convention => C,
575 External_Name => "iso9660_read";
576 function C_Read (Buf : System.Address; Len : int) return int
577 with
578 SPARK_Mode => Off
579 is
580 begin
581 return C.C_Read (Buf, Len);
582 end C_Read;
583
584end FILO.FS.ISO9660;