1 /**
2     Create temporary files and folders. These are wrappers around the
3     corresponding functions in `core.sys.posix.stdlib`.
4 
5     Copyright: © 2019 Arne Ludwig <arne.ludwig@posteo.de>
6     License: Subject to the terms of the MIT license, as written in the
7              included LICENSE file.
8     Authors: Arne Ludwig <arne.ludwig@posteo.de>
9 */
10 module dalicious.tempfile;
11 
12 import std.stdio;
13 
14 version (Posix)
15 {
16     import std.algorithm : endsWith;
17     import std.conv : to;
18     import std.exception : errnoEnforce;
19     import std.stdio : File;
20     import std..string : fromStringz;
21     import std.typecons : Tuple, tuple;
22 
23     /**
24         Generates a uniquely named temporary directory from template.
25 
26         The last six characters of template must be XXXXXX and these are
27         replaced with a string that makes the directory name unique. The
28         directory is then created with permissions 0700.
29 
30         Returns: The generated directory name.
31     */
32     string mkdtemp(in string templateString) @trusted
33     {
34         import core.sys.posix.stdlib : c_mkdtemp = mkdtemp;
35 
36         char[255] dirnameBuffer;
37         auto len = templateString.length;
38         assert(len < dirnameBuffer.length);
39         assert(templateString.endsWith("XXXXXX"));
40 
41         dirnameBuffer[0 .. len] = templateString[];
42         dirnameBuffer[len] = 0;
43 
44         errnoEnforce(null != c_mkdtemp(dirnameBuffer.ptr), "cannot create temporary directory");
45 
46         return to!string(fromStringz(dirnameBuffer.ptr));
47     }
48 
49     ///
50     unittest
51     {
52         import std.algorithm : startsWith;
53         import std.file : isDir, rmdir;
54 
55         string tempDir = mkdtemp(".unittest-XXXXXX");
56 
57         try
58         {
59             assert(isDir(tempDir));
60             assert(tempDir.startsWith(".unittest-"));
61         }
62         finally
63         {
64             rmdir(tempDir);
65         }
66     }
67 
68     /**
69         Generate a unique temporary filename from templateString, creates and
70         opens the file, and returns the open file and generated name.
71 
72         The last six characters of template must be "XXXXXX" and these are
73         replaced with a string that makes the filename unique.
74 
75         The optional templateSuffix will be appended to the file name.
76 
77         Returns: The open file and generated name.
78     */
79     Tuple!(File, "file", string, "name") mkstemp(in string templateString) @trusted
80     {
81         import core.sys.posix.stdlib : mkstemp;
82 
83         char[255] dirnameBuffer;
84         auto len = templateString.length;
85         assert(len < dirnameBuffer.length);
86         assert(templateString.endsWith("XXXXXX"));
87 
88         dirnameBuffer[0 .. len] = templateString[];
89         dirnameBuffer[len] = 0;
90 
91         auto fd = mkstemp(dirnameBuffer.ptr);
92 
93         errnoEnforce(fd != -1, "cannot create temporary file");
94 
95         File tempFile;
96         tempFile.fdopen(fd, "r+");
97 
98         return tuple!("file", "name")(tempFile, dirnameBuffer.ptr.fromStringz.to!string);
99     }
100 
101     ///
102     unittest
103     {
104         import std.algorithm : startsWith;
105         import std.file : remove;
106 
107         auto tempFile = mkstemp(".unittest-XXXXXX");
108         scope (exit)
109             remove(tempFile.name);
110 
111         assert(tempFile.name.startsWith(".unittest-"));
112         assert(tempFile.file.isOpen);
113         assert(!tempFile.file.error);
114         tempFile.file.writeln("foobar");
115         tempFile.file.flush();
116         tempFile.file.rewind();
117         assert(tempFile.file.readln() == "foobar\n");
118     }
119 
120     private extern (C) int mkstemps(char*, int);
121 
122     /// ditto
123     Tuple!(File, "file", string, "name") mkstemp(in string templateString, in string templateSuffix) @trusted
124     {
125         char[255] dirnameBuffer;
126         auto len = templateString.length + templateSuffix.length;
127         assert(len < dirnameBuffer.length);
128         assert(templateString.endsWith("XXXXXX"));
129 
130         dirnameBuffer[0 .. len] = templateString[] ~ templateSuffix[];
131         dirnameBuffer[len] = 0;
132 
133         auto fd = mkstemps(dirnameBuffer.ptr, templateSuffix.length.to!int);
134 
135         errnoEnforce(fd != -1, "cannot create temporary file");
136 
137         File tempFile;
138         tempFile.fdopen(fd, "r+");
139 
140         return tuple!("file", "name")(tempFile, dirnameBuffer.ptr.fromStringz.to!string);
141     }
142 
143     ///
144     unittest
145     {
146         import std.algorithm : endsWith, startsWith;
147         import std.file : remove;
148 
149         auto tempFile = mkstemp(".unittest-XXXXXX", ".ext");
150         scope (exit)
151             remove(tempFile.name);
152 
153         assert(tempFile.name.startsWith(".unittest-"));
154         assert(tempFile.name.endsWith(".ext"));
155         assert(tempFile.file.isOpen);
156         assert(!tempFile.file.error);
157         tempFile.file.writeln("foobar");
158         tempFile.file.flush();
159         tempFile.file.rewind();
160         assert(tempFile.file.readln() == "foobar\n");
161     }
162 }
163 else
164 {
165     static assert(0, "Only intended for use on POSIX compliant OS.");
166 }