blob: 8c1b4411ec75d920d7428a00b92a14ee383fe918 [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
25 (Buf : in out Buffer_Type;
26 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
38 Success := False;
39 return;
40 end if;
41 Blockdev.Read (Buf, Blockdev_Length (Block_64 * FSBlock_Size + Offset_64), Success);
42 end Read;
43
44 procedure Mount
45 (State : in out T;
46 Part_Len : in Partition_Length;
47 Success : out Boolean)
48 is
49 D_Type : constant Index_Type := 0;
50 D_Id : constant Index_Type := 1;
51 D_Ver : constant Index_Type := 6;
52
53 D_Type_Primary : constant := 1;
54 D_Type_Terminator : constant := 255;
55
56 CD001 : constant Buffer_Type :=
57 (Character'Pos ('C'), Character'Pos ('D'),
58 Character'Pos ('0'), Character'Pos ('0'), Character'Pos ('1'));
59
60 Static : Mount_State renames State.Static;
61
62 Volume_Descriptor : Buffer_Type (0 .. 190 - 1);
63 Block : FSBlock := 32;
64 begin
65 loop
66 pragma Loop_Invariant (Block < 1024);
67
68 if Part_Len < Partition_Length (Block + 1) * FSBlock_Size then
69 Success := False;
70 return;
71 end if;
72
73 Read (Volume_Descriptor (0 .. D_Ver), Block, 0, Part_Len, Success);
74 if not Success then
75 return;
76 end if;
77
78 if Volume_Descriptor (D_Ver) /= 1 or
79 Volume_Descriptor (D_Id .. D_Ver - 1) /= CD001 or
80 Volume_Descriptor (D_Type) = D_Type_Terminator
81 then
82 return;
83 end if;
84
85 exit when Volume_Descriptor (D_Type) = D_Type_Primary;
86
87 Block := Block + 1;
88 if Block >= 1024 then
89 return;
90 end if;
91 end loop;
92
93 Static.Part_Len := Part_Len;
94 Static.Root_Inode := (Block, 156);
95
96 State.S := Mounted;
97 end Mount;
98
99 procedure Open
100 (State : in out T;
101 I : in Inode_Index;
102 Success : out Boolean)
103 with
104 Pre => Is_Mounted (State),
105 Post => Is_Mounted (State) and (Success = Is_Open (State))
106 is
107 Static : Mount_State renames State.Static;
108 Inode : Inode_Info renames State.Inode;
109
110 Dir_Rec : Directory_Record;
111 begin
112 Inode := (I, others => <>);
113
114 Read (Dir_Rec, I.Block, I.Offset, Static.Part_Len, Success);
115 if not Success then
116 return;
117 end if;
118
119 declare
120 D_Flag_Dir : constant := 16#02#;
121 D_Flag_Cont : constant := 16#80#;
122 D_Flags : constant Unsigned_8 := Dir_Rec (25);
123 begin
124 if (D_Flags and D_Flag_Cont) /= 0 then
125 Success := False;
126 return;
127 end if;
128 if (D_Flags and D_Flag_Dir) /= 0 then
129 Inode.Mode := Dir;
130 else
131 Inode.Mode := Regular;
132 end if;
133 Inode.Size := Inode_Length (Read_LE32 (Dir_Rec, 10));
134 Inode.Extents (0) :=
135 (Start => FSBlock (Read_LE32 (Dir_Rec, 2)),
136 Count => FSBlock_Count (Inode.Size / Inode_Length (FSBlock_Size)));
137 State.S := File_Opened;
138 end;
139 end Open;
140
141 procedure Open
142 (State : in out T;
143 File_Len : out File_Length;
144 File_Type : out FS.File_Type;
145 File_Name : in String;
146 In_Root : in Boolean;
147 Success : out Boolean)
148 is
149 File_Name_Max : constant := 255;
150
151 function Str_Buf_Equal (Str : String; Buf : Buffer_Type) return Boolean
152 with
153 Pre => Str'Length <= Buf'Length
154 is
155 begin
156 for I in Str'Range loop
157 if Character'Pos (Str (I)) /= Buf (Buf'First + (I - Str'First)) then
158 return False;
159 end if;
160 end loop;
161 return True;
162 end Str_Buf_Equal;
163
164 Found_Inode : Inode_Index;
165 Dir_Pos : File_Length;
166 begin
167 File_Len := 0;
168 File_Type := FS.File_Type'First;
169
170 if File_Name'Length > File_Name_Max then
171 Success := False;
172 return;
173 end if;
174
175 -- Ensure dir is opened:
176 --
177 if State.S = File_Opened then
178 if State.Cur_Dir /= State.Inode.I then
179 Success := False;
180 return;
181 end if;
182 else
183 if In_Root then
184 State.Cur_Dir := State.Static.Root_Inode;
185 end if;
186 declare
187 Cur_Dir : constant Inode_Index := State.Cur_Dir;
188 begin
189 Open (State, Cur_Dir, Success);
190 if not Success then
191 return;
192 end if;
193 end;
194 end if;
195
196 -- Lookup file in opened dir:
197 --
198 Dir_Pos := 0;
199 Success := False;
200 loop
201 pragma Loop_Invariant (Is_Open (State) and not Success);
202 declare
203 Dir_Rec : Directory_Record;
204 Dir_Rec_Name : Buffer_Type (0 .. File_Name_Max - 1);
205 Record_Dir_Pos : File_Offset := Dir_Pos;
206 Dir_Rec_Length : File_Length;
207 Inode : Inode_Index;
208 Len : Natural;
209 begin
210 Read
211 (State => State,
212 File_Pos => Record_Dir_Pos,
213 Buf => Dir_Rec,
214 Len => Len);
215 if Len < Dir_Rec'Length then
216 return;
217 end if;
218
219 -- FIXME: need to strip trailing . and file version
220 if File_Name'Length = Natural (Dir_Rec (32)) then
221 pragma Warnings
222 (GNATprove, Off, """Record_Dir_Pos"" is set*but not used",
223 Reason => "We only care about intermedidate values.");
224 Read
225 (State => State,
226 File_Pos => Record_Dir_Pos,
227 Buf => Dir_Rec_Name,
228 Len => Len);
229 pragma Warnings (GNATprove, On, """Record_Dir_Pos"" is set*but not used");
230 if Len < File_Name'Length then
231 return;
232 end if;
233
234 Success := Str_Buf_Equal (File_Name, Dir_Rec_Name);
235 if Success then
236 Found_Inode :=
237 (Block => FSBlock (Dir_Pos / FSBlock_Size),
238 Offset => FSBlock_Index (Dir_Pos mod FSBlock_Size));
239 exit;
240 end if;
241 end if;
242
243 Dir_Rec_Length := File_Length (Dir_Rec (0));
244 if Dir_Rec_Length = 0 or
245 Dir_Pos > File_Length'Last - Dir_Rec_Length or
246 Unsigned_64 (Dir_Pos) >= Unsigned_64 (State.Inode.Size) - Unsigned_64 (Dir_Rec_Length)
247 then
248 return;
249 end if;
250 Dir_Pos := Dir_Pos + Dir_Rec_Length;
251 end;
252 end loop;
253 pragma Assert_And_Cut (Success and Is_Mounted (State));
254
255 Open (State, Found_Inode, Success);
256 if not Success then
257 return;
258 end if;
259
260 if State.Inode.Mode = Dir then
261 State.Cur_Dir := Found_Inode;
262 end if;
263
264 --Success := State.Inode.Size <= Inode_Length (File_Length'Last);
265 if Success then
266 File_Len := File_Length (State.Inode.Size);
267 File_Type := State.Inode.Mode;
268 else
269 Close (State);
270 end if;
271 end Open;
272
273 procedure Close (State : in out T) is
274 begin
275 State.S := Mounted;
276 end Close;
277
278 procedure Read
279 (State : in out T;
280 File_Pos : in out File_Offset;
281 Buf : out Buffer_Type;
282 Len : out Natural)
283 is
284 Static : Mount_State renames State.Static;
285
286 Pos : Natural range Buf'First .. Buf'Last + 1;
287 begin
288 Len := 0;
289 Pos := Buf'First;
290 while Pos <= Buf'Last and
291 File_Pos < File_Offset'Last and
292 Inode_Length (File_Pos) < State.Inode.Size
293 loop
294 pragma Loop_Invariant (Is_Open (State));
295 declare
296 In_Block : constant FSBlock_Index := Natural (File_Pos mod FSBlock_Size);
297 Logical : constant FSBlock_Logical := FSBlock_Logical (File_Pos / FSBlock_Size);
298 In_Block_Space : constant Positive := Index_Type (FSBlock_Size) - In_Block;
299 In_File_Space : constant Inode_Length := State.Inode.Size - Inode_Length (File_Pos);
300 In_Buf_Space : constant Positive := Buf'Last - Pos + 1;
301 Len_Here : Positive;
302 begin
303 exit when FSBlock (Logical) > FSBlock'Last - State.Inode.Extents (0).Start;
304
305 Len_Here := In_Block_Space;
306 if In_File_Space < Inode_Length (Len_Here) then
307 Len_Here := Positive (In_File_Space);
308 end if;
309 if In_Buf_Space < Len_Here then
310 Len_Here := In_Buf_Space;
311 end if;
312 if File_Offset'Last - File_Pos < File_Length (Len_Here) then
313 Len_Here := Positive (File_Offset'Last - File_Pos);
314 end if;
315
316 declare
317 Last : constant Index_Type := Pos + Len_Here - 1;
318 Physical : constant FSBlock := State.Inode.Extents (0).Start + FSBlock (Logical);
319 Success : Boolean;
320 begin
321 Read
322 (Buf => Buf (Pos .. Last),
323 Block => Physical,
324 Offset => In_Block,
325 FS_Len => Static.Part_Len,
326 Success => Success);
327 exit when not Success;
328
329 File_Pos := File_Pos + File_Length (Len_Here);
330 Pos := Pos + Len_Here;
331 Len := Pos - Buf'First;
332 end;
333 end;
334 end loop;
335 Buf (Pos .. Buf'Last) := (others => 16#00#);
336 end Read;
337
338 --------------------------------------------------------------------------
339
340 package C is new VFS (T => T, Initial => (S => Unmounted, others => <>));
341
342 function C_Mount return int
343 with
344 Export,
345 Convention => C,
346 External_Name => "iso9660_mount";
347 function C_Mount return int
348 with
349 SPARK_Mode => Off
350 is
351 begin
352 return C.C_Mount;
353 end C_Mount;
354
355 function C_Open (File_Path : System.Address) return int
356 with
357 Export,
358 Convention => C,
359 External_Name => "iso9660_dir";
360 function C_Open (File_Path : System.Address) return int
361 with
362 SPARK_Mode => Off
363 is
364 begin
365 return C.C_Open (File_Path);
366 end C_Open;
367
368 function C_Read (Buf : System.Address; Len : int) return int
369 with
370 Export,
371 Convention => C,
372 External_Name => "iso9660_read";
373 function C_Read (Buf : System.Address; Len : int) return int
374 with
375 SPARK_Mode => Off
376 is
377 begin
378 return C.C_Read (Buf, Len);
379 end C_Read;
380
381end FILO.FS.ISO9660;