14 Linux Programming


  <--Last Chapter Table of Contents Next Chapter-->  

Fun Fact:The original Apple Macintosh operating system was written in Pascal the ancestor language of Ada.

14.1 Gnat OS Library

C Equivalent
Create a Linux file
Delete a Linux file

The gnat OS library gnat.os_lib provides common UNIX operations independent of what flavour of UNIX gnat is running on. It provides an extensive set of file utilities as well as the ability to run blocked and non-blocked child processes.The price for this low-level OS support is the need to use a lot of addresses 'access and C strings.

Notethere is also a thin binding available for basic C stream functions described below.

with text_io gnat.os_lib;

use text_io gnat.os_lib;
procedure ostest is
  fd : File_Descriptor;
  FilePath : constant string := "testfile.xxx" & ASCII.NUL;

  -- for write test

  FirstLine : constant string := "This is the first line in the file";

  AmountWritten : integer;
  -- for time stamp test
  ts : OS_Time;
  Year : Year_Type;
  Month : Month_Type;
  Day : Day_Type;
  Hour : Hour_Type;
  Minute : Minute_Type;
  Second : Second_Type;
  -- for location test
  sp : String_Access;
  -- for delete test

  WasDeleted : boolean;

  -- for spawn test

  Arguments : Argument_List( 1..1 );
  Ls : constant string := "/bin/ls";
  WasSpawned: boolean;
  RootDir : aliased string := "/";
  Put_Line( "This is an example of the Gnat's OS library:" );
  Put_Line( "Creating a new file..." );
  fd := create_file( FilePath'address Binary );
  if fd = invalid_fd then
    Put_Line( "Unable to create " & FilePath );
    Put_Line( "Created " & FilePath );
  end if;
  Put_Line( "Getting the timestamp on the file..." );
  ts := File_Time_Stamp( fd );
  GM_Split( ts Year Month Day Hour Minute Second );
  Put_Line( "The time stamp is" &
    Year'img & "/" & Month'img & "/" & Day'img &
    Hour'img & ":" & Minute'img & ":" & Second'img );
  Put_Line( "Writing to the file..." );
  Put_Line( FirstLine );
  AmountWritten := Write( fd FirstLine'Address FirstLine'Length );
  Put_Line( "Wrote" & AmountWritten'img & " bytes" );
  Put_Line( "The file length is" & File_Length( fd )'img );
  Close( fd );
  Put_Line( "Closed the file" );
  Put_Line( "Locating the file we just made..." );
  sp := Locate_Regular_File( File_Name => FilePath
  Path => GetEnv( "PATH" ).all );
  Put_Line( "The file is '" & sp.all & "'" );
  Put_Line( "Deleting the file..." );
  Delete_File( FilePath'address WasDeleted );
  if WasDeleted then
   Put_Line( "File was deleted" );
    Put_Line( "File was not deleted" );
  end if;
  Put_Line( "Running ls / ..." );
  Arguments(1) := RootDir'unchecked_access;
  -- unchecked to avoid unless accessibility warning
  Spawn( Ls Arguments WasSpawned );
  if WasSpawned then
    Put_Line( "End of ls output - Spawned worked" );
    Put_Line( "Spawn failed" );
  end if;

end ostest;

This is an example of the Gnat's OS library:

Creating a new file...

Created testfile.xxx
Getting the timestamp on the file...
The time stamp is 1998/ 12/ 18 23: 42: 24
Writing to the file...
This is the first line in the file
Wrote 34 bytes
The file length is 34
Closed the file

Locating the file we just made...

The file is './testfile.xxx'
Deleting the file...
File was deleted
Running ls / ...


End of ls output - Spawned worked


14.2 Installing Binding Packages

A variety of Ada packages exist to allow you to call C libraries from Ada. These packages are called bindings. For example there are Ada bindings to Motif TCL WWW CGI and Posix (that is the kernel).
A thin binding gives you direct access to library calls. A thick binding provides indirect access where the package does some setup before invoking the library calls. The gnat.os_lib library is an example of a thick binding to basic Linux file operations.

When installing binding libraries:

14.3 Catching Linux Signals

A programs has to be able to respond to unexpected events. What do you do when somebody types control-C? How do you gracefully stop the program when somebody kills it with the kill command? These unexpected events are referred to a signals in Linux and Gnat provides libraries for you to "catch" these signals and respond to them gracefully.
The standard Ada 95 package Ada.Interrupts and its children handle unexpected operating system events. Under Linux these packages provide support for signal handling.

A complete list of Linux signals is listed in an appendix. The package Ada.Interrupt.Names defines the names of these signals for you.
Signal Handlers are protected type procedures with no parameters. The body of the procedure performs whatever actions you want to do when you receive a signal.

For example to catch the SIGTERM signal the signal that indicates that the program has been killed with the "kill" shell command you can write a handler like this:

protectedbodySignalHandler is

  procedure HandleSIGTERM is

  -- normal kill signal handler
    Put_Line( "Ouch! I've been killed!" );
  -- perform any other cleanup here
 end HandleSIGTERM;
end  SignalHandler;

To put the handler in place permanently use pragma Attach_Handler.

  pragma Attach_Handler( HandleSIGTERM SIGTERM );

Now whenever your program receives a SIGTERM signal your handler will automatically run.

If you don't want to install a permanent handler a handler can be installed or changed while the program is running. To indicate that a procedure is an interrupt handler that can be installed at a later time use pragma Interrupt_Handler.

  pragma Interrupt_Handler( HandleSIGTERM );

Gnat automatically handles one signal for you: SIGINT the interrupt signal. This is the signal that is sent to your program when control-c is pressed. If you want to handle control-c presses yourself you have to use pragma Unreserve_All_Interrupts. Despite it's long name this pragma simply tells Gnat to ignore SIGINT's.

Certain signals can never be caught. SIGUNUSED the unused signal can't be caught for obvious reasons. Some signals are used by the multithreading software and are not available for use in applications. In particular if you are running native Linux threads you can't catch SIGFPE SIGILL SIGSEGV SIGBUS SIGTRAP SIGABRT SIGINT SIGVTALRM SIGUNUSED SIGSTOP or SIGKILL. On 2.0 kernels or older native Linux threads use SIGUSR1 and SIGSUR2 and are not available. If you're running FSU threads then SIGALRM is also not available.

Ada.Interrupts also contains several subprograms for signal handling.

The following package sets up three signal handlers which display a message at set the EMERGENCY_SHUTDOWN variable to true. The demo program demonstrates some of the Ada.Interrupts subprograms and enters into a slow loop. The main program was killed with the "kill —SIGPWR" shell command simulating a power failure signal.

with Ada.Interrupts.Names;

use Ada.Interrupts Ada.Interrupts.Names;
package SigHand is

  -- Package to handle basic Linux signals

  pragma Unreserve_All_Interrupts;

  -- Gnat will no longer handle SIGINT for us
  EMERGENCY_SHUTDOWN : boolean := false;
  -- set in the event of a signal to shut down the program
  -- SignalHandler will handle the signals independently
  -- from the main program using multithreading
  protected SignalHandler is

    procedure HandleControlC;

    pragma Attach_Handler( HandleControlC SIGINT );
    -- SIGINT (Control-C) signals will be intercepted by
    -- HandleControlC
    procedure HandleKill;
    pragma Attach_Handler( HandleKill SIGTERM );
    -- SIGTERM (kill command) signals will be intercepted by
    -- HandleKill
    procedure HandlePowerFailure;
    pragma Attach_Handler( HandlePowerFailure SIGPWR );
    -- SIGPWR (power failure signal) intercepted by
    -- HandlePowerFailure
  end SignalHandler;

end SigHand;

with Ada.Text_IO;

use Ada.Text_IO;
package body SigHand is

  -- Package to handle basic Linux signals

protected body SignalHandler is

  -- This protected type contains all our signal handlers
  procedure HandleControlC is
  -- Control-C signal handler
      Put_Line( "HandleControlC: The program is already shutting down" );
      Put_Line( "HandleControlC: Control-C was pressed shutting down" );
   end if;

  end HandleControlC;

  procedure HandleKill is

    -- normal kill signal handler
      Put_Line( "HandleKill: The program is already shutting down" );
      Put_Line( "HandleKill: Program is shutting down" );
    end if;
  end HandleKill;
  procedure HandlePowerFailure is
  -- power failure handler
      Put_Line( "HandlePowerFailure: The program is already shutting down" );
      Put_Line( "HandlePowerFailure: Program is shutting down" );
   end if;

  end HandlePowerFailure;

  end SignalHandler;

end SigHand;

with Ada.Text_IO SigHand Ada.Interrupts.Names;

use Ada.Text_IO SigHand Ada.Interrupts Ada.Interrupts.Names;
procedure SigDemo is
  Handler : Parameterless_Handler;
  Counter : integer := 2;
  Put_Line( "This program demonstrates signal handling." );
  Put_Line( "To stop this program type Control-C or " );
  Put_Line( "kill it with the shell kill command." );
  -- Is_Reserved example

  if Is_Reserved( SIGTERM ) then

    Put_Line( "The SIGTERM handler is reserved" );
    Put_Line( "The SIGTERM handler isn't reserved" );
  end if;
  -- Is_Reserved example

  if Is_Attached( SIGINT ) then

    Put_Line( "There is a SIGINT handler installed" );
    Put_Line( "There is no SIGINT handler installed" );
  end if;
  -- Current_Handler example

  Put_Line( "Testing SIGTERM handler..." );

  Handler := Current_Handler( SIGTERM );
  -- Current_Handler gives a callback to the handler
  -- run the handler callback
    Put_Line( "Handler works" );
    Put_Line( "Handler doesn't work" );
  end if;
  -- test complete: reset emergency shutdown flag

  -- a long loop


  Put_Line( "The number is " & Counter'img );
    Counter := Counter * 2;
    Put_Line( "Doubling the number is " & Counter'img );
    delay 1.0;
  end loop;
  Put_Line( "The program has shut down" );
end SigDemo;

This program demonstrates signal handling.
To stop this program type Control-C or
kill it with the shell kill command.
The SIGTERM handler isn't reserved
There is a SIGINT handler installed
Testing SIGTERM handler...
HandleKill: Program is shutting down
Handler works
The number is 2
Doubling the number is 4
Doubling the number is 8
Doubling the number is 16
Doubling the number is 32
Doubling the number is 64
Doubling the number is 128
Doubling the number is 256
Doubling the number is 512
HandlePowerFailure: Program is shutting down
The program has shut down

14.4 Working with the Command Line

C Equivalent
Function Command_Name return string;
The name of this command (path?).
Function ArgumentCount return natural;
The number of arguments.
Function Argument( n : natural ) return string;
The n'th argument.
Procedure Set_Exit_Status( e : Exit_Status ); 
The exit status to return.
exit( e )
Ada interacts with the outside world through the standard Ada package Ada.Command_Line.
Suppose you have an Ada program called "myprog" and a user types in the following command: "myprog -v sally.com".

Command_Name returns the name of the command. If the program was run from a shell it returns the name as typed in by the user. In the above example Command_Name returns "myprog".
ArgumentCount returns the number of arguments not including the name of the program. The shell determines how arguments are grouped together but typically each argument is separated by a space. In the above example there are two arguments -v and "sally".

Argument returns an argument. In the above example argument( 1 ) returns "-v".

Set_Exit_Status  gives Ada the error code you want to return when the program is finished running. Ada defines two  Exit_Status  values   Success  and  Failure . Since  Exit_Status  is just an integer you can return other values. Zero indicates that the program ran without error non-zero values indicate an error. The predefined values of  Success  and  Failure  are 0 and 1.

Properly flagging errors is important for shell programming. For example you have to return the proper exit status for "myprog && echo 'all is well'" to work properly. You can retrieve the exit status of the last command using "$?". For example:


myprog -v sally
if [ $? -eq 0 ] ; then
  echo "There were no errors"
  echo "The program returned error code = $?"
See the example program in the next section for an example using this package.


14.5 Linux Environment Variables

Ada.Command_Line.Environment is a gnat package for accessing Linux environment variables.

C Equivalent
Function Environment_Count return natural;
The number of environment variables
Function Environment_Value(n) return string;
The name value of the nth variable

The Environment_Count function returns the number of environment variables.

The Environment_Value function returns the name and value of a variable separated by an equals sign. For example Environment_Value( 5 ) returns the name and value of the fifth environment variable.

The following program is an example of Ada.Command_Line and Ada.Command_Line.Environment. The results assume that you started the program by typing "cmdtest -v".

with text_io Ada.Command_Line.Environment;

use text_io Ada.Command_Line Ada.Command_Line.Environment;
procedure cmdtest is


  Put_Line( "This is an example of Ada.Command_Line" );
  Put_Line( "The command to invoke this example was '" & Command_Name & "'" );
  Put_Line( "There is/are" & Argument_Count'img & " command line arguments" );
  if Argument_Count > 0 then
    Put_Line( "The first argument is '" & Argument(1) & "'" );
  end if;
  Put_Line( "There is/are" & Environment_Count'img & " environment variables." );
  Put_Line( "The first environment variable is '" &
    Environment_Value( 1 ) & "'" );
  Set_Exit_Status( Success );
end cmdtest;

This is an example of Ada.Command_Line

The command to invoke this example was 'cmdtest'

There is/are 1 command line arguments
The first argument is '-v'

There is/are 24 environment variables.

The first environment variable is 'LESSOPEN=|lesspipe.sh %s'

Environment variables can be removed using the Gnat Ada.Command_Line.Remove package.


14.6 GNAT.Directory_Operations Package

This Gnat package allows you to create and explore directories. Although the package is portable to all operating systems the format of the directory depends on the particular operating system.

For this package a directory name string (Dir_Name_Str) is a pathname in the standard Linux format. The trailing '/' character is optional when using this package but directory names returned will always have a trailing '/'. "." is the current directory. ".." is the parent directory of the current directory.

Get_Current_Dir returns the name of the current directory. Change_Dir changes the current directory to a new location.

with ada.text_io
use ada.text_io

procedure gdir is
  dir : string(1..80);
  len : natural;
  Put( "The current working directory is " );
  Put_Line( Get_Current_Dir );

  Change_Dir( ".." );
  Put( "Moving up
the current working directory is " );
  Put_Line( Get_Current_Dir );

  Change_Dir( "work" );
  Get_Current_Dir( dir
len );
  Put( "Moving down to 'work'
the current working directory is " );
  Put_Line( dir(1..len) );
end dir;

The current working directory is /home/kburtch/work/
Moving up
the current working directory is /home/kburtch/
Moving down to 'work'
the current working directory is /home/kburtch/work/

For viewing directories the package opens directories like a Text_IO file. Dir_Type is a limited private directory type corresponds to a file_type in Text_IO. Directories can only be read.

Any error will raise a DIRECTORY_ERROR exception

with ada.text_io
use ada.text_io

procedure gdir2 is
  dir     : Dir_Type;
  dirname : string( 1..80 );
  len     : natural;
  if Read_Is_Thread_Safe then
     put_line( "Tasks may read the same directory" );
     put_line( "Tasks may not read the same directory" );
  end if;

  Open( dir
  if Is_Open( dir ) then
     put_Line( "The directory was opened" );
     put_Line( "The directory was not opened" );
  end if;
    Read( dir
len );
    exit when len = 0;
    Put_Line( dirname( 1..len ) );
  end loop;
  Put_Line( "End of directory" );

  Close( dir );

end gdir2;

Tasks may not read the same directory

The directory was opened
End of directory

The directories "." and ".." are always returned.

New directories can be made with Make_Dir.

Make_Dir( "logs" ); -- make a new "logs" directory

If you need more features that these the Linux kernel calls for directories are described in 16.9. The section includes a command to remove directories which cannot be done with Gnat.Directory_Operations.


14.7 GNAT.Lock_Files Package

This Gnat package contains subprograms for obtaining exclusive access to a particular file or directory. When a file is locked only your program may use the file until the file is unlocked.

Locks are implemented using lock files. When a file is locked Gnat checks for the presence of a separate file. If the file exists the file has been locked by another application. If a file cannot be locked a LOCK_ERROR is raised.

The programmer supplies the lock file name. Linux programs usually place lock files in the /var/lock/ directory.

The Lock_File procedure locks a particular file. By default if the procedure will continue trying to relock the file every second forever (actually for Natural'Last seconds a very long time). The delay and the number of retries can be changed.

Lock_File( "/var/lock/"
Lock_File( "/var/lock/customers.txt" );
Lock_File( "/var/lock/customers.txt"
Wait => 5.0
Retries => 10 );

Files are unlocked using Unlock_File. This procedure deletes the lock file.

Unlock_File( LockDir
Unlock_File( "/var/lock/customers.txt" );

The lock file approach is a voluntary convention. Programs that honour the convention can share the file in an orderly way. A program that doesn't use the package will not be denied access. For true file locking use the Linux kernel calls described in 16.7.


14.8 GNAT.Sockets


Note: The Gnat socket package is very simple and reportedly returns an error if a socket is blocked. If you need to connect to blockable sockets you'll need to write your own O/S socket bindings.  

  <--Last Chapter Table of Contents Next Chapter-->