1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
|
//! Creates a temporary directory at runtime that can be safely used to
//! store temporary data and is destroyed on deinit.
const TempDir = @This();
const builtin = @import("builtin");
const std = @import("std");
const Dir = std.Io.Dir;
const file = @import("file.zig");
const global = @import("../global.zig");
const log = std.log.scoped(.tempdir);
/// Dir is the directory handle
dir: Dir,
/// Parent directory
parent: Dir,
/// Name buffer that name points into. Generally do not use. To get the
/// name call the name() function.
name_buf: [file.random_basename_len:0]u8,
/// Create the temporary directory.
pub fn init() !TempDir {
// Note: the tmp_path_buf sentinel is important because it ensures
// we actually always have random_basename_len+1 bytes of available
// space. We need that so we can set the sentinel in the case we use
// all the possible length.
var tmp_path_buf: [file.random_basename_len:0]u8 = undefined;
const dir = dir: {
const cwd = std.Io.Dir.cwd();
const tmp_dir = try file.allocTmpDir(std.heap.page_allocator, global.environ());
defer file.freeTmpDir(std.heap.page_allocator, tmp_dir);
break :dir try cwd.openDir(global.io(), tmp_dir, .{});
};
// We now loop forever until we can find a directory that we can create.
while (true) {
const tmp_path = try file.randomBasename(&tmp_path_buf);
tmp_path_buf[tmp_path.len] = 0;
dir.createDir(global.io(), tmp_path, .default_dir) catch |err| switch (err) {
error.PathAlreadyExists => continue,
else => |e| return e,
};
return TempDir{
.dir = try dir.openDir(global.io(), tmp_path, .{}),
.parent = dir,
.name_buf = tmp_path_buf,
};
}
}
/// Name returns the name of the directory. This is just the basename
/// and is not the full absolute path.
pub fn name(self: *TempDir) []const u8 {
return std.mem.sliceTo(&self.name_buf, 0);
}
/// Finish with the temporary directory. This deletes all contents in the
/// directory.
pub fn deinit(self: *TempDir) void {
self.close(.delete);
}
pub const CloseMode = enum { delete, retain };
/// Close the directory handles, optionally retaining the temporary directory
/// and its contents on disk.
pub fn close(self: *TempDir, mode: CloseMode) void {
self.dir.close(global.io());
switch (mode) {
.delete => self.parent.deleteTree(global.io(), self.name()) catch |err|
log.err("error deleting temp dir err={}", .{err}),
.retain => {},
}
self.parent.close(global.io());
}
test {
const testing = std.testing;
var path_buf: [std.fs.max_path_bytes]u8 = undefined;
var path_len: usize = undefined;
var dir_handle: Dir.Handle = undefined;
var parent_handle: Dir.Handle = undefined;
{
var td = try init();
errdefer td.deinit();
const nameval = td.name();
try testing.expect(nameval.len > 0);
// Can open a new handle to it proves it exists.
var dir = try td.parent.openDir(testing.io, nameval, .{});
dir.close(testing.io);
path_len = try td.dir.realPath(testing.io, &path_buf);
dir_handle = td.dir.handle;
parent_handle = td.parent.handle;
// Should be deleted after we deinit.
td.deinit();
}
switch (builtin.os.tag) {
.freebsd, .ios, .linux, .macos => {
for ([_]Dir.Handle{ dir_handle, parent_handle }) |handle| {
const result = std.posix.system.fcntl(handle, std.posix.F.GETFD);
try testing.expectEqual(std.posix.E.BADF, std.posix.errno(result));
}
},
else => {},
}
try testing.expectError(
error.FileNotFound,
Dir.openDirAbsolute(testing.io, path_buf[0..path_len], .{}),
);
}
test "close retains temporary directory" {
const testing = std.testing;
var path_buf: [std.fs.max_path_bytes]u8 = undefined;
var path_len: usize = undefined;
var dir_handle: Dir.Handle = undefined;
var parent_handle: Dir.Handle = undefined;
{
var td = try init();
errdefer td.deinit();
path_len = try td.dir.realPath(testing.io, &path_buf);
dir_handle = td.dir.handle;
parent_handle = td.parent.handle;
td.close(.retain);
}
defer Dir.deleteDirAbsolute(testing.io, path_buf[0..path_len]) catch {};
switch (builtin.os.tag) {
.freebsd, .ios, .linux, .macos => {
for ([_]Dir.Handle{ dir_handle, parent_handle }) |handle| {
const result = std.posix.system.fcntl(handle, std.posix.F.GETFD);
try testing.expectEqual(std.posix.E.BADF, std.posix.errno(result));
}
},
else => {},
}
var dir = try Dir.openDirAbsolute(testing.io, path_buf[0..path_len], .{});
dir.close(testing.io);
}
|