Getting a safe path for a temporary file

Jonathan M Davis newsgroup.d at jmdavisprog.com
Sat Oct 28 03:38:55 UTC 2017


On Saturday, October 28, 2017 02:46:00 Shriramana Sharma via Digitalmars-d-
learn wrote:
> On Wednesday, 25 October 2017 at 00:35:29 UTC, Ali Çehreli wrote:
> > > char[] name = "/tmp/XXXXXX".dup;
> >
> > remain valid. The actual issue is the missing '\0'. So,
> >
> > consider toStringz in this case:
> >   https://dlang.org/library/std/string/to_stringz.html
>
> Thanks for your reply, but can you clarify exactly I should use
> this?
>
> char[] name = "/tmp/XXXXXX".toStringz;
>
> gives
>
> <src>(13): Error: cannot implicitly convert expression
> `toStringz("/tmp/XXXXXX")` of type `immutable(char)*` to `char[]`
>
> So I tried:
>
> char[] name = "/tmp/XXXXXX".toStringz.dup;
>
> which gives
>
> <src>(13): Error: template object.dup cannot deduce function from
> argument types !()(immutable(char)*), candidates are:
> /usr/include/dmd/druntime/import/object.d(1943):
> object.dup(T : V[K], K, V)(T aa)
> /usr/include/dmd/druntime/import/object.d(1979):
> object.dup(T : V[K], K, V)(T* aa)
> /usr/include/dmd/druntime/import/object.d(3764):
> object.dup(T)(T[] a) if (!is(const(T) : T))
> /usr/include/dmd/druntime/import/object.d(3780):
> object.dup(T)(const(T)[] a) if (is(const(T) : T))
>
> And:
>
> char[] name = "/tmp/XXXXXX".dup.toStringz;
>
> gives the error <src>(13): Error: cannot implicitly convert
> expression `toStringz(dup("/tmp/XXXXXX"))` of type
> `immutable(char)*` to `char[]`
>
> So I'm not sure what to do?!

Well, for starters, toStringz returns a pointer, not a dynamic array. If you
want a dynamic array that's null-terminated, then you'll need to explicitly
put a null character on the end unless you're just going to use a string
literal (which would mean a string, not a char[], so that won't work here).

Also, toStringz specifically returns an immutable(char)* - though looking it
over right now, I'd say that that's a bug for the overload that takes
const(char)[] instead of string. It really should return const(char)* in
that case.

But regardless, that means that even changing char[] to char* wouldn't cut
it if you're using toStringz, since you're starting with a string. If you
want a specific constness, then you can use the more general function,
std.utf.toUTFz, which works with multiple character types and differing
constness rather than being designed specifically for string like toStringz
is.

However, something to take into account is that toStringz and toUTFz don't
always return the same string (and in fact, they probably never should,
because the trick they use to check for the null character one past the end
of the string doesn't always work correctly). So, if you're passing a string
to a C function that's then going to mutate it, you don't want toStringz or
toUTFZ. In that case, it's better to just manually put the null terminator
at the end of the string. So, you probably would end up with something like

char[] name = "/tmp/XXXXXX\0".dup;
auto fd = mkstemp(name.ptr);

Then when you return the name, you do something like

name[0 .. $ - 1].idup;

Alternatively, you could use a static array, but either way, you'd want to
put the null terminator in there manually. And fromStringz really isn't
necessary, since mkstemp is just going to fill in the array that you gave
it, and you know exactly where the null terminator is going to be, since
it's just filling in the X's rather than doing something that could end up
putting a null terminator anywhere in the array. You can just slice the
array to chop off the null terminator, and then dup it or idup if it's a
static array (so that you don't return a slice of a local variable), or if
it's a dynamic array, then either return it as-is or idup it if you want a
string; you could even use std.exception.assumeUnique to just cast it to
string if you know that no other references to that data exists (which they
wouldn't if you allocated the string inside of the function).

Also, you _really_ wouldn't want to use fromStringz if you used a static
array, since fromStringz always returns a slice of the original input:

inout(char)[] fromStringz(inout(char)* cString) @nogc @system pure nothrow {
    import core.stdc.string : strlen;
    return cString ? cString[0 .. strlen(cString)] : null;
}

But if I were you, I'd just create a char[] with an explicit null
terminator, and then afterwards, slice off the null character, and pass it
to assumeUnique to get a string, since then you allocate only once and don't
have to copy the contents of the array.

- Jonathan M Davis




More information about the Digitalmars-d-learn mailing list