POSIX from Ada

There has been a standard way to make OS calls for a long time, it used to be called POSIX.5. More officially, the ISO/IEC 14519:2001 standard, which a friend told me is on some service called libgen but I wouldn't know about that (if you're interested for $DAYJOB, just get your company to buy the standard). The current implementation is in Alire as crate "florist_blady". It's very complete, here are some snippets that illustrate simple usages.

Florist_blady crate

This is a major advantage of Ada, with other languages you might have to help out and write bindings, but in Ada standard (i.e. IEEE/ISO) technology is "batteries included". So you can concentrate on the application instead.

Previous Work

A complete OS interface has probably been a requirement since Perl became popular. Some good documents I'm aware of are:

For Scheme, although only partial this is quite useful
"Posix" structure and children for Standard ML

Ada POSIX.5 should really only be compared against the Standard ML library (the Scheme one is much smaller). But I personally found Standard ML libraries unstable if you use thread extensions (since these were not part of the original language design, unlike Ada).

Text User Interfaces (like curses)

Unfortunately the ncursesada Alire crate wouldn't compile for me (I'll log a defect report presently) so I had to fall back to something more primitive. This was the "Console" module in my scatcdsk_lib project. However, this only supports output, not input. Two "unusual" features I was interested in were raw/noecho input; and non-blocking input. These can be implemented with short snippets.

Reusable packages from the SCATC DSK (scatcdsk_lib)

For raw & noecho input, you would use something like this:

with POSIX.IO, POSIX.Terminal_Functions; use POSIX.IO, POSIX.Terminal_Functions;
...
   original_Tc, Tc : Terminal_Characteristics;
   Modes : Terminal_Modes_Set;
...
   original_Tc := Get_Terminal_Characteristics (POSIX.IO.Standard_Input); -- like tcgetattr
   Tc := original_Tc;
   Modes := Terminal_Modes_Of (Tc);
   Modes (Canonical_Input) := False;
   Modes (Echo) := False;
   Define_Terminal_Modes (Tc, Modes);
   Set_Terminal_Characteristics (POSIX.IO.Standard_Input, Tc, Immediately); -- like tcsetattr
   -- Do work here
...
   Set_Terminal_Characteristics (POSIX.IO.Standard_Input, original_Tc, Immediately);

And for non-blocking input, something like this:

   Mode : POSIX.IO.File_Mode;
   original_Fc, Fc : POSIX.IO.Open_Option_Set;
...
   POSIX.IO.Get_File_Control(POSIX.IO.Standard_Input, Mode, original_Fc); -- like fcntl F_GETFL
   Fc := original_Fc;
   Fc := Fc + POSIX.IO.Non_Blocking;
   POSIX.IO.Set_File_Control(POSIX.IO.Standard_Input, Fc); -- like fcntl F_SETFL
   -- Do work here
...
   POSIX.IO.Set_File_Control(POSIX.IO.Standard_Input, original_Fc);

This brings us up to about the feature level of curses that I actually cared about, with much less dependencies.

OS Databases

A simple test I like is the standard "groups" command. Ada (well, POSIX.5) does well here, it's quite readable:

with POSIX.Process_Identification; use POSIX.Process_Identification;
with POSIX.Group_Database; use POSIX.Group_Database;
with Ada.Text_IO;
with POSIX;
procedure Groups is
   Groups : constant Group_List := Get_Groups;
begin
   for I in Groups'Range loop
      Ada.Text_IO.Put (POSIX.To_String (Group_Name_Of (Get_Group_Database_Item (Groups (I)))));
      Ada.Text_IO.Put (' ');
   end loop;
   Ada.Text_IO.New_Line;
end Groups;

Limits

This is the equivalent of C's "limits.h".

with POSIX.Configurable_System_Limits;
with POSIX.Configurable_File_Limits;
...
   -- Prints "TRUE 255 TRUE 4096" on my system
   Ada.Text_IO.Put_Line(POSIX.Configurable_File_Limits.Filename_Is_Limited("/")'Image);
   Ada.Text_IO.Put_Line(POSIX.Configurable_File_Limits.Filename_Maximum("/")'Image);
   Ada.Text_IO.Put_Line(POSIX.Configurable_File_Limits.Pathname_Is_Limited("/")'Image);
   Ada.Text_IO.Put_Line(POSIX.Configurable_File_Limits.Pathname_Maximum("/")'Image);
   -- Don't use POSIX.Limits.Portable_Filename_Maximum, etc.,
   -- they're the minimum standard values and not what is actually supported on your system

There are also many boolean flags (and a whole other POSIX.Configurable_System_Limits package) that might replace some usage of autoconf in C.

Others

I wrote the above by reading the ".ads" files in the florist_blady source code, which I got by running "alr get florist_blady". In particular, the "libsrc" and "gensrc" directories (the latter is populated only after running "alr build"). The ISO/IEEE standard may be more helpful.

Limitations

NLS thin Ada binding (intl)
Portable Text Formatter (ptf)
Implementing a dbm-like interface using sqlite3

If I had to come up with an order of preference, it looks like "POSIX.5, Alire, GNAT". Nice to keep the option of other Ada2012 compilers like ObjectAda open.

Conclusion

I'm impressed. I've been writing C POSIX code for way too long, this is a definite improvement. In fact, it could be the first time I actually enjoyed learning about a standard, I found how they turned the terse C interface into something more readable very interesting. The fact that such a "fat" binding succeeded at all also surprised me based on past experience, maybe it's because this time there was budget for a standard committee.

Back to my gemlog