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.
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:
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.
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
- No IPv6. Have to continue using GNAT.Sockets.
- Even though I'm pretty sure they're in POSIX, I haven't found regexes. So probably fall back to GNAT.Regpat.
- Similarly, no catgets. But personally I prefer the gettext interface, which is well supported by the "intl" Alire crate.
- No syslog. But could use a combination of the "Activity Log" from scatcdsk_lib & Error_Log from ptf?
- No ndbm. But could use sqlite3 with the same schema that Python uses.
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.