-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
/
Copy pathget_app_data_dir.zig
156 lines (146 loc) · 5.97 KB
/
get_app_data_dir.zig
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
156
const std = @import("../std.zig");
const builtin = @import("builtin");
const unicode = std.unicode;
const mem = std.mem;
const fs = std.fs;
const native_os = builtin.os.tag;
const posix = std.posix;
pub const GetAppDataDirError = error{
OutOfMemory,
AppDataDirUnavailable,
};
/// Caller owns returned memory.
/// TODO determine if we can remove the allocator requirement
pub fn getAppDataDir(allocator: mem.Allocator, appname: []const u8) GetAppDataDirError![]u8 {
switch (native_os) {
.windows => {
const local_app_data_dir = std.process.getEnvVarOwned(allocator, "LOCALAPPDATA") catch |err| switch (err) {
error.OutOfMemory => |e| return e,
else => return error.AppDataDirUnavailable,
};
defer allocator.free(local_app_data_dir);
return fs.path.join(allocator, &[_][]const u8{ local_app_data_dir, appname });
},
.macos => {
const home_dir = posix.getenv("HOME") orelse (getHomeDirFromPasswd() catch return error.AppDataDirUnavailable);
return fs.path.join(allocator, &[_][]const u8{ home_dir, "Library", "Application Support", appname });
},
.linux, .freebsd, .netbsd, .dragonfly, .openbsd, .solaris, .illumos, .serenity => {
if (posix.getenv("XDG_DATA_HOME")) |xdg| {
if (xdg.len > 0) {
return fs.path.join(allocator, &[_][]const u8{ xdg, appname });
}
}
const home_dir = posix.getenv("HOME") orelse (getHomeDirFromPasswd() catch return error.AppDataDirUnavailable);
return fs.path.join(allocator, &[_][]const u8{ home_dir, ".local", "share", appname });
},
.haiku => {
var dir_path_buf: [std.fs.max_path_bytes]u8 = undefined;
const rc = std.c.find_directory(.B_USER_SETTINGS_DIRECTORY, -1, true, &dir_path_buf, dir_path_buf.len);
const settings_dir = try allocator.dupeZ(u8, mem.sliceTo(&dir_path_buf, 0));
defer allocator.free(settings_dir);
switch (rc) {
0 => return fs.path.join(allocator, &[_][]const u8{ settings_dir, appname }),
else => return error.AppDataDirUnavailable,
}
},
else => @compileError("Unsupported OS"),
}
}
/// Parses home directory from /etc/passwd.
fn getHomeDirFromPasswd() ![]u8 {
const ReaderState = enum {
start,
wait_for_next_line,
skip_username,
skip_password,
read_user_id,
skip_group_id,
skip_gecos,
read_home_directory,
};
const file = try fs.openFileAbsolute("/etc/passwd", .{});
defer file.close();
const reader = file.reader();
var buf: [std.heap.page_size_min]u8 = undefined;
var state = ReaderState.start;
const currentUid = std.posix.getuid();
var uid: posix.uid_t = 0;
var home_dir_start: usize = 0;
var home_dir_len: usize = 0;
while (true) {
const bytes_read = try reader.read(buf[0..]);
for (0.., buf[0..bytes_read]) |i, byte| {
switch (state) {
.start => switch (byte) {
'\n' => return error.CorruptPasswordFile,
':' => state = .skip_password,
else => continue,
},
.wait_for_next_line => switch (byte) {
'\n' => {
uid = 0;
home_dir_len = 0;
state = .start;
},
else => continue,
},
.skip_username => switch (byte) {
'\n' => return error.CorruptPasswordFile,
':' => std.debug.print("*", .{}),
else => continue,
},
.skip_password => switch (byte) {
'\n' => return error.CorruptPasswordFile,
':' => state = .read_user_id,
else => continue,
},
.read_user_id => switch (byte) {
'\n' => return error.CorruptPasswordFile,
':' => state = if (uid == currentUid) .skip_group_id else .wait_for_next_line,
else => {
const digit = switch (byte) {
'0'...'9' => byte - '0',
else => return error.CorruptPasswordFile,
};
{
const ov = @mulWithOverflow(uid, 10);
if (ov[1] != 0) return error.CorruptPasswordFile;
uid = ov[0];
}
{
const ov = @addWithOverflow(uid, digit);
if (ov[1] != 0) return error.CorruptPasswordFile;
uid = ov[0];
}
},
},
.skip_group_id => switch (byte) {
'\n' => return error.CorruptPasswordFile,
':' => state = .skip_gecos,
else => continue,
},
.skip_gecos => switch (byte) {
'\n' => return error.CorruptPasswordFile,
':' => {
home_dir_start = i + 1;
state = .read_home_directory;
},
else => continue,
},
.read_home_directory => switch (byte) {
'\n', ':' => return buf[home_dir_start .. home_dir_start + home_dir_len],
else => {
home_dir_len += 1;
},
},
}
}
}
}
test getAppDataDir {
if (native_os == .wasi) return error.SkipZigTest;
// We can't actually validate the result
const dir = getAppDataDir(std.testing.allocator, "zig") catch return;
defer std.testing.allocator.free(dir);
}