# File system
## Accessing the file system

The user interface to read and write files in `Ada_Drivers_Library` is located
in the `File_IO` package.

At initialization, there is no file system available, so you have to mount one
before using the operations described below. Please read the section `Mounting
a file system` to learn how to add a file system.

### Opening a file

To open a file, use the `Open` function:

```ada
   FD : File_Descriptor;
begin
   if Open (FD, "/host/tmp/file.txt", Read_Only) /= OK then
      --  Error handling...
   end if;
```

You can open the file in `Read_Only`, `Write_Only` or `Read_Write` mode.

### Reading from a file

Once the file descriptor is open in `Read_Only` or `Read_Write` mode you can
use the `Read` function to get data from the file:

```ada

   Data : HAL.UInt8_Array (1 .. 10);
begin
   if Read (FD, Data'Address, Data'Length) /= Data'Length then
      --  Error handling...
   end if;
```

This uses the `'Address` attribute to get the address of the data in memory.
You also have to specify the size of the data in number of bytes. The return
value is the number of bytes actually read.

A a safer and cleaner alternative is to use the generic function `Generic_Read`
and instantiate it for the type of data that you want to read:

```ada
   type Custom_Type is [...];

   function Read is new Generic_Read (Custom_Type);
   Data : Custom_Type;
begin
   if Read (FD, Data) /= OK then
      --  Error handling...
   end if;
```

### Writing to a file

Writing operations are very similar to the reading operations:

```ada

   Data : HAL.UInt8_Array (1 .. 10) := (others => 0);
begin
   if Write (FD, Data'Address, Data'Length) /= Data'Length then
      --  Error handling...
   end if;
```

And the generic version:

```ada
   type Custom_Type is [...];

   function Write is new Generic_Write (Custom_Type);
   Data : Custom_Type;
begin
   if Write (FD, Data) /= OK then
      --  Error handling...
   end if;
```

### Seek and Offset

You can use the `Offset` and `Seek` function to manipulate the file descriptor
offset.

`Offset` will give you the current value of the Offset.

```ada
   Current_Offset : File_Size;
begin
   Current_Offset := Offset (FD);
```

The function `Seek` is used to change the offset:

```ada
   Amount : File_Size := 10;
begin
   if Seek (FD, Forward, Amount) /= OK then
      --  Error handling...
   end if;
```

There are 4 seek modes available:
 - `Forward`: Increase the offset by the given `Amount` 
 - `backward`: Decrease the offset by the given `Amount` 
 - `From_Start`: Set to offset to `Amount`
 - `From_End`: Set the offset to the size of the file minus `Amount`

### Flush

Use `Flush` to force all buffered data to be written on the file.

### Close file

Use `Close` to close the file descriptor and free the associated resources.

### Opening a directory

To open a directory, use the `Open` function:

```ada
   DD : Directory_Descriptor;
begin
   if Open (DD, "/host/tmp/") /= OK then
      --  Error handling...
   end if;
```

### Traversing a directory

Use the `Read` function read a `Directory_Entry`:

```ada
type Directory_Entry (Name_Length : Natural) is record
      Name         : String (1 .. Name_Length);
      Subdirectory : Boolean;
      Read_Only    : Boolean;
      Hidden       : Boolean;
      Symlink      : Boolean;
      Size         : File_Size;
end record;
```

Be careful, `Directory_Entry` is a discriminated type which means that once you
have declared a variable of this type you can only assign a value with the same
discriminant.

For this reason, it's safer to always declare a new variable for each call to
`Read`. Here is a typical example of how to traverse all the entries of a
directory:

```ada
loop
   declare
      E : constant Directory_Entry := Read (DD);
   begin

      exit when E = Invalid_Dir_Entry;

      --  Use the directory entry here

   end;
end loop;
```

Once you went through all the entries of the directory, the `Read` function
will return `Invalid_Dir_Entry`.

You can use the `Reset` function to start reading from the first entry again.

### Close directory

Use `Close` to close the directory descriptor and free the associated resources.

## Mounting a file system

To mount a file system you will need a file system driver. Please find below
detailed instructions on how to instantiate and mount the different file
systems available in `Ada_Drivers_Library`.

```Ada
   if Mount_Volume ("mount_point", A_File_System_Driver) /= OK then
      --  Error handling...
   end
```

You can now access file in the volume, for example `/mount_point/tmp/file.txt`.

## Mounting a drive

It is also possible to mount a disk drive that has one or more file system.
Please note that for the moment only FAT file systems are supported.

In `Ada_Drivers_Library` disk drive are accessed with a `Block_Driver` interface.

```Ada
   if Mount_Drive ("mount_point", A_Block_Driver) /= OK then
      --  Error handling...
   end if;
```

## File system drivers available

### File Allocation Table (FAT) drives

`Work in progress...`

### ARM Semihosting file system

The ARM semihosting file system driver uses the [ARM semihosting
interface](http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0471c/Bgbjhiea.html)
to provide a bridge to the host file system.

It is implemented in the package `Semihosting.Filesystem`.

#### Limitation

Given the semihosting operation available, it is not possible to implement a
complete file system driver, for instance listing files in a directory. However
this implementation allows you to open, read and write files on the host
computer.

#### How to mount an ARM semihosting file system

To use the ARM semihosting file system you first have to declare it:

```ada

with Semihosting.Filesystem; use Semihosting.Filesystem;

package body My_Package is

  Semihosting_FS : aliased SHFS;
  
```

and finally, mount the file system:

```ada
   if Mount_Volume ("host", Semihosting_FS'Access) /= OK then
      --  Error handling
   end if;
```

You can now use the `File_IO` package to access the host file system. 

For instance, opening `/host/tmp/test.txt` with `File_IO.Open` will actually
open `/tmp/test.txt` on the host machine.

### Native file system

The native file system is used on non embedded platform (Windows, Linux, etc.)
to provide access to the OS file system through the common interface of
`Ada_Drivers_Library`. This is mostly useful for testing purposes.

It is implemented in the package `Filesystem.Native`.

#### How to mount a native file system

To use the native file system you first have to declare it:

```ada

with Filesystem.Native; use Filesystem.Native;

package body My_Package is

  Native_FS : aliased Native_FS_Driver;
  
```

then use the `Create` function to specify which directory on the host file
system will be the root of the Native file system driver:

```ada
   if Native_FS.Create ("/home/username") /= OK then
      --  Error handling
   end if;
```

and finally, mount the file system:

```ada
   if Mount_Volume ("mount_point", FS'Access) /= OK then
      --  Error handling
   end if;
```

You can now use the `File_IO` package to access the native file system.

For instance, opening `/mount_point/test.txt` with `File_IO.Open` will actually
open `/home/username/test.txt` on your machine.

### Writing a new driver

You may want to write your own file system driver because you use a custom
format or if you want add support for a format that is not available in
`Ada_Drivers_Library`.

File system drivers have to implement the `HAL.Filesystem.Filesystem_Driver`
interface.

If your file system is located on a disk drive or any other type of mass
storage device, the driver must use the `HAL.Block_Driver` interface to make
sure that the code can be re-used on different drives. The FAT driver is an
example of that.
