14 Linux Programming
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.
there 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:
- Make sure that the filename endings are the right
ones for gnat. Different compilers use different conventions.
- Compile the binding package(s).
- Install the library the binding is for (if
necessary)
- Include -lname on the link line, where name is the
library. Remember that the order of the -l's is important.
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.
- Is_Reserved is true if a particular signal
is uncatchable.
- Is_Attached is true if a particular signal
has a handler attached.
- Current_Handler returns a pointer to the
handler for a particular interrupt.
- Exchange_Handler will put a new handler in
place and return a pointer to the previous handler.
- Detach_Handler will uninstall a handler
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".
- "myprog" is the name of the command.
- "-v" and "sally.com" are arguments to the command.
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.
- Open - Open a directory for reading
- Close - Close a directory
- Read - Read a directory entry. When a null string is
returned, there are no more entries
- Is_Open - True if the directory is open
- Read_Is_Thread_Safe - True if the directory can be read
by separate tasks (threads). That is, if there is a readdir_r
kernel call
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.
14.8 GNAT.Sockets
Incomplete
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.