LxF. Linux Files - JulTob/Ada GitHub Wiki

16.8 Opening and Closing Files

The standard Ada packages Text_IO, Sequential_IO and Direct_IO are suitable for simple projects, but they were never intended as a complete solution for large-scale applications. If you want to do efficient file manipulation, you'll have to write your own routines based on kernel calls or the standard C library.

gnat's OSLIB package contains low-level commands to work with UNIX files. However, you can always create your own.

The following bindings assume these types have been defined.

type file_id is new integer; type mode_t is new integer; type off_t is new long_integer; type size_t is new long_integer; subype ssize_t is size_t;

function open( path : string; flags : integer; mode : mode_t ) return file_id; pragma import( c, open ); Open a file and return and file identification number. flags indicates how the file should be opened and what kind of access the file should allow (defined in /usr/include/fcntlbits.h). Mode defines the access permissions you want on the file.

The flags are a set of bits with different meanings:

O_RDONLY : constant integer := 8#00#; -- open for reading only O_WRONLY : constant integer := 8#01#; -- open for writing only O_RDWR : constant integer := 8#02#; -- open for reading and writing O_CREAT : constant integer := 8#0100#; -- no file? create it O_EXCL : constant integer := 8#0200#; -- lock file (see below) O_NOCTTY : constant integer := 8#0400#; -- if tty, don't acquire it O_TRUNC : constant integer := 8#01000#; -- file exists? truncate it O_APPEND : constant integer := 8#02000#; -- file exists? move to end O_NONBLOCK : constant integer := 8#04000#; -- if pipe, don't wait for data O_SYNC : constant integer := 8#010000#; -- don't cache writes O_ASYNC : constant integer := 8#020000#; -- async. IO via SIGIO O_DIRECT : constant integer := 8#040000#; -- direct disk access O_LARGEFILE: constant integer := 8#0100000#; -- not implemented in Linux (yet) O_DIRECTORY: constant integer := 8#0200000#; -- error if file isn't a dir O_NOFOLLOW : constant integer := 8#0400000#; -- if sym link, open link itself

Flags may be added together. O_EXCL is somewhat obsolete and has limitations on certain file systems. Use fcntl to lock files instead. O_SYNC only works on the ext2 file system or on block devices.

function creat( path : string, mode : mode_t ) return file_id; pragma import( c, creat ); Creat is a short form for open( path, create + writeonly + truncate, mode )

function close( file : file_id ) return integer; pragma import( C, close ); Closes a file.

function truncate( path : string; length : size_t ) return integer; pragma import( C, truncate ); function ftruncate( file : file_id; length : size_t ) return integer; pragma import( C, ftruncate); Shorten a file to a specific length. Despite its name, ftruncate is a kernel call, not a standard C library call like fopen.

function read( file : file_id; b : in out buffer; length : size_t ) return ssize_t; pragma import( C, read ); Read bytes from the specified file into a buffer. Buffer is any type of destination for the bytes read, with length being the size of the buffer in bytes. The number of bytes read is returned, or -1 on an error.

function write( file : file_id; b : in out buffer; length : size_t ) return ssize_t; pragma import( C, write ); Write bytes from a buffer into the specified file. Buffer is any type of destination for the bytes read, with length being the size of the buffer in bytes. The number of bytes written is returned, or -1 on an error.

function lseek( file : file_id; offset : off_t; whence : integer ) return integer; pragma import( C, lseek ); Move to a particular position in the specified file. Whence is a code representing where your starting position is. Offset is how many bytes to move.

There are three possible "whence" values:

SEEK_SET : constant integer := 0; -- from start of file SEEK_CUR : constant integer := 1; -- offset from current position SEEK_END : constant integer := 2; -- from end of file

File input/output is naturally suited to generic packages. You can use the generic package to hide the low-level details of the standard C library. In following example, SeqIO is a generic package for reading and writing a sequential file of some type, using the kernel calls mentioned above.

-- SeqIO

-- A simple sequential IO package using standard C functions

generic type AFileElement is private;

package SeqIO is

type AFileID is new short_integer; seqio_error : exception;

function Open( path : string; read : boolean := true ) return AFileID; -- open a new file for read or write

procedure Close( fid : AFileID ); -- close a file

procedure Read( fid : AFileID; data : in out AFileElement); -- read one data item from the file. seqio_error is raised -- if the data couldn't be read

procedure Write( fid : AFileID; data : AFileElement ); -- write one data item to the file. seqio_error is raised -- if the data couldn't be written

end SeqIO;

package body SeqIO is pragma optimize( space);

-- Import C file handling functions type mode_t is new integer; -- C mode_t type type size_t is new integer; -- C size_t type subtype ssize_t is size_t; -- C ssize_t type

-- The C file functions we'll be using -- (denoted with a C_ prefix for clarity )

function C_Open( path : string; flags : integer; mode : mode_t) return AFileID; pragma import( C, C_Open, "open");

function C_Close( file : AFileID ) return integer; pragma import( C, C_Close, "close" );

procedure C_Read( size : out ssize_t; file : AFileID; data : in out AFileElement; count: size_t); pragma import( C, C_Read, "read"); pragma import_valued_procedure( C_Read); -- Using an "in out" parameter is the easiest way to pass -- the address of the data element. Because Ada doesn't -- allow in out parameters in functions, we'll use gnat's -- valued procedure pragma to pretend read is a procedure

procedure C_Write( size : out ssize_t; file : AFileID; data : in out AFileElement; count: size_t); pragma import( C, C_Write, "write"); pragma import_valued_procedure( C_Write); -- Using an "in out" parameter is the easiest way to pass -- the address of the data element. Because Ada doesn't -- allow in out parameters in functions, we'll use gnat's -- valued procedure pragma to pretend write is a procedure -- Our Ada subprograms

function Open( path : string; read : boolean := true ) return AFileID is -- open a new file for read or write flags : integer; begin

-- the flag values are listed in fcntlbits.h and man 2 open if read then flags := 0; -- read only, existing file else flags := 1000 + 100 + 1; -- write only, create or truncate end if; -- octal 640 => usr=read/write, group=read, others=no access

return C_Open( path & ASCII.NUL, flags, 8#640# ); end Open;

procedure Close( fid : AFileID ) is -- close a file Result : integer; -- we'll ignore it begin Result := C_Close( fid); end Close;

procedure Read( fid : AFileID; data : in out AFileElement ) is -- read one data item from the file BytesRead : ssize_t; begin -- 'size returns the size of the type in bits, so we -- divide by 8 for number of bytes to read C_Read( BytesRead, fid, data, AFileElement'size / 8 ); if BytesRead /= AFileElement'size / 8 then raise seqio_error; end if; end Read;

procedure Write( fid : AFileID; data : AFileElement ) is -- write one data item to the file BytesWritten : ssize_t; data2write : AFileElement; begin -- can't use data directly because it's an "in" parameter data2write := data; -- 'size returns the size of the type in bits, so we -- divide by 8 for number of bytes to write C_Write( BytesWritten, fid, data2write, AFileElement'size / 8); if BytesWritten /= AFileElement'size / 8 then raise seqio_error; end if; end Write;

end SeqIO;

You can test SeqIO with the following program:

with SeqIO; with Ada.Text_IO; use Ada.Text_IO;

procedure SeqIOtest is -- program to test SeqIO

package IntIO is new SeqIO( integer); -- IntIO is a SeqIO for integer numbers

id : IntIO.AFileID; int2read : integer;

begin

Put_Line( "Testing SeqIO package..." ); New_Line;

-- Part #1: Write numbers to a file

Put_Line( "Writing numbers 1 to 10 to a file..."); id := IntIO.Open( "int_list.txt", read => false ); for i in 1..10 loop IntIO.Write( id, i) end loop; IntIO.Close( id);

-- Part #2: Read the numbers back from the same file

Put_Line( "Reading numbers back..." ); id := IntIO.Open( "int_list.txt", read => true); for i in 1..10 loop IntIO.Read( id, int2read); Put_Line( "Number" & i'img & " =" & int2read'img ); end loop; IntIO.Close( id );

exception when IntIO.seqio_error => Put_Line( "Oh, oh! seqio_error!"); end SeqIOtest;

Note: This should be rewritten because a failure to write all the bytes is not necessarily an error--Linux has a buffer limit on how much it writes at one time--KB

Writing numbers 1 to 10 to a file... Reading numbers back... Number 1 = 1 Number 2 = 2 Number 3 = 3 Number 4 = 4 Number 5 = 5 Number 6 = 6 Number 7 = 7 Number 8 = 8 Number 9 = 9 Number 10 = 10

File Multiplexing Operations

These kernel calls help programs that have to monitor several file descriptors at once for activity. procedure select( result : out integer; topplusone : integer; readset : in out fdset; writeset : in out fd_set; errorset : in out fd_set; timeout : in out timeval ); pragma import( C, select ); pragma import_valued_procedure( select ); Select checks one or more file descriptors to see if they are ready for reading, writing, or if there is an error. It will wait up to timeout microseconds before timing out (0 wll return immediately). topplusone is the numerically highest file descriptor to wait on, plue one. The result is 0 for a timeout, -1 for failure, or the number of file discriptors that are ready and the file discriptor sets indicate which ones. Unlike most UNIX's, Linux leaves the time remaining in the timeout record so that you can use select in a timing loop--to repeatedly select file descriptors until the timeout counts down to zero. Other UNIX's leave the timeout unchanged.

type pollfd is record fd : integer; events : short_integer; revents : short_integer; end record;

Poll Events POLLIN := 16#1#; POLLPRI := 16#2#; POLLOUT := 16#4#; POLLERR := 16#8#; POLLHUP := 16#10#; POLLNVAL := 16#20#;

These are defined in asm/poll.h. procedure poll( result : out integer; ufds : in out pollfd; nfds : integer; timeout_milliseconds : integer ); pragma import( C, poll ); pragma import_valued_procedure( poll ); The name of this kernel call is misleading: poll is a form of select(). timeout_milliseconds is a timeout in milliseconds, -1 for no timeout. ufds is an array of pollfd records for files that poll() should monitor. Poll returns the number of pollfd array elements that have something to report, 0 in a timeout, or -1 for an error. Each bit in events, when set, indicates a particular event that the program is waiting for. Revents represents the events which occurred.

16.9 Directories

Directories are "folders" containing collections of files and other directories. In Linux, a directory is a special kind of file. Some of the standard file operations work on directories and some other file operations are specific to directories.

The top-most directory is /, or the root directory. All files on a system are located under the root directory. Disk drives do not have separate designations as in MS-DOS.

A period (.) represents the current directory, and a double period (..) represents the parent directory of the current directory. All directories have . and .. defined. The root directory, of course, doesn't have a parent: it's .. entry points to itself.

Many of the kernel calls and standard C library functions dealing with directories use functions that return C pointers. As mentioned in the bindings section, the only way to convert these kind of functions to Ada is by declaring the C pointers as a System.Address type and changing the C pointers to Ada access types using the Address_To_Access_Conversions package. procedure getcwd( buffer : out string; maxsize : size_t ); pragma import( C, getcwd ); Returns the name of the current working directory as a C string in buffer. Maxsize is the size of the buffer. All symbolic links are dereferenced.

function get_current_dir_name return System.Address; pragma import( C, get_current_dir_name ); Like getcwd, returns the current working directory name as a pointer to a C string. Unlike getcwd, symbolic links aren't dereferenced. Use this function to show the current directory to a user.

procedure chdir( path : string); pragma import( C, chdir ); Change the current working directory to the specified path. Example: chdir( "/home/bob/stuff" & ASCII.NUL );

function mkdir( path : string; mode : size_t ) return integer; pragma import( C, mkdir ); Create a new directory with permission bits as specified by mode.

function rmdir( path : string ) return integer; pragma import( C, rmdir ); Remove a directory.

function opendir( path : string ) return System.Address; pragma import( C, opendir); Open a directory in order to read its contents with readdir.

function closedir( info : System.Address) return integer; pragma import( C, closedir); Close a directory openned with opendir. Info is the C pointer returned by opendir.

function readdir( info : System.Address ) return DirEntCPtr; pragma import( C, readdir); Read the next entry in the directory. A null C pointer is returned if there is no more entries. Info is the C pointer returned by opendir.

function rewinddir( info : System.Address ) return integer; pragma import( C, rewinddir); Begin reading from the top of the directory. Info is the C pointer returned by opendir.

function telldir( info : System.Address) return integer; pragma import( C, telldir); Mark the current position in the directory, to return to it later using the seekdir function. Info is the C pointer returned by opendir.

function seekdir( info : System.Address; position : integer ) return integer; pragma import( C, seekdir ); Return to a position in the directory marked by telldir. Info is the C pointer returned by opendir.

function chroot( newroot : string ) return int; pragma import( C, chroot ); Make Linux think that a different directory is the root directory (for your program). This is used by programs such as FTP servers to prevent uses from trying to access files outside of a designated FTP directory. Example: Result := chroot( "/home/server/stay-in-this-directory" & ASCII.NUL);

There is also a scandir function that reads a directory and sorts the entries, but this is difficult to use directly from Ada.

The following program demonstrates some of the directory subprograms in Linux.

with Ada.Text_IO, Interfaces.C, Ada.Strings.Fixed; use Ada.Text_IO, Interfaces.C, Ada.Strings.Fixed; with System.Address_To_Access_Conversions;

procedure direct is

-- Working with directories

subtype size_t is Interfaces.C.size_t; -- renaming size_t to save some typing

package CStringPtrs is new System.Address_To_Access_Conversions( string ); use CStringPtrs; -- Convert between C and Ada pointers to a string

subtype DirInfoCPtr is System.Address; subtype DirEntCPtr is System.Address; -- two C pointers (System.Address types), renamed for -- clarity below

type DirEnt is record inode : long_integer; -- inode number offset : integer; -- system dependent offset2: unsigned_char; -- system dependent reclen : unsigned_short; -- system dependent name : string( 1..257 ); -- name of file end record; pragma pack( dirent); -- dirent is defined in /usr/src/linux../linux/dirent.h

package DirEntPtrs is new System.Address_To_Access_Conversions( DirEnt ); use DirEntPtrs; -- Convert between C and Ada pointers to a directory entry

procedure getcwd( buffer : out string; maxsize : size_t ); pragma import( C, getcwd );

function get_current_dir_name return System.Address; pragma import( C, get_current_dir_name);

function mkdir( path : string; mode : size_t ) return integer; pragma import( C, mkdir );

function rmdir( path : string ) return integer; pragma import( C, rmdir );

function opendir( path : string ) return DirInfoCPtr; pragma import( C, opendir );

function closedir( info : DirInfoCPtr ) return integer; pragma import( C, closedir );

function readdir( info : DirInfoCPtr ) return DirEntCPtr; pragma import( C, readdir );

function rewinddir( info : DirInfoCPtr ) return integer; pragma import( C, rewinddir );

function telldir( info : DirInfoCPtr ) return integer; pragma import( C, telldir );

function seekdir( info : DirInfoCPtr; position : integer ) return integer; pragma import( C, seekdir );

-- scandir hard to use from Ada

s: string(1..80); csop: CStringPtrs.Object_Pointer; Result: integer; DirInfo: DirInfoCPtr; direntop : DirEntPtrs.Object_Pointer; Position : integer; LastPosition : integer;

begin

Put_Line( "This program demonstrates Linux's directory functions" ); New_Line;

-- getcwd example

getcwd( s, s'length ); Put( "The current path (simplified) is " ); Put_Line( Head( s, Index( s, ASCII.NUL & "" )-1 ));

-- Index for fixed strings takes a string as the second parameter -- We'll make a string containing an ASCII.NUL with &

-- get_current_dir_name example

csop := To_Pointer( get_current_dir_name ); Put( "The current path is " ); Put_Line( Head( csop.all, Index( csop.all, ASCII.NUL & "" )-1 ) );

-- mkdir example: create a directory named "temp"

Result := mkdir( "temp" & ASCII.NUL, 755 ); if Result /= 0 then Put_Line( "mkdir error" ); else Put_Line( "temp directory created" ); end if;

-- rmdir example: remove the directory we just made

Result := rmdir( "temp" & ASCII.NUL ); if Result /= 0 then Put_Line( "rmdir error" ); else Put_Line( "temp directory removed" ); end if; New_Line;

-- directory reading

DirInfo := OpenDir( "/home/ken/ada" & ASCII.NUL); Put_Line( "Directory /home/ken/ada contains these files:"); loop direntop := To_Pointer( ReadDir( DirInfo ) ); exit when direntop = null;

-- TellDir returns the position in the directory
-- LastPosition will hold the position of the last entry read

LastPosition := Position;
Position := TellDir( DirInfo );
Put_Line( Head( Direntop.name, Index( Direntop.name, ASCII.NUL & "" )-1 ) );

end loop; New_Line;

-- SeekDir: move to last position in directory Result := SeekDir( DirInfo, LastPosition ); Put( "The last position is " ); direntop := To_Pointer( ReadDir( DirInfo ) ); Put_Line( Head( Direntop.name, Index( Direntop.name, ASCII.NUL & "" )-1 ) ); New_Line;

-- RewindDir: Start reading again

Result := RewindDir( DirInfo ); Put( "The first position is " ); direntop := To_Pointer( ReadDir( DirInfo ) ); Put_Line( Head( Direntop.name, Index( Direntop.name, ASCII.NUL & "" )-1 ) ); New_Line;

-- close the directory

Result := CloseDir( DirInfo ); end direct;

This program demonstrates Linux's directory functions The current path (simplified) is /home/ken/ada/trials The current path is /home/ken/ada/trial temp directory created temp directory removed Directory /home/ken/ada contains these files: . .. temp all.zip README posix.zip sm posix cgi tia x rcsinfo.txt text_only original lintel texttools smbeta2.zip trials plugins texttools.zip

The last position is texttools.zip

The first position is .