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

 
Ada
Description
C Equivalent
create_file
Create a Linux file
creat
delete_file
Delete a Linux file
unlink
etc.

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 := "/";
begin
  Put_Line( "This is an example of the Gnat's OS library:" );
  New_Line;
  Put_Line( "Creating a new file..." );
  fd := create_file( FilePath'address, Binary );
  if fd = invalid_fd then
    Put_Line( "Unable to create " & FilePath );
  else
    Put_Line( "Created " & FilePath );
  end if;
  New_Line;
  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 );
  New_Line;
  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 );
  New_Line;
  Close( fd );
  Put_Line( "Closed the file" );
  New_Line;
  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 & "'" );
  New_Line;
  Put_Line( "Deleting the file..." );
  Delete_File( FilePath'address, WasDeleted );
  if WasDeleted then
   Put_Line( "File was deleted" );
  else
    Put_Line( "File was not deleted" );
  end if;
  New_Line;
  Put_Line( "Running ls / ..." );
  New_Line;
  Arguments(1) := RootDir'unchecked_access;
  -- unchecked to avoid unless accessibility warning
  Spawn( Ls, Arguments, WasSpawned );
  if WasSpawned then
    New_Line;
    Put_Line( "End of ls output - Spawned worked" );
  else
    Put_Line( "Spawn failed" );
  end if;
  New_Line;

 
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 / ...

STARTUP

System.map
System.old
bin
boot
cdrom
dev
dosc
etc
fd
home
lib
lost+found
mnt
opt
proc
root
sbin
tmp
usr
var
vmlinuz
vmlinuz.old
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
  begin
    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
  begin
    if EMERGENCY_SHUTDOWN then
      Put_Line( "HandleControlC: The program is already shutting down" );
    else
      Put_Line( "HandleControlC: Control-C was pressed, shutting down" );
   end if;
   EMERGENCY_SHUTDOWN := true;

  end HandleControlC;

  procedure HandleKill is

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

  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;
begin
  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." );
  New_Line;
  -- Is_Reserved example

  if Is_Reserved( SIGTERM ) then

    Put_Line( "The SIGTERM handler is reserved" );
  else
    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" );
  else
    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
  Handler.all;
  -- run the handler callback
  if EMERGENCY_SHUTDOWN then
    Put_Line( "Handler works" );
  else
    Put_Line( "Handler doesn't work" );
  end if;
  -- test complete: reset emergency shutdown flag
  EMERGENCY_SHUTDOWN := false;

  -- a long loop

  New_Line;

  Put_Line( "The number is " & Counter'img );
  loop
    exit when EMERGENCY_SHUTDOWN;
    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

 
Ada
Description
C Equivalent
Function Command_Name return string;
The name of this command (path?).
argv[0]
Function ArgumentCount return natural;
The number of arguments.
argn
Function Argument( n : natural ) return string;
The n'th argument.
argv[n]
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:

#!/bin/bash

myprog -v sally
if [ $? -eq 0 ] ; then
  echo "There were no errors"
else
  echo "The program returned error code = $?"
fi
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.

 
 
 
Ada
Description
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
getenv(n)

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

begin

  Put_Line( "This is an example of Ada.Command_Line" );
  New_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;
  New_Line;
  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, gnat.directory_operations;
use ada.text_io, gnat.directory_operations;

procedure gdir is
  dir : string(1..80);
  len : natural;
begin
  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, gnat.directory_operations;
use ada.text_io, gnat.directory_operations;

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

  Open( dir, "." );
  if Is_Open( dir ) then
     put_Line( "The directory was opened" );
  else
     put_Line( "The directory was not opened" );
  end if;
  loop
    Read( dir, dirname, 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
.
..
gdir.ads
gdir.ali
gdir.adb
gdir.o
gdir
gdir2.adb
gdir2.ali
gdir2.o
gdir2
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/", "customers.txt" );
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, "customers.txt" );
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.

 
 

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