blob: ad462f091f76e88292d1d955fccaa6a1e0b56672 [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
19 function Is_Mounted (State : T) return Boolean is (State.S >= Mounted);
20 function Is_Open (State : T) return Boolean is (State.S = File_Opened);
21
22 --------------------------------------------------------------------------
23
24 procedure Read
Nico Huber4210fcc2024-01-29 16:33:47 +010025 (Buf : out Buffer_Type;
Nico Hubercc960f22024-01-29 01:13:45 +010026 Block : in FSBlock;
27 Offset : in FSBlock_Index;
28 FS_Len : in Partition_Length;
29 Success : out Boolean)
30 with
31 Pre => Buf'Length <= FSBlock_Size - Offset
32 is
33 Block_64 : constant Integer_64 := Integer_64 (Block);
34 Offset_64 : constant Integer_64 := Integer_64 (Offset);
35 Max_Block_Offset : constant Integer_64 := Integer_64 (FS_Len) / FSBlock_Size - 1;
36 begin
37 if Block_64 > Max_Block_Offset then
Nico Huber4210fcc2024-01-29 16:33:47 +010038 Buf := (others => 16#00#);
Nico Hubercc960f22024-01-29 01:13:45 +010039 Success := False;
40 return;
41 end if;
42 Blockdev.Read (Buf, Blockdev_Length (Block_64 * FSBlock_Size + Offset_64), Success);
43 end Read;
44
45 procedure Mount
46 (State : in out T;
47 Part_Len : in Partition_Length;
48 Success : out Boolean)
49 is
50 D_Type : constant Index_Type := 0;
51 D_Id : constant Index_Type := 1;
52 D_Ver : constant Index_Type := 6;
53
54 D_Type_Primary : constant := 1;
55 D_Type_Terminator : constant := 255;
56
57 CD001 : constant Buffer_Type :=
58 (Character'Pos ('C'), Character'Pos ('D'),
59 Character'Pos ('0'), Character'Pos ('0'), Character'Pos ('1'));
60
61 Static : Mount_State renames State.Static;
62
Nico Huber4210fcc2024-01-29 16:33:47 +010063 Volume_Descriptor : Buffer_Type (0 .. D_Ver);
Nico Huber65f2ea22024-02-06 17:37:35 +010064 Block : FSBlock := 16;
Nico Hubercc960f22024-01-29 01:13:45 +010065 begin
66 loop
67 pragma Loop_Invariant (Block < 1024);
Nico Huber4210fcc2024-01-29 16:33:47 +010068 pragma Loop_Invariant (not Is_Mounted (State));
Nico Hubercc960f22024-01-29 01:13:45 +010069
70 if Part_Len < Partition_Length (Block + 1) * FSBlock_Size then
71 Success := False;
72 return;
73 end if;
74
Nico Huber4210fcc2024-01-29 16:33:47 +010075 Read (Volume_Descriptor, Block, 0, Part_Len, Success);
Nico Hubercc960f22024-01-29 01:13:45 +010076 if not Success then
77 return;
78 end if;
79
80 if Volume_Descriptor (D_Ver) /= 1 or
81 Volume_Descriptor (D_Id .. D_Ver - 1) /= CD001 or
82 Volume_Descriptor (D_Type) = D_Type_Terminator
83 then
Nico Huber4210fcc2024-01-29 16:33:47 +010084 Success := False;
Nico Hubercc960f22024-01-29 01:13:45 +010085 return;
86 end if;
87
88 exit when Volume_Descriptor (D_Type) = D_Type_Primary;
89
90 Block := Block + 1;
91 if Block >= 1024 then
Nico Huber4210fcc2024-01-29 16:33:47 +010092 Success := False;
Nico Hubercc960f22024-01-29 01:13:45 +010093 return;
94 end if;
95 end loop;
96
97 Static.Part_Len := Part_Len;
98 Static.Root_Inode := (Block, 156);
99
100 State.S := Mounted;
101 end Mount;
102
103 procedure Open
104 (State : in out T;
105 I : in Inode_Index;
106 Success : out Boolean)
107 with
108 Pre => Is_Mounted (State),
109 Post => Is_Mounted (State) and (Success = Is_Open (State))
110 is
111 Static : Mount_State renames State.Static;
112 Inode : Inode_Info renames State.Inode;
113
114 Dir_Rec : Directory_Record;
115 begin
Nico Huber4210fcc2024-01-29 16:33:47 +0100116 State.S := Mounted;
117
Nico Hubercc960f22024-01-29 01:13:45 +0100118 Inode := (I, others => <>);
119
120 Read (Dir_Rec, I.Block, I.Offset, Static.Part_Len, Success);
121 if not Success then
122 return;
123 end if;
124
125 declare
126 D_Flag_Dir : constant := 16#02#;
127 D_Flag_Cont : constant := 16#80#;
128 D_Flags : constant Unsigned_8 := Dir_Rec (25);
Nico Huber4210fcc2024-01-29 16:33:47 +0100129 D_Extent : constant Unsigned_32 := Read_LE32 (Dir_Rec, 2);
Nico Hubercc960f22024-01-29 01:13:45 +0100130 begin
Nico Huber4210fcc2024-01-29 16:33:47 +0100131 if D_Extent > Unsigned_32 (FSBlock'Last) then
132 Success := False;
133 return;
134 end if;
Nico Hubercc960f22024-01-29 01:13:45 +0100135 if (D_Flags and D_Flag_Cont) /= 0 then
136 Success := False;
137 return;
138 end if;
Nico Huber4210fcc2024-01-29 16:33:47 +0100139
Nico Hubercc960f22024-01-29 01:13:45 +0100140 if (D_Flags and D_Flag_Dir) /= 0 then
141 Inode.Mode := Dir;
142 else
143 Inode.Mode := Regular;
144 end if;
145 Inode.Size := Inode_Length (Read_LE32 (Dir_Rec, 10));
146 Inode.Extents (0) :=
Nico Huber4210fcc2024-01-29 16:33:47 +0100147 (Start => FSBlock (D_Extent),
Nico Hubercc960f22024-01-29 01:13:45 +0100148 Count => FSBlock_Count (Inode.Size / Inode_Length (FSBlock_Size)));
149 State.S := File_Opened;
150 end;
151 end Open;
152
153 procedure Open
154 (State : in out T;
155 File_Len : out File_Length;
156 File_Type : out FS.File_Type;
157 File_Name : in String;
158 In_Root : in Boolean;
159 Success : out Boolean)
160 is
161 File_Name_Max : constant := 255;
162
Nico Huberab933b82024-02-07 11:50:59 +0100163 procedure Match_ISO_Name
164 (ISO_Name_Length : in Natural;
165 Record_Dir_Pos : in File_Offset;
166 Success : in out Boolean)
Nico Hubercc960f22024-01-29 01:13:45 +0100167 with
Nico Huberab933b82024-02-07 11:50:59 +0100168 Post => State.Static = State.Static'Old
Nico Hubercc960f22024-01-29 01:13:45 +0100169 is
Nico Huberab933b82024-02-07 11:50:59 +0100170 function Upper_Case_Equal (Str : String; Buf : Buffer_Type) return Boolean
171 with
172 Pre => Str'Length <= Buf'Length
173 is
174 begin
175 for I in Str'Range loop
176 declare
177 Chr : constant Unsigned_8 :=
178 (if 'a' <= Str (I) and Str (I) <= 'z' then
179 Character'Pos (Str (I)) - Character'Pos ('a') + Character'Pos ('A')
180 else Character'Pos (Str (I)));
181 begin
182 if Chr /= Buf (Buf'First + (I - Str'First)) then
183 return False;
184 end if;
185 end;
186 end loop;
187 return True;
188 end Upper_Case_Equal;
189
190 Dir_Pos : File_Offset := Record_Dir_Pos;
191 ISO_Name : Buffer_Type (0 .. ISO_Name_Length - 1);
192 Name_Len : Natural;
Nico Hubercc960f22024-01-29 01:13:45 +0100193 begin
Nico Huberab933b82024-02-07 11:50:59 +0100194 -- Check if length could match.
195 -- First for the weird `.' and `..' encoding,
196 -- then for regular file names + `.' and file version `;1'.
197 if not (ISO_Name_Length = 1 and File_Name'Length <= 2) and
198 ISO_Name_Length not in File_Name'Length .. File_Name'Length + 3
199 then
200 return;
201 end if;
202
203 pragma Warnings
204 (GNATprove, Off, """Dir_Pos"" is set*but not used",
205 Reason => "This is the last part of the record we care about.");
206 Read
207 (State => State,
208 File_Pos => Dir_Pos,
209 Buf => ISO_Name,
210 Len => Name_Len);
211 if Name_Len /= ISO_Name_Length then
212 return;
213 end if;
214 pragma Warnings (GNATprove, On, """Dir_Pos"" is set*but not used");
215
216 if (Name_Len = 1 and ISO_Name (0) = 16#00# and File_Name = ".") or
217 (Name_Len = 1 and ISO_Name (0) = 16#01# and File_Name = "..")
218 then
219 Success := True;
220 return;
221 end if;
222
223 -- Ignore file version `;1'
224 if Name_Len >= File_Name'Length + 2 and
225 ISO_Name (Name_Len - 2) = Character'Pos (';') and
226 ISO_Name (Name_Len - 1) = Character'Pos ('1')
227 then
228 Name_Len := Name_Len - 2;
229 end if;
230
231 -- Ignore trailing `.'
232 if Name_Len >= File_Name'Length + 1 and
233 ISO_Name (Name_Len - 1) = Character'Pos ('.')
234 then
235 Name_Len := Name_Len - 1;
236 end if;
237
238 Success := Name_Len = File_Name'Length and then
239 Upper_Case_Equal (File_Name, ISO_Name);
240 end Match_ISO_Name;
Nico Hubercc960f22024-01-29 01:13:45 +0100241
242 Found_Inode : Inode_Index;
243 Dir_Pos : File_Length;
244 begin
245 File_Len := 0;
246 File_Type := FS.File_Type'First;
247
248 if File_Name'Length > File_Name_Max then
249 Success := False;
250 return;
251 end if;
252
253 -- Ensure dir is opened:
254 --
255 if State.S = File_Opened then
256 if State.Cur_Dir /= State.Inode.I then
257 Success := False;
258 return;
259 end if;
260 else
261 if In_Root then
262 State.Cur_Dir := State.Static.Root_Inode;
263 end if;
264 declare
265 Cur_Dir : constant Inode_Index := State.Cur_Dir;
266 begin
267 Open (State, Cur_Dir, Success);
268 if not Success then
269 return;
270 end if;
271 end;
272 end if;
273
274 -- Lookup file in opened dir:
275 --
276 Dir_Pos := 0;
277 Success := False;
278 loop
279 pragma Loop_Invariant (Is_Open (State) and not Success);
Nico Huber4210fcc2024-01-29 16:33:47 +0100280 pragma Loop_Invariant
281 (FSBlock_Index (Dir_Pos mod FSBlock_Size) <= Directory_Record_Offset'Last);
282
Nico Hubercc960f22024-01-29 01:13:45 +0100283 declare
Nico Huberab933b82024-02-07 11:50:59 +0100284 Dir_Rec_Head : Directory_Record;
Nico Hubercc960f22024-01-29 01:13:45 +0100285 Record_Dir_Pos : File_Offset := Dir_Pos;
Nico Hubercc960f22024-01-29 01:13:45 +0100286 Len : Natural;
287 begin
288 Read
289 (State => State,
290 File_Pos => Record_Dir_Pos,
Nico Huberab933b82024-02-07 11:50:59 +0100291 Buf => Dir_Rec_Head,
Nico Hubercc960f22024-01-29 01:13:45 +0100292 Len => Len);
Nico Huberab933b82024-02-07 11:50:59 +0100293 if Len /= Dir_Rec_Head'Length then
Nico Hubercc960f22024-01-29 01:13:45 +0100294 return;
295 end if;
296
Nico Huberb18691e2024-02-06 17:52:17 +0100297 declare
Nico Huberab933b82024-02-07 11:50:59 +0100298 Dir_Rec_Length : constant Natural := Natural (Dir_Rec_Head (0));
299 Dir_Rec_Name_Length : constant Natural := Natural (Dir_Rec_Head (32));
Nico Huberb18691e2024-02-06 17:52:17 +0100300 begin
Nico Huberab933b82024-02-07 11:50:59 +0100301 if Dir_Rec_Length /= 0 then
302
303 if Unsigned_64 (Dir_Pos) > Unsigned_64 (State.Inode.Size) - Unsigned_64 (Dir_Rec_Length) or
304 Dir_Rec_Name_Length > Dir_Rec_Length - Dir_Rec_Head'Length
305 then
Nico Huberb18691e2024-02-06 17:52:17 +0100306 return;
Nico Huberb18691e2024-02-06 17:52:17 +0100307 end if;
Nico Huberab933b82024-02-07 11:50:59 +0100308
309
310 Match_ISO_Name (Dir_Rec_Name_Length, Record_Dir_Pos, Success);
311
312 if Success then
313 Found_Inode :=
314 (Block => FSBlock (Dir_Pos / FSBlock_Size) + State.Inode.Extents (0).Start,
315 Offset => FSBlock_Index (Dir_Pos mod FSBlock_Size));
316 exit;
317 end if;
318
319 Dir_Pos := Dir_Pos + File_Length (Dir_Rec_Length);
320
Nico Huberb18691e2024-02-06 17:52:17 +0100321 else
Nico Huberab933b82024-02-07 11:50:59 +0100322 -- `Dir_Rec_Length = 0` means we have to skip to the next fs block:
323 declare
324 Block_Gap : constant File_Length := FSBlock_Size - (Dir_Pos mod FSBlock_Size);
325 begin
326 if Unsigned_64 (Dir_Pos) >= Unsigned_64 (State.Inode.Size) - Unsigned_64 (Block_Gap) then
327 return;
328 else
329 Dir_Pos := Dir_Pos + Block_Gap;
330 end if;
331 end;
Nico Huberb18691e2024-02-06 17:52:17 +0100332 end if;
333 end;
Nico Hubercc960f22024-01-29 01:13:45 +0100334 end;
335 end loop;
336 pragma Assert_And_Cut (Success and Is_Mounted (State));
337
338 Open (State, Found_Inode, Success);
339 if not Success then
340 return;
341 end if;
342
343 if State.Inode.Mode = Dir then
344 State.Cur_Dir := Found_Inode;
345 end if;
346
Nico Huber4210fcc2024-01-29 16:33:47 +0100347 Success := State.Inode.Size <= Inode_Length (File_Length'Last);
Nico Hubercc960f22024-01-29 01:13:45 +0100348 if Success then
349 File_Len := File_Length (State.Inode.Size);
350 File_Type := State.Inode.Mode;
351 else
352 Close (State);
353 end if;
354 end Open;
355
356 procedure Close (State : in out T) is
357 begin
358 State.S := Mounted;
359 end Close;
360
361 procedure Read
362 (State : in out T;
363 File_Pos : in out File_Offset;
364 Buf : out Buffer_Type;
365 Len : out Natural)
366 is
367 Static : Mount_State renames State.Static;
368
369 Pos : Natural range Buf'First .. Buf'Last + 1;
370 begin
371 Len := 0;
372 Pos := Buf'First;
373 while Pos <= Buf'Last and
374 File_Pos < File_Offset'Last and
375 Inode_Length (File_Pos) < State.Inode.Size
376 loop
Nico Huber4210fcc2024-01-29 16:33:47 +0100377 pragma Loop_Invariant (for all I in Buf'First .. Pos - 1 => Buf (I)'Initialized);
Nico Hubercc960f22024-01-29 01:13:45 +0100378 pragma Loop_Invariant (Is_Open (State));
379 declare
380 In_Block : constant FSBlock_Index := Natural (File_Pos mod FSBlock_Size);
381 Logical : constant FSBlock_Logical := FSBlock_Logical (File_Pos / FSBlock_Size);
382 In_Block_Space : constant Positive := Index_Type (FSBlock_Size) - In_Block;
383 In_File_Space : constant Inode_Length := State.Inode.Size - Inode_Length (File_Pos);
384 In_Buf_Space : constant Positive := Buf'Last - Pos + 1;
385 Len_Here : Positive;
386 begin
387 exit when FSBlock (Logical) > FSBlock'Last - State.Inode.Extents (0).Start;
388
389 Len_Here := In_Block_Space;
390 if In_File_Space < Inode_Length (Len_Here) then
391 Len_Here := Positive (In_File_Space);
392 end if;
393 if In_Buf_Space < Len_Here then
394 Len_Here := In_Buf_Space;
395 end if;
396 if File_Offset'Last - File_Pos < File_Length (Len_Here) then
397 Len_Here := Positive (File_Offset'Last - File_Pos);
398 end if;
399
400 declare
401 Last : constant Index_Type := Pos + Len_Here - 1;
402 Physical : constant FSBlock := State.Inode.Extents (0).Start + FSBlock (Logical);
403 Success : Boolean;
404 begin
405 Read
406 (Buf => Buf (Pos .. Last),
407 Block => Physical,
408 Offset => In_Block,
409 FS_Len => Static.Part_Len,
410 Success => Success);
411 exit when not Success;
412
413 File_Pos := File_Pos + File_Length (Len_Here);
414 Pos := Pos + Len_Here;
415 Len := Pos - Buf'First;
416 end;
417 end;
418 end loop;
419 Buf (Pos .. Buf'Last) := (others => 16#00#);
420 end Read;
421
422 --------------------------------------------------------------------------
423
424 package C is new VFS (T => T, Initial => (S => Unmounted, others => <>));
425
426 function C_Mount return int
427 with
428 Export,
429 Convention => C,
430 External_Name => "iso9660_mount";
431 function C_Mount return int
432 with
433 SPARK_Mode => Off
434 is
435 begin
436 return C.C_Mount;
437 end C_Mount;
438
439 function C_Open (File_Path : System.Address) return int
440 with
441 Export,
442 Convention => C,
443 External_Name => "iso9660_dir";
444 function C_Open (File_Path : System.Address) return int
445 with
446 SPARK_Mode => Off
447 is
448 begin
449 return C.C_Open (File_Path);
450 end C_Open;
451
452 function C_Read (Buf : System.Address; Len : int) return int
453 with
454 Export,
455 Convention => C,
456 External_Name => "iso9660_read";
457 function C_Read (Buf : System.Address; Len : int) return int
458 with
459 SPARK_Mode => Off
460 is
461 begin
462 return C.C_Read (Buf, Len);
463 end C_Read;
464
465end FILO.FS.ISO9660;