blob: eb216107b7b881ef0fdfd66495ac7e6254920c80 [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 Huber666da672024-02-08 15:30:20 +010026 Rec : in FS_Record;
Nico Hubercc960f22024-01-29 01:13:45 +010027 Success : out Boolean)
28 with
Nico Huber666da672024-02-08 15:30:20 +010029 Pre => Buf'Length <= Rec.Size
Nico Hubercc960f22024-01-29 01:13:45 +010030 is
Nico Huber666da672024-02-08 15:30:20 +010031 Block_64 : constant Integer_64 := Integer_64 (Rec.Block);
32 Offset_64 : constant Integer_64 := Integer_64 (Rec.Offset);
Nico Hubercc960f22024-01-29 01:13:45 +010033 begin
Nico Hubercc960f22024-01-29 01:13:45 +010034 Blockdev.Read (Buf, Blockdev_Length (Block_64 * FSBlock_Size + Offset_64), Success);
35 end Read;
36
37 procedure Mount
38 (State : in out T;
39 Part_Len : in Partition_Length;
40 Success : out Boolean)
41 is
42 D_Type : constant Index_Type := 0;
43 D_Id : constant Index_Type := 1;
44 D_Ver : constant Index_Type := 6;
45
46 D_Type_Primary : constant := 1;
47 D_Type_Terminator : constant := 255;
48
49 CD001 : constant Buffer_Type :=
50 (Character'Pos ('C'), Character'Pos ('D'),
51 Character'Pos ('0'), Character'Pos ('0'), Character'Pos ('1'));
52
53 Static : Mount_State renames State.Static;
54
Nico Huber4210fcc2024-01-29 16:33:47 +010055 Volume_Descriptor : Buffer_Type (0 .. D_Ver);
Nico Huber65f2ea22024-02-06 17:37:35 +010056 Block : FSBlock := 16;
Nico Hubercc960f22024-01-29 01:13:45 +010057 begin
58 loop
59 pragma Loop_Invariant (Block < 1024);
Nico Huber4210fcc2024-01-29 16:33:47 +010060 pragma Loop_Invariant (not Is_Mounted (State));
Nico Hubercc960f22024-01-29 01:13:45 +010061
62 if Part_Len < Partition_Length (Block + 1) * FSBlock_Size then
63 Success := False;
64 return;
65 end if;
Nico Huber666da672024-02-08 15:30:20 +010066 Static.Part_Len := Part_Len;
Nico Hubercc960f22024-01-29 01:13:45 +010067
Nico Huber666da672024-02-08 15:30:20 +010068 Read (Volume_Descriptor, FS_Record'(Block, 0, FSBlock_Size), Success);
Nico Hubercc960f22024-01-29 01:13:45 +010069 if not Success then
70 return;
71 end if;
72
73 if Volume_Descriptor (D_Ver) /= 1 or
74 Volume_Descriptor (D_Id .. D_Ver - 1) /= CD001 or
75 Volume_Descriptor (D_Type) = D_Type_Terminator
76 then
Nico Huber4210fcc2024-01-29 16:33:47 +010077 Success := False;
Nico Hubercc960f22024-01-29 01:13:45 +010078 return;
79 end if;
80
81 exit when Volume_Descriptor (D_Type) = D_Type_Primary;
82
83 Block := Block + 1;
84 if Block >= 1024 then
Nico Huber4210fcc2024-01-29 16:33:47 +010085 Success := False;
Nico Hubercc960f22024-01-29 01:13:45 +010086 return;
87 end if;
88 end loop;
89
Nico Huber666da672024-02-08 15:30:20 +010090 Static.Root_Inode := (Block, 156, Directory_Record'Length);
Nico Hubercc960f22024-01-29 01:13:45 +010091
92 State.S := Mounted;
93 end Mount;
94
95 procedure Open
96 (State : in out T;
97 I : in Inode_Index;
98 Success : out Boolean)
99 with
100 Pre => Is_Mounted (State),
101 Post => Is_Mounted (State) and (Success = Is_Open (State))
102 is
Nico Hubercc960f22024-01-29 01:13:45 +0100103 Inode : Inode_Info renames State.Inode;
104
105 Dir_Rec : Directory_Record;
106 begin
Nico Huber4210fcc2024-01-29 16:33:47 +0100107 State.S := Mounted;
108
Nico Hubercc960f22024-01-29 01:13:45 +0100109 Inode := (I, others => <>);
110
Nico Huber666da672024-02-08 15:30:20 +0100111 Read (Dir_Rec, I, Success);
Nico Hubercc960f22024-01-29 01:13:45 +0100112 if not Success then
113 return;
114 end if;
115
116 declare
117 D_Flag_Dir : constant := 16#02#;
118 D_Flag_Cont : constant := 16#80#;
119 D_Flags : constant Unsigned_8 := Dir_Rec (25);
Nico Huber4210fcc2024-01-29 16:33:47 +0100120 D_Extent : constant Unsigned_32 := Read_LE32 (Dir_Rec, 2);
Nico Hubercc960f22024-01-29 01:13:45 +0100121 begin
Nico Huber4210fcc2024-01-29 16:33:47 +0100122 if D_Extent > Unsigned_32 (FSBlock'Last) then
123 Success := False;
124 return;
125 end if;
Nico Hubercc960f22024-01-29 01:13:45 +0100126 if (D_Flags and D_Flag_Cont) /= 0 then
127 Success := False;
128 return;
129 end if;
Nico Huber4210fcc2024-01-29 16:33:47 +0100130
Nico Hubercc960f22024-01-29 01:13:45 +0100131 if (D_Flags and D_Flag_Dir) /= 0 then
132 Inode.Mode := Dir;
133 else
134 Inode.Mode := Regular;
135 end if;
136 Inode.Size := Inode_Length (Read_LE32 (Dir_Rec, 10));
137 Inode.Extents (0) :=
Nico Huber4210fcc2024-01-29 16:33:47 +0100138 (Start => FSBlock (D_Extent),
Nico Hubercc960f22024-01-29 01:13:45 +0100139 Count => FSBlock_Count (Inode.Size / Inode_Length (FSBlock_Size)));
140 State.S := File_Opened;
141 end;
142 end Open;
143
144 procedure Open
145 (State : in out T;
146 File_Len : out File_Length;
147 File_Type : out FS.File_Type;
148 File_Name : in String;
149 In_Root : in Boolean;
150 Success : out Boolean)
151 is
152 File_Name_Max : constant := 255;
153
Nico Huberab933b82024-02-07 11:50:59 +0100154 procedure Match_ISO_Name
155 (ISO_Name_Length : in Natural;
156 Record_Dir_Pos : in File_Offset;
157 Success : in out Boolean)
Nico Hubercc960f22024-01-29 01:13:45 +0100158 with
Nico Huberab933b82024-02-07 11:50:59 +0100159 Post => State.Static = State.Static'Old
Nico Hubercc960f22024-01-29 01:13:45 +0100160 is
Nico Huberab933b82024-02-07 11:50:59 +0100161 function Upper_Case_Equal (Str : String; Buf : Buffer_Type) return Boolean
162 with
163 Pre => Str'Length <= Buf'Length
164 is
165 begin
166 for I in Str'Range loop
167 declare
168 Chr : constant Unsigned_8 :=
169 (if 'a' <= Str (I) and Str (I) <= 'z' then
170 Character'Pos (Str (I)) - Character'Pos ('a') + Character'Pos ('A')
171 else Character'Pos (Str (I)));
172 begin
173 if Chr /= Buf (Buf'First + (I - Str'First)) then
174 return False;
175 end if;
176 end;
177 end loop;
178 return True;
179 end Upper_Case_Equal;
180
181 Dir_Pos : File_Offset := Record_Dir_Pos;
182 ISO_Name : Buffer_Type (0 .. ISO_Name_Length - 1);
183 Name_Len : Natural;
Nico Hubercc960f22024-01-29 01:13:45 +0100184 begin
Nico Huberab933b82024-02-07 11:50:59 +0100185 -- Check if length could match.
186 -- First for the weird `.' and `..' encoding,
187 -- then for regular file names + `.' and file version `;1'.
188 if not (ISO_Name_Length = 1 and File_Name'Length <= 2) and
189 ISO_Name_Length not in File_Name'Length .. File_Name'Length + 3
190 then
191 return;
192 end if;
193
194 pragma Warnings
195 (GNATprove, Off, """Dir_Pos"" is set*but not used",
196 Reason => "This is the last part of the record we care about.");
197 Read
198 (State => State,
199 File_Pos => Dir_Pos,
200 Buf => ISO_Name,
201 Len => Name_Len);
202 if Name_Len /= ISO_Name_Length then
203 return;
204 end if;
205 pragma Warnings (GNATprove, On, """Dir_Pos"" is set*but not used");
206
207 if (Name_Len = 1 and ISO_Name (0) = 16#00# and File_Name = ".") or
208 (Name_Len = 1 and ISO_Name (0) = 16#01# and File_Name = "..")
209 then
210 Success := True;
211 return;
212 end if;
213
214 -- Ignore file version `;1'
215 if Name_Len >= File_Name'Length + 2 and
216 ISO_Name (Name_Len - 2) = Character'Pos (';') and
217 ISO_Name (Name_Len - 1) = Character'Pos ('1')
218 then
219 Name_Len := Name_Len - 2;
220 end if;
221
222 -- Ignore trailing `.'
223 if Name_Len >= File_Name'Length + 1 and
224 ISO_Name (Name_Len - 1) = Character'Pos ('.')
225 then
226 Name_Len := Name_Len - 1;
227 end if;
228
229 Success := Name_Len = File_Name'Length and then
230 Upper_Case_Equal (File_Name, ISO_Name);
231 end Match_ISO_Name;
Nico Hubercc960f22024-01-29 01:13:45 +0100232
233 Found_Inode : Inode_Index;
234 Dir_Pos : File_Length;
235 begin
236 File_Len := 0;
237 File_Type := FS.File_Type'First;
238
239 if File_Name'Length > File_Name_Max then
240 Success := False;
241 return;
242 end if;
243
244 -- Ensure dir is opened:
245 --
246 if State.S = File_Opened then
247 if State.Cur_Dir /= State.Inode.I then
248 Success := False;
249 return;
250 end if;
251 else
252 if In_Root then
253 State.Cur_Dir := State.Static.Root_Inode;
254 end if;
255 declare
256 Cur_Dir : constant Inode_Index := State.Cur_Dir;
257 begin
258 Open (State, Cur_Dir, Success);
259 if not Success then
260 return;
261 end if;
262 end;
263 end if;
264
265 -- Lookup file in opened dir:
266 --
267 Dir_Pos := 0;
268 Success := False;
269 loop
270 pragma Loop_Invariant (Is_Open (State) and not Success);
Nico Huber4210fcc2024-01-29 16:33:47 +0100271 pragma Loop_Invariant
272 (FSBlock_Index (Dir_Pos mod FSBlock_Size) <= Directory_Record_Offset'Last);
273
Nico Hubercc960f22024-01-29 01:13:45 +0100274 declare
Nico Huberab933b82024-02-07 11:50:59 +0100275 Dir_Rec_Head : Directory_Record;
Nico Hubercc960f22024-01-29 01:13:45 +0100276 Record_Dir_Pos : File_Offset := Dir_Pos;
Nico Hubercc960f22024-01-29 01:13:45 +0100277 Len : Natural;
278 begin
279 Read
280 (State => State,
281 File_Pos => Record_Dir_Pos,
Nico Huberab933b82024-02-07 11:50:59 +0100282 Buf => Dir_Rec_Head,
Nico Hubercc960f22024-01-29 01:13:45 +0100283 Len => Len);
Nico Huberab933b82024-02-07 11:50:59 +0100284 if Len /= Dir_Rec_Head'Length then
Nico Hubercc960f22024-01-29 01:13:45 +0100285 return;
286 end if;
287
Nico Huberb18691e2024-02-06 17:52:17 +0100288 declare
Nico Huberab933b82024-02-07 11:50:59 +0100289 Dir_Rec_Length : constant Natural := Natural (Dir_Rec_Head (0));
290 Dir_Rec_Name_Length : constant Natural := Natural (Dir_Rec_Head (32));
Nico Huberb18691e2024-02-06 17:52:17 +0100291 begin
Nico Huberab933b82024-02-07 11:50:59 +0100292 if Dir_Rec_Length /= 0 then
293
294 if Unsigned_64 (Dir_Pos) > Unsigned_64 (State.Inode.Size) - Unsigned_64 (Dir_Rec_Length) or
295 Dir_Rec_Name_Length > Dir_Rec_Length - Dir_Rec_Head'Length
296 then
Nico Huberb18691e2024-02-06 17:52:17 +0100297 return;
Nico Huberb18691e2024-02-06 17:52:17 +0100298 end if;
Nico Huberab933b82024-02-07 11:50:59 +0100299
300
301 Match_ISO_Name (Dir_Rec_Name_Length, Record_Dir_Pos, Success);
302
303 if Success then
304 Found_Inode :=
Nico Huber666da672024-02-08 15:30:20 +0100305 (Block => FSBlock (Dir_Pos / FSBlock_Size) + State.Inode.Extents (0).Start,
306 Offset => FSBlock_Index (Dir_Pos mod FSBlock_Size),
307 Size => Dir_Rec_Length);
Nico Huberab933b82024-02-07 11:50:59 +0100308 exit;
309 end if;
310
311 Dir_Pos := Dir_Pos + File_Length (Dir_Rec_Length);
312
Nico Huberb18691e2024-02-06 17:52:17 +0100313 else
Nico Huberab933b82024-02-07 11:50:59 +0100314 -- `Dir_Rec_Length = 0` means we have to skip to the next fs block:
315 declare
316 Block_Gap : constant File_Length := FSBlock_Size - (Dir_Pos mod FSBlock_Size);
317 begin
318 if Unsigned_64 (Dir_Pos) >= Unsigned_64 (State.Inode.Size) - Unsigned_64 (Block_Gap) then
319 return;
320 else
321 Dir_Pos := Dir_Pos + Block_Gap;
322 end if;
323 end;
Nico Huberb18691e2024-02-06 17:52:17 +0100324 end if;
325 end;
Nico Hubercc960f22024-01-29 01:13:45 +0100326 end;
327 end loop;
328 pragma Assert_And_Cut (Success and Is_Mounted (State));
329
330 Open (State, Found_Inode, Success);
331 if not Success then
332 return;
333 end if;
334
335 if State.Inode.Mode = Dir then
336 State.Cur_Dir := Found_Inode;
337 end if;
338
Nico Huber4210fcc2024-01-29 16:33:47 +0100339 Success := State.Inode.Size <= Inode_Length (File_Length'Last);
Nico Hubercc960f22024-01-29 01:13:45 +0100340 if Success then
341 File_Len := File_Length (State.Inode.Size);
342 File_Type := State.Inode.Mode;
343 else
344 Close (State);
345 end if;
346 end Open;
347
348 procedure Close (State : in out T) is
349 begin
350 State.S := Mounted;
351 end Close;
352
353 procedure Read
354 (State : in out T;
355 File_Pos : in out File_Offset;
356 Buf : out Buffer_Type;
357 Len : out Natural)
358 is
359 Static : Mount_State renames State.Static;
360
361 Pos : Natural range Buf'First .. Buf'Last + 1;
362 begin
363 Len := 0;
364 Pos := Buf'First;
365 while Pos <= Buf'Last and
366 File_Pos < File_Offset'Last and
367 Inode_Length (File_Pos) < State.Inode.Size
368 loop
Nico Huber4210fcc2024-01-29 16:33:47 +0100369 pragma Loop_Invariant (for all I in Buf'First .. Pos - 1 => Buf (I)'Initialized);
Nico Hubercc960f22024-01-29 01:13:45 +0100370 pragma Loop_Invariant (Is_Open (State));
371 declare
372 In_Block : constant FSBlock_Index := Natural (File_Pos mod FSBlock_Size);
373 Logical : constant FSBlock_Logical := FSBlock_Logical (File_Pos / FSBlock_Size);
374 In_Block_Space : constant Positive := Index_Type (FSBlock_Size) - In_Block;
375 In_File_Space : constant Inode_Length := State.Inode.Size - Inode_Length (File_Pos);
376 In_Buf_Space : constant Positive := Buf'Last - Pos + 1;
377 Len_Here : Positive;
378 begin
379 exit when FSBlock (Logical) > FSBlock'Last - State.Inode.Extents (0).Start;
380
381 Len_Here := In_Block_Space;
382 if In_File_Space < Inode_Length (Len_Here) then
383 Len_Here := Positive (In_File_Space);
384 end if;
385 if In_Buf_Space < Len_Here then
386 Len_Here := In_Buf_Space;
387 end if;
388 if File_Offset'Last - File_Pos < File_Length (Len_Here) then
389 Len_Here := Positive (File_Offset'Last - File_Pos);
390 end if;
391
392 declare
393 Last : constant Index_Type := Pos + Len_Here - 1;
394 Physical : constant FSBlock := State.Inode.Extents (0).Start + FSBlock (Logical);
395 Success : Boolean;
396 begin
397 Read
398 (Buf => Buf (Pos .. Last),
Nico Huber666da672024-02-08 15:30:20 +0100399 Rec => FS_Record'(Physical, In_Block, In_Block_Space),
Nico Hubercc960f22024-01-29 01:13:45 +0100400 Success => Success);
401 exit when not Success;
402
403 File_Pos := File_Pos + File_Length (Len_Here);
404 Pos := Pos + Len_Here;
405 Len := Pos - Buf'First;
406 end;
407 end;
408 end loop;
409 Buf (Pos .. Buf'Last) := (others => 16#00#);
410 end Read;
411
412 --------------------------------------------------------------------------
413
414 package C is new VFS (T => T, Initial => (S => Unmounted, others => <>));
415
416 function C_Mount return int
417 with
418 Export,
419 Convention => C,
420 External_Name => "iso9660_mount";
421 function C_Mount return int
422 with
423 SPARK_Mode => Off
424 is
425 begin
426 return C.C_Mount;
427 end C_Mount;
428
429 function C_Open (File_Path : System.Address) return int
430 with
431 Export,
432 Convention => C,
433 External_Name => "iso9660_dir";
434 function C_Open (File_Path : System.Address) return int
435 with
436 SPARK_Mode => Off
437 is
438 begin
439 return C.C_Open (File_Path);
440 end C_Open;
441
442 function C_Read (Buf : System.Address; Len : int) return int
443 with
444 Export,
445 Convention => C,
446 External_Name => "iso9660_read";
447 function C_Read (Buf : System.Address; Len : int) return int
448 with
449 SPARK_Mode => Off
450 is
451 begin
452 return C.C_Read (Buf, Len);
453 end C_Read;
454
455end FILO.FS.ISO9660;