2017-12-23 19:08:53 -08:00
|
|
|
|
const std = @import("../index.zig");
|
2017-05-01 10:12:38 -07:00
|
|
|
|
const builtin = @import("builtin");
|
|
|
|
|
const Os = builtin.Os;
|
2017-10-11 07:16:13 -07:00
|
|
|
|
const is_windows = builtin.os == Os.windows;
|
2017-10-31 01:47:55 -07:00
|
|
|
|
const os = this;
|
2017-10-11 07:16:13 -07:00
|
|
|
|
|
2018-03-07 00:55:52 -08:00
|
|
|
|
test "std.os" {
|
|
|
|
|
_ = @import("child_process.zig");
|
|
|
|
|
_ = @import("darwin.zig");
|
|
|
|
|
_ = @import("darwin_errno.zig");
|
|
|
|
|
_ = @import("get_user_id.zig");
|
|
|
|
|
_ = @import("linux/errno.zig");
|
|
|
|
|
_ = @import("linux/index.zig");
|
|
|
|
|
_ = @import("linux/x86_64.zig");
|
|
|
|
|
_ = @import("path.zig");
|
|
|
|
|
_ = @import("test.zig");
|
|
|
|
|
_ = @import("windows/index.zig");
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-04 07:08:55 -07:00
|
|
|
|
pub const windows = @import("windows/index.zig");
|
2017-03-26 03:39:28 -07:00
|
|
|
|
pub const darwin = @import("darwin.zig");
|
2018-02-05 06:26:39 -08:00
|
|
|
|
pub const linux = @import("linux/index.zig");
|
2018-01-07 01:43:08 -08:00
|
|
|
|
pub const zen = @import("zen.zig");
|
2017-05-01 10:12:38 -07:00
|
|
|
|
pub const posix = switch(builtin.os) {
|
2017-03-26 03:39:28 -07:00
|
|
|
|
Os.linux => linux,
|
2018-01-06 20:10:53 -08:00
|
|
|
|
Os.macosx, Os.ios => darwin,
|
2018-01-07 01:43:08 -08:00
|
|
|
|
Os.zen => zen,
|
2016-09-11 21:01:06 -07:00
|
|
|
|
else => @compileError("Unsupported OS"),
|
|
|
|
|
};
|
2018-03-07 00:55:52 -08:00
|
|
|
|
pub const net = @import("net.zig");
|
2017-04-02 16:14:23 -07:00
|
|
|
|
|
2017-04-03 21:17:24 -07:00
|
|
|
|
pub const ChildProcess = @import("child_process.zig").ChildProcess;
|
2017-04-17 03:45:44 -07:00
|
|
|
|
pub const path = @import("path.zig");
|
2018-02-10 17:55:13 -08:00
|
|
|
|
pub const File = @import("file.zig").File;
|
2017-04-03 01:58:19 -07:00
|
|
|
|
|
2018-02-10 17:55:13 -08:00
|
|
|
|
pub const FileMode = switch (builtin.os) {
|
|
|
|
|
Os.windows => void,
|
|
|
|
|
else => u32,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pub const default_file_mode = switch (builtin.os) {
|
|
|
|
|
Os.windows => {},
|
|
|
|
|
else => 0o666,
|
2017-04-18 22:13:15 -07:00
|
|
|
|
};
|
|
|
|
|
|
2017-04-19 23:26:36 -07:00
|
|
|
|
pub const page_size = 4 * 1024;
|
|
|
|
|
|
2017-09-25 23:42:06 -07:00
|
|
|
|
pub const UserInfo = @import("get_user_id.zig").UserInfo;
|
|
|
|
|
pub const getUserInfo = @import("get_user_id.zig").getUserInfo;
|
2017-09-25 22:01:49 -07:00
|
|
|
|
|
2017-10-14 13:59:43 -07:00
|
|
|
|
const windows_util = @import("windows/util.zig");
|
|
|
|
|
pub const windowsWaitSingle = windows_util.windowsWaitSingle;
|
|
|
|
|
pub const windowsWrite = windows_util.windowsWrite;
|
|
|
|
|
pub const windowsIsCygwinPty = windows_util.windowsIsCygwinPty;
|
|
|
|
|
pub const windowsOpen = windows_util.windowsOpen;
|
2017-11-15 12:46:49 -08:00
|
|
|
|
pub const windowsLoadDll = windows_util.windowsLoadDll;
|
|
|
|
|
pub const windowsUnloadDll = windows_util.windowsUnloadDll;
|
2017-10-14 22:23:10 -07:00
|
|
|
|
pub const createWindowsEnvBlock = windows_util.createWindowsEnvBlock;
|
2017-10-14 13:59:43 -07:00
|
|
|
|
|
2018-02-08 21:24:23 -08:00
|
|
|
|
pub const WindowsWaitError = windows_util.WaitError;
|
2018-02-02 15:13:32 -08:00
|
|
|
|
pub const WindowsOpenError = windows_util.OpenError;
|
|
|
|
|
pub const WindowsWriteError = windows_util.WriteError;
|
|
|
|
|
|
2017-10-31 01:47:55 -07:00
|
|
|
|
pub const FileHandle = if (is_windows) windows.HANDLE else i32;
|
|
|
|
|
|
2017-12-23 19:08:53 -08:00
|
|
|
|
const debug = std.debug;
|
2017-04-02 15:19:59 -07:00
|
|
|
|
const assert = debug.assert;
|
2017-03-26 03:39:28 -07:00
|
|
|
|
|
2017-12-23 19:08:53 -08:00
|
|
|
|
const c = std.c;
|
2016-02-04 00:00:54 -08:00
|
|
|
|
|
2017-12-23 19:08:53 -08:00
|
|
|
|
const mem = std.mem;
|
2017-04-02 15:19:59 -07:00
|
|
|
|
const Allocator = mem.Allocator;
|
|
|
|
|
|
2017-12-23 19:08:53 -08:00
|
|
|
|
const BufMap = std.BufMap;
|
|
|
|
|
const cstr = std.cstr;
|
2017-04-02 15:19:59 -07:00
|
|
|
|
|
2017-12-23 19:08:53 -08:00
|
|
|
|
const io = std.io;
|
|
|
|
|
const base64 = std.base64;
|
|
|
|
|
const ArrayList = std.ArrayList;
|
|
|
|
|
const Buffer = std.Buffer;
|
|
|
|
|
const math = std.math;
|
2017-04-17 03:45:44 -07:00
|
|
|
|
|
2017-03-22 23:59:58 -07:00
|
|
|
|
/// Fills `buf` with random bytes. If linking against libc, this calls the
|
|
|
|
|
/// appropriate OS-specific library call. Otherwise it uses the zig standard
|
|
|
|
|
/// library implementation.
|
2018-01-30 22:51:15 -08:00
|
|
|
|
pub fn getRandomBytes(buf: []u8) !void {
|
2017-08-26 21:11:09 -07:00
|
|
|
|
switch (builtin.os) {
|
|
|
|
|
Os.linux => while (true) {
|
|
|
|
|
// TODO check libc version and potentially call c.getrandom.
|
|
|
|
|
// See #397
|
|
|
|
|
const err = posix.getErrno(posix.getrandom(buf.ptr, buf.len, 0));
|
|
|
|
|
if (err > 0) {
|
2018-02-05 06:26:39 -08:00
|
|
|
|
switch (err) {
|
2017-08-27 02:15:24 -07:00
|
|
|
|
posix.EINVAL => unreachable,
|
|
|
|
|
posix.EFAULT => unreachable,
|
|
|
|
|
posix.EINTR => continue,
|
2018-02-05 06:26:39 -08:00
|
|
|
|
posix.ENOSYS => {
|
|
|
|
|
const fd = try posixOpenC(c"/dev/urandom", posix.O_RDONLY|posix.O_CLOEXEC, 0);
|
|
|
|
|
defer close(fd);
|
|
|
|
|
|
|
|
|
|
try posixRead(fd, buf);
|
|
|
|
|
return;
|
|
|
|
|
},
|
|
|
|
|
else => return unexpectedErrorPosix(err),
|
|
|
|
|
}
|
2017-08-26 21:11:09 -07:00
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
},
|
2018-01-06 20:10:53 -08:00
|
|
|
|
Os.macosx, Os.ios => {
|
2018-02-05 06:26:39 -08:00
|
|
|
|
const fd = try posixOpenC(c"/dev/urandom", posix.O_RDONLY|posix.O_CLOEXEC, 0);
|
2017-10-31 01:47:55 -07:00
|
|
|
|
defer close(fd);
|
2017-03-22 23:59:58 -07:00
|
|
|
|
|
2018-01-07 13:51:46 -08:00
|
|
|
|
try posixRead(fd, buf);
|
2017-08-26 21:11:09 -07:00
|
|
|
|
},
|
|
|
|
|
Os.windows => {
|
|
|
|
|
var hCryptProv: windows.HCRYPTPROV = undefined;
|
2017-10-15 11:01:55 -07:00
|
|
|
|
if (windows.CryptAcquireContextA(&hCryptProv, null, null, windows.PROV_RSA_FULL, 0) == 0) {
|
2017-10-15 13:45:43 -07:00
|
|
|
|
const err = windows.GetLastError();
|
|
|
|
|
return switch (err) {
|
|
|
|
|
else => unexpectedErrorWindows(err),
|
|
|
|
|
};
|
2016-02-13 21:59:49 -08:00
|
|
|
|
}
|
2017-08-26 21:11:09 -07:00
|
|
|
|
defer _ = windows.CryptReleaseContext(hCryptProv, 0);
|
|
|
|
|
|
2017-10-15 11:01:55 -07:00
|
|
|
|
if (windows.CryptGenRandom(hCryptProv, windows.DWORD(buf.len), buf.ptr) == 0) {
|
2017-10-15 13:45:43 -07:00
|
|
|
|
const err = windows.GetLastError();
|
|
|
|
|
return switch (err) {
|
|
|
|
|
else => unexpectedErrorWindows(err),
|
|
|
|
|
};
|
2017-08-26 21:11:09 -07:00
|
|
|
|
}
|
|
|
|
|
},
|
2018-03-20 13:09:30 -07:00
|
|
|
|
Os.zen => {
|
|
|
|
|
const randomness = []u8 {42, 1, 7, 12, 22, 17, 99, 16, 26, 87, 41, 45};
|
|
|
|
|
var i: usize = 0;
|
|
|
|
|
while (i < buf.len) : (i += 1) {
|
|
|
|
|
if (i > randomness.len) return error.Unknown;
|
|
|
|
|
buf[i] = randomness[i];
|
|
|
|
|
}
|
|
|
|
|
},
|
2017-08-26 21:11:09 -07:00
|
|
|
|
else => @compileError("Unsupported OS"),
|
2016-02-04 00:00:54 -08:00
|
|
|
|
}
|
|
|
|
|
}
|
2016-04-18 16:42:56 -07:00
|
|
|
|
|
2017-10-11 20:14:48 -07:00
|
|
|
|
test "os.getRandomBytes" {
|
|
|
|
|
var buf: [50]u8 = undefined;
|
2018-01-08 21:07:01 -08:00
|
|
|
|
try getRandomBytes(buf[0..]);
|
2017-10-11 20:14:48 -07:00
|
|
|
|
}
|
|
|
|
|
|
2017-03-22 23:59:58 -07:00
|
|
|
|
/// Raises a signal in the current kernel thread, ending its execution.
|
|
|
|
|
/// If linking against libc, this calls the abort() libc function. Otherwise
|
|
|
|
|
/// it uses the zig standard library implementation.
|
2018-01-25 01:10:11 -08:00
|
|
|
|
pub fn abort() noreturn {
|
2018-01-22 19:24:07 -08:00
|
|
|
|
@setCold(true);
|
2017-06-13 21:04:34 -07:00
|
|
|
|
if (builtin.link_libc) {
|
2017-03-22 23:59:58 -07:00
|
|
|
|
c.abort();
|
|
|
|
|
}
|
2017-05-01 10:12:38 -07:00
|
|
|
|
switch (builtin.os) {
|
2018-01-06 20:10:53 -08:00
|
|
|
|
Os.linux, Os.macosx, Os.ios => {
|
2017-03-22 23:59:58 -07:00
|
|
|
|
_ = posix.raise(posix.SIGABRT);
|
|
|
|
|
_ = posix.raise(posix.SIGKILL);
|
2016-08-17 20:11:04 -07:00
|
|
|
|
while (true) {}
|
|
|
|
|
},
|
2017-06-04 07:08:55 -07:00
|
|
|
|
Os.windows => {
|
2017-10-10 17:34:12 -07:00
|
|
|
|
if (builtin.mode == builtin.Mode.Debug) {
|
|
|
|
|
@breakpoint();
|
|
|
|
|
}
|
|
|
|
|
windows.ExitProcess(3);
|
2017-06-04 07:08:55 -07:00
|
|
|
|
},
|
2017-03-22 23:59:58 -07:00
|
|
|
|
else => @compileError("Unsupported OS"),
|
2016-08-17 20:11:04 -07:00
|
|
|
|
}
|
2016-04-18 16:42:56 -07:00
|
|
|
|
}
|
2017-04-02 15:19:59 -07:00
|
|
|
|
|
2017-09-09 10:48:44 -07:00
|
|
|
|
/// Exits the program cleanly with the specified status code.
|
2018-01-25 01:10:11 -08:00
|
|
|
|
pub fn exit(status: u8) noreturn {
|
2018-01-22 19:24:07 -08:00
|
|
|
|
@setCold(true);
|
2017-09-09 10:48:44 -07:00
|
|
|
|
if (builtin.link_libc) {
|
|
|
|
|
c.exit(status);
|
|
|
|
|
}
|
|
|
|
|
switch (builtin.os) {
|
2018-01-06 20:10:53 -08:00
|
|
|
|
Os.linux, Os.macosx, Os.ios => {
|
2017-12-21 21:50:30 -08:00
|
|
|
|
posix.exit(status);
|
2017-09-09 10:48:44 -07:00
|
|
|
|
},
|
|
|
|
|
Os.windows => {
|
2018-01-14 21:01:02 -08:00
|
|
|
|
windows.ExitProcess(status);
|
2017-09-09 10:48:44 -07:00
|
|
|
|
},
|
|
|
|
|
else => @compileError("Unsupported OS"),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-07 00:55:52 -08:00
|
|
|
|
/// When a file descriptor is closed on linux, it pops the first
|
|
|
|
|
/// node from this queue and resumes it.
|
|
|
|
|
/// Async functions which get the EMFILE error code can suspend,
|
|
|
|
|
/// putting their coroutine handle into this list.
|
|
|
|
|
/// TODO make this an atomic linked list
|
|
|
|
|
pub var emfile_promise_queue = std.LinkedList(promise).init();
|
|
|
|
|
|
2017-10-31 01:47:55 -07:00
|
|
|
|
/// Closes the file handle. Keeps trying if it gets interrupted by a signal.
|
2018-01-25 01:10:11 -08:00
|
|
|
|
pub fn close(handle: FileHandle) void {
|
2017-10-31 01:47:55 -07:00
|
|
|
|
if (is_windows) {
|
|
|
|
|
windows_util.windowsClose(handle);
|
|
|
|
|
} else {
|
|
|
|
|
while (true) {
|
|
|
|
|
const err = posix.getErrno(posix.close(handle));
|
2018-03-07 00:55:52 -08:00
|
|
|
|
switch (err) {
|
|
|
|
|
posix.EINTR => continue,
|
|
|
|
|
else => {
|
|
|
|
|
if (emfile_promise_queue.popFirst()) |p| resume p.data;
|
|
|
|
|
return;
|
|
|
|
|
},
|
2017-10-31 01:47:55 -07:00
|
|
|
|
}
|
2017-04-02 15:19:59 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-26 21:11:09 -07:00
|
|
|
|
/// Calls POSIX read, and keeps trying if it gets interrupted.
|
2018-01-30 22:51:15 -08:00
|
|
|
|
pub fn posixRead(fd: i32, buf: []u8) !void {
|
2018-02-05 09:47:59 -08:00
|
|
|
|
// Linux can return EINVAL when read amount is > 0x7ffff000
|
|
|
|
|
// See https://github.com/zig-lang/zig/pull/743#issuecomment-363158274
|
|
|
|
|
const max_buf_len = 0x7ffff000;
|
|
|
|
|
|
2017-08-26 21:11:09 -07:00
|
|
|
|
var index: usize = 0;
|
|
|
|
|
while (index < buf.len) {
|
2018-02-05 09:47:59 -08:00
|
|
|
|
const want_to_read = math.min(buf.len - index, usize(max_buf_len));
|
|
|
|
|
const rc = posix.read(fd, &buf[index], want_to_read);
|
|
|
|
|
const err = posix.getErrno(rc);
|
2017-08-26 21:11:09 -07:00
|
|
|
|
if (err > 0) {
|
|
|
|
|
return switch (err) {
|
2017-08-27 02:15:24 -07:00
|
|
|
|
posix.EINTR => continue,
|
|
|
|
|
posix.EINVAL, posix.EFAULT => unreachable,
|
|
|
|
|
posix.EAGAIN => error.WouldBlock,
|
|
|
|
|
posix.EBADF => error.FileClosed,
|
|
|
|
|
posix.EIO => error.InputOutput,
|
|
|
|
|
posix.EISDIR => error.IsDir,
|
|
|
|
|
posix.ENOBUFS, posix.ENOMEM => error.SystemResources,
|
2017-10-15 13:45:43 -07:00
|
|
|
|
else => unexpectedErrorPosix(err),
|
|
|
|
|
};
|
2017-08-26 21:11:09 -07:00
|
|
|
|
}
|
2018-02-05 09:47:59 -08:00
|
|
|
|
index += rc;
|
2017-08-26 21:11:09 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-02 15:13:32 -08:00
|
|
|
|
pub const PosixWriteError = error {
|
|
|
|
|
WouldBlock,
|
|
|
|
|
FileClosed,
|
|
|
|
|
DestinationAddressRequired,
|
|
|
|
|
DiskQuota,
|
|
|
|
|
FileTooBig,
|
|
|
|
|
InputOutput,
|
|
|
|
|
NoSpaceLeft,
|
|
|
|
|
AccessDenied,
|
|
|
|
|
BrokenPipe,
|
|
|
|
|
Unexpected,
|
|
|
|
|
};
|
|
|
|
|
|
2017-04-03 01:58:19 -07:00
|
|
|
|
/// Calls POSIX write, and keeps trying if it gets interrupted.
|
2018-02-08 17:45:26 -08:00
|
|
|
|
pub fn posixWrite(fd: i32, bytes: []const u8) !void {
|
2018-02-05 10:21:08 -08:00
|
|
|
|
// Linux can return EINVAL when write amount is > 0x7ffff000
|
|
|
|
|
// See https://github.com/zig-lang/zig/pull/743#issuecomment-363165856
|
|
|
|
|
const max_bytes_len = 0x7ffff000;
|
|
|
|
|
|
|
|
|
|
var index: usize = 0;
|
|
|
|
|
while (index < bytes.len) {
|
|
|
|
|
const amt_to_write = math.min(bytes.len - index, usize(max_bytes_len));
|
|
|
|
|
const rc = posix.write(fd, &bytes[index], amt_to_write);
|
|
|
|
|
const write_err = posix.getErrno(rc);
|
2017-04-03 01:58:19 -07:00
|
|
|
|
if (write_err > 0) {
|
|
|
|
|
return switch (write_err) {
|
2017-08-27 02:15:24 -07:00
|
|
|
|
posix.EINTR => continue,
|
|
|
|
|
posix.EINVAL, posix.EFAULT => unreachable,
|
2018-02-02 15:13:32 -08:00
|
|
|
|
posix.EAGAIN => PosixWriteError.WouldBlock,
|
|
|
|
|
posix.EBADF => PosixWriteError.FileClosed,
|
|
|
|
|
posix.EDESTADDRREQ => PosixWriteError.DestinationAddressRequired,
|
|
|
|
|
posix.EDQUOT => PosixWriteError.DiskQuota,
|
|
|
|
|
posix.EFBIG => PosixWriteError.FileTooBig,
|
|
|
|
|
posix.EIO => PosixWriteError.InputOutput,
|
|
|
|
|
posix.ENOSPC => PosixWriteError.NoSpaceLeft,
|
|
|
|
|
posix.EPERM => PosixWriteError.AccessDenied,
|
|
|
|
|
posix.EPIPE => PosixWriteError.BrokenPipe,
|
2017-10-15 13:45:43 -07:00
|
|
|
|
else => unexpectedErrorPosix(write_err),
|
|
|
|
|
};
|
2017-04-03 01:58:19 -07:00
|
|
|
|
}
|
2018-02-05 10:21:08 -08:00
|
|
|
|
index += rc;
|
2017-04-03 01:58:19 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-02 15:13:32 -08:00
|
|
|
|
pub const PosixOpenError = error {
|
|
|
|
|
OutOfMemory,
|
|
|
|
|
AccessDenied,
|
|
|
|
|
FileTooBig,
|
|
|
|
|
IsDir,
|
|
|
|
|
SymLinkLoop,
|
|
|
|
|
ProcessFdQuotaExceeded,
|
|
|
|
|
NameTooLong,
|
|
|
|
|
SystemFdQuotaExceeded,
|
|
|
|
|
NoDevice,
|
|
|
|
|
PathNotFound,
|
|
|
|
|
SystemResources,
|
|
|
|
|
NoSpaceLeft,
|
|
|
|
|
NotDir,
|
|
|
|
|
PathAlreadyExists,
|
|
|
|
|
Unexpected,
|
|
|
|
|
};
|
|
|
|
|
|
2018-02-09 15:27:50 -08:00
|
|
|
|
/// ::file_path needs to be copied in memory to add a null terminating byte.
|
2017-04-03 01:58:19 -07:00
|
|
|
|
/// Calls POSIX open, keeps trying if it gets interrupted, and translates
|
|
|
|
|
/// the return value into zig errors.
|
2018-02-09 15:27:50 -08:00
|
|
|
|
pub fn posixOpen(allocator: &Allocator, file_path: []const u8, flags: u32, perm: usize) PosixOpenError!i32 {
|
|
|
|
|
const path_with_null = try cstr.addNullByte(allocator, file_path);
|
|
|
|
|
defer allocator.free(path_with_null);
|
2017-04-03 01:58:19 -07:00
|
|
|
|
|
2018-02-09 15:27:50 -08:00
|
|
|
|
return posixOpenC(path_with_null.ptr, flags, perm);
|
2018-02-05 06:26:39 -08:00
|
|
|
|
}
|
|
|
|
|
|
2018-02-08 17:45:26 -08:00
|
|
|
|
pub fn posixOpenC(file_path: &const u8, flags: u32, perm: usize) !i32 {
|
2017-04-02 15:19:59 -07:00
|
|
|
|
while (true) {
|
2018-02-05 06:26:39 -08:00
|
|
|
|
const result = posix.open(file_path, flags, perm);
|
2017-04-02 15:19:59 -07:00
|
|
|
|
const err = posix.getErrno(result);
|
|
|
|
|
if (err > 0) {
|
2018-02-11 02:26:51 -08:00
|
|
|
|
switch (err) {
|
2017-08-27 02:15:24 -07:00
|
|
|
|
posix.EINTR => continue,
|
|
|
|
|
|
|
|
|
|
posix.EFAULT => unreachable,
|
|
|
|
|
posix.EINVAL => unreachable,
|
2018-02-11 02:26:51 -08:00
|
|
|
|
posix.EACCES => return PosixOpenError.AccessDenied,
|
|
|
|
|
posix.EFBIG, posix.EOVERFLOW => return PosixOpenError.FileTooBig,
|
|
|
|
|
posix.EISDIR => return PosixOpenError.IsDir,
|
|
|
|
|
posix.ELOOP => return PosixOpenError.SymLinkLoop,
|
|
|
|
|
posix.EMFILE => return PosixOpenError.ProcessFdQuotaExceeded,
|
|
|
|
|
posix.ENAMETOOLONG => return PosixOpenError.NameTooLong,
|
|
|
|
|
posix.ENFILE => return PosixOpenError.SystemFdQuotaExceeded,
|
|
|
|
|
posix.ENODEV => return PosixOpenError.NoDevice,
|
|
|
|
|
posix.ENOENT => return PosixOpenError.PathNotFound,
|
|
|
|
|
posix.ENOMEM => return PosixOpenError.SystemResources,
|
|
|
|
|
posix.ENOSPC => return PosixOpenError.NoSpaceLeft,
|
|
|
|
|
posix.ENOTDIR => return PosixOpenError.NotDir,
|
|
|
|
|
posix.EPERM => return PosixOpenError.AccessDenied,
|
|
|
|
|
posix.EEXIST => return PosixOpenError.PathAlreadyExists,
|
|
|
|
|
else => return unexpectedErrorPosix(err),
|
|
|
|
|
}
|
2017-04-02 15:19:59 -07:00
|
|
|
|
}
|
|
|
|
|
return i32(result);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-30 22:51:15 -08:00
|
|
|
|
pub fn posixDup2(old_fd: i32, new_fd: i32) !void {
|
2017-04-02 15:19:59 -07:00
|
|
|
|
while (true) {
|
|
|
|
|
const err = posix.getErrno(posix.dup2(old_fd, new_fd));
|
|
|
|
|
if (err > 0) {
|
|
|
|
|
return switch (err) {
|
2017-08-27 02:15:24 -07:00
|
|
|
|
posix.EBUSY, posix.EINTR => continue,
|
|
|
|
|
posix.EMFILE => error.ProcessFdQuotaExceeded,
|
|
|
|
|
posix.EINVAL => unreachable,
|
2017-10-15 13:45:43 -07:00
|
|
|
|
else => unexpectedErrorPosix(err),
|
2017-04-02 15:19:59 -07:00
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-30 22:51:15 -08:00
|
|
|
|
pub fn createNullDelimitedEnvMap(allocator: &Allocator, env_map: &const BufMap) ![]?&u8 {
|
2017-10-13 06:31:03 -07:00
|
|
|
|
const envp_count = env_map.count();
|
2018-01-07 13:51:46 -08:00
|
|
|
|
const envp_buf = try allocator.alloc(?&u8, envp_count + 1);
|
2017-10-13 06:31:03 -07:00
|
|
|
|
mem.set(?&u8, envp_buf, null);
|
2018-01-23 20:08:09 -08:00
|
|
|
|
errdefer freeNullDelimitedEnvMap(allocator, envp_buf);
|
2017-10-13 06:31:03 -07:00
|
|
|
|
{
|
|
|
|
|
var it = env_map.iterator();
|
|
|
|
|
var i: usize = 0;
|
|
|
|
|
while (it.next()) |pair| : (i += 1) {
|
2018-01-07 13:51:46 -08:00
|
|
|
|
const env_buf = try allocator.alloc(u8, pair.key.len + pair.value.len + 2);
|
2017-10-13 06:31:03 -07:00
|
|
|
|
@memcpy(&env_buf[0], pair.key.ptr, pair.key.len);
|
|
|
|
|
env_buf[pair.key.len] = '=';
|
|
|
|
|
@memcpy(&env_buf[pair.key.len + 1], pair.value.ptr, pair.value.len);
|
|
|
|
|
env_buf[env_buf.len - 1] = 0;
|
|
|
|
|
|
|
|
|
|
envp_buf[i] = env_buf.ptr;
|
|
|
|
|
}
|
|
|
|
|
assert(i == envp_count);
|
|
|
|
|
}
|
|
|
|
|
assert(envp_buf[envp_count] == null);
|
|
|
|
|
return envp_buf;
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-25 01:10:11 -08:00
|
|
|
|
pub fn freeNullDelimitedEnvMap(allocator: &Allocator, envp_buf: []?&u8) void {
|
2017-10-13 06:31:03 -07:00
|
|
|
|
for (envp_buf) |env| {
|
2017-10-14 14:39:44 -07:00
|
|
|
|
const env_buf = if (env) |ptr| ptr[0 .. cstr.len(ptr) + 1] else break;
|
2017-10-13 06:31:03 -07:00
|
|
|
|
allocator.free(env_buf);
|
|
|
|
|
}
|
|
|
|
|
allocator.free(envp_buf);
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-02 17:44:04 -07:00
|
|
|
|
/// This function must allocate memory to add a null terminating bytes on path and each arg.
|
|
|
|
|
/// It must also convert to KEY=VALUE\0 format for environment variables, and include null
|
|
|
|
|
/// pointers after the args and after the environment variables.
|
2017-09-25 22:01:49 -07:00
|
|
|
|
/// `argv[0]` is the executable path.
|
2017-04-05 14:55:50 -07:00
|
|
|
|
/// This function also uses the PATH environment variable to get the full path to the executable.
|
2017-09-25 22:01:49 -07:00
|
|
|
|
pub fn posixExecve(argv: []const []const u8, env_map: &const BufMap,
|
2018-01-30 22:51:15 -08:00
|
|
|
|
allocator: &Allocator) !void
|
2017-04-03 21:17:24 -07:00
|
|
|
|
{
|
2018-01-07 13:51:46 -08:00
|
|
|
|
const argv_buf = try allocator.alloc(?&u8, argv.len + 1);
|
2017-04-20 22:56:12 -07:00
|
|
|
|
mem.set(?&u8, argv_buf, null);
|
2017-04-02 17:44:04 -07:00
|
|
|
|
defer {
|
2017-04-04 03:47:42 -07:00
|
|
|
|
for (argv_buf) |arg| {
|
2017-05-03 14:23:11 -07:00
|
|
|
|
const arg_buf = if (arg) |ptr| cstr.toSlice(ptr) else break;
|
2017-04-02 17:44:04 -07:00
|
|
|
|
allocator.free(arg_buf);
|
|
|
|
|
}
|
|
|
|
|
allocator.free(argv_buf);
|
|
|
|
|
}
|
|
|
|
|
for (argv) |arg, i| {
|
2018-01-07 13:51:46 -08:00
|
|
|
|
const arg_buf = try allocator.alloc(u8, arg.len + 1);
|
2017-04-02 17:44:04 -07:00
|
|
|
|
@memcpy(&arg_buf[0], arg.ptr, arg.len);
|
|
|
|
|
arg_buf[arg.len] = 0;
|
|
|
|
|
|
2017-09-25 22:01:49 -07:00
|
|
|
|
argv_buf[i] = arg_buf.ptr;
|
2017-04-02 17:44:04 -07:00
|
|
|
|
}
|
2017-09-25 22:01:49 -07:00
|
|
|
|
argv_buf[argv.len] = null;
|
2017-04-02 17:44:04 -07:00
|
|
|
|
|
2018-01-07 13:51:46 -08:00
|
|
|
|
const envp_buf = try createNullDelimitedEnvMap(allocator, env_map);
|
2017-10-13 06:31:03 -07:00
|
|
|
|
defer freeNullDelimitedEnvMap(allocator, envp_buf);
|
2017-04-02 17:44:04 -07:00
|
|
|
|
|
2017-09-25 22:01:49 -07:00
|
|
|
|
const exe_path = argv[0];
|
2017-04-05 14:55:50 -07:00
|
|
|
|
if (mem.indexOfScalar(u8, exe_path, '/') != null) {
|
2017-09-25 22:01:49 -07:00
|
|
|
|
return posixExecveErrnoToErr(posix.getErrno(posix.execve(??argv_buf[0], argv_buf.ptr, envp_buf.ptr)));
|
2017-04-05 14:55:50 -07:00
|
|
|
|
}
|
|
|
|
|
|
2017-10-14 22:23:10 -07:00
|
|
|
|
const PATH = getEnvPosix("PATH") ?? "/usr/local/bin:/bin/:/usr/bin";
|
2017-04-05 14:55:50 -07:00
|
|
|
|
// PATH.len because it is >= the largest search_path
|
|
|
|
|
// +1 for the / to join the search path and exe_path
|
|
|
|
|
// +1 for the null terminating byte
|
2018-01-07 13:51:46 -08:00
|
|
|
|
const path_buf = try allocator.alloc(u8, PATH.len + exe_path.len + 2);
|
2017-04-05 14:55:50 -07:00
|
|
|
|
defer allocator.free(path_buf);
|
2017-10-06 14:21:21 -07:00
|
|
|
|
var it = mem.split(PATH, ":");
|
2017-04-05 14:55:50 -07:00
|
|
|
|
var seen_eacces = false;
|
|
|
|
|
var err: usize = undefined;
|
2017-05-04 07:37:19 -07:00
|
|
|
|
while (it.next()) |search_path| {
|
2017-04-05 14:55:50 -07:00
|
|
|
|
mem.copy(u8, path_buf, search_path);
|
|
|
|
|
path_buf[search_path.len] = '/';
|
2017-05-19 07:39:59 -07:00
|
|
|
|
mem.copy(u8, path_buf[search_path.len + 1 ..], exe_path);
|
2017-04-21 10:12:30 -07:00
|
|
|
|
path_buf[search_path.len + exe_path.len + 1] = 0;
|
2017-04-05 14:55:50 -07:00
|
|
|
|
err = posix.getErrno(posix.execve(path_buf.ptr, argv_buf.ptr, envp_buf.ptr));
|
|
|
|
|
assert(err > 0);
|
2017-08-27 02:15:24 -07:00
|
|
|
|
if (err == posix.EACCES) {
|
2017-04-05 14:55:50 -07:00
|
|
|
|
seen_eacces = true;
|
2017-08-27 02:15:24 -07:00
|
|
|
|
} else if (err != posix.ENOENT) {
|
2017-04-05 14:55:50 -07:00
|
|
|
|
return posixExecveErrnoToErr(err);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (seen_eacces) {
|
2017-08-27 02:15:24 -07:00
|
|
|
|
err = posix.EACCES;
|
2017-04-05 14:55:50 -07:00
|
|
|
|
}
|
|
|
|
|
return posixExecveErrnoToErr(err);
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-07 23:08:45 -08:00
|
|
|
|
pub const PosixExecveError = error {
|
|
|
|
|
SystemResources,
|
|
|
|
|
AccessDenied,
|
|
|
|
|
InvalidExe,
|
|
|
|
|
FileSystem,
|
|
|
|
|
IsDir,
|
|
|
|
|
FileNotFound,
|
|
|
|
|
NotDir,
|
|
|
|
|
FileBusy,
|
|
|
|
|
Unexpected,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
fn posixExecveErrnoToErr(err: usize) PosixExecveError {
|
2017-04-05 14:55:50 -07:00
|
|
|
|
assert(err > 0);
|
|
|
|
|
return switch (err) {
|
2017-08-27 02:15:24 -07:00
|
|
|
|
posix.EFAULT => unreachable,
|
|
|
|
|
posix.E2BIG, posix.EMFILE, posix.ENAMETOOLONG, posix.ENFILE, posix.ENOMEM => error.SystemResources,
|
|
|
|
|
posix.EACCES, posix.EPERM => error.AccessDenied,
|
|
|
|
|
posix.EINVAL, posix.ENOEXEC => error.InvalidExe,
|
|
|
|
|
posix.EIO, posix.ELOOP => error.FileSystem,
|
|
|
|
|
posix.EISDIR => error.IsDir,
|
|
|
|
|
posix.ENOENT => error.FileNotFound,
|
|
|
|
|
posix.ENOTDIR => error.NotDir,
|
|
|
|
|
posix.ETXTBSY => error.FileBusy,
|
2017-10-15 13:45:43 -07:00
|
|
|
|
else => unexpectedErrorPosix(err),
|
2017-04-05 14:55:50 -07:00
|
|
|
|
};
|
2017-04-02 17:44:04 -07:00
|
|
|
|
}
|
|
|
|
|
|
2017-10-14 22:23:10 -07:00
|
|
|
|
pub var posix_environ_raw: []&u8 = undefined;
|
2017-04-03 15:11:57 -07:00
|
|
|
|
|
2017-09-25 22:01:49 -07:00
|
|
|
|
/// Caller must free result when done.
|
2018-01-30 22:51:15 -08:00
|
|
|
|
pub fn getEnvMap(allocator: &Allocator) !BufMap {
|
2017-04-03 22:52:20 -07:00
|
|
|
|
var result = BufMap.init(allocator);
|
2018-01-23 20:08:09 -08:00
|
|
|
|
errdefer result.deinit();
|
2017-04-03 15:11:57 -07:00
|
|
|
|
|
2017-10-14 22:23:10 -07:00
|
|
|
|
if (is_windows) {
|
|
|
|
|
const ptr = windows.GetEnvironmentStringsA() ?? return error.OutOfMemory;
|
2017-10-15 11:01:55 -07:00
|
|
|
|
defer assert(windows.FreeEnvironmentStringsA(ptr) != 0);
|
2017-04-03 15:11:57 -07:00
|
|
|
|
|
2017-10-14 22:23:10 -07:00
|
|
|
|
var i: usize = 0;
|
|
|
|
|
while (true) {
|
|
|
|
|
if (ptr[i] == 0)
|
|
|
|
|
return result;
|
|
|
|
|
|
|
|
|
|
const key_start = i;
|
|
|
|
|
|
|
|
|
|
while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {}
|
|
|
|
|
const key = ptr[key_start..i];
|
|
|
|
|
|
|
|
|
|
if (ptr[i] == '=') i += 1;
|
2017-04-03 15:11:57 -07:00
|
|
|
|
|
2017-10-14 22:23:10 -07:00
|
|
|
|
const value_start = i;
|
|
|
|
|
while (ptr[i] != 0) : (i += 1) {}
|
|
|
|
|
const value = ptr[value_start..i];
|
|
|
|
|
|
|
|
|
|
i += 1; // skip over null byte
|
|
|
|
|
|
2018-01-07 13:51:46 -08:00
|
|
|
|
try result.set(key, value);
|
2017-10-14 22:23:10 -07:00
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
for (posix_environ_raw) |ptr| {
|
|
|
|
|
var line_i: usize = 0;
|
|
|
|
|
while (ptr[line_i] != 0 and ptr[line_i] != '=') : (line_i += 1) {}
|
|
|
|
|
const key = ptr[0..line_i];
|
|
|
|
|
|
|
|
|
|
var end_i: usize = line_i;
|
|
|
|
|
while (ptr[end_i] != 0) : (end_i += 1) {}
|
|
|
|
|
const value = ptr[line_i + 1..end_i];
|
|
|
|
|
|
2018-01-07 13:51:46 -08:00
|
|
|
|
try result.set(key, value);
|
2017-10-14 22:23:10 -07:00
|
|
|
|
}
|
|
|
|
|
return result;
|
2017-04-03 15:11:57 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
2017-04-02 16:14:23 -07:00
|
|
|
|
|
2018-01-25 01:10:11 -08:00
|
|
|
|
pub fn getEnvPosix(key: []const u8) ?[]const u8 {
|
2017-10-14 22:23:10 -07:00
|
|
|
|
for (posix_environ_raw) |ptr| {
|
2017-04-03 15:11:57 -07:00
|
|
|
|
var line_i: usize = 0;
|
2017-05-03 15:12:07 -07:00
|
|
|
|
while (ptr[line_i] != 0 and ptr[line_i] != '=') : (line_i += 1) {}
|
2017-05-19 07:39:59 -07:00
|
|
|
|
const this_key = ptr[0..line_i];
|
2017-04-03 15:11:57 -07:00
|
|
|
|
if (!mem.eql(u8, key, this_key))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
var end_i: usize = line_i;
|
2017-05-03 15:12:07 -07:00
|
|
|
|
while (ptr[end_i] != 0) : (end_i += 1) {}
|
2017-05-19 07:39:59 -07:00
|
|
|
|
const this_value = ptr[line_i + 1..end_i];
|
2017-04-03 15:11:57 -07:00
|
|
|
|
|
|
|
|
|
return this_value;
|
2017-04-02 16:14:23 -07:00
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2017-04-03 15:11:57 -07:00
|
|
|
|
|
2017-10-14 22:23:10 -07:00
|
|
|
|
/// Caller must free returned memory.
|
2018-01-30 22:51:15 -08:00
|
|
|
|
pub fn getEnvVarOwned(allocator: &mem.Allocator, key: []const u8) ![]u8 {
|
2017-10-14 22:23:10 -07:00
|
|
|
|
if (is_windows) {
|
2018-01-07 13:51:46 -08:00
|
|
|
|
const key_with_null = try cstr.addNullByte(allocator, key);
|
2017-10-14 22:23:10 -07:00
|
|
|
|
defer allocator.free(key_with_null);
|
|
|
|
|
|
2018-01-07 13:51:46 -08:00
|
|
|
|
var buf = try allocator.alloc(u8, 256);
|
2018-01-23 20:08:09 -08:00
|
|
|
|
errdefer allocator.free(buf);
|
2017-10-14 22:23:10 -07:00
|
|
|
|
|
|
|
|
|
while (true) {
|
2018-01-07 13:51:46 -08:00
|
|
|
|
const windows_buf_len = try math.cast(windows.DWORD, buf.len);
|
2017-10-14 22:23:10 -07:00
|
|
|
|
const result = windows.GetEnvironmentVariableA(key_with_null.ptr, buf.ptr, windows_buf_len);
|
|
|
|
|
|
|
|
|
|
if (result == 0) {
|
|
|
|
|
const err = windows.GetLastError();
|
|
|
|
|
return switch (err) {
|
|
|
|
|
windows.ERROR.ENVVAR_NOT_FOUND => error.EnvironmentVariableNotFound,
|
2017-10-15 13:45:43 -07:00
|
|
|
|
else => unexpectedErrorWindows(err),
|
2017-10-14 22:23:10 -07:00
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (result > buf.len) {
|
2018-01-07 13:51:46 -08:00
|
|
|
|
buf = try allocator.realloc(u8, buf, result);
|
2017-10-14 22:23:10 -07:00
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return allocator.shrink(u8, buf, result);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
const result = getEnvPosix(key) ?? return error.EnvironmentVariableNotFound;
|
2017-10-14 22:24:39 -07:00
|
|
|
|
return mem.dupe(allocator, u8, result);
|
2017-10-14 22:23:10 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-16 11:14:11 -07:00
|
|
|
|
/// Caller must free the returned memory.
|
2018-01-30 22:51:15 -08:00
|
|
|
|
pub fn getCwd(allocator: &Allocator) ![]u8 {
|
2017-10-05 21:27:15 -07:00
|
|
|
|
switch (builtin.os) {
|
|
|
|
|
Os.windows => {
|
2018-01-07 13:51:46 -08:00
|
|
|
|
var buf = try allocator.alloc(u8, 256);
|
2018-01-23 20:08:09 -08:00
|
|
|
|
errdefer allocator.free(buf);
|
2017-10-08 18:40:33 -07:00
|
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
const result = windows.GetCurrentDirectoryA(windows.WORD(buf.len), buf.ptr);
|
|
|
|
|
|
|
|
|
|
if (result == 0) {
|
2017-10-15 13:45:43 -07:00
|
|
|
|
const err = windows.GetLastError();
|
|
|
|
|
return switch (err) {
|
|
|
|
|
else => unexpectedErrorWindows(err),
|
|
|
|
|
};
|
2017-10-08 18:40:33 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (result > buf.len) {
|
2018-01-07 13:51:46 -08:00
|
|
|
|
buf = try allocator.realloc(u8, buf, result);
|
2017-10-08 18:40:33 -07:00
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-09 11:21:35 -07:00
|
|
|
|
return allocator.shrink(u8, buf, result);
|
2017-10-08 18:40:33 -07:00
|
|
|
|
}
|
2017-10-05 21:27:15 -07:00
|
|
|
|
},
|
|
|
|
|
else => {
|
2018-01-07 13:51:46 -08:00
|
|
|
|
var buf = try allocator.alloc(u8, 1024);
|
2018-01-23 20:08:09 -08:00
|
|
|
|
errdefer allocator.free(buf);
|
2017-10-05 21:27:15 -07:00
|
|
|
|
while (true) {
|
|
|
|
|
const err = posix.getErrno(posix.getcwd(buf.ptr, buf.len));
|
|
|
|
|
if (err == posix.ERANGE) {
|
2018-01-07 13:51:46 -08:00
|
|
|
|
buf = try allocator.realloc(u8, buf, buf.len * 2);
|
2017-10-05 21:27:15 -07:00
|
|
|
|
continue;
|
|
|
|
|
} else if (err > 0) {
|
2017-10-15 13:45:43 -07:00
|
|
|
|
return unexpectedErrorPosix(err);
|
2017-10-05 21:27:15 -07:00
|
|
|
|
}
|
2017-04-16 11:14:11 -07:00
|
|
|
|
|
2017-10-09 11:21:35 -07:00
|
|
|
|
return allocator.shrink(u8, buf, cstr.len(buf.ptr));
|
2017-10-05 21:27:15 -07:00
|
|
|
|
}
|
|
|
|
|
},
|
2017-04-16 11:14:11 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
2017-04-16 23:58:42 -07:00
|
|
|
|
|
2017-10-09 11:21:35 -07:00
|
|
|
|
test "os.getCwd" {
|
|
|
|
|
// at least call it so it gets compiled
|
2017-11-10 11:02:45 -08:00
|
|
|
|
_ = getCwd(debug.global_allocator);
|
2017-10-09 11:21:35 -07:00
|
|
|
|
}
|
|
|
|
|
|
2018-02-08 21:24:23 -08:00
|
|
|
|
pub const SymLinkError = PosixSymLinkError || WindowsSymLinkError;
|
|
|
|
|
|
|
|
|
|
pub fn symLink(allocator: &Allocator, existing_path: []const u8, new_path: []const u8) SymLinkError!void {
|
2017-10-14 14:39:44 -07:00
|
|
|
|
if (is_windows) {
|
|
|
|
|
return symLinkWindows(allocator, existing_path, new_path);
|
|
|
|
|
} else {
|
|
|
|
|
return symLinkPosix(allocator, existing_path, new_path);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-08 21:24:23 -08:00
|
|
|
|
pub const WindowsSymLinkError = error {
|
|
|
|
|
OutOfMemory,
|
|
|
|
|
Unexpected,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pub fn symLinkWindows(allocator: &Allocator, existing_path: []const u8, new_path: []const u8) WindowsSymLinkError!void {
|
2018-01-07 13:51:46 -08:00
|
|
|
|
const existing_with_null = try cstr.addNullByte(allocator, existing_path);
|
2017-10-14 14:39:44 -07:00
|
|
|
|
defer allocator.free(existing_with_null);
|
2018-01-07 13:51:46 -08:00
|
|
|
|
const new_with_null = try cstr.addNullByte(allocator, new_path);
|
2017-10-14 14:39:44 -07:00
|
|
|
|
defer allocator.free(new_with_null);
|
|
|
|
|
|
|
|
|
|
if (windows.CreateSymbolicLinkA(existing_with_null.ptr, new_with_null.ptr, 0) == 0) {
|
|
|
|
|
const err = windows.GetLastError();
|
|
|
|
|
return switch (err) {
|
2017-10-15 13:45:43 -07:00
|
|
|
|
else => unexpectedErrorWindows(err),
|
2017-10-14 14:39:44 -07:00
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-08 21:24:23 -08:00
|
|
|
|
pub const PosixSymLinkError = error {
|
|
|
|
|
OutOfMemory,
|
|
|
|
|
AccessDenied,
|
|
|
|
|
DiskQuota,
|
|
|
|
|
PathAlreadyExists,
|
|
|
|
|
FileSystem,
|
|
|
|
|
SymLinkLoop,
|
|
|
|
|
NameTooLong,
|
|
|
|
|
FileNotFound,
|
|
|
|
|
SystemResources,
|
|
|
|
|
NoSpaceLeft,
|
|
|
|
|
ReadOnlyFileSystem,
|
|
|
|
|
NotDir,
|
|
|
|
|
Unexpected,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pub fn symLinkPosix(allocator: &Allocator, existing_path: []const u8, new_path: []const u8) PosixSymLinkError!void {
|
2018-01-07 13:51:46 -08:00
|
|
|
|
const full_buf = try allocator.alloc(u8, existing_path.len + new_path.len + 2);
|
2017-04-16 23:58:42 -07:00
|
|
|
|
defer allocator.free(full_buf);
|
|
|
|
|
|
|
|
|
|
const existing_buf = full_buf;
|
|
|
|
|
mem.copy(u8, existing_buf, existing_path);
|
|
|
|
|
existing_buf[existing_path.len] = 0;
|
|
|
|
|
|
2017-05-19 07:39:59 -07:00
|
|
|
|
const new_buf = full_buf[existing_path.len + 1..];
|
2017-04-16 23:58:42 -07:00
|
|
|
|
mem.copy(u8, new_buf, new_path);
|
|
|
|
|
new_buf[new_path.len] = 0;
|
|
|
|
|
|
|
|
|
|
const err = posix.getErrno(posix.symlink(existing_buf.ptr, new_buf.ptr));
|
|
|
|
|
if (err > 0) {
|
|
|
|
|
return switch (err) {
|
2017-08-27 02:15:24 -07:00
|
|
|
|
posix.EFAULT, posix.EINVAL => unreachable,
|
|
|
|
|
posix.EACCES, posix.EPERM => error.AccessDenied,
|
|
|
|
|
posix.EDQUOT => error.DiskQuota,
|
|
|
|
|
posix.EEXIST => error.PathAlreadyExists,
|
|
|
|
|
posix.EIO => error.FileSystem,
|
|
|
|
|
posix.ELOOP => error.SymLinkLoop,
|
|
|
|
|
posix.ENAMETOOLONG => error.NameTooLong,
|
|
|
|
|
posix.ENOENT => error.FileNotFound,
|
|
|
|
|
posix.ENOTDIR => error.NotDir,
|
|
|
|
|
posix.ENOMEM => error.SystemResources,
|
|
|
|
|
posix.ENOSPC => error.NoSpaceLeft,
|
|
|
|
|
posix.EROFS => error.ReadOnlyFileSystem,
|
2017-10-15 13:45:43 -07:00
|
|
|
|
else => unexpectedErrorPosix(err),
|
2017-04-16 23:58:42 -07:00
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-17 16:08:41 -07:00
|
|
|
|
// here we replace the standard +/ with -_ so that it can be used in a file name
|
2017-11-20 20:36:18 -08:00
|
|
|
|
const b64_fs_encoder = base64.Base64Encoder.init(
|
|
|
|
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",
|
|
|
|
|
base64.standard_pad_char);
|
2017-04-17 16:08:41 -07:00
|
|
|
|
|
2018-01-30 22:51:15 -08:00
|
|
|
|
pub fn atomicSymLink(allocator: &Allocator, existing_path: []const u8, new_path: []const u8) !void {
|
2017-05-03 14:23:11 -07:00
|
|
|
|
if (symLink(allocator, existing_path, new_path)) {
|
2017-04-17 16:08:41 -07:00
|
|
|
|
return;
|
2018-02-10 17:55:13 -08:00
|
|
|
|
} else |err| switch (err) {
|
|
|
|
|
error.PathAlreadyExists => {},
|
|
|
|
|
else => return err, // TODO zig should know this set does not include PathAlreadyExists
|
2017-04-17 16:08:41 -07:00
|
|
|
|
}
|
|
|
|
|
|
2018-02-10 17:55:13 -08:00
|
|
|
|
const dirname = os.path.dirname(new_path);
|
|
|
|
|
|
2017-04-17 16:08:41 -07:00
|
|
|
|
var rand_buf: [12]u8 = undefined;
|
2018-02-10 17:55:13 -08:00
|
|
|
|
const tmp_path = try allocator.alloc(u8, dirname.len + 1 + base64.Base64Encoder.calcSize(rand_buf.len));
|
2017-04-17 16:08:41 -07:00
|
|
|
|
defer allocator.free(tmp_path);
|
2018-02-10 17:55:13 -08:00
|
|
|
|
mem.copy(u8, tmp_path[0..], dirname);
|
|
|
|
|
tmp_path[dirname.len] = os.path.sep;
|
2017-04-17 16:08:41 -07:00
|
|
|
|
while (true) {
|
2018-01-07 13:51:46 -08:00
|
|
|
|
try getRandomBytes(rand_buf[0..]);
|
2018-02-10 17:55:13 -08:00
|
|
|
|
b64_fs_encoder.encode(tmp_path[dirname.len + 1 ..], rand_buf);
|
|
|
|
|
|
2017-05-03 14:23:11 -07:00
|
|
|
|
if (symLink(allocator, existing_path, tmp_path)) {
|
2017-04-17 16:08:41 -07:00
|
|
|
|
return rename(allocator, tmp_path, new_path);
|
2018-02-10 17:55:13 -08:00
|
|
|
|
} else |err| switch (err) {
|
|
|
|
|
error.PathAlreadyExists => continue,
|
|
|
|
|
else => return err, // TODO zig should know this set does not include PathAlreadyExists
|
2017-04-17 16:08:41 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-30 22:51:15 -08:00
|
|
|
|
pub fn deleteFile(allocator: &Allocator, file_path: []const u8) !void {
|
2017-10-08 20:06:56 -07:00
|
|
|
|
if (builtin.os == Os.windows) {
|
|
|
|
|
return deleteFileWindows(allocator, file_path);
|
|
|
|
|
} else {
|
|
|
|
|
return deleteFilePosix(allocator, file_path);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-30 22:51:15 -08:00
|
|
|
|
pub fn deleteFileWindows(allocator: &Allocator, file_path: []const u8) !void {
|
2018-01-07 13:51:46 -08:00
|
|
|
|
const buf = try allocator.alloc(u8, file_path.len + 1);
|
2017-10-08 20:06:56 -07:00
|
|
|
|
defer allocator.free(buf);
|
|
|
|
|
|
|
|
|
|
mem.copy(u8, buf, file_path);
|
|
|
|
|
buf[file_path.len] = 0;
|
|
|
|
|
|
2017-10-15 11:01:55 -07:00
|
|
|
|
if (windows.DeleteFileA(buf.ptr) == 0) {
|
2017-10-08 20:06:56 -07:00
|
|
|
|
const err = windows.GetLastError();
|
|
|
|
|
return switch (err) {
|
|
|
|
|
windows.ERROR.FILE_NOT_FOUND => error.FileNotFound,
|
|
|
|
|
windows.ERROR.ACCESS_DENIED => error.AccessDenied,
|
|
|
|
|
windows.ERROR.FILENAME_EXCED_RANGE, windows.ERROR.INVALID_PARAMETER => error.NameTooLong,
|
2017-10-15 13:45:43 -07:00
|
|
|
|
else => unexpectedErrorWindows(err),
|
2017-12-21 21:50:30 -08:00
|
|
|
|
};
|
2017-10-08 20:06:56 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-30 22:51:15 -08:00
|
|
|
|
pub fn deleteFilePosix(allocator: &Allocator, file_path: []const u8) !void {
|
2018-01-07 13:51:46 -08:00
|
|
|
|
const buf = try allocator.alloc(u8, file_path.len + 1);
|
2017-04-16 23:58:42 -07:00
|
|
|
|
defer allocator.free(buf);
|
|
|
|
|
|
2017-04-17 03:45:44 -07:00
|
|
|
|
mem.copy(u8, buf, file_path);
|
|
|
|
|
buf[file_path.len] = 0;
|
2017-04-16 23:58:42 -07:00
|
|
|
|
|
|
|
|
|
const err = posix.getErrno(posix.unlink(buf.ptr));
|
|
|
|
|
if (err > 0) {
|
|
|
|
|
return switch (err) {
|
2017-08-27 02:15:24 -07:00
|
|
|
|
posix.EACCES, posix.EPERM => error.AccessDenied,
|
|
|
|
|
posix.EBUSY => error.FileBusy,
|
|
|
|
|
posix.EFAULT, posix.EINVAL => unreachable,
|
|
|
|
|
posix.EIO => error.FileSystem,
|
|
|
|
|
posix.EISDIR => error.IsDir,
|
|
|
|
|
posix.ELOOP => error.SymLinkLoop,
|
|
|
|
|
posix.ENAMETOOLONG => error.NameTooLong,
|
|
|
|
|
posix.ENOENT => error.FileNotFound,
|
|
|
|
|
posix.ENOTDIR => error.NotDir,
|
|
|
|
|
posix.ENOMEM => error.SystemResources,
|
|
|
|
|
posix.EROFS => error.ReadOnlyFileSystem,
|
2017-10-15 13:45:43 -07:00
|
|
|
|
else => unexpectedErrorPosix(err),
|
2017-04-16 23:58:42 -07:00
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-04-17 03:45:44 -07:00
|
|
|
|
|
2018-02-10 17:55:13 -08:00
|
|
|
|
/// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is
|
|
|
|
|
/// merged and readily available,
|
|
|
|
|
/// there is a possibility of power loss or application termination leaving temporary files present
|
|
|
|
|
/// in the same directory as dest_path.
|
|
|
|
|
/// Destination file will have the same mode as the source file.
|
2018-01-30 22:51:15 -08:00
|
|
|
|
pub fn copyFile(allocator: &Allocator, source_path: []const u8, dest_path: []const u8) !void {
|
2018-02-10 17:55:13 -08:00
|
|
|
|
var in_file = try os.File.openRead(allocator, source_path);
|
|
|
|
|
defer in_file.close();
|
2017-04-30 19:09:44 -07:00
|
|
|
|
|
2018-02-10 17:55:13 -08:00
|
|
|
|
const mode = try in_file.mode();
|
2017-04-30 19:09:44 -07:00
|
|
|
|
|
2018-02-10 17:55:13 -08:00
|
|
|
|
var atomic_file = try AtomicFile.init(allocator, dest_path, mode);
|
|
|
|
|
defer atomic_file.deinit();
|
2017-04-30 19:09:44 -07:00
|
|
|
|
|
2018-02-10 17:55:13 -08:00
|
|
|
|
var buf: [page_size]u8 = undefined;
|
|
|
|
|
while (true) {
|
|
|
|
|
const amt = try in_file.read(buf[0..]);
|
|
|
|
|
try atomic_file.file.write(buf[0..amt]);
|
|
|
|
|
if (amt != buf.len) {
|
|
|
|
|
return atomic_file.finish();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is
|
|
|
|
|
/// merged and readily available,
|
|
|
|
|
/// there is a possibility of power loss or application termination leaving temporary files present
|
|
|
|
|
pub fn copyFileMode(allocator: &Allocator, source_path: []const u8, dest_path: []const u8, mode: FileMode) !void {
|
|
|
|
|
var in_file = try os.File.openRead(allocator, source_path);
|
2017-10-31 01:47:55 -07:00
|
|
|
|
defer in_file.close();
|
2017-04-17 03:45:44 -07:00
|
|
|
|
|
2018-02-10 17:55:13 -08:00
|
|
|
|
var atomic_file = try AtomicFile.init(allocator, dest_path, mode);
|
|
|
|
|
defer atomic_file.deinit();
|
|
|
|
|
|
2017-10-31 01:47:55 -07:00
|
|
|
|
var buf: [page_size]u8 = undefined;
|
2017-04-17 03:45:44 -07:00
|
|
|
|
while (true) {
|
2018-01-07 13:51:46 -08:00
|
|
|
|
const amt = try in_file.read(buf[0..]);
|
2018-02-10 17:55:13 -08:00
|
|
|
|
try atomic_file.file.write(buf[0..amt]);
|
|
|
|
|
if (amt != buf.len) {
|
|
|
|
|
return atomic_file.finish();
|
|
|
|
|
}
|
2017-04-17 03:45:44 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
2017-04-17 16:08:41 -07:00
|
|
|
|
|
2018-02-10 17:55:13 -08:00
|
|
|
|
pub const AtomicFile = struct {
|
|
|
|
|
allocator: &Allocator,
|
|
|
|
|
file: os.File,
|
|
|
|
|
tmp_path: []u8,
|
|
|
|
|
dest_path: []const u8,
|
|
|
|
|
finished: bool,
|
|
|
|
|
|
|
|
|
|
/// dest_path must remain valid for the lifetime of AtomicFile
|
|
|
|
|
/// call finish to atomically replace dest_path with contents
|
|
|
|
|
pub fn init(allocator: &Allocator, dest_path: []const u8, mode: FileMode) !AtomicFile {
|
|
|
|
|
const dirname = os.path.dirname(dest_path);
|
|
|
|
|
|
|
|
|
|
var rand_buf: [12]u8 = undefined;
|
|
|
|
|
const tmp_path = try allocator.alloc(u8, dirname.len + 1 + base64.Base64Encoder.calcSize(rand_buf.len));
|
|
|
|
|
errdefer allocator.free(tmp_path);
|
|
|
|
|
mem.copy(u8, tmp_path[0..], dirname);
|
|
|
|
|
tmp_path[dirname.len] = os.path.sep;
|
|
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
try getRandomBytes(rand_buf[0..]);
|
|
|
|
|
b64_fs_encoder.encode(tmp_path[dirname.len + 1 ..], rand_buf);
|
|
|
|
|
|
|
|
|
|
const file = os.File.openWriteNoClobber(allocator, tmp_path, mode) catch |err| switch (err) {
|
|
|
|
|
error.PathAlreadyExists => continue,
|
|
|
|
|
// TODO zig should figure out that this error set does not include PathAlreadyExists since
|
|
|
|
|
// it is handled in the above switch
|
|
|
|
|
else => return err,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return AtomicFile {
|
|
|
|
|
.allocator = allocator,
|
|
|
|
|
.file = file,
|
|
|
|
|
.tmp_path = tmp_path,
|
|
|
|
|
.dest_path = dest_path,
|
|
|
|
|
.finished = false,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// always call deinit, even after successful finish()
|
|
|
|
|
pub fn deinit(self: &AtomicFile) void {
|
|
|
|
|
if (!self.finished) {
|
|
|
|
|
self.file.close();
|
|
|
|
|
deleteFile(self.allocator, self.tmp_path) catch {};
|
|
|
|
|
self.allocator.free(self.tmp_path);
|
|
|
|
|
self.finished = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn finish(self: &AtomicFile) !void {
|
|
|
|
|
assert(!self.finished);
|
|
|
|
|
self.file.close();
|
|
|
|
|
try rename(self.allocator, self.tmp_path, self.dest_path);
|
|
|
|
|
self.allocator.free(self.tmp_path);
|
|
|
|
|
self.finished = true;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2018-01-30 22:51:15 -08:00
|
|
|
|
pub fn rename(allocator: &Allocator, old_path: []const u8, new_path: []const u8) !void {
|
2018-01-07 13:51:46 -08:00
|
|
|
|
const full_buf = try allocator.alloc(u8, old_path.len + new_path.len + 2);
|
2017-04-17 16:08:41 -07:00
|
|
|
|
defer allocator.free(full_buf);
|
|
|
|
|
|
|
|
|
|
const old_buf = full_buf;
|
|
|
|
|
mem.copy(u8, old_buf, old_path);
|
|
|
|
|
old_buf[old_path.len] = 0;
|
|
|
|
|
|
2017-05-19 07:39:59 -07:00
|
|
|
|
const new_buf = full_buf[old_path.len + 1..];
|
2017-04-17 16:08:41 -07:00
|
|
|
|
mem.copy(u8, new_buf, new_path);
|
|
|
|
|
new_buf[new_path.len] = 0;
|
|
|
|
|
|
2017-10-14 14:56:18 -07:00
|
|
|
|
if (is_windows) {
|
|
|
|
|
const flags = windows.MOVEFILE_REPLACE_EXISTING|windows.MOVEFILE_WRITE_THROUGH;
|
2017-10-15 11:01:55 -07:00
|
|
|
|
if (windows.MoveFileExA(old_buf.ptr, new_buf.ptr, flags) == 0) {
|
2017-10-14 14:56:18 -07:00
|
|
|
|
const err = windows.GetLastError();
|
|
|
|
|
return switch (err) {
|
2017-10-15 13:45:43 -07:00
|
|
|
|
else => unexpectedErrorWindows(err),
|
2017-10-14 14:56:18 -07:00
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
const err = posix.getErrno(posix.rename(old_buf.ptr, new_buf.ptr));
|
|
|
|
|
if (err > 0) {
|
|
|
|
|
return switch (err) {
|
|
|
|
|
posix.EACCES, posix.EPERM => error.AccessDenied,
|
|
|
|
|
posix.EBUSY => error.FileBusy,
|
|
|
|
|
posix.EDQUOT => error.DiskQuota,
|
|
|
|
|
posix.EFAULT, posix.EINVAL => unreachable,
|
|
|
|
|
posix.EISDIR => error.IsDir,
|
|
|
|
|
posix.ELOOP => error.SymLinkLoop,
|
|
|
|
|
posix.EMLINK => error.LinkQuotaExceeded,
|
|
|
|
|
posix.ENAMETOOLONG => error.NameTooLong,
|
|
|
|
|
posix.ENOENT => error.FileNotFound,
|
|
|
|
|
posix.ENOTDIR => error.NotDir,
|
|
|
|
|
posix.ENOMEM => error.SystemResources,
|
|
|
|
|
posix.ENOSPC => error.NoSpaceLeft,
|
|
|
|
|
posix.EEXIST, posix.ENOTEMPTY => error.PathAlreadyExists,
|
|
|
|
|
posix.EROFS => error.ReadOnlyFileSystem,
|
|
|
|
|
posix.EXDEV => error.RenameAcrossMountPoints,
|
2017-10-15 13:45:43 -07:00
|
|
|
|
else => unexpectedErrorPosix(err),
|
2017-10-14 14:56:18 -07:00
|
|
|
|
};
|
|
|
|
|
}
|
2017-04-17 16:08:41 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
2017-04-18 22:13:15 -07:00
|
|
|
|
|
2018-01-30 22:51:15 -08:00
|
|
|
|
pub fn makeDir(allocator: &Allocator, dir_path: []const u8) !void {
|
2017-10-14 12:31:24 -07:00
|
|
|
|
if (is_windows) {
|
|
|
|
|
return makeDirWindows(allocator, dir_path);
|
|
|
|
|
} else {
|
|
|
|
|
return makeDirPosix(allocator, dir_path);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-30 22:51:15 -08:00
|
|
|
|
pub fn makeDirWindows(allocator: &Allocator, dir_path: []const u8) !void {
|
2018-01-07 13:51:46 -08:00
|
|
|
|
const path_buf = try cstr.addNullByte(allocator, dir_path);
|
2017-04-18 22:13:15 -07:00
|
|
|
|
defer allocator.free(path_buf);
|
|
|
|
|
|
2017-10-15 11:01:55 -07:00
|
|
|
|
if (windows.CreateDirectoryA(path_buf.ptr, null) == 0) {
|
2017-10-14 12:31:24 -07:00
|
|
|
|
const err = windows.GetLastError();
|
|
|
|
|
return switch (err) {
|
|
|
|
|
windows.ERROR.ALREADY_EXISTS => error.PathAlreadyExists,
|
|
|
|
|
windows.ERROR.PATH_NOT_FOUND => error.FileNotFound,
|
2017-10-15 13:45:43 -07:00
|
|
|
|
else => unexpectedErrorWindows(err),
|
2017-10-14 12:31:24 -07:00
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-30 22:51:15 -08:00
|
|
|
|
pub fn makeDirPosix(allocator: &Allocator, dir_path: []const u8) !void {
|
2018-01-07 13:51:46 -08:00
|
|
|
|
const path_buf = try cstr.addNullByte(allocator, dir_path);
|
2017-10-14 12:31:24 -07:00
|
|
|
|
defer allocator.free(path_buf);
|
2017-04-18 22:13:15 -07:00
|
|
|
|
|
|
|
|
|
const err = posix.getErrno(posix.mkdir(path_buf.ptr, 0o755));
|
|
|
|
|
if (err > 0) {
|
|
|
|
|
return switch (err) {
|
2017-08-27 02:15:24 -07:00
|
|
|
|
posix.EACCES, posix.EPERM => error.AccessDenied,
|
|
|
|
|
posix.EDQUOT => error.DiskQuota,
|
|
|
|
|
posix.EEXIST => error.PathAlreadyExists,
|
|
|
|
|
posix.EFAULT => unreachable,
|
|
|
|
|
posix.ELOOP => error.SymLinkLoop,
|
|
|
|
|
posix.EMLINK => error.LinkQuotaExceeded,
|
|
|
|
|
posix.ENAMETOOLONG => error.NameTooLong,
|
|
|
|
|
posix.ENOENT => error.FileNotFound,
|
|
|
|
|
posix.ENOMEM => error.SystemResources,
|
|
|
|
|
posix.ENOSPC => error.NoSpaceLeft,
|
|
|
|
|
posix.ENOTDIR => error.NotDir,
|
|
|
|
|
posix.EROFS => error.ReadOnlyFileSystem,
|
2017-10-15 13:45:43 -07:00
|
|
|
|
else => unexpectedErrorPosix(err),
|
2017-04-18 22:13:15 -07:00
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Calls makeDir recursively to make an entire path. Returns success if the path
|
|
|
|
|
/// already exists and is a directory.
|
2018-01-30 22:51:15 -08:00
|
|
|
|
pub fn makePath(allocator: &Allocator, full_path: []const u8) !void {
|
2018-01-07 13:51:46 -08:00
|
|
|
|
const resolved_path = try path.resolve(allocator, full_path);
|
2017-04-20 22:56:12 -07:00
|
|
|
|
defer allocator.free(resolved_path);
|
2017-04-18 22:13:15 -07:00
|
|
|
|
|
2017-04-20 22:56:12 -07:00
|
|
|
|
var end_index: usize = resolved_path.len;
|
|
|
|
|
while (true) {
|
2018-01-07 14:28:20 -08:00
|
|
|
|
makeDir(allocator, resolved_path[0..end_index]) catch |err| {
|
2017-04-20 22:56:12 -07:00
|
|
|
|
if (err == error.PathAlreadyExists) {
|
|
|
|
|
// TODO stat the file and return an error if it's not a directory
|
|
|
|
|
// this is important because otherwise a dangling symlink
|
|
|
|
|
// could cause an infinite loop
|
|
|
|
|
if (end_index == resolved_path.len)
|
|
|
|
|
return;
|
|
|
|
|
} else if (err == error.FileNotFound) {
|
|
|
|
|
// march end_index backward until next path component
|
|
|
|
|
while (true) {
|
|
|
|
|
end_index -= 1;
|
2018-01-03 01:55:16 -08:00
|
|
|
|
if (os.path.isSep(resolved_path[end_index]))
|
2017-04-20 22:56:12 -07:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
} else {
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
if (end_index == resolved_path.len)
|
|
|
|
|
return;
|
|
|
|
|
// march end_index forward until next path component
|
|
|
|
|
while (true) {
|
|
|
|
|
end_index += 1;
|
2018-01-03 01:55:16 -08:00
|
|
|
|
if (end_index == resolved_path.len or os.path.isSep(resolved_path[end_index]))
|
2017-04-20 22:56:12 -07:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-04-18 22:13:15 -07:00
|
|
|
|
}
|
2017-04-19 23:26:36 -07:00
|
|
|
|
|
|
|
|
|
/// Returns ::error.DirNotEmpty if the directory is not empty.
|
|
|
|
|
/// To delete a directory recursively, see ::deleteTree
|
2018-01-30 22:51:15 -08:00
|
|
|
|
pub fn deleteDir(allocator: &Allocator, dir_path: []const u8) !void {
|
2018-01-07 13:51:46 -08:00
|
|
|
|
const path_buf = try allocator.alloc(u8, dir_path.len + 1);
|
2017-04-19 23:26:36 -07:00
|
|
|
|
defer allocator.free(path_buf);
|
|
|
|
|
|
|
|
|
|
mem.copy(u8, path_buf, dir_path);
|
|
|
|
|
path_buf[dir_path.len] = 0;
|
|
|
|
|
|
|
|
|
|
const err = posix.getErrno(posix.rmdir(path_buf.ptr));
|
|
|
|
|
if (err > 0) {
|
|
|
|
|
return switch (err) {
|
2017-08-27 02:15:24 -07:00
|
|
|
|
posix.EACCES, posix.EPERM => error.AccessDenied,
|
|
|
|
|
posix.EBUSY => error.FileBusy,
|
|
|
|
|
posix.EFAULT, posix.EINVAL => unreachable,
|
|
|
|
|
posix.ELOOP => error.SymLinkLoop,
|
|
|
|
|
posix.ENAMETOOLONG => error.NameTooLong,
|
|
|
|
|
posix.ENOENT => error.FileNotFound,
|
|
|
|
|
posix.ENOMEM => error.SystemResources,
|
|
|
|
|
posix.ENOTDIR => error.NotDir,
|
|
|
|
|
posix.EEXIST, posix.ENOTEMPTY => error.DirNotEmpty,
|
|
|
|
|
posix.EROFS => error.ReadOnlyFileSystem,
|
2017-10-15 13:45:43 -07:00
|
|
|
|
else => unexpectedErrorPosix(err),
|
2017-04-19 23:26:36 -07:00
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Whether ::full_path describes a symlink, file, or directory, this function
|
|
|
|
|
/// removes it. If it cannot be removed because it is a non-empty directory,
|
|
|
|
|
/// this function recursively removes its entries and then tries again.
|
|
|
|
|
// TODO non-recursive implementation
|
2018-02-07 23:08:45 -08:00
|
|
|
|
const DeleteTreeError = error {
|
|
|
|
|
OutOfMemory,
|
|
|
|
|
AccessDenied,
|
|
|
|
|
FileTooBig,
|
|
|
|
|
IsDir,
|
|
|
|
|
SymLinkLoop,
|
|
|
|
|
ProcessFdQuotaExceeded,
|
|
|
|
|
NameTooLong,
|
|
|
|
|
SystemFdQuotaExceeded,
|
|
|
|
|
NoDevice,
|
|
|
|
|
PathNotFound,
|
|
|
|
|
SystemResources,
|
|
|
|
|
NoSpaceLeft,
|
|
|
|
|
PathAlreadyExists,
|
|
|
|
|
ReadOnlyFileSystem,
|
|
|
|
|
NotDir,
|
|
|
|
|
FileNotFound,
|
|
|
|
|
FileSystem,
|
|
|
|
|
FileBusy,
|
|
|
|
|
DirNotEmpty,
|
|
|
|
|
Unexpected,
|
|
|
|
|
};
|
|
|
|
|
pub fn deleteTree(allocator: &Allocator, full_path: []const u8) DeleteTreeError!void {
|
2017-12-20 19:55:24 -08:00
|
|
|
|
start_over: while (true) {
|
2018-04-03 21:08:10 -07:00
|
|
|
|
var got_access_denied = false;
|
2017-12-20 19:55:24 -08:00
|
|
|
|
// First, try deleting the item as a file. This way we don't follow sym links.
|
|
|
|
|
if (deleteFile(allocator, full_path)) {
|
2017-04-19 23:26:36 -07:00
|
|
|
|
return;
|
2018-02-07 23:08:45 -08:00
|
|
|
|
} else |err| switch (err) {
|
|
|
|
|
error.FileNotFound => return,
|
|
|
|
|
error.IsDir => {},
|
2018-04-03 21:08:10 -07:00
|
|
|
|
error.AccessDenied => got_access_denied = true,
|
2018-02-07 23:08:45 -08:00
|
|
|
|
|
|
|
|
|
error.OutOfMemory,
|
|
|
|
|
error.SymLinkLoop,
|
|
|
|
|
error.NameTooLong,
|
|
|
|
|
error.SystemResources,
|
|
|
|
|
error.ReadOnlyFileSystem,
|
|
|
|
|
error.NotDir,
|
|
|
|
|
error.FileSystem,
|
|
|
|
|
error.FileBusy,
|
|
|
|
|
error.Unexpected
|
|
|
|
|
=> return err,
|
2017-12-20 19:55:24 -08:00
|
|
|
|
}
|
|
|
|
|
{
|
2018-02-07 23:08:45 -08:00
|
|
|
|
var dir = Dir.open(allocator, full_path) catch |err| switch (err) {
|
2018-04-03 21:08:10 -07:00
|
|
|
|
error.NotDir => {
|
|
|
|
|
if (got_access_denied) {
|
|
|
|
|
return error.AccessDenied;
|
|
|
|
|
}
|
|
|
|
|
continue :start_over;
|
|
|
|
|
},
|
2018-02-07 23:08:45 -08:00
|
|
|
|
|
|
|
|
|
error.OutOfMemory,
|
|
|
|
|
error.AccessDenied,
|
|
|
|
|
error.FileTooBig,
|
|
|
|
|
error.IsDir,
|
|
|
|
|
error.SymLinkLoop,
|
|
|
|
|
error.ProcessFdQuotaExceeded,
|
|
|
|
|
error.NameTooLong,
|
|
|
|
|
error.SystemFdQuotaExceeded,
|
|
|
|
|
error.NoDevice,
|
|
|
|
|
error.PathNotFound,
|
|
|
|
|
error.SystemResources,
|
|
|
|
|
error.NoSpaceLeft,
|
|
|
|
|
error.PathAlreadyExists,
|
|
|
|
|
error.Unexpected
|
|
|
|
|
=> return err,
|
2017-12-20 19:55:24 -08:00
|
|
|
|
};
|
|
|
|
|
defer dir.close();
|
2017-04-19 23:26:36 -07:00
|
|
|
|
|
2017-12-20 19:55:24 -08:00
|
|
|
|
var full_entry_buf = ArrayList(u8).init(allocator);
|
|
|
|
|
defer full_entry_buf.deinit();
|
2017-04-19 23:26:36 -07:00
|
|
|
|
|
2018-01-07 13:51:46 -08:00
|
|
|
|
while (try dir.next()) |entry| {
|
|
|
|
|
try full_entry_buf.resize(full_path.len + entry.name.len + 1);
|
2017-12-20 19:55:24 -08:00
|
|
|
|
const full_entry_path = full_entry_buf.toSlice();
|
|
|
|
|
mem.copy(u8, full_entry_path, full_path);
|
|
|
|
|
full_entry_path[full_path.len] = '/';
|
|
|
|
|
mem.copy(u8, full_entry_path[full_path.len + 1..], entry.name);
|
2017-04-19 23:26:36 -07:00
|
|
|
|
|
2018-01-07 13:51:46 -08:00
|
|
|
|
try deleteTree(allocator, full_entry_path);
|
2017-12-20 19:55:24 -08:00
|
|
|
|
}
|
2017-04-19 23:26:36 -07:00
|
|
|
|
}
|
2017-12-20 19:55:24 -08:00
|
|
|
|
return deleteDir(allocator, full_path);
|
2017-04-19 23:26:36 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub const Dir = struct {
|
|
|
|
|
fd: i32,
|
2018-03-04 21:57:02 -08:00
|
|
|
|
darwin_seek: darwin_seek_t,
|
2017-04-19 23:26:36 -07:00
|
|
|
|
allocator: &Allocator,
|
|
|
|
|
buf: []u8,
|
|
|
|
|
index: usize,
|
|
|
|
|
end_index: usize,
|
|
|
|
|
|
2018-03-04 21:57:02 -08:00
|
|
|
|
const darwin_seek_t = switch (builtin.os) {
|
|
|
|
|
Os.macosx, Os.ios => i64,
|
|
|
|
|
else => void,
|
2017-04-19 23:26:36 -07:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pub const Entry = struct {
|
|
|
|
|
name: []const u8,
|
|
|
|
|
kind: Kind,
|
|
|
|
|
|
|
|
|
|
pub const Kind = enum {
|
|
|
|
|
BlockDevice,
|
|
|
|
|
CharacterDevice,
|
|
|
|
|
Directory,
|
|
|
|
|
NamedPipe,
|
|
|
|
|
SymLink,
|
|
|
|
|
File,
|
|
|
|
|
UnixDomainSocket,
|
2018-03-29 01:23:44 -07:00
|
|
|
|
Whiteout,
|
2017-04-19 23:26:36 -07:00
|
|
|
|
Unknown,
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
2018-01-30 22:51:15 -08:00
|
|
|
|
pub fn open(allocator: &Allocator, dir_path: []const u8) !Dir {
|
2018-03-04 21:57:02 -08:00
|
|
|
|
const fd = switch (builtin.os) {
|
|
|
|
|
Os.windows => @compileError("TODO support Dir.open for windows"),
|
|
|
|
|
Os.linux => try posixOpen(allocator, dir_path, posix.O_RDONLY|posix.O_DIRECTORY|posix.O_CLOEXEC, 0),
|
|
|
|
|
Os.macosx, Os.ios => try posixOpen(allocator, dir_path, posix.O_RDONLY|posix.O_NONBLOCK|posix.O_DIRECTORY|posix.O_CLOEXEC, 0),
|
|
|
|
|
else => @compileError("Dir.open is not supported for this platform"),
|
|
|
|
|
};
|
|
|
|
|
const darwin_seek_init = switch (builtin.os) {
|
|
|
|
|
Os.macosx, Os.ios => 0,
|
|
|
|
|
else => {},
|
|
|
|
|
};
|
2017-04-19 23:26:36 -07:00
|
|
|
|
return Dir {
|
|
|
|
|
.allocator = allocator,
|
|
|
|
|
.fd = fd,
|
2018-03-04 21:57:02 -08:00
|
|
|
|
.darwin_seek = darwin_seek_init,
|
2017-04-19 23:26:36 -07:00
|
|
|
|
.index = 0,
|
|
|
|
|
.end_index = 0,
|
|
|
|
|
.buf = []u8{},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-25 01:10:11 -08:00
|
|
|
|
pub fn close(self: &Dir) void {
|
2017-04-19 23:26:36 -07:00
|
|
|
|
self.allocator.free(self.buf);
|
2017-11-29 16:52:58 -08:00
|
|
|
|
os.close(self.fd);
|
2017-04-19 23:26:36 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Memory such as file names referenced in this returned entry becomes invalid
|
|
|
|
|
/// with subsequent calls to next, as well as when this ::Dir is deinitialized.
|
2018-01-30 22:51:15 -08:00
|
|
|
|
pub fn next(self: &Dir) !?Entry {
|
2018-03-04 21:57:02 -08:00
|
|
|
|
switch (builtin.os) {
|
|
|
|
|
Os.linux => return self.nextLinux(),
|
|
|
|
|
Os.macosx, Os.ios => return self.nextDarwin(),
|
|
|
|
|
Os.windows => return self.nextWindows(),
|
|
|
|
|
else => @compileError("Dir.next not supported on " ++ @tagName(builtin.os)),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn nextDarwin(self: &Dir) !?Entry {
|
|
|
|
|
start_over: while (true) {
|
|
|
|
|
if (self.index >= self.end_index) {
|
|
|
|
|
if (self.buf.len == 0) {
|
|
|
|
|
self.buf = try self.allocator.alloc(u8, page_size);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
const result = posix.getdirentries64(self.fd, self.buf.ptr, self.buf.len,
|
|
|
|
|
&self.darwin_seek);
|
|
|
|
|
const err = posix.getErrno(result);
|
|
|
|
|
if (err > 0) {
|
|
|
|
|
switch (err) {
|
|
|
|
|
posix.EBADF, posix.EFAULT, posix.ENOTDIR => unreachable,
|
|
|
|
|
posix.EINVAL => {
|
|
|
|
|
self.buf = try self.allocator.realloc(u8, self.buf, self.buf.len * 2);
|
|
|
|
|
continue;
|
|
|
|
|
},
|
|
|
|
|
else => return unexpectedErrorPosix(err),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (result == 0)
|
|
|
|
|
return null;
|
|
|
|
|
self.index = 0;
|
|
|
|
|
self.end_index = result;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const darwin_entry = @ptrCast(& align(1) posix.dirent, &self.buf[self.index]);
|
|
|
|
|
const next_index = self.index + darwin_entry.d_reclen;
|
|
|
|
|
self.index = next_index;
|
|
|
|
|
|
|
|
|
|
const name = (&darwin_entry.d_name)[0..darwin_entry.d_namlen];
|
|
|
|
|
|
|
|
|
|
// skip . and .. entries
|
|
|
|
|
if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
|
|
|
|
|
continue :start_over;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const entry_kind = switch (darwin_entry.d_type) {
|
|
|
|
|
posix.DT_BLK => Entry.Kind.BlockDevice,
|
|
|
|
|
posix.DT_CHR => Entry.Kind.CharacterDevice,
|
|
|
|
|
posix.DT_DIR => Entry.Kind.Directory,
|
|
|
|
|
posix.DT_FIFO => Entry.Kind.NamedPipe,
|
|
|
|
|
posix.DT_LNK => Entry.Kind.SymLink,
|
|
|
|
|
posix.DT_REG => Entry.Kind.File,
|
|
|
|
|
posix.DT_SOCK => Entry.Kind.UnixDomainSocket,
|
2018-03-29 01:23:44 -07:00
|
|
|
|
posix.DT_WHT => Entry.Kind.Whiteout,
|
2018-03-04 21:57:02 -08:00
|
|
|
|
else => Entry.Kind.Unknown,
|
|
|
|
|
};
|
|
|
|
|
return Entry {
|
|
|
|
|
.name = name,
|
|
|
|
|
.kind = entry_kind,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn nextWindows(self: &Dir) !?Entry {
|
|
|
|
|
@compileError("TODO support Dir.next for windows");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn nextLinux(self: &Dir) !?Entry {
|
2017-12-20 19:55:24 -08:00
|
|
|
|
start_over: while (true) {
|
|
|
|
|
if (self.index >= self.end_index) {
|
|
|
|
|
if (self.buf.len == 0) {
|
2018-01-07 13:51:46 -08:00
|
|
|
|
self.buf = try self.allocator.alloc(u8, page_size);
|
2017-12-20 19:55:24 -08:00
|
|
|
|
}
|
2017-04-19 23:26:36 -07:00
|
|
|
|
|
2017-12-20 19:55:24 -08:00
|
|
|
|
while (true) {
|
|
|
|
|
const result = posix.getdents(self.fd, self.buf.ptr, self.buf.len);
|
2018-03-04 21:57:02 -08:00
|
|
|
|
const err = posix.getErrno(result);
|
2017-12-20 19:55:24 -08:00
|
|
|
|
if (err > 0) {
|
|
|
|
|
switch (err) {
|
|
|
|
|
posix.EBADF, posix.EFAULT, posix.ENOTDIR => unreachable,
|
|
|
|
|
posix.EINVAL => {
|
2018-01-07 13:51:46 -08:00
|
|
|
|
self.buf = try self.allocator.realloc(u8, self.buf, self.buf.len * 2);
|
2017-12-20 19:55:24 -08:00
|
|
|
|
continue;
|
|
|
|
|
},
|
|
|
|
|
else => return unexpectedErrorPosix(err),
|
2017-12-21 21:50:30 -08:00
|
|
|
|
}
|
2017-12-20 19:55:24 -08:00
|
|
|
|
}
|
|
|
|
|
if (result == 0)
|
|
|
|
|
return null;
|
|
|
|
|
self.index = 0;
|
|
|
|
|
self.end_index = result;
|
|
|
|
|
break;
|
2017-04-19 23:26:36 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
2018-03-04 21:57:02 -08:00
|
|
|
|
const linux_entry = @ptrCast(& align(1) posix.dirent, &self.buf[self.index]);
|
2017-12-20 19:55:24 -08:00
|
|
|
|
const next_index = self.index + linux_entry.d_reclen;
|
|
|
|
|
self.index = next_index;
|
2017-04-19 23:26:36 -07:00
|
|
|
|
|
2017-12-20 19:55:24 -08:00
|
|
|
|
const name = cstr.toSlice(&linux_entry.d_name);
|
2017-04-19 23:26:36 -07:00
|
|
|
|
|
2017-12-20 19:55:24 -08:00
|
|
|
|
// skip . and .. entries
|
|
|
|
|
if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
|
|
|
|
|
continue :start_over;
|
|
|
|
|
}
|
2017-04-19 23:26:36 -07:00
|
|
|
|
|
2017-12-20 19:55:24 -08:00
|
|
|
|
const type_char = self.buf[next_index - 1];
|
|
|
|
|
const entry_kind = switch (type_char) {
|
|
|
|
|
posix.DT_BLK => Entry.Kind.BlockDevice,
|
|
|
|
|
posix.DT_CHR => Entry.Kind.CharacterDevice,
|
|
|
|
|
posix.DT_DIR => Entry.Kind.Directory,
|
|
|
|
|
posix.DT_FIFO => Entry.Kind.NamedPipe,
|
|
|
|
|
posix.DT_LNK => Entry.Kind.SymLink,
|
|
|
|
|
posix.DT_REG => Entry.Kind.File,
|
|
|
|
|
posix.DT_SOCK => Entry.Kind.UnixDomainSocket,
|
|
|
|
|
else => Entry.Kind.Unknown,
|
|
|
|
|
};
|
|
|
|
|
return Entry {
|
|
|
|
|
.name = name,
|
|
|
|
|
.kind = entry_kind,
|
|
|
|
|
};
|
|
|
|
|
}
|
2017-04-19 23:26:36 -07:00
|
|
|
|
}
|
|
|
|
|
};
|
2017-04-30 15:56:24 -07:00
|
|
|
|
|
2018-01-30 22:51:15 -08:00
|
|
|
|
pub fn changeCurDir(allocator: &Allocator, dir_path: []const u8) !void {
|
2018-01-07 13:51:46 -08:00
|
|
|
|
const path_buf = try allocator.alloc(u8, dir_path.len + 1);
|
2017-04-30 15:56:24 -07:00
|
|
|
|
defer allocator.free(path_buf);
|
|
|
|
|
|
|
|
|
|
mem.copy(u8, path_buf, dir_path);
|
|
|
|
|
path_buf[dir_path.len] = 0;
|
|
|
|
|
|
|
|
|
|
const err = posix.getErrno(posix.chdir(path_buf.ptr));
|
|
|
|
|
if (err > 0) {
|
|
|
|
|
return switch (err) {
|
2017-08-27 02:15:24 -07:00
|
|
|
|
posix.EACCES => error.AccessDenied,
|
|
|
|
|
posix.EFAULT => unreachable,
|
|
|
|
|
posix.EIO => error.FileSystem,
|
|
|
|
|
posix.ELOOP => error.SymLinkLoop,
|
|
|
|
|
posix.ENAMETOOLONG => error.NameTooLong,
|
|
|
|
|
posix.ENOENT => error.FileNotFound,
|
|
|
|
|
posix.ENOMEM => error.SystemResources,
|
|
|
|
|
posix.ENOTDIR => error.NotDir,
|
2017-10-15 13:45:43 -07:00
|
|
|
|
else => unexpectedErrorPosix(err),
|
2017-04-30 15:56:24 -07:00
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Read value of a symbolic link.
|
2018-02-03 11:06:37 -08:00
|
|
|
|
pub fn readLink(allocator: &Allocator, pathname: []const u8) ![]u8 {
|
2018-01-07 13:51:46 -08:00
|
|
|
|
const path_buf = try allocator.alloc(u8, pathname.len + 1);
|
2017-04-30 15:56:24 -07:00
|
|
|
|
defer allocator.free(path_buf);
|
|
|
|
|
|
|
|
|
|
mem.copy(u8, path_buf, pathname);
|
|
|
|
|
path_buf[pathname.len] = 0;
|
|
|
|
|
|
2018-01-07 13:51:46 -08:00
|
|
|
|
var result_buf = try allocator.alloc(u8, 1024);
|
2018-01-23 20:08:09 -08:00
|
|
|
|
errdefer allocator.free(result_buf);
|
2017-04-30 15:56:24 -07:00
|
|
|
|
while (true) {
|
|
|
|
|
const ret_val = posix.readlink(path_buf.ptr, result_buf.ptr, result_buf.len);
|
|
|
|
|
const err = posix.getErrno(ret_val);
|
|
|
|
|
if (err > 0) {
|
|
|
|
|
return switch (err) {
|
2017-08-27 02:15:24 -07:00
|
|
|
|
posix.EACCES => error.AccessDenied,
|
|
|
|
|
posix.EFAULT, posix.EINVAL => unreachable,
|
|
|
|
|
posix.EIO => error.FileSystem,
|
|
|
|
|
posix.ELOOP => error.SymLinkLoop,
|
|
|
|
|
posix.ENAMETOOLONG => error.NameTooLong,
|
|
|
|
|
posix.ENOENT => error.FileNotFound,
|
|
|
|
|
posix.ENOMEM => error.SystemResources,
|
|
|
|
|
posix.ENOTDIR => error.NotDir,
|
2017-10-15 13:45:43 -07:00
|
|
|
|
else => unexpectedErrorPosix(err),
|
2017-04-30 15:56:24 -07:00
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
if (ret_val == result_buf.len) {
|
2018-01-07 13:51:46 -08:00
|
|
|
|
result_buf = try allocator.realloc(u8, result_buf, result_buf.len * 2);
|
2017-04-30 15:56:24 -07:00
|
|
|
|
continue;
|
|
|
|
|
}
|
2017-10-09 11:21:35 -07:00
|
|
|
|
return allocator.shrink(u8, result_buf, ret_val);
|
2017-04-30 15:56:24 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
2017-09-06 13:59:22 -07:00
|
|
|
|
|
2018-01-25 01:10:11 -08:00
|
|
|
|
pub fn sleep(seconds: usize, nanoseconds: usize) void {
|
2017-09-19 06:46:41 -07:00
|
|
|
|
switch(builtin.os) {
|
2018-01-06 20:10:53 -08:00
|
|
|
|
Os.linux, Os.macosx, Os.ios => {
|
2017-09-19 06:46:41 -07:00
|
|
|
|
posixSleep(u63(seconds), u63(nanoseconds));
|
|
|
|
|
},
|
|
|
|
|
Os.windows => {
|
|
|
|
|
const milliseconds = seconds * 1000 + nanoseconds / 1000000;
|
|
|
|
|
windows.Sleep(windows.DWORD(milliseconds));
|
|
|
|
|
},
|
|
|
|
|
else => @compileError("Unsupported OS"),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const u63 = @IntType(false, 63);
|
2018-01-25 01:10:11 -08:00
|
|
|
|
pub fn posixSleep(seconds: u63, nanoseconds: u63) void {
|
2017-09-06 13:59:22 -07:00
|
|
|
|
var req = posix.timespec {
|
|
|
|
|
.tv_sec = seconds,
|
|
|
|
|
.tv_nsec = nanoseconds,
|
|
|
|
|
};
|
|
|
|
|
var rem: posix.timespec = undefined;
|
|
|
|
|
while (true) {
|
|
|
|
|
const ret_val = posix.nanosleep(&req, &rem);
|
|
|
|
|
const err = posix.getErrno(ret_val);
|
|
|
|
|
if (err == 0) return;
|
|
|
|
|
switch (err) {
|
|
|
|
|
posix.EFAULT => unreachable,
|
2017-09-19 06:46:41 -07:00
|
|
|
|
posix.EINVAL => {
|
|
|
|
|
// Sometimes Darwin returns EINVAL for no reason.
|
|
|
|
|
// We treat it as a spurious wakeup.
|
|
|
|
|
return;
|
|
|
|
|
},
|
2017-09-06 13:59:22 -07:00
|
|
|
|
posix.EINTR => {
|
|
|
|
|
req = rem;
|
|
|
|
|
continue;
|
|
|
|
|
},
|
|
|
|
|
else => return,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-09-19 06:46:41 -07:00
|
|
|
|
|
|
|
|
|
test "os.sleep" {
|
|
|
|
|
sleep(0, 1);
|
|
|
|
|
}
|
2017-09-25 22:01:49 -07:00
|
|
|
|
|
2018-01-30 22:51:15 -08:00
|
|
|
|
pub fn posix_setuid(uid: u32) !void {
|
2017-09-25 22:01:49 -07:00
|
|
|
|
const err = posix.getErrno(posix.setuid(uid));
|
|
|
|
|
if (err == 0) return;
|
|
|
|
|
return switch (err) {
|
|
|
|
|
posix.EAGAIN => error.ResourceLimitReached,
|
|
|
|
|
posix.EINVAL => error.InvalidUserId,
|
|
|
|
|
posix.EPERM => error.PermissionDenied,
|
2017-10-15 13:45:43 -07:00
|
|
|
|
else => unexpectedErrorPosix(err),
|
2017-09-25 22:01:49 -07:00
|
|
|
|
};
|
|
|
|
|
}
|
2017-09-25 23:42:06 -07:00
|
|
|
|
|
2018-01-30 22:51:15 -08:00
|
|
|
|
pub fn posix_setreuid(ruid: u32, euid: u32) !void {
|
2017-09-25 23:42:06 -07:00
|
|
|
|
const err = posix.getErrno(posix.setreuid(ruid, euid));
|
|
|
|
|
if (err == 0) return;
|
|
|
|
|
return switch (err) {
|
|
|
|
|
posix.EAGAIN => error.ResourceLimitReached,
|
|
|
|
|
posix.EINVAL => error.InvalidUserId,
|
|
|
|
|
posix.EPERM => error.PermissionDenied,
|
2017-10-15 13:45:43 -07:00
|
|
|
|
else => unexpectedErrorPosix(err),
|
2017-09-25 23:42:06 -07:00
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-30 22:51:15 -08:00
|
|
|
|
pub fn posix_setgid(gid: u32) !void {
|
2017-09-25 23:42:06 -07:00
|
|
|
|
const err = posix.getErrno(posix.setgid(gid));
|
|
|
|
|
if (err == 0) return;
|
|
|
|
|
return switch (err) {
|
|
|
|
|
posix.EAGAIN => error.ResourceLimitReached,
|
|
|
|
|
posix.EINVAL => error.InvalidUserId,
|
|
|
|
|
posix.EPERM => error.PermissionDenied,
|
2017-10-15 13:45:43 -07:00
|
|
|
|
else => unexpectedErrorPosix(err),
|
2017-09-25 23:42:06 -07:00
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-30 22:51:15 -08:00
|
|
|
|
pub fn posix_setregid(rgid: u32, egid: u32) !void {
|
2017-09-25 23:42:06 -07:00
|
|
|
|
const err = posix.getErrno(posix.setregid(rgid, egid));
|
|
|
|
|
if (err == 0) return;
|
|
|
|
|
return switch (err) {
|
|
|
|
|
posix.EAGAIN => error.ResourceLimitReached,
|
|
|
|
|
posix.EINVAL => error.InvalidUserId,
|
|
|
|
|
posix.EPERM => error.PermissionDenied,
|
2017-10-15 13:45:43 -07:00
|
|
|
|
else => unexpectedErrorPosix(err),
|
2017-09-25 23:42:06 -07:00
|
|
|
|
};
|
|
|
|
|
}
|
2017-10-08 18:16:10 -07:00
|
|
|
|
|
2018-02-01 07:23:25 -08:00
|
|
|
|
pub const WindowsGetStdHandleErrs = error {
|
|
|
|
|
NoStdHandles,
|
|
|
|
|
Unexpected,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pub fn windowsGetStdHandle(handle_id: windows.DWORD) WindowsGetStdHandleErrs!windows.HANDLE {
|
2017-10-31 01:47:55 -07:00
|
|
|
|
if (windows.GetStdHandle(handle_id)) |handle| {
|
|
|
|
|
if (handle == windows.INVALID_HANDLE_VALUE) {
|
|
|
|
|
const err = windows.GetLastError();
|
|
|
|
|
return switch (err) {
|
|
|
|
|
else => os.unexpectedErrorWindows(err),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
return handle;
|
|
|
|
|
} else {
|
|
|
|
|
return error.NoStdHandles;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-11 07:16:13 -07:00
|
|
|
|
pub const ArgIteratorPosix = struct {
|
|
|
|
|
index: usize,
|
|
|
|
|
count: usize,
|
|
|
|
|
|
2018-01-25 01:10:11 -08:00
|
|
|
|
pub fn init() ArgIteratorPosix {
|
2017-10-11 07:16:13 -07:00
|
|
|
|
return ArgIteratorPosix {
|
|
|
|
|
.index = 0,
|
|
|
|
|
.count = raw.len,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-25 01:10:11 -08:00
|
|
|
|
pub fn next(self: &ArgIteratorPosix) ?[]const u8 {
|
2017-10-11 07:16:13 -07:00
|
|
|
|
if (self.index == self.count)
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
const s = raw[self.index];
|
|
|
|
|
self.index += 1;
|
|
|
|
|
return cstr.toSlice(s);
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-25 01:10:11 -08:00
|
|
|
|
pub fn skip(self: &ArgIteratorPosix) bool {
|
2017-10-11 07:16:13 -07:00
|
|
|
|
if (self.index == self.count)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
self.index += 1;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// This is marked as public but actually it's only meant to be used
|
|
|
|
|
/// internally by zig's startup code.
|
|
|
|
|
pub var raw: []&u8 = undefined;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pub const ArgIteratorWindows = struct {
|
|
|
|
|
index: usize,
|
|
|
|
|
cmd_line: &const u8,
|
|
|
|
|
in_quote: bool,
|
|
|
|
|
quote_count: usize,
|
|
|
|
|
seen_quote_count: usize,
|
|
|
|
|
|
2018-02-07 23:08:45 -08:00
|
|
|
|
pub const NextError = error{OutOfMemory};
|
|
|
|
|
|
2018-01-25 01:10:11 -08:00
|
|
|
|
pub fn init() ArgIteratorWindows {
|
2017-10-11 07:16:13 -07:00
|
|
|
|
return initWithCmdLine(windows.GetCommandLineA());
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-25 01:10:11 -08:00
|
|
|
|
pub fn initWithCmdLine(cmd_line: &const u8) ArgIteratorWindows {
|
2017-10-11 07:16:13 -07:00
|
|
|
|
return ArgIteratorWindows {
|
|
|
|
|
.index = 0,
|
|
|
|
|
.cmd_line = cmd_line,
|
|
|
|
|
.in_quote = false,
|
|
|
|
|
.quote_count = countQuotes(cmd_line),
|
|
|
|
|
.seen_quote_count = 0,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// You must free the returned memory when done.
|
2018-02-07 23:08:45 -08:00
|
|
|
|
pub fn next(self: &ArgIteratorWindows, allocator: &Allocator) ?(NextError![]u8) {
|
2017-10-11 07:16:13 -07:00
|
|
|
|
// march forward over whitespace
|
|
|
|
|
while (true) : (self.index += 1) {
|
|
|
|
|
const byte = self.cmd_line[self.index];
|
|
|
|
|
switch (byte) {
|
|
|
|
|
0 => return null,
|
|
|
|
|
' ', '\t' => continue,
|
|
|
|
|
else => break,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return self.internalNext(allocator);
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-25 01:10:11 -08:00
|
|
|
|
pub fn skip(self: &ArgIteratorWindows) bool {
|
2017-10-11 07:16:13 -07:00
|
|
|
|
// march forward over whitespace
|
|
|
|
|
while (true) : (self.index += 1) {
|
|
|
|
|
const byte = self.cmd_line[self.index];
|
|
|
|
|
switch (byte) {
|
|
|
|
|
0 => return false,
|
|
|
|
|
' ', '\t' => continue,
|
|
|
|
|
else => break,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-15 17:15:01 -07:00
|
|
|
|
var backslash_count: usize = 0;
|
2017-10-11 07:16:13 -07:00
|
|
|
|
while (true) : (self.index += 1) {
|
|
|
|
|
const byte = self.cmd_line[self.index];
|
|
|
|
|
switch (byte) {
|
|
|
|
|
0 => return true,
|
|
|
|
|
'"' => {
|
2017-10-15 17:15:01 -07:00
|
|
|
|
const quote_is_real = backslash_count % 2 == 0;
|
2017-10-11 07:16:13 -07:00
|
|
|
|
if (quote_is_real) {
|
|
|
|
|
self.seen_quote_count += 1;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
'\\' => {
|
2017-10-15 17:15:01 -07:00
|
|
|
|
backslash_count += 1;
|
2017-10-11 07:16:13 -07:00
|
|
|
|
},
|
|
|
|
|
' ', '\t' => {
|
|
|
|
|
if (self.seen_quote_count % 2 == 0 or self.seen_quote_count == self.quote_count) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2017-10-15 17:15:01 -07:00
|
|
|
|
backslash_count = 0;
|
|
|
|
|
},
|
|
|
|
|
else => {
|
|
|
|
|
backslash_count = 0;
|
|
|
|
|
continue;
|
2017-10-11 07:16:13 -07:00
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-07 23:08:45 -08:00
|
|
|
|
fn internalNext(self: &ArgIteratorWindows, allocator: &Allocator) NextError![]u8 {
|
2018-01-07 13:51:46 -08:00
|
|
|
|
var buf = try Buffer.initSize(allocator, 0);
|
2017-10-11 07:16:13 -07:00
|
|
|
|
defer buf.deinit();
|
|
|
|
|
|
2017-10-15 17:15:01 -07:00
|
|
|
|
var backslash_count: usize = 0;
|
2017-10-11 07:16:13 -07:00
|
|
|
|
while (true) : (self.index += 1) {
|
|
|
|
|
const byte = self.cmd_line[self.index];
|
|
|
|
|
switch (byte) {
|
|
|
|
|
0 => return buf.toOwnedSlice(),
|
|
|
|
|
'"' => {
|
2017-10-15 17:15:01 -07:00
|
|
|
|
const quote_is_real = backslash_count % 2 == 0;
|
2018-01-07 13:51:46 -08:00
|
|
|
|
try self.emitBackslashes(&buf, backslash_count / 2);
|
2017-10-15 17:15:01 -07:00
|
|
|
|
backslash_count = 0;
|
2017-10-11 07:16:13 -07:00
|
|
|
|
|
|
|
|
|
if (quote_is_real) {
|
|
|
|
|
self.seen_quote_count += 1;
|
|
|
|
|
if (self.seen_quote_count == self.quote_count and self.seen_quote_count % 2 == 1) {
|
2018-01-07 13:51:46 -08:00
|
|
|
|
try buf.appendByte('"');
|
2017-10-11 07:16:13 -07:00
|
|
|
|
}
|
|
|
|
|
} else {
|
2018-01-07 13:51:46 -08:00
|
|
|
|
try buf.appendByte('"');
|
2017-10-11 07:16:13 -07:00
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
'\\' => {
|
2017-10-15 17:15:01 -07:00
|
|
|
|
backslash_count += 1;
|
2017-10-11 07:16:13 -07:00
|
|
|
|
},
|
|
|
|
|
' ', '\t' => {
|
2018-01-07 13:51:46 -08:00
|
|
|
|
try self.emitBackslashes(&buf, backslash_count);
|
2017-10-15 17:15:01 -07:00
|
|
|
|
backslash_count = 0;
|
2017-10-11 07:16:13 -07:00
|
|
|
|
if (self.seen_quote_count % 2 == 1 and self.seen_quote_count != self.quote_count) {
|
2018-01-07 13:51:46 -08:00
|
|
|
|
try buf.appendByte(byte);
|
2017-10-11 07:16:13 -07:00
|
|
|
|
} else {
|
|
|
|
|
return buf.toOwnedSlice();
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
else => {
|
2018-01-07 13:51:46 -08:00
|
|
|
|
try self.emitBackslashes(&buf, backslash_count);
|
2017-10-15 17:15:01 -07:00
|
|
|
|
backslash_count = 0;
|
2018-01-07 13:51:46 -08:00
|
|
|
|
try buf.appendByte(byte);
|
2017-10-11 07:16:13 -07:00
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-30 22:51:15 -08:00
|
|
|
|
fn emitBackslashes(self: &ArgIteratorWindows, buf: &Buffer, emit_count: usize) !void {
|
2017-10-11 07:16:13 -07:00
|
|
|
|
var i: usize = 0;
|
|
|
|
|
while (i < emit_count) : (i += 1) {
|
2018-01-07 13:51:46 -08:00
|
|
|
|
try buf.appendByte('\\');
|
2017-10-11 07:16:13 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-25 01:10:11 -08:00
|
|
|
|
fn countQuotes(cmd_line: &const u8) usize {
|
2017-10-11 07:16:13 -07:00
|
|
|
|
var result: usize = 0;
|
|
|
|
|
var backslash_count: usize = 0;
|
|
|
|
|
var index: usize = 0;
|
|
|
|
|
while (true) : (index += 1) {
|
|
|
|
|
const byte = cmd_line[index];
|
|
|
|
|
switch (byte) {
|
|
|
|
|
0 => return result,
|
|
|
|
|
'\\' => backslash_count += 1,
|
|
|
|
|
'"' => {
|
|
|
|
|
result += 1 - (backslash_count % 2);
|
|
|
|
|
backslash_count = 0;
|
|
|
|
|
},
|
|
|
|
|
else => {
|
|
|
|
|
backslash_count = 0;
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pub const ArgIterator = struct {
|
2018-02-07 23:08:45 -08:00
|
|
|
|
const InnerType = if (builtin.os == Os.windows) ArgIteratorWindows else ArgIteratorPosix;
|
|
|
|
|
|
|
|
|
|
inner: InnerType,
|
2017-10-11 07:16:13 -07:00
|
|
|
|
|
2018-01-25 01:10:11 -08:00
|
|
|
|
pub fn init() ArgIterator {
|
2017-10-11 07:16:13 -07:00
|
|
|
|
return ArgIterator {
|
2018-02-07 23:08:45 -08:00
|
|
|
|
.inner = InnerType.init(),
|
2017-10-11 07:16:13 -07:00
|
|
|
|
};
|
|
|
|
|
}
|
2018-02-07 23:08:45 -08:00
|
|
|
|
|
|
|
|
|
pub const NextError = ArgIteratorWindows.NextError;
|
2017-10-11 07:16:13 -07:00
|
|
|
|
|
|
|
|
|
/// You must free the returned memory when done.
|
2018-02-07 23:08:45 -08:00
|
|
|
|
pub fn next(self: &ArgIterator, allocator: &Allocator) ?(NextError![]u8) {
|
2017-10-11 07:16:13 -07:00
|
|
|
|
if (builtin.os == Os.windows) {
|
|
|
|
|
return self.inner.next(allocator);
|
|
|
|
|
} else {
|
|
|
|
|
return mem.dupe(allocator, u8, self.inner.next() ?? return null);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// If you only are targeting posix you can call this and not need an allocator.
|
2018-01-25 01:10:11 -08:00
|
|
|
|
pub fn nextPosix(self: &ArgIterator) ?[]const u8 {
|
2017-10-11 07:16:13 -07:00
|
|
|
|
return self.inner.next();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Parse past 1 argument without capturing it.
|
|
|
|
|
/// Returns `true` if skipped an arg, `false` if we are at the end.
|
2018-01-25 01:10:11 -08:00
|
|
|
|
pub fn skip(self: &ArgIterator) bool {
|
2017-10-11 07:16:13 -07:00
|
|
|
|
return self.inner.skip();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2018-01-25 01:10:11 -08:00
|
|
|
|
pub fn args() ArgIterator {
|
2017-10-11 07:16:13 -07:00
|
|
|
|
return ArgIterator.init();
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-06 15:12:05 -08:00
|
|
|
|
/// Caller must call freeArgs on result.
|
2018-01-30 22:51:15 -08:00
|
|
|
|
pub fn argsAlloc(allocator: &mem.Allocator) ![]const []u8 {
|
2017-12-06 15:12:05 -08:00
|
|
|
|
// TODO refactor to only make 1 allocation.
|
|
|
|
|
var it = args();
|
2018-01-07 13:51:46 -08:00
|
|
|
|
var contents = try Buffer.initSize(allocator, 0);
|
2017-12-06 15:12:05 -08:00
|
|
|
|
defer contents.deinit();
|
|
|
|
|
|
|
|
|
|
var slice_list = ArrayList(usize).init(allocator);
|
|
|
|
|
defer slice_list.deinit();
|
|
|
|
|
|
|
|
|
|
while (it.next(allocator)) |arg_or_err| {
|
2018-01-07 13:51:46 -08:00
|
|
|
|
const arg = try arg_or_err;
|
2017-12-06 15:12:05 -08:00
|
|
|
|
defer allocator.free(arg);
|
2018-01-07 13:51:46 -08:00
|
|
|
|
try contents.append(arg);
|
|
|
|
|
try slice_list.append(arg.len);
|
2017-12-06 15:12:05 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const contents_slice = contents.toSliceConst();
|
|
|
|
|
const slice_sizes = slice_list.toSliceConst();
|
2018-01-07 13:51:46 -08:00
|
|
|
|
const slice_list_bytes = try math.mul(usize, @sizeOf([]u8), slice_sizes.len);
|
|
|
|
|
const total_bytes = try math.add(usize, slice_list_bytes, contents_slice.len);
|
|
|
|
|
const buf = try allocator.alignedAlloc(u8, @alignOf([]u8), total_bytes);
|
2018-01-23 20:08:09 -08:00
|
|
|
|
errdefer allocator.free(buf);
|
2017-12-06 15:12:05 -08:00
|
|
|
|
|
|
|
|
|
const result_slice_list = ([][]u8)(buf[0..slice_list_bytes]);
|
|
|
|
|
const result_contents = buf[slice_list_bytes..];
|
|
|
|
|
mem.copy(u8, result_contents, contents_slice);
|
|
|
|
|
|
|
|
|
|
var contents_index: usize = 0;
|
|
|
|
|
for (slice_sizes) |len, i| {
|
|
|
|
|
const new_index = contents_index + len;
|
|
|
|
|
result_slice_list[i] = result_contents[contents_index..new_index];
|
|
|
|
|
contents_index = new_index;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result_slice_list;
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-25 01:10:11 -08:00
|
|
|
|
pub fn argsFree(allocator: &mem.Allocator, args_alloc: []const []u8) void {
|
2017-12-06 15:12:05 -08:00
|
|
|
|
var total_bytes: usize = 0;
|
|
|
|
|
for (args_alloc) |arg| {
|
|
|
|
|
total_bytes += @sizeOf([]u8) + arg.len;
|
|
|
|
|
}
|
2018-03-06 13:37:03 -08:00
|
|
|
|
const unaligned_allocated_buf = @ptrCast(&const u8, args_alloc.ptr)[0..total_bytes];
|
2017-12-06 15:12:05 -08:00
|
|
|
|
const aligned_allocated_buf = @alignCast(@alignOf([]u8), unaligned_allocated_buf);
|
|
|
|
|
return allocator.free(aligned_allocated_buf);
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-11 07:16:13 -07:00
|
|
|
|
test "windows arg parsing" {
|
|
|
|
|
testWindowsCmdLine(c"a b\tc d", [][]const u8{"a", "b", "c", "d"});
|
|
|
|
|
testWindowsCmdLine(c"\"abc\" d e", [][]const u8{"abc", "d", "e"});
|
|
|
|
|
testWindowsCmdLine(c"a\\\\\\b d\"e f\"g h", [][]const u8{"a\\\\\\b", "de fg", "h"});
|
|
|
|
|
testWindowsCmdLine(c"a\\\\\\\"b c d", [][]const u8{"a\\\"b", "c", "d"});
|
|
|
|
|
testWindowsCmdLine(c"a\\\\\\\\\"b c\" d e", [][]const u8{"a\\\\b c", "d", "e"});
|
|
|
|
|
testWindowsCmdLine(c"a b\tc \"d f", [][]const u8{"a", "b", "c", "\"d", "f"});
|
2017-10-15 17:15:01 -07:00
|
|
|
|
|
|
|
|
|
testWindowsCmdLine(c"\".\\..\\zig-cache\\build\" \"bin\\zig.exe\" \".\\..\" \".\\..\\zig-cache\" \"--help\"",
|
|
|
|
|
[][]const u8{".\\..\\zig-cache\\build", "bin\\zig.exe", ".\\..", ".\\..\\zig-cache", "--help"});
|
2017-10-11 07:16:13 -07:00
|
|
|
|
}
|
|
|
|
|
|
2018-01-25 01:10:11 -08:00
|
|
|
|
fn testWindowsCmdLine(input_cmd_line: &const u8, expected_args: []const []const u8) void {
|
2017-10-11 07:16:13 -07:00
|
|
|
|
var it = ArgIteratorWindows.initWithCmdLine(input_cmd_line);
|
|
|
|
|
for (expected_args) |expected_arg| {
|
2018-01-08 21:07:01 -08:00
|
|
|
|
const arg = ??it.next(debug.global_allocator) catch unreachable;
|
2017-10-11 07:16:13 -07:00
|
|
|
|
assert(mem.eql(u8, arg, expected_arg));
|
|
|
|
|
}
|
2017-11-10 11:02:45 -08:00
|
|
|
|
assert(it.next(debug.global_allocator) == null);
|
2017-10-11 07:16:13 -07:00
|
|
|
|
}
|
|
|
|
|
|
2017-10-15 13:45:43 -07:00
|
|
|
|
// TODO make this a build variable that you can set
|
2018-03-29 01:23:44 -07:00
|
|
|
|
const unexpected_error_tracing = false;
|
2018-03-07 00:55:52 -08:00
|
|
|
|
const UnexpectedError = error {
|
|
|
|
|
/// The Operating System returned an undocumented error code.
|
|
|
|
|
Unexpected,
|
|
|
|
|
};
|
2017-10-15 13:45:43 -07:00
|
|
|
|
|
|
|
|
|
/// Call this when you made a syscall or something that sets errno
|
|
|
|
|
/// and you get an unexpected error.
|
2018-03-07 00:55:52 -08:00
|
|
|
|
pub fn unexpectedErrorPosix(errno: usize) UnexpectedError {
|
2017-10-15 13:45:43 -07:00
|
|
|
|
if (unexpected_error_tracing) {
|
2018-01-03 19:38:13 -08:00
|
|
|
|
debug.warn("unexpected errno: {}\n", errno);
|
2018-03-27 07:43:51 -07:00
|
|
|
|
debug.dumpCurrentStackTrace(null);
|
2017-10-15 13:45:43 -07:00
|
|
|
|
}
|
|
|
|
|
return error.Unexpected;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Call this when you made a windows DLL call or something that does SetLastError
|
|
|
|
|
/// and you get an unexpected error.
|
2018-03-07 00:55:52 -08:00
|
|
|
|
pub fn unexpectedErrorWindows(err: windows.DWORD) UnexpectedError {
|
2017-10-15 13:45:43 -07:00
|
|
|
|
if (unexpected_error_tracing) {
|
2018-01-03 19:38:13 -08:00
|
|
|
|
debug.warn("unexpected GetLastError(): {}\n", err);
|
2018-03-27 07:43:51 -07:00
|
|
|
|
debug.dumpCurrentStackTrace(null);
|
2017-10-15 13:45:43 -07:00
|
|
|
|
}
|
|
|
|
|
return error.Unexpected;
|
|
|
|
|
}
|
2017-10-31 01:47:55 -07:00
|
|
|
|
|
2018-02-10 17:55:13 -08:00
|
|
|
|
pub fn openSelfExe() !os.File {
|
2017-10-31 01:47:55 -07:00
|
|
|
|
switch (builtin.os) {
|
|
|
|
|
Os.linux => {
|
2018-02-09 15:27:50 -08:00
|
|
|
|
const proc_file_path = "/proc/self/exe";
|
|
|
|
|
var fixed_buffer_mem: [proc_file_path.len + 1]u8 = undefined;
|
2018-02-11 23:27:02 -08:00
|
|
|
|
var fixed_allocator = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]);
|
2018-02-10 17:55:13 -08:00
|
|
|
|
return os.File.openRead(&fixed_allocator.allocator, proc_file_path);
|
2017-10-31 01:47:55 -07:00
|
|
|
|
},
|
2018-01-06 20:10:53 -08:00
|
|
|
|
Os.macosx, Os.ios => {
|
2018-02-09 15:27:50 -08:00
|
|
|
|
var fixed_buffer_mem: [darwin.PATH_MAX * 2]u8 = undefined;
|
2018-02-11 23:27:02 -08:00
|
|
|
|
var fixed_allocator = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]);
|
2018-02-07 15:14:32 -08:00
|
|
|
|
const self_exe_path = try selfExePath(&fixed_allocator.allocator);
|
2018-02-10 17:55:13 -08:00
|
|
|
|
return os.File.openRead(&fixed_allocator.allocator, self_exe_path);
|
2017-10-31 01:47:55 -07:00
|
|
|
|
},
|
|
|
|
|
else => @compileError("Unsupported OS"),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-07 15:14:32 -08:00
|
|
|
|
test "openSelfExe" {
|
|
|
|
|
switch (builtin.os) {
|
|
|
|
|
Os.linux, Os.macosx, Os.ios => (try openSelfExe()).close(),
|
|
|
|
|
else => return, // Unsupported OS.
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-04 10:48:45 -08:00
|
|
|
|
/// Get the path to the current executable.
|
|
|
|
|
/// If you only need the directory, use selfExeDirPath.
|
|
|
|
|
/// If you only want an open file handle, use openSelfExe.
|
|
|
|
|
/// This function may return an error if the current executable
|
|
|
|
|
/// was deleted after spawning.
|
|
|
|
|
/// Caller owns returned memory.
|
2018-01-30 22:51:15 -08:00
|
|
|
|
pub fn selfExePath(allocator: &mem.Allocator) ![]u8 {
|
2018-01-04 10:48:45 -08:00
|
|
|
|
switch (builtin.os) {
|
|
|
|
|
Os.linux => {
|
2018-01-04 12:30:22 -08:00
|
|
|
|
// If the currently executing binary has been deleted,
|
|
|
|
|
// the file path looks something like `/a/b/c/exe (deleted)`
|
|
|
|
|
return readLink(allocator, "/proc/self/exe");
|
2018-01-04 10:48:45 -08:00
|
|
|
|
},
|
|
|
|
|
Os.windows => {
|
2018-01-07 13:51:46 -08:00
|
|
|
|
var out_path = try Buffer.initSize(allocator, 0xff);
|
2018-01-23 20:08:09 -08:00
|
|
|
|
errdefer out_path.deinit();
|
2018-01-04 10:48:45 -08:00
|
|
|
|
while (true) {
|
2018-01-07 13:51:46 -08:00
|
|
|
|
const dword_len = try math.cast(windows.DWORD, out_path.len());
|
2018-01-04 10:48:45 -08:00
|
|
|
|
const copied_amt = windows.GetModuleFileNameA(null, out_path.ptr(), dword_len);
|
|
|
|
|
if (copied_amt <= 0) {
|
|
|
|
|
const err = windows.GetLastError();
|
|
|
|
|
return switch (err) {
|
|
|
|
|
else => unexpectedErrorWindows(err),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
if (copied_amt < out_path.len()) {
|
|
|
|
|
out_path.shrink(copied_amt);
|
|
|
|
|
return out_path.toOwnedSlice();
|
|
|
|
|
}
|
2018-01-04 13:36:59 -08:00
|
|
|
|
const new_len = (out_path.len() << 1) | 0b1;
|
2018-01-07 13:51:46 -08:00
|
|
|
|
try out_path.resize(new_len);
|
2018-01-04 10:48:45 -08:00
|
|
|
|
}
|
|
|
|
|
},
|
2018-01-06 20:10:53 -08:00
|
|
|
|
Os.macosx, Os.ios => {
|
2018-01-04 12:30:22 -08:00
|
|
|
|
var u32_len: u32 = 0;
|
|
|
|
|
const ret1 = c._NSGetExecutablePath(undefined, &u32_len);
|
|
|
|
|
assert(ret1 != 0);
|
2018-01-07 13:51:46 -08:00
|
|
|
|
const bytes = try allocator.alloc(u8, u32_len);
|
2018-01-23 20:08:09 -08:00
|
|
|
|
errdefer allocator.free(bytes);
|
2018-01-04 12:30:22 -08:00
|
|
|
|
const ret2 = c._NSGetExecutablePath(bytes.ptr, &u32_len);
|
|
|
|
|
assert(ret2 == 0);
|
|
|
|
|
return bytes;
|
|
|
|
|
},
|
2018-01-04 10:48:45 -08:00
|
|
|
|
else => @compileError("Unsupported OS"),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-22 21:29:39 -08:00
|
|
|
|
/// Get the directory path that contains the current executable.
|
|
|
|
|
/// Caller owns returned memory.
|
2018-01-30 22:51:15 -08:00
|
|
|
|
pub fn selfExeDirPath(allocator: &mem.Allocator) ![]u8 {
|
2017-12-22 21:29:39 -08:00
|
|
|
|
switch (builtin.os) {
|
|
|
|
|
Os.linux => {
|
|
|
|
|
// If the currently executing binary has been deleted,
|
|
|
|
|
// the file path looks something like `/a/b/c/exe (deleted)`
|
|
|
|
|
// This path cannot be opened, but it's valid for determining the directory
|
|
|
|
|
// the executable was in when it was run.
|
2018-01-07 13:51:46 -08:00
|
|
|
|
const full_exe_path = try readLink(allocator, "/proc/self/exe");
|
2018-01-23 20:08:09 -08:00
|
|
|
|
errdefer allocator.free(full_exe_path);
|
2017-12-22 21:29:39 -08:00
|
|
|
|
const dir = path.dirname(full_exe_path);
|
|
|
|
|
return allocator.shrink(u8, full_exe_path, dir.len);
|
|
|
|
|
},
|
2018-01-06 20:10:53 -08:00
|
|
|
|
Os.windows, Os.macosx, Os.ios => {
|
2018-01-07 13:51:46 -08:00
|
|
|
|
const self_exe_path = try selfExePath(allocator);
|
2018-01-23 20:08:09 -08:00
|
|
|
|
errdefer allocator.free(self_exe_path);
|
2018-01-04 10:48:45 -08:00
|
|
|
|
const dirname = os.path.dirname(self_exe_path);
|
|
|
|
|
return allocator.shrink(u8, self_exe_path, dirname.len);
|
2017-12-22 21:29:39 -08:00
|
|
|
|
},
|
|
|
|
|
else => @compileError("unimplemented: std.os.selfExeDirPath for " ++ @tagName(builtin.os)),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-25 01:10:11 -08:00
|
|
|
|
pub fn isTty(handle: FileHandle) bool {
|
2017-10-31 01:47:55 -07:00
|
|
|
|
if (is_windows) {
|
|
|
|
|
return windows_util.windowsIsTty(handle);
|
|
|
|
|
} else {
|
|
|
|
|
if (builtin.link_libc) {
|
|
|
|
|
return c.isatty(handle) != 0;
|
|
|
|
|
} else {
|
|
|
|
|
return posix.isatty(handle);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-03-07 00:55:52 -08:00
|
|
|
|
|
|
|
|
|
pub const PosixSocketError = error {
|
|
|
|
|
/// Permission to create a socket of the specified type and/or
|
|
|
|
|
/// pro‐tocol is denied.
|
|
|
|
|
PermissionDenied,
|
|
|
|
|
|
|
|
|
|
/// The implementation does not support the specified address family.
|
|
|
|
|
AddressFamilyNotSupported,
|
|
|
|
|
|
|
|
|
|
/// Unknown protocol, or protocol family not available.
|
|
|
|
|
ProtocolFamilyNotAvailable,
|
|
|
|
|
|
|
|
|
|
/// The per-process limit on the number of open file descriptors has been reached.
|
|
|
|
|
ProcessFdQuotaExceeded,
|
|
|
|
|
|
|
|
|
|
/// The system-wide limit on the total number of open files has been reached.
|
|
|
|
|
SystemFdQuotaExceeded,
|
|
|
|
|
|
|
|
|
|
/// Insufficient memory is available. The socket cannot be created until sufficient
|
|
|
|
|
/// resources are freed.
|
|
|
|
|
SystemResources,
|
|
|
|
|
|
|
|
|
|
/// The protocol type or the specified protocol is not supported within this domain.
|
|
|
|
|
ProtocolNotSupported,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pub fn posixSocket(domain: u32, socket_type: u32, protocol: u32) !i32 {
|
|
|
|
|
const rc = posix.socket(domain, socket_type, protocol);
|
|
|
|
|
const err = posix.getErrno(rc);
|
|
|
|
|
switch (err) {
|
|
|
|
|
0 => return i32(rc),
|
|
|
|
|
posix.EACCES => return PosixSocketError.PermissionDenied,
|
|
|
|
|
posix.EAFNOSUPPORT => return PosixSocketError.AddressFamilyNotSupported,
|
|
|
|
|
posix.EINVAL => return PosixSocketError.ProtocolFamilyNotAvailable,
|
|
|
|
|
posix.EMFILE => return PosixSocketError.ProcessFdQuotaExceeded,
|
|
|
|
|
posix.ENFILE => return PosixSocketError.SystemFdQuotaExceeded,
|
|
|
|
|
posix.ENOBUFS, posix.ENOMEM => return PosixSocketError.SystemResources,
|
|
|
|
|
posix.EPROTONOSUPPORT => return PosixSocketError.ProtocolNotSupported,
|
|
|
|
|
else => return unexpectedErrorPosix(err),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub const PosixBindError = error {
|
|
|
|
|
/// The address is protected, and the user is not the superuser.
|
|
|
|
|
/// For UNIX domain sockets: Search permission is denied on a component
|
|
|
|
|
/// of the path prefix.
|
|
|
|
|
AccessDenied,
|
|
|
|
|
|
|
|
|
|
/// The given address is already in use, or in the case of Internet domain sockets,
|
|
|
|
|
/// The port number was specified as zero in the socket
|
|
|
|
|
/// address structure, but, upon attempting to bind to an ephemeral port, it was
|
|
|
|
|
/// determined that all port numbers in the ephemeral port range are currently in
|
|
|
|
|
/// use. See the discussion of /proc/sys/net/ipv4/ip_local_port_range ip(7).
|
|
|
|
|
AddressInUse,
|
|
|
|
|
|
|
|
|
|
/// sockfd is not a valid file descriptor.
|
|
|
|
|
InvalidFileDescriptor,
|
|
|
|
|
|
|
|
|
|
/// The socket is already bound to an address, or addrlen is wrong, or addr is not
|
|
|
|
|
/// a valid address for this socket's domain.
|
|
|
|
|
InvalidSocketOrAddress,
|
|
|
|
|
|
|
|
|
|
/// The file descriptor sockfd does not refer to a socket.
|
|
|
|
|
FileDescriptorNotASocket,
|
|
|
|
|
|
|
|
|
|
/// A nonexistent interface was requested or the requested address was not local.
|
|
|
|
|
AddressNotAvailable,
|
|
|
|
|
|
|
|
|
|
/// addr points outside the user's accessible address space.
|
|
|
|
|
PageFault,
|
|
|
|
|
|
|
|
|
|
/// Too many symbolic links were encountered in resolving addr.
|
|
|
|
|
SymLinkLoop,
|
|
|
|
|
|
|
|
|
|
/// addr is too long.
|
|
|
|
|
NameTooLong,
|
|
|
|
|
|
|
|
|
|
/// A component in the directory prefix of the socket pathname does not exist.
|
|
|
|
|
FileNotFound,
|
|
|
|
|
|
|
|
|
|
/// Insufficient kernel memory was available.
|
|
|
|
|
SystemResources,
|
|
|
|
|
|
|
|
|
|
/// A component of the path prefix is not a directory.
|
|
|
|
|
NotDir,
|
|
|
|
|
|
|
|
|
|
/// The socket inode would reside on a read-only filesystem.
|
|
|
|
|
ReadOnlyFileSystem,
|
|
|
|
|
|
|
|
|
|
Unexpected,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/// addr is `&const T` where T is one of the sockaddr
|
|
|
|
|
pub fn posixBind(fd: i32, addr: &const posix.sockaddr) PosixBindError!void {
|
|
|
|
|
const rc = posix.bind(fd, addr, @sizeOf(posix.sockaddr));
|
|
|
|
|
const err = posix.getErrno(rc);
|
|
|
|
|
switch (err) {
|
|
|
|
|
0 => return,
|
|
|
|
|
posix.EACCES => return PosixBindError.AccessDenied,
|
|
|
|
|
posix.EADDRINUSE => return PosixBindError.AddressInUse,
|
|
|
|
|
posix.EBADF => return PosixBindError.InvalidFileDescriptor,
|
|
|
|
|
posix.EINVAL => return PosixBindError.InvalidSocketOrAddress,
|
|
|
|
|
posix.ENOTSOCK => return PosixBindError.FileDescriptorNotASocket,
|
|
|
|
|
posix.EADDRNOTAVAIL => return PosixBindError.AddressNotAvailable,
|
|
|
|
|
posix.EFAULT => return PosixBindError.PageFault,
|
|
|
|
|
posix.ELOOP => return PosixBindError.SymLinkLoop,
|
|
|
|
|
posix.ENAMETOOLONG => return PosixBindError.NameTooLong,
|
|
|
|
|
posix.ENOENT => return PosixBindError.FileNotFound,
|
|
|
|
|
posix.ENOMEM => return PosixBindError.SystemResources,
|
|
|
|
|
posix.ENOTDIR => return PosixBindError.NotDir,
|
|
|
|
|
posix.EROFS => return PosixBindError.ReadOnlyFileSystem,
|
|
|
|
|
else => return unexpectedErrorPosix(err),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const PosixListenError = error {
|
|
|
|
|
/// Another socket is already listening on the same port.
|
|
|
|
|
/// For Internet domain sockets, the socket referred to by sockfd had not previously
|
|
|
|
|
/// been bound to an address and, upon attempting to bind it to an ephemeral port, it
|
|
|
|
|
/// was determined that all port numbers in the ephemeral port range are currently in
|
|
|
|
|
/// use. See the discussion of /proc/sys/net/ipv4/ip_local_port_range in ip(7).
|
|
|
|
|
AddressInUse,
|
|
|
|
|
|
|
|
|
|
/// The argument sockfd is not a valid file descriptor.
|
|
|
|
|
InvalidFileDescriptor,
|
|
|
|
|
|
|
|
|
|
/// The file descriptor sockfd does not refer to a socket.
|
|
|
|
|
FileDescriptorNotASocket,
|
|
|
|
|
|
|
|
|
|
/// The socket is not of a type that supports the listen() operation.
|
|
|
|
|
OperationNotSupported,
|
|
|
|
|
|
|
|
|
|
Unexpected,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pub fn posixListen(sockfd: i32, backlog: u32) PosixListenError!void {
|
|
|
|
|
const rc = posix.listen(sockfd, backlog);
|
|
|
|
|
const err = posix.getErrno(rc);
|
|
|
|
|
switch (err) {
|
|
|
|
|
0 => return,
|
|
|
|
|
posix.EADDRINUSE => return PosixListenError.AddressInUse,
|
|
|
|
|
posix.EBADF => return PosixListenError.InvalidFileDescriptor,
|
|
|
|
|
posix.ENOTSOCK => return PosixListenError.FileDescriptorNotASocket,
|
|
|
|
|
posix.EOPNOTSUPP => return PosixListenError.OperationNotSupported,
|
|
|
|
|
else => return unexpectedErrorPosix(err),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub const PosixAcceptError = error {
|
|
|
|
|
/// The socket is marked nonblocking and no connections are present to be accepted.
|
|
|
|
|
WouldBlock,
|
|
|
|
|
|
|
|
|
|
/// sockfd is not an open file descriptor.
|
|
|
|
|
FileDescriptorClosed,
|
|
|
|
|
|
|
|
|
|
ConnectionAborted,
|
|
|
|
|
|
|
|
|
|
/// The addr argument is not in a writable part of the user address space.
|
|
|
|
|
PageFault,
|
|
|
|
|
|
|
|
|
|
/// Socket is not listening for connections, or addrlen is invalid (e.g., is negative),
|
|
|
|
|
/// or invalid value in flags.
|
|
|
|
|
InvalidSyscall,
|
|
|
|
|
|
|
|
|
|
/// The per-process limit on the number of open file descriptors has been reached.
|
|
|
|
|
ProcessFdQuotaExceeded,
|
|
|
|
|
|
|
|
|
|
/// The system-wide limit on the total number of open files has been reached.
|
|
|
|
|
SystemFdQuotaExceeded,
|
|
|
|
|
|
|
|
|
|
/// Not enough free memory. This often means that the memory allocation is limited
|
|
|
|
|
/// by the socket buffer limits, not by the system memory.
|
|
|
|
|
SystemResources,
|
|
|
|
|
|
|
|
|
|
/// The file descriptor sockfd does not refer to a socket.
|
|
|
|
|
FileDescriptorNotASocket,
|
|
|
|
|
|
|
|
|
|
/// The referenced socket is not of type SOCK_STREAM.
|
|
|
|
|
OperationNotSupported,
|
|
|
|
|
|
|
|
|
|
ProtocolFailure,
|
|
|
|
|
|
|
|
|
|
/// Firewall rules forbid connection.
|
|
|
|
|
BlockedByFirewall,
|
|
|
|
|
|
|
|
|
|
Unexpected,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pub fn posixAccept(fd: i32, addr: &posix.sockaddr, flags: u32) PosixAcceptError!i32 {
|
|
|
|
|
while (true) {
|
|
|
|
|
var sockaddr_size = u32(@sizeOf(posix.sockaddr));
|
|
|
|
|
const rc = posix.accept4(fd, addr, &sockaddr_size, flags);
|
|
|
|
|
const err = posix.getErrno(rc);
|
|
|
|
|
switch (err) {
|
|
|
|
|
0 => return i32(rc),
|
|
|
|
|
posix.EINTR => continue,
|
|
|
|
|
else => return unexpectedErrorPosix(err),
|
|
|
|
|
|
|
|
|
|
posix.EAGAIN => return PosixAcceptError.WouldBlock,
|
|
|
|
|
posix.EBADF => return PosixAcceptError.FileDescriptorClosed,
|
|
|
|
|
posix.ECONNABORTED => return PosixAcceptError.ConnectionAborted,
|
|
|
|
|
posix.EFAULT => return PosixAcceptError.PageFault,
|
|
|
|
|
posix.EINVAL => return PosixAcceptError.InvalidSyscall,
|
|
|
|
|
posix.EMFILE => return PosixAcceptError.ProcessFdQuotaExceeded,
|
|
|
|
|
posix.ENFILE => return PosixAcceptError.SystemFdQuotaExceeded,
|
|
|
|
|
posix.ENOBUFS, posix.ENOMEM => return PosixAcceptError.SystemResources,
|
|
|
|
|
posix.ENOTSOCK => return PosixAcceptError.FileDescriptorNotASocket,
|
|
|
|
|
posix.EOPNOTSUPP => return PosixAcceptError.OperationNotSupported,
|
|
|
|
|
posix.EPROTO => return PosixAcceptError.ProtocolFailure,
|
|
|
|
|
posix.EPERM => return PosixAcceptError.BlockedByFirewall,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub const LinuxEpollCreateError = error {
|
|
|
|
|
/// Invalid value specified in flags.
|
|
|
|
|
InvalidSyscall,
|
|
|
|
|
|
|
|
|
|
/// The per-user limit on the number of epoll instances imposed by
|
|
|
|
|
/// /proc/sys/fs/epoll/max_user_instances was encountered. See epoll(7) for further
|
|
|
|
|
/// details.
|
|
|
|
|
/// Or, The per-process limit on the number of open file descriptors has been reached.
|
|
|
|
|
ProcessFdQuotaExceeded,
|
|
|
|
|
|
|
|
|
|
/// The system-wide limit on the total number of open files has been reached.
|
|
|
|
|
SystemFdQuotaExceeded,
|
|
|
|
|
|
|
|
|
|
/// There was insufficient memory to create the kernel object.
|
|
|
|
|
SystemResources,
|
|
|
|
|
|
|
|
|
|
Unexpected,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pub fn linuxEpollCreate(flags: u32) LinuxEpollCreateError!i32 {
|
|
|
|
|
const rc = posix.epoll_create1(flags);
|
|
|
|
|
const err = posix.getErrno(rc);
|
|
|
|
|
switch (err) {
|
|
|
|
|
0 => return i32(rc),
|
|
|
|
|
else => return unexpectedErrorPosix(err),
|
|
|
|
|
|
|
|
|
|
posix.EINVAL => return LinuxEpollCreateError.InvalidSyscall,
|
|
|
|
|
posix.EMFILE => return LinuxEpollCreateError.ProcessFdQuotaExceeded,
|
|
|
|
|
posix.ENFILE => return LinuxEpollCreateError.SystemFdQuotaExceeded,
|
|
|
|
|
posix.ENOMEM => return LinuxEpollCreateError.SystemResources,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub const LinuxEpollCtlError = error {
|
|
|
|
|
/// epfd or fd is not a valid file descriptor.
|
|
|
|
|
InvalidFileDescriptor,
|
|
|
|
|
|
|
|
|
|
/// op was EPOLL_CTL_ADD, and the supplied file descriptor fd is already registered
|
|
|
|
|
/// with this epoll instance.
|
|
|
|
|
FileDescriptorAlreadyPresentInSet,
|
|
|
|
|
|
|
|
|
|
/// epfd is not an epoll file descriptor, or fd is the same as epfd, or the requested
|
|
|
|
|
/// operation op is not supported by this interface, or
|
|
|
|
|
/// An invalid event type was specified along with EPOLLEXCLUSIVE in events, or
|
|
|
|
|
/// op was EPOLL_CTL_MOD and events included EPOLLEXCLUSIVE, or
|
|
|
|
|
/// op was EPOLL_CTL_MOD and the EPOLLEXCLUSIVE flag has previously been applied to
|
|
|
|
|
/// this epfd, fd pair, or
|
|
|
|
|
/// EPOLLEXCLUSIVE was specified in event and fd refers to an epoll instance.
|
|
|
|
|
InvalidSyscall,
|
|
|
|
|
|
|
|
|
|
/// fd refers to an epoll instance and this EPOLL_CTL_ADD operation would result in a
|
|
|
|
|
/// circular loop of epoll instances monitoring one another.
|
|
|
|
|
OperationCausesCircularLoop,
|
|
|
|
|
|
|
|
|
|
/// op was EPOLL_CTL_MOD or EPOLL_CTL_DEL, and fd is not registered with this epoll
|
|
|
|
|
/// instance.
|
|
|
|
|
FileDescriptorNotRegistered,
|
|
|
|
|
|
|
|
|
|
/// There was insufficient memory to handle the requested op control operation.
|
|
|
|
|
SystemResources,
|
|
|
|
|
|
|
|
|
|
/// The limit imposed by /proc/sys/fs/epoll/max_user_watches was encountered while
|
|
|
|
|
/// trying to register (EPOLL_CTL_ADD) a new file descriptor on an epoll instance.
|
|
|
|
|
/// See epoll(7) for further details.
|
|
|
|
|
UserResourceLimitReached,
|
|
|
|
|
|
|
|
|
|
/// The target file fd does not support epoll. This error can occur if fd refers to,
|
|
|
|
|
/// for example, a regular file or a directory.
|
|
|
|
|
FileDescriptorIncompatibleWithEpoll,
|
|
|
|
|
|
|
|
|
|
Unexpected,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pub fn linuxEpollCtl(epfd: i32, op: u32, fd: i32, event: &linux.epoll_event) LinuxEpollCtlError!void {
|
|
|
|
|
const rc = posix.epoll_ctl(epfd, op, fd, event);
|
|
|
|
|
const err = posix.getErrno(rc);
|
|
|
|
|
switch (err) {
|
|
|
|
|
0 => return,
|
|
|
|
|
else => return unexpectedErrorPosix(err),
|
|
|
|
|
|
|
|
|
|
posix.EBADF => return LinuxEpollCtlError.InvalidFileDescriptor,
|
|
|
|
|
posix.EEXIST => return LinuxEpollCtlError.FileDescriptorAlreadyPresentInSet,
|
|
|
|
|
posix.EINVAL => return LinuxEpollCtlError.InvalidSyscall,
|
|
|
|
|
posix.ELOOP => return LinuxEpollCtlError.OperationCausesCircularLoop,
|
|
|
|
|
posix.ENOENT => return LinuxEpollCtlError.FileDescriptorNotRegistered,
|
|
|
|
|
posix.ENOMEM => return LinuxEpollCtlError.SystemResources,
|
|
|
|
|
posix.ENOSPC => return LinuxEpollCtlError.UserResourceLimitReached,
|
|
|
|
|
posix.EPERM => return LinuxEpollCtlError.FileDescriptorIncompatibleWithEpoll,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn linuxEpollWait(epfd: i32, events: []linux.epoll_event, timeout: i32) usize {
|
|
|
|
|
while (true) {
|
2018-04-08 21:52:45 -07:00
|
|
|
|
const rc = posix.epoll_wait(epfd, events.ptr, u32(events.len), timeout);
|
2018-03-07 00:55:52 -08:00
|
|
|
|
const err = posix.getErrno(rc);
|
|
|
|
|
switch (err) {
|
|
|
|
|
0 => return rc,
|
|
|
|
|
posix.EINTR => continue,
|
|
|
|
|
posix.EBADF => unreachable,
|
|
|
|
|
posix.EFAULT => unreachable,
|
|
|
|
|
posix.EINVAL => unreachable,
|
|
|
|
|
else => unreachable,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-04-08 17:08:40 -07:00
|
|
|
|
|
|
|
|
|
pub const PosixGetSockNameError = error {
|
|
|
|
|
/// Insufficient resources were available in the system to perform the operation.
|
|
|
|
|
SystemResources,
|
|
|
|
|
|
|
|
|
|
Unexpected,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pub fn posixGetSockName(sockfd: i32) PosixGetSockNameError!posix.sockaddr {
|
|
|
|
|
var addr: posix.sockaddr = undefined;
|
|
|
|
|
var addrlen: posix.socklen_t = @sizeOf(posix.sockaddr);
|
|
|
|
|
const rc = posix.getsockname(sockfd, &addr, &addrlen);
|
|
|
|
|
const err = posix.getErrno(rc);
|
|
|
|
|
switch (err) {
|
|
|
|
|
0 => return addr,
|
|
|
|
|
else => return unexpectedErrorPosix(err),
|
|
|
|
|
|
|
|
|
|
posix.EBADF => unreachable,
|
|
|
|
|
posix.EFAULT => unreachable,
|
|
|
|
|
posix.EINVAL => unreachable,
|
|
|
|
|
posix.ENOTSOCK => unreachable,
|
|
|
|
|
posix.ENOBUFS => return PosixGetSockNameError.SystemResources,
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-04-08 21:52:45 -07:00
|
|
|
|
|
|
|
|
|
pub const PosixConnectError = error {
|
|
|
|
|
/// For UNIX domain sockets, which are identified by pathname: Write permission is denied on the socket
|
|
|
|
|
/// file, or search permission is denied for one of the directories in the path prefix.
|
|
|
|
|
/// or
|
|
|
|
|
/// The user tried to connect to a broadcast address without having the socket broadcast flag enabled or
|
|
|
|
|
/// the connection request failed because of a local firewall rule.
|
|
|
|
|
PermissionDenied,
|
|
|
|
|
|
|
|
|
|
/// Local address is already in use.
|
|
|
|
|
AddressInUse,
|
|
|
|
|
|
|
|
|
|
/// (Internet domain sockets) The socket referred to by sockfd had not previously been bound to an
|
|
|
|
|
/// address and, upon attempting to bind it to an ephemeral port, it was determined that all port numbers
|
|
|
|
|
/// in the ephemeral port range are currently in use. See the discussion of
|
|
|
|
|
/// /proc/sys/net/ipv4/ip_local_port_range in ip(7).
|
|
|
|
|
AddressNotAvailable,
|
|
|
|
|
|
|
|
|
|
/// The passed address didn't have the correct address family in its sa_family field.
|
|
|
|
|
AddressFamilyNotSupported,
|
|
|
|
|
|
|
|
|
|
/// Insufficient entries in the routing cache.
|
|
|
|
|
SystemResources,
|
|
|
|
|
|
|
|
|
|
/// A connect() on a stream socket found no one listening on the remote address.
|
|
|
|
|
ConnectionRefused,
|
|
|
|
|
|
|
|
|
|
/// Network is unreachable.
|
|
|
|
|
NetworkUnreachable,
|
|
|
|
|
|
|
|
|
|
/// Timeout while attempting connection. The server may be too busy to accept new connections. Note
|
|
|
|
|
/// that for IP sockets the timeout may be very long when syncookies are enabled on the server.
|
|
|
|
|
ConnectionTimedOut,
|
|
|
|
|
|
|
|
|
|
Unexpected,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pub fn posixConnect(sockfd: i32, sockaddr: &const posix.sockaddr) PosixConnectError!void {
|
|
|
|
|
while (true) {
|
|
|
|
|
const rc = posix.connect(sockfd, sockaddr, @sizeOf(posix.sockaddr));
|
|
|
|
|
const err = posix.getErrno(rc);
|
|
|
|
|
switch (err) {
|
|
|
|
|
0 => return,
|
|
|
|
|
else => return unexpectedErrorPosix(err),
|
|
|
|
|
|
|
|
|
|
posix.EACCES => return PosixConnectError.PermissionDenied,
|
|
|
|
|
posix.EPERM => return PosixConnectError.PermissionDenied,
|
|
|
|
|
posix.EADDRINUSE => return PosixConnectError.AddressInUse,
|
|
|
|
|
posix.EADDRNOTAVAIL => return PosixConnectError.AddressNotAvailable,
|
|
|
|
|
posix.EAFNOSUPPORT => return PosixConnectError.AddressFamilyNotSupported,
|
|
|
|
|
posix.EAGAIN => return PosixConnectError.SystemResources,
|
|
|
|
|
posix.EALREADY => unreachable, // The socket is nonblocking and a previous connection attempt has not yet been completed.
|
|
|
|
|
posix.EBADF => unreachable, // sockfd is not a valid open file descriptor.
|
|
|
|
|
posix.ECONNREFUSED => return PosixConnectError.ConnectionRefused,
|
|
|
|
|
posix.EFAULT => unreachable, // The socket structure address is outside the user's address space.
|
|
|
|
|
posix.EINPROGRESS => unreachable, // The socket is nonblocking and the connection cannot be completed immediately.
|
|
|
|
|
posix.EINTR => continue,
|
|
|
|
|
posix.EISCONN => unreachable, // The socket is already connected.
|
|
|
|
|
posix.ENETUNREACH => return PosixConnectError.NetworkUnreachable,
|
|
|
|
|
posix.ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
|
|
|
|
|
posix.EPROTOTYPE => unreachable, // The socket type does not support the requested communications protocol.
|
|
|
|
|
posix.ETIMEDOUT => return PosixConnectError.ConnectionTimedOut,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Same as posixConnect except it is for blocking socket file descriptors.
|
|
|
|
|
/// It expects to receive EINPROGRESS.
|
|
|
|
|
pub fn posixConnectAsync(sockfd: i32, sockaddr: &const posix.sockaddr) PosixConnectError!void {
|
|
|
|
|
while (true) {
|
|
|
|
|
const rc = posix.connect(sockfd, sockaddr, @sizeOf(posix.sockaddr));
|
|
|
|
|
const err = posix.getErrno(rc);
|
|
|
|
|
switch (err) {
|
|
|
|
|
0, posix.EINPROGRESS => return,
|
|
|
|
|
else => return unexpectedErrorPosix(err),
|
|
|
|
|
|
|
|
|
|
posix.EACCES => return PosixConnectError.PermissionDenied,
|
|
|
|
|
posix.EPERM => return PosixConnectError.PermissionDenied,
|
|
|
|
|
posix.EADDRINUSE => return PosixConnectError.AddressInUse,
|
|
|
|
|
posix.EADDRNOTAVAIL => return PosixConnectError.AddressNotAvailable,
|
|
|
|
|
posix.EAFNOSUPPORT => return PosixConnectError.AddressFamilyNotSupported,
|
|
|
|
|
posix.EAGAIN => return PosixConnectError.SystemResources,
|
|
|
|
|
posix.EALREADY => unreachable, // The socket is nonblocking and a previous connection attempt has not yet been completed.
|
|
|
|
|
posix.EBADF => unreachable, // sockfd is not a valid open file descriptor.
|
|
|
|
|
posix.ECONNREFUSED => return PosixConnectError.ConnectionRefused,
|
|
|
|
|
posix.EFAULT => unreachable, // The socket structure address is outside the user's address space.
|
|
|
|
|
posix.EINTR => continue,
|
|
|
|
|
posix.EISCONN => unreachable, // The socket is already connected.
|
|
|
|
|
posix.ENETUNREACH => return PosixConnectError.NetworkUnreachable,
|
|
|
|
|
posix.ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
|
|
|
|
|
posix.EPROTOTYPE => unreachable, // The socket type does not support the requested communications protocol.
|
|
|
|
|
posix.ETIMEDOUT => return PosixConnectError.ConnectionTimedOut,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn posixGetSockOptConnectError(sockfd: i32) PosixConnectError!void {
|
|
|
|
|
var err_code: i32 = undefined;
|
|
|
|
|
var size: u32 = @sizeOf(i32);
|
|
|
|
|
const rc = posix.getsockopt(sockfd, posix.SOL_SOCKET, posix.SO_ERROR, @ptrCast(&u8, &err_code), &size);
|
|
|
|
|
assert(size == 4);
|
|
|
|
|
const err = posix.getErrno(rc);
|
|
|
|
|
switch (err) {
|
|
|
|
|
0 => switch (err_code) {
|
|
|
|
|
0 => return,
|
|
|
|
|
else => return unexpectedErrorPosix(err),
|
|
|
|
|
|
|
|
|
|
posix.EACCES => return PosixConnectError.PermissionDenied,
|
|
|
|
|
posix.EPERM => return PosixConnectError.PermissionDenied,
|
|
|
|
|
posix.EADDRINUSE => return PosixConnectError.AddressInUse,
|
|
|
|
|
posix.EADDRNOTAVAIL => return PosixConnectError.AddressNotAvailable,
|
|
|
|
|
posix.EAFNOSUPPORT => return PosixConnectError.AddressFamilyNotSupported,
|
|
|
|
|
posix.EAGAIN => return PosixConnectError.SystemResources,
|
|
|
|
|
posix.EALREADY => unreachable, // The socket is nonblocking and a previous connection attempt has not yet been completed.
|
|
|
|
|
posix.EBADF => unreachable, // sockfd is not a valid open file descriptor.
|
|
|
|
|
posix.ECONNREFUSED => return PosixConnectError.ConnectionRefused,
|
|
|
|
|
posix.EFAULT => unreachable, // The socket structure address is outside the user's address space.
|
|
|
|
|
posix.EISCONN => unreachable, // The socket is already connected.
|
|
|
|
|
posix.ENETUNREACH => return PosixConnectError.NetworkUnreachable,
|
|
|
|
|
posix.ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
|
|
|
|
|
posix.EPROTOTYPE => unreachable, // The socket type does not support the requested communications protocol.
|
|
|
|
|
posix.ETIMEDOUT => return PosixConnectError.ConnectionTimedOut,
|
|
|
|
|
},
|
|
|
|
|
else => return unexpectedErrorPosix(err),
|
|
|
|
|
posix.EBADF => unreachable, // The argument sockfd is not a valid file descriptor.
|
|
|
|
|
posix.EFAULT => unreachable, // The address pointed to by optval or optlen is not in a valid part of the process address space.
|
|
|
|
|
posix.EINVAL => unreachable,
|
|
|
|
|
posix.ENOPROTOOPT => unreachable, // The option is unknown at the level indicated.
|
|
|
|
|
posix.ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
|
|
|
|
|
}
|
|
|
|
|
}
|