Background
While I’ve used Ada for work projects, those projects did not interface with C code and did not use access types. So, I’m lacking experience needed for my personal projects that interface with C libraries.
When trying to learn how to interface with C code, the various tutorials seemed like they would be helpful. But once I faced real-world code, I realized the tutorials did not cover examples of what I needed to do.
During the process of creating this post, I solved the problems, that prevented compiling, that I originally intended to include as questions. But this still stands as an example some of the difficulties new Ada users face. The Ada ecosystem is not as comprehensive as, for example, Python with its “batteries included” philosophy. So, new Ada users are likely to need to interface with C libraries.
This C library function, that I needed to interface with, used the common C practice of taking a “buffer” argument to output to. I could not find any example of interfacing with this kind of function. So I was left to decipher the Ada Reference Manual’s Appendix B.3.1 “The Package Interfaces.C.Strings”. Which didn’t really provide any help with how to use it.
Although my original intent was to get help making this code compile, I am still posting it because it may help others who are facing a similar problem. I also do have other (less important) questions about the code I wrote.
Problem
For a Linux utility, I need to read the target of symbolic links (symlinks). GNAT.OS_Lib
has a function Is_Symbolic_Link
that I can use to verify a file is (or is not) a symbolic link, but it doesn’t have a function to read the target of the link (what it links to). I’ve tried to find an existing Ada package that has this function, but I have not found one.
So, I need to create a binding to the POSIX function. The function readlink
in unistd.h
has the following signature:
ssize_t readlink(const char* restrict path,
char* restrict buf,
size_t bufsize);
Seems like it would be straight forward to use…
Auto Generating the Thin Binding
I used g++
to generate thin bindings to the unistd.h
library:
g++ -c -fdump-ada-spec -C /usr/include/unistd.h
This produced bindings to everything in that file and all its dependencies, creating the following files (in no particular order):
unistd_h.ads
stddef_h.ads
bits_unistd_ext_h.ads
bits_types_h.ads
bits_getopt_core_h.ads
bits_confname_h.ads
Most of these I won’t need, But it doesn’t hurt to leave them.
The thin binding generated for readlink
is as follows:
-- Read the contents of the symbolic link PATH into no more than
-- LEN bytes of BUF. The contents are not null-terminated.
-- Returns the number of characters read, or -1 for errors.
function readlink
(uu_path : Interfaces.C.Strings.chars_ptr;
uu_buf : Interfaces.C.Strings.chars_ptr;
uu_len : stddef_h.size_t) return ssize_t -- /usr/include/unistd.h:838
with Import => True,
Convention => C,
External_Name => "readlink";
Creating a Thick Binding
The trouble of creating a thick binding is the same as the trouble of using the thin binding. So, even if I was to use this thin binding directly, I would be having the same problem(s).
Concept of use:
- A string containing the path/name of the symlink file is passed in.
- A pre-allocated string buffer is used to pass out the target path/name.
- This is where I was struggling.
- An integer is used to pass in the size of the buffer.
- An integer is returned as the size of the string put in the buffer.
An Attempt at a Thick Binding:
function Read_Link(filename: String) return String is
path : Interfaces.C.Strings.chars_ptr := Interfaces.C.Strings.New_String(filename);
-- path/filename of the symlink to read
buf : Interfaces.C.Strings.chars_ptr := Interfaces.C.Strings.New_String("How do I allocate a large size without a very long literal?");
-- Pre-allocated string (buffer) to receive the target path/filename
Len : stddef_h.size_t := buf'size;
-- Size (length) of the buffer; Maximum length that can be used for the target path/filename
Used : unistd_h.ssize_t;
-- How many characters of "buf" were used, or an error code if negitive
begin
-- Make the call to the system library:
Used := unistd_h.readlink(path, buf, Len);
declare
Target : String(1..Integer(Used)); -- Return variable
begin
-- If "used" is within the range of "buf":
if Used > 0 and then Used <= buf'size then -- can't use buf'range
-- Copy just the valid part of "buf" into the return variable
Target := Interfaces.C.Strings.Value(buf, Interfaces.C.size_t(Used));
else
-- The value of "Used" is out of bounds. This signals an error code was returned.
Target := ""; -- Just eat the errors for now; TODO: raise exceptions for the errors.
end if;
-- "New_String" allocated memory. We are done with them.
Interfaces.C.Strings.Free(path);
Interfaces.C.Strings.Free(buf);
return Target;
end;
end Read_Link;
This compiles and seems to run successfully.
Questions:
- I think
New_String
is allocating memory and making a copy of the string. But in this use case, the string,filename
, on the stack will live for the life of the call toreadlink
so I would like to pass that existing string. Can I do that cleanly? buf
is being created using a literal string. I would prefer to just specify the bounds or size. Can I do that? And how?- Bonus question: Is there anything else wrong with this code?
Thank you for taking the time to read this post and consider my questions.