Split out the macOS specific parts

This commit is contained in:
Jarred Sumner
2021-10-01 02:06:35 -07:00
parent 8764fa6d4e
commit c8127d0ae6
4 changed files with 233 additions and 202 deletions

View File

@@ -4,3 +4,88 @@ pub usingnamespace switch (std.Target.current.os.tag) {
.macos => @import("./darwin_c.zig"),
else => struct {},
};
usingnamespace std.c;
const builtin = @import("builtin");
const os = std.os;
const mem = std.mem;
const Stat = std.fs.File.Stat;
const Kind = std.fs.File.Kind;
const StatError = std.fs.File.StatError;
const errno = os.errno;
const zeroes = mem.zeroes;
pub extern "c" fn chmod([*c]const u8, mode_t) c_int;
pub extern "c" fn fchmod(c_int, mode_t) c_int;
pub extern "c" fn umask(mode_t) mode_t;
pub extern "c" fn fchmodat(c_int, [*c]const u8, mode_t, c_int) c_int;
pub extern "c" fn lstat([*c]const u8, [*c]libc_stat) c_int;
pub extern "c" fn lstat64([*c]const u8, [*c]libc_stat) c_int;
pub fn lstat_absolute(path: [:0]const u8) StatError!Stat {
if (builtin.os.tag == .windows) {
var io_status_block: windows.IO_STATUS_BLOCK = undefined;
var info: windows.FILE_ALL_INFORMATION = undefined;
const rc = windows.ntdll.NtQueryInformationFile(self.handle, &io_status_block, &info, @sizeOf(windows.FILE_ALL_INFORMATION), .FileAllInformation);
switch (rc) {
.SUCCESS => {},
.BUFFER_OVERFLOW => {},
.INVALID_PARAMETER => unreachable,
.ACCESS_DENIED => return error.AccessDenied,
else => return windows.unexpectedStatus(rc),
}
return Stat{
.inode = info.InternalInformation.IndexNumber,
.size = @bitCast(u64, info.StandardInformation.EndOfFile),
.mode = 0,
.kind = if (info.StandardInformation.Directory == 0) .File else .Directory,
.atime = windows.fromSysTime(info.BasicInformation.LastAccessTime),
.mtime = windows.fromSysTime(info.BasicInformation.LastWriteTime),
.ctime = windows.fromSysTime(info.BasicInformation.CreationTime),
};
}
var st = zeroes(libc_stat);
switch (errno(lstat64(path.ptr, &st))) {
.SUCCESS => {},
// .EINVAL => unreachable,
.BADF => unreachable, // Always a race condition.
.NOMEM => return error.SystemResources,
.ACCES => return error.AccessDenied,
else => |err| return os.unexpectedErrno(err),
}
const atime = st.atime();
const mtime = st.mtime();
const ctime = st.ctime();
return Stat{
.inode = st.ino,
.size = @bitCast(u64, st.size),
.mode = st.mode,
.kind = switch (builtin.os.tag) {
.wasi => switch (st.filetype) {
os.FILETYPE_BLOCK_DEVICE => Kind.BlockDevice,
os.FILETYPE_CHARACTER_DEVICE => Kind.CharacterDevice,
os.FILETYPE_DIRECTORY => Kind.Directory,
os.FILETYPE_SYMBOLIC_LINK => Kind.SymLink,
os.FILETYPE_REGULAR_FILE => Kind.File,
os.FILETYPE_SOCKET_STREAM, os.FILETYPE_SOCKET_DGRAM => Kind.UnixDomainSocket,
else => Kind.Unknown,
},
else => switch (st.mode & os.S_IFMT) {
os.S_IFBLK => Kind.BlockDevice,
os.S_IFCHR => Kind.CharacterDevice,
os.S_IFDIR => Kind.Directory,
os.S_IFIFO => Kind.NamedPipe,
os.S_IFLNK => Kind.SymLink,
os.S_IFREG => Kind.File,
os.S_IFSOCK => Kind.UnixDomainSocket,
else => Kind.Unknown,
},
},
.atime = @as(i128, atime.tv_sec) * std.time.ns_per_s + atime.tv_nsec,
.mtime = @as(i128, mtime.tv_sec) * std.time.ns_per_s + mtime.tv_nsec,
.ctime = @as(i128, ctime.tv_sec) * std.time.ns_per_s + ctime.tv_nsec,
};
}

View File

@@ -8,6 +8,7 @@ const Kind = std.fs.File.Kind;
const StatError = std.fs.File.StatError;
const errno = os.errno;
const zeroes = mem.zeroes;
// int clonefileat(int src_dirfd, const char * src, int dst_dirfd, const char * dst, int flags);
pub extern "c" fn clonefileat(c_int, [*c]const u8, c_int, [*c]const u8, uint32_t: c_int) c_int;
// int fclonefileat(int srcfd, int dst_dirfd, const char * dst, int flags);
@@ -15,147 +16,72 @@ pub extern "c" fn fclonefileat(c_int, c_int, [*c]const u8, uint32_t: c_int) c_in
// int clonefile(const char * src, const char * dst, int flags);
pub extern "c" fn clonefile([*c]const u8, [*c]const u8, uint32_t: c_int) c_int;
pub extern "c" fn chmod([*c]const u8, mode_t) c_int;
pub extern "c" fn fchmod(c_int, mode_t) c_int;
pub extern "c" fn umask(mode_t) mode_t;
pub extern "c" fn fchmodat(c_int, [*c]const u8, mode_t, c_int) c_int;
// pub fn stat_absolute(path: [:0]const u8) StatError!Stat {
// if (builtin.os.tag == .windows) {
// var io_status_block: windows.IO_STATUS_BLOCK = undefined;
// var info: windows.FILE_ALL_INFORMATION = undefined;
// const rc = windows.ntdll.NtQueryInformationFile(self.handle, &io_status_block, &info, @sizeOf(windows.FILE_ALL_INFORMATION), .FileAllInformation);
// switch (rc) {
// .SUCCESS => {},
// .BUFFER_OVERFLOW => {},
// .INVALID_PARAMETER => unreachable,
// .ACCESS_DENIED => return error.AccessDenied,
// else => return windows.unexpectedStatus(rc),
// }
// return Stat{
// .inode = info.InternalInformation.IndexNumber,
// .size = @bitCast(u64, info.StandardInformation.EndOfFile),
// .mode = 0,
// .kind = if (info.StandardInformation.Directory == 0) .File else .Directory,
// .atime = windows.fromSysTime(info.BasicInformation.LastAccessTime),
// .mtime = windows.fromSysTime(info.BasicInformation.LastWriteTime),
// .ctime = windows.fromSysTime(info.BasicInformation.CreationTime),
// };
// }
pub extern "c" fn lstat([*c]const u8, [*c]libc_stat) c_int;
pub extern "c" fn lstat64([*c]const u8, [*c]libc_stat) c_int;
// var st = zeroes(libc_stat);
// switch (errno(stat(path.ptr, &st))) {
// 0 => {},
// // .EINVAL => unreachable,
// .EBADF => unreachable, // Always a race condition.
// .ENOMEM => return error.SystemResources,
// .EACCES => return error.AccessDenied,
// else => |err| return os.unexpectedErrno(err),
// }
pub fn lstat_absolute(path: [:0]const u8) StatError!Stat {
if (builtin.os.tag == .windows) {
var io_status_block: windows.IO_STATUS_BLOCK = undefined;
var info: windows.FILE_ALL_INFORMATION = undefined;
const rc = windows.ntdll.NtQueryInformationFile(self.handle, &io_status_block, &info, @sizeOf(windows.FILE_ALL_INFORMATION), .FileAllInformation);
switch (rc) {
.SUCCESS => {},
.BUFFER_OVERFLOW => {},
.INVALID_PARAMETER => unreachable,
.ACCESS_DENIED => return error.AccessDenied,
else => return windows.unexpectedStatus(rc),
}
return Stat{
.inode = info.InternalInformation.IndexNumber,
.size = @bitCast(u64, info.StandardInformation.EndOfFile),
.mode = 0,
.kind = if (info.StandardInformation.Directory == 0) .File else .Directory,
.atime = windows.fromSysTime(info.BasicInformation.LastAccessTime),
.mtime = windows.fromSysTime(info.BasicInformation.LastWriteTime),
.ctime = windows.fromSysTime(info.BasicInformation.CreationTime),
};
}
var st = zeroes(libc_stat);
switch (errno(lstat64(path.ptr, &st))) {
.SUCCESS => {},
// .EINVAL => unreachable,
.BADF => unreachable, // Always a race condition.
.NOMEM => return error.SystemResources,
.ACCES => return error.AccessDenied,
else => |err| return os.unexpectedErrno(err),
}
const atime = st.atime();
const mtime = st.mtime();
const ctime = st.ctime();
return Stat{
.inode = st.ino,
.size = @bitCast(u64, st.size),
.mode = st.mode,
.kind = switch (builtin.os.tag) {
.wasi => switch (st.filetype) {
os.FILETYPE_BLOCK_DEVICE => Kind.BlockDevice,
os.FILETYPE_CHARACTER_DEVICE => Kind.CharacterDevice,
os.FILETYPE_DIRECTORY => Kind.Directory,
os.FILETYPE_SYMBOLIC_LINK => Kind.SymLink,
os.FILETYPE_REGULAR_FILE => Kind.File,
os.FILETYPE_SOCKET_STREAM, os.FILETYPE_SOCKET_DGRAM => Kind.UnixDomainSocket,
else => Kind.Unknown,
},
else => switch (st.mode & os.S_IFMT) {
os.S_IFBLK => Kind.BlockDevice,
os.S_IFCHR => Kind.CharacterDevice,
os.S_IFDIR => Kind.Directory,
os.S_IFIFO => Kind.NamedPipe,
os.S_IFLNK => Kind.SymLink,
os.S_IFREG => Kind.File,
os.S_IFSOCK => Kind.UnixDomainSocket,
else => Kind.Unknown,
},
},
.atime = @as(i128, atime.tv_sec) * std.time.ns_per_s + atime.tv_nsec,
.mtime = @as(i128, mtime.tv_sec) * std.time.ns_per_s + mtime.tv_nsec,
.ctime = @as(i128, ctime.tv_sec) * std.time.ns_per_s + ctime.tv_nsec,
};
}
pub fn stat_absolute(path: [:0]const u8) StatError!Stat {
if (builtin.os.tag == .windows) {
var io_status_block: windows.IO_STATUS_BLOCK = undefined;
var info: windows.FILE_ALL_INFORMATION = undefined;
const rc = windows.ntdll.NtQueryInformationFile(self.handle, &io_status_block, &info, @sizeOf(windows.FILE_ALL_INFORMATION), .FileAllInformation);
switch (rc) {
.SUCCESS => {},
.BUFFER_OVERFLOW => {},
.INVALID_PARAMETER => unreachable,
.ACCESS_DENIED => return error.AccessDenied,
else => return windows.unexpectedStatus(rc),
}
return Stat{
.inode = info.InternalInformation.IndexNumber,
.size = @bitCast(u64, info.StandardInformation.EndOfFile),
.mode = 0,
.kind = if (info.StandardInformation.Directory == 0) .File else .Directory,
.atime = windows.fromSysTime(info.BasicInformation.LastAccessTime),
.mtime = windows.fromSysTime(info.BasicInformation.LastWriteTime),
.ctime = windows.fromSysTime(info.BasicInformation.CreationTime),
};
}
var st = zeroes(libc_stat);
switch (errno(stat(path.ptr, &st))) {
0 => {},
// .EINVAL => unreachable,
.EBADF => unreachable, // Always a race condition.
.ENOMEM => return error.SystemResources,
.EACCES => return error.AccessDenied,
else => |err| return os.unexpectedErrno(err),
}
const atime = st.atime();
const mtime = st.mtime();
const ctime = st.ctime();
return Stat{
.inode = st.ino,
.size = @bitCast(u64, st.size),
.mode = st.mode,
.kind = switch (builtin.os.tag) {
.wasi => switch (st.filetype) {
os.FILETYPE_BLOCK_DEVICE => Kind.BlockDevice,
os.FILETYPE_CHARACTER_DEVICE => Kind.CharacterDevice,
os.FILETYPE_DIRECTORY => Kind.Directory,
os.FILETYPE_SYMBOLIC_LINK => Kind.SymLink,
os.FILETYPE_REGULAR_FILE => Kind.File,
os.FILETYPE_SOCKET_STREAM, os.FILETYPE_SOCKET_DGRAM => Kind.UnixDomainSocket,
else => Kind.Unknown,
},
else => switch (st.mode & os.S_IFMT) {
os.S_IFBLK => Kind.BlockDevice,
os.S_IFCHR => Kind.CharacterDevice,
os.S_IFDIR => Kind.Directory,
os.S_IFIFO => Kind.NamedPipe,
os.S_IFLNK => Kind.SymLink,
os.S_IFREG => Kind.File,
os.S_IFSOCK => Kind.UnixDomainSocket,
else => Kind.Unknown,
},
},
.atime = @as(i128, atime.tv_sec) * std.time.ns_per_s + atime.tv_nsec,
.mtime = @as(i128, mtime.tv_sec) * std.time.ns_per_s + mtime.tv_nsec,
.ctime = @as(i128, ctime.tv_sec) * std.time.ns_per_s + ctime.tv_nsec,
};
}
// const atime = st.atime();
// const mtime = st.mtime();
// const ctime = st.ctime();
// return Stat{
// .inode = st.ino,
// .size = @bitCast(u64, st.size),
// .mode = st.mode,
// .kind = switch (builtin.os.tag) {
// .wasi => switch (st.filetype) {
// os.FILETYPE_BLOCK_DEVICE => Kind.BlockDevice,
// os.FILETYPE_CHARACTER_DEVICE => Kind.CharacterDevice,
// os.FILETYPE_DIRECTORY => Kind.Directory,
// os.FILETYPE_SYMBOLIC_LINK => Kind.SymLink,
// os.FILETYPE_REGULAR_FILE => Kind.File,
// os.FILETYPE_SOCKET_STREAM, os.FILETYPE_SOCKET_DGRAM => Kind.UnixDomainSocket,
// else => Kind.Unknown,
// },
// else => switch (st.mode & os.S_IFMT) {
// os.S_IFBLK => Kind.BlockDevice,
// os.S_IFCHR => Kind.CharacterDevice,
// os.S_IFDIR => Kind.Directory,
// os.S_IFIFO => Kind.NamedPipe,
// os.S_IFLNK => Kind.SymLink,
// os.S_IFREG => Kind.File,
// os.S_IFSOCK => Kind.UnixDomainSocket,
// else => Kind.Unknown,
// },
// },
// .atime = @as(i128, atime.tv_sec) * std.time.ns_per_s + atime.tv_nsec,
// .mtime = @as(i128, mtime.tv_sec) * std.time.ns_per_s + mtime.tv_nsec,
// .ctime = @as(i128, ctime.tv_sec) * std.time.ns_per_s + ctime.tv_nsec,
// };
// }
pub const struct_fstore = extern struct {
fst_flags: c_uint,

View File

@@ -4,7 +4,8 @@ pub usingnamespace @import("strings.zig");
pub const default_allocator: *std.mem.Allocator = if (isTest) std.heap.c_allocator else @import("./memory_allocator.zig").c_allocator;
pub const C = @import("c.zig");
pub usingnamespace @import("env.zig");
pub const Environment = @import("env.zig");
pub usingnamespace Environment;
pub const FeatureFlags = @import("feature_flags.zig");
const root = @import("root");

View File

@@ -5,6 +5,7 @@ const options = @import("./options.zig");
const IndexType = @import("./allocators.zig").IndexType;
const os = std.os;
const KEvent = std.os.Kevent;
const Mutex = @import("./lock.zig").Lock;
@@ -12,6 +13,8 @@ const WatchItemIndex = u16;
const NoWatchItem: WatchItemIndex = std.math.maxInt(WatchItemIndex);
const PackageJSON = @import("./resolver/package_json.zig").PackageJSON;
const WATCHER_MAX_LIST = 8096;
pub const WatchItem = struct {
file_path: string,
// filepath hash for quick comparison
@@ -49,6 +52,20 @@ pub const WatchEvent = struct {
pub const Watchlist = std.MultiArrayList(WatchItem);
const DarwinWatcher = struct {
// Internal
changelist: [128]KEvent = undefined,
// Everything being watched
eventlist: [WATCHER_MAX_LIST]KEvent = undefined,
};
const PlatformWatcher = if (Environment.isMac)
DarwinWatcher
else
void;
// This implementation only works on macOS, for now.
// The Internet seems to suggest basically always using FSEvents instead of kqueue
// It seems like the main concern is max open file descriptors
@@ -57,23 +74,17 @@ pub fn NewWatcher(comptime ContextType: type) type {
return struct {
const Watcher = @This();
const KEventArrayList = std.ArrayList(KEvent);
const WATCHER_MAX_LIST = 8096;
watchlist: Watchlist,
watched_count: usize = 0,
mutex: Mutex,
// Internal
changelist: [128]KEvent = undefined,
eventlist_used: usize = 0,
platform: PlatformWatcher = PlatformWatcher{},
// User-facing
watch_events: [128]WatchEvent = undefined,
// Everything being watched
eventlist: [WATCHER_MAX_LIST]KEvent = undefined,
eventlist_used: usize = 0,
fs: *Fs.FileSystem,
// this is what kqueue knows about
fd: StoredFileDescriptorType,
@@ -108,9 +119,11 @@ pub fn NewWatcher(comptime ContextType: type) type {
pub fn getQueue(this: *Watcher) !StoredFileDescriptorType {
if (this.fd == 0) {
this.fd = try os.kqueue();
if (this.fd == 0) {
return error.WatcherFailed;
if (Environment.isMac) {
this.fd = try os.kqueue();
if (this.fd == 0) {
return error.WatcherFailed;
}
}
}
@@ -202,28 +215,31 @@ pub fn NewWatcher(comptime ContextType: type) type {
fn _watchLoop(this: *Watcher) !void {
const time = std.time;
std.debug.assert(this.fd > 0);
if (Environment.isMac) {
std.debug.assert(this.fd > 0);
var changelist_array: [1]KEvent = std.mem.zeroes([1]KEvent);
var changelist = &changelist_array;
while (true) {
defer Output.flush();
var code = std.os.system.kevent(
try this.getQueue(),
@as([*]KEvent, changelist),
0,
@as([*]KEvent, changelist),
1,
var changelist_array: [1]KEvent = std.mem.zeroes([1]KEvent);
var changelist = &changelist_array;
while (true) {
defer Output.flush();
null,
);
_ = std.os.system.kevent(
try this.getQueue(),
@as([*]KEvent, changelist),
0,
@as([*]KEvent, changelist),
1,
var watchevents = this.watch_events[0..1];
for (changelist) |event, i| {
watchevents[i].fromKEvent(&event);
null,
);
var watchevents = this.watch_events[0..1];
for (changelist) |event, i| {
watchevents[i].fromKEvent(&event);
}
this.ctx.onFileUpdate(watchevents, this.watchlist);
}
this.ctx.onFileUpdate(watchevents, this.watchlist);
}
}
@@ -266,7 +282,7 @@ pub fn NewWatcher(comptime ContextType: type) type {
const index = this.eventlist_used;
const watchlist_id = this.watchlist.len;
if (isMac) {
if (Environment.isMac) {
// https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/kqueue.2.html
var event = std.mem.zeroes(KEvent);
@@ -290,7 +306,7 @@ pub fn NewWatcher(comptime ContextType: type) type {
// Store the hash for fast filtering later
event.udata = @intCast(usize, watchlist_id);
this.eventlist[index] = event;
this.platform.eventlist[index] = event;
// This took a lot of work to figure out the right permutation
// Basically:
@@ -298,9 +314,9 @@ pub fn NewWatcher(comptime ContextType: type) type {
// our while(true) loop above receives notification of changes to any of the events created here.
_ = std.os.system.kevent(
try this.getQueue(),
this.eventlist[index .. index + 1].ptr,
this.platform.eventlist[index .. index + 1].ptr,
1,
this.eventlist[index .. index + 1].ptr,
this.platform.eventlist[index .. index + 1].ptr,
0,
null,
);
@@ -337,39 +353,42 @@ pub fn NewWatcher(comptime ContextType: type) type {
const index = this.eventlist_used;
const watchlist_id = this.watchlist.len;
// https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/kqueue.2.html
var event = std.mem.zeroes(KEvent);
if (Environment.isMac) {
event.flags = os.EV_ADD | os.EV_CLEAR | os.EV_ENABLE;
// we want to know about the vnode
event.filter = std.os.EVFILT_VNODE;
// https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/kqueue.2.html
var event = std.mem.zeroes(KEvent);
// monitor:
// - Write
// - Rename
// - Delete
event.fflags = std.os.NOTE_WRITE | std.os.NOTE_RENAME | std.os.NOTE_DELETE;
event.flags = os.EV_ADD | os.EV_CLEAR | os.EV_ENABLE;
// we want to know about the vnode
event.filter = std.os.EVFILT_VNODE;
// id
event.ident = @intCast(usize, fd);
// monitor:
// - Write
// - Rename
// - Delete
event.fflags = std.os.NOTE_WRITE | std.os.NOTE_RENAME | std.os.NOTE_DELETE;
this.eventlist_used += 1;
// Store the hash for fast filtering later
event.udata = @intCast(usize, watchlist_id);
this.eventlist[index] = event;
// id
event.ident = @intCast(usize, fd);
// This took a lot of work to figure out the right permutation
// Basically:
// - We register the event here.
// our while(true) loop above receives notification of changes to any of the events created here.
_ = std.os.system.kevent(
try this.getQueue(),
this.eventlist[index .. index + 1].ptr,
1,
this.eventlist[index .. index + 1].ptr,
0,
null,
);
this.eventlist_used += 1;
// Store the hash for fast filtering later
event.udata = @intCast(usize, watchlist_id);
this.platform.eventlist[index] = event;
// This took a lot of work to figure out the right permutation
// Basically:
// - We register the event here.
// our while(true) loop above receives notification of changes to any of the events created here.
_ = std.os.system.kevent(
try this.getQueue(),
this.platform.eventlist[index .. index + 1].ptr,
1,
this.platform.eventlist[index .. index + 1].ptr,
0,
null,
);
}
this.watchlist.appendAssumeCapacity(.{
.file_path = if (copy_file_path) try this.allocator.dupe(u8, file_path) else file_path,