authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2018-08-21 00:46:42-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2018-08-21 00:46:42-04:00
logbda5539e9d8b5f15b8165393e4118c8601188276
tree017556e65eaa6abb24bfe414760f258f6aacff62
parent302936309a30c9c0bcfe222ec1de470b36c18a06

*WIP* std.os assumes comptime-known max path size

this allows us to remove the requirement of allocators for a lot of functions See #1392

8 files changed, 254 insertions(+), 251 deletions(-)

std/debug/index.zig+1-1
...@@ -340,7 +340,7 @@ pub fn openSelfDebugInfo(allocator: *mem.Allocator) !*ElfStackTrace {...@@ -340,7 +340,7 @@ pub fn openSelfDebugInfo(allocator: *mem.Allocator) !*ElfStackTrace {
340 }340 }
341}341}
342342
343fn printLineFromFile(allocator: *mem.Allocator, out_stream: var, line_info: *const LineInfo) !void {343fn printLineFromFile(out_stream: var, line_info: *const LineInfo) !void {
344 var f = try os.File.openRead(line_info.file_name);344 var f = try os.File.openRead(line_info.file_name);
345 defer f.close();345 defer f.close();
346 // TODO fstat and make sure that the file has the correct size346 // TODO fstat and make sure that the file has the correct size
std/io_test.zig+3-3
...@@ -16,7 +16,7 @@ test "write a file, read it, then delete it" {...@@ -16,7 +16,7 @@ test "write a file, read it, then delete it" {
16 prng.random.bytes(data[0..]);16 prng.random.bytes(data[0..]);
17 const tmp_file_name = "temp_test_file.txt";17 const tmp_file_name = "temp_test_file.txt";
18 {18 {
19 var file = try os.File.openWrite(allocator, tmp_file_name);19 var file = try os.File.openWrite(tmp_file_name);
20 defer file.close();20 defer file.close();
2121
22 var file_out_stream = io.FileOutStream.init(&file);22 var file_out_stream = io.FileOutStream.init(&file);
...@@ -63,7 +63,7 @@ test "BufferOutStream" {...@@ -63,7 +63,7 @@ test "BufferOutStream" {
63}63}
6464
65test "SliceInStream" {65test "SliceInStream" {
66 const bytes = []const u8 { 1, 2, 3, 4, 5, 6, 7 };66 const bytes = []const u8{ 1, 2, 3, 4, 5, 6, 7 };
67 var ss = io.SliceInStream.init(bytes);67 var ss = io.SliceInStream.init(bytes);
6868
69 var dest: [4]u8 = undefined;69 var dest: [4]u8 = undefined;
...@@ -81,7 +81,7 @@ test "SliceInStream" {...@@ -81,7 +81,7 @@ test "SliceInStream" {
81}81}
8282
83test "PeekStream" {83test "PeekStream" {
84 const bytes = []const u8 { 1, 2, 3, 4, 5, 6, 7, 8 };84 const bytes = []const u8{ 1, 2, 3, 4, 5, 6, 7, 8 };
85 var ss = io.SliceInStream.init(bytes);85 var ss = io.SliceInStream.init(bytes);
86 var ps = io.PeekStream(2, io.SliceInStream.Error).init(&ss.stream);86 var ps = io.PeekStream(2, io.SliceInStream.Error).init(&ss.stream);
8787
std/os/file.zig+27-15
...@@ -27,7 +27,6 @@ pub const File = struct {...@@ -27,7 +27,6 @@ pub const File = struct {
2727
28 pub const OpenError = os.WindowsOpenError || os.PosixOpenError;28 pub const OpenError = os.WindowsOpenError || os.PosixOpenError;
2929
30 /// `path` needs to be copied in memory to add a null terminating byte, hence the allocator.
31 /// Call close to clean up.30 /// Call close to clean up.
32 pub fn openRead(path: []const u8) OpenError!File {31 pub fn openRead(path: []const u8) OpenError!File {
33 if (is_posix) {32 if (is_posix) {
...@@ -49,15 +48,14 @@ pub const File = struct {...@@ -49,15 +48,14 @@ pub const File = struct {
49 }48 }
5049
51 /// Calls `openWriteMode` with os.File.default_mode for the mode.50 /// Calls `openWriteMode` with os.File.default_mode for the mode.
52 pub fn openWrite(allocator: *mem.Allocator, path: []const u8) OpenError!File {51 pub fn openWrite(path: []const u8) OpenError!File {
53 return openWriteMode(allocator, path, os.File.default_mode);52 return openWriteMode(path, os.File.default_mode);
54 }53 }
5554
56 /// If the path does not exist it will be created.55 /// If the path does not exist it will be created.
57 /// If a file already exists in the destination it will be truncated.56 /// If a file already exists in the destination it will be truncated.
58 /// `path` needs to be copied in memory to add a null terminating byte, hence the allocator.
59 /// Call close to clean up.57 /// Call close to clean up.
60 pub fn openWriteMode(allocator: *mem.Allocator, path: []const u8, file_mode: Mode) OpenError!File {58 pub fn openWriteMode(path: []const u8, file_mode: Mode) OpenError!File {
61 if (is_posix) {59 if (is_posix) {
62 const flags = posix.O_LARGEFILE | posix.O_WRONLY | posix.O_CREAT | posix.O_CLOEXEC | posix.O_TRUNC;60 const flags = posix.O_LARGEFILE | posix.O_WRONLY | posix.O_CREAT | posix.O_CLOEXEC | posix.O_TRUNC;
63 const fd = try os.posixOpen(path, flags, file_mode);61 const fd = try os.posixOpen(path, flags, file_mode);
...@@ -78,16 +76,14 @@ pub const File = struct {...@@ -78,16 +76,14 @@ pub const File = struct {
7876
79 /// If the path does not exist it will be created.77 /// If the path does not exist it will be created.
80 /// If a file already exists in the destination this returns OpenError.PathAlreadyExists78 /// If a file already exists in the destination this returns OpenError.PathAlreadyExists
81 /// `path` needs to be copied in memory to add a null terminating byte, hence the allocator.
82 /// Call close to clean up.79 /// Call close to clean up.
83 pub fn openWriteNoClobber(allocator: *mem.Allocator, path: []const u8, file_mode: Mode) OpenError!File {80 pub fn openWriteNoClobber(path: []const u8, file_mode: Mode) OpenError!File {
84 if (is_posix) {81 if (is_posix) {
85 const flags = posix.O_LARGEFILE | posix.O_WRONLY | posix.O_CREAT | posix.O_CLOEXEC | posix.O_EXCL;82 const flags = posix.O_LARGEFILE | posix.O_WRONLY | posix.O_CREAT | posix.O_CLOEXEC | posix.O_EXCL;
86 const fd = try os.posixOpen(allocator, path, flags, file_mode);83 const fd = try os.posixOpen(path, flags, file_mode);
87 return openHandle(fd);84 return openHandle(fd);
88 } else if (is_windows) {85 } else if (is_windows) {
89 const handle = try os.windowsOpen(86 const handle = try os.windowsOpen(
90 allocator,
91 path,87 path,
92 windows.GENERIC_WRITE,88 windows.GENERIC_WRITE,
93 windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,89 windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,
...@@ -117,12 +113,13 @@ pub const File = struct {...@@ -117,12 +113,13 @@ pub const File = struct {
117 Unexpected,113 Unexpected,
118 };114 };
119115
120 pub fn access(allocator: *mem.Allocator, path: []const u8) AccessError!void {116 pub fn accessC(path: [*]const u8) AccessError!void {
121 const path_with_null = try std.cstr.addNullByte(allocator, path);117 if (is_windows) {
122 defer allocator.free(path_with_null);118 // this needs to convert to UTF-16LE and call accessW
123119 @compileError("TODO support windows");
120 }
124 if (is_posix) {121 if (is_posix) {
125 const result = posix.access(path_with_null.ptr, posix.F_OK);122 const result = posix.access(path, posix.F_OK);
126 const err = posix.getErrno(result);123 const err = posix.getErrno(result);
127 switch (err) {124 switch (err) {
128 0 => return,125 0 => return,
...@@ -141,7 +138,7 @@ pub const File = struct {...@@ -141,7 +138,7 @@ pub const File = struct {
141 else => return os.unexpectedErrorPosix(err),138 else => return os.unexpectedErrorPosix(err),
142 }139 }
143 } else if (is_windows) {140 } else if (is_windows) {
144 if (os.windows.GetFileAttributesA(path_with_null.ptr) != os.windows.INVALID_FILE_ATTRIBUTES) {141 if (os.windows.GetFileAttributesA(path) != os.windows.INVALID_FILE_ATTRIBUTES) {
145 return;142 return;
146 }143 }
147144
...@@ -158,6 +155,21 @@ pub const File = struct {...@@ -158,6 +155,21 @@ pub const File = struct {
158 }155 }
159 }156 }
160157
158 pub fn access(path: []const u8) AccessError!void {
159 if (is_windows) {
160 // this needs to convert to UTF-16LE and call accessW
161 @compileError("TODO support windows");
162 }
163 if (is_posix) {
164 var path_with_null: [posix.PATH_MAX]u8 = undefined;
165 if (path.len >= posix.PATH_MAX) return error.NameTooLong;
166 mem.copy(u8, path_with_null[0..], path);
167 path_with_null[path.len] = 0;
168 return accessC(&path_with_null);
169 }
170 @compileError("TODO implement access for this OS");
171 }
172
161 /// Upon success, the stream is in an uninitialized state. To continue using it,173 /// Upon success, the stream is in an uninitialized state. To continue using it,
162 /// you must use the open() function.174 /// you must use the open() function.
163 pub fn close(self: *File) void {175 pub fn close(self: *File) void {
std/os/index.zig+168-178
...@@ -39,11 +39,14 @@ pub const File = @import("file.zig").File;...@@ -39,11 +39,14 @@ pub const File = @import("file.zig").File;
39pub const time = @import("time.zig");39pub const time = @import("time.zig");
4040
41pub const page_size = 4 * 1024;41pub const page_size = 4 * 1024;
42pub const PATH_MAX = switch (builtin.os) {42pub const MAX_PATH_BYTES = switch (builtin.os) {
43 Os.linux => linux.PATH_MAX,43 Os.linux, Os.macosx, Os.ios => posix.PATH_MAX,
44 Os.macosx, Os.ios => darwin.PATH_MAX,44 // Each UTF-16LE character may be expanded to 3 UTF-8 bytes.
45 // If it would require 4 UTF-8 bytes, then there would be a surrogate
46 // pair in the UTF-16LE, and we (over)account 3 bytes for it that way.
47 // +1 for the null byte at the end, which can be encoded in 1 byte.
48 Os.windows => 32767 * 3 + 1,
45 else => @compileError("Unsupported OS"),49 else => @compileError("Unsupported OS"),
46 // https://msdn.microsoft.com/en-us/library/930f87yf.aspx
47};50};
4851
49pub const UserInfo = @import("get_user_id.zig").UserInfo;52pub const UserInfo = @import("get_user_id.zig").UserInfo;
...@@ -423,7 +426,6 @@ pub fn posix_pwritev(fd: i32, iov: [*]const posix.iovec_const, count: usize, off...@@ -423,7 +426,6 @@ pub fn posix_pwritev(fd: i32, iov: [*]const posix.iovec_const, count: usize, off
423}426}
424427
425pub const PosixOpenError = error{428pub const PosixOpenError = error{
426 OutOfMemory,
427 AccessDenied,429 AccessDenied,
428 FileTooBig,430 FileTooBig,
429 IsDir,431 IsDir,
...@@ -444,12 +446,10 @@ pub const PosixOpenError = error{...@@ -444,12 +446,10 @@ pub const PosixOpenError = error{
444/// Calls POSIX open, keeps trying if it gets interrupted, and translates446/// Calls POSIX open, keeps trying if it gets interrupted, and translates
445/// the return value into zig errors.447/// the return value into zig errors.
446pub fn posixOpen(file_path: []const u8, flags: u32, perm: usize) PosixOpenError!i32 {448pub fn posixOpen(file_path: []const u8, flags: u32, perm: usize) PosixOpenError!i32 {
447 var path_with_null: [PATH_MAX]u8 = undefined;449 var path_with_null: [posix.PATH_MAX]u8 = undefined;
448 if (file_path.len > PATH_MAX - 1)450 if (file_path.len >= posix.PATH_MAX) return error.NameTooLong;
449 return error.NameTooLong;451 mem.copy(u8, path_with_null[0..], file_path);
450 mem.copy(u8, path_with_null[0..PATH_MAX - 1], file_path);452 path_with_null[file_path.len] = 0;
451 path_with_null[file_path.len] = '\x00';
452
453 return posixOpenC(&path_with_null, flags, perm);453 return posixOpenC(&path_with_null, flags, perm);
454}454}
455455
...@@ -728,43 +728,35 @@ pub fn getEnvVarOwned(allocator: *mem.Allocator, key: []const u8) GetEnvVarOwned...@@ -728,43 +728,35 @@ pub fn getEnvVarOwned(allocator: *mem.Allocator, key: []const u8) GetEnvVarOwned
728}728}
729729
730/// Caller must free the returned memory.730/// Caller must free the returned memory.
731pub fn getCwd(allocator: *Allocator) ![]u8 {731pub fn getCwdAlloc(allocator: *Allocator) ![]u8 {
732 switch (builtin.os) {732 var buf: [MAX_PATH_BYTES]u8 = undefined;
733 Os.windows => {733 return mem.dupe(allocator, u8, try getCwd(&buf));
734 var buf = try allocator.alloc(u8, 256);734}
735 errdefer allocator.free(buf);
736
737 while (true) {
738 const result = windows.GetCurrentDirectoryA(@intCast(windows.WORD, buf.len), buf.ptr);
739735
740 if (result == 0) {736pub const GetCwdError = error{Unexpected};
741 const err = windows.GetLastError();
742 return switch (err) {
743 else => unexpectedErrorWindows(err),
744 };
745 }
746737
747 if (result > buf.len) {738/// The result is a slice of out_buffer.
748 buf = try allocator.realloc(u8, buf, result);739pub fn getCwd(out_buffer: *[MAX_PATH_BYTES]u8) GetCwdError![]u8 {
749 continue;740 switch (builtin.os) {
741 Os.windows => {
742 var utf16le_buf: [windows_util.PATH_MAX_UTF16]u16 = undefined;
743 const result = windows.GetCurrentDirectoryW(utf16le_buf.len, &utf16le_buf);
744 if (result == 0) {
745 const err = windows.GetLastError();
746 switch (err) {
747 else => return unexpectedErrorWindows(err),
750 }748 }
751
752 return allocator.shrink(u8, buf, result);
753 }749 }
750 assert(result <= buf.len);
751 const utf16le_slice = utf16le_buf[0..result];
752 return std.unicode.utf16leToUtf8(out_buffer, utf16le_buf);
754 },753 },
755 else => {754 else => {
756 var buf = try allocator.alloc(u8, 1024);755 const err = posix.getErrno(posix.getcwd(out_buffer, out_buffer.len));
757 errdefer allocator.free(buf);756 switch (err) {
758 while (true) {757 0 => return cstr.toSlice(out_buffer),
759 const err = posix.getErrno(posix.getcwd(buf.ptr, buf.len));758 posix.ERANGE => unreachable,
760 if (err == posix.ERANGE) {759 else => return unexpectedErrorPosix(err),
761 buf = try allocator.realloc(u8, buf, buf.len * 2);
762 continue;
763 } else if (err > 0) {
764 return unexpectedErrorPosix(err);
765 }
766
767 return allocator.shrink(u8, buf, cstr.len(buf.ptr));
768 }760 }
769 },761 },
770 }762 }
...@@ -899,56 +891,45 @@ pub const DeleteFileError = error{...@@ -899,56 +891,45 @@ pub const DeleteFileError = error{
899 Unexpected,891 Unexpected,
900};892};
901893
902pub fn deleteFile(allocator: *Allocator, file_path: []const u8) DeleteFileError!void {894pub fn deleteFile(file_path: []const u8) DeleteFileError!void {
903 if (builtin.os == Os.windows) {895 if (builtin.os == Os.windows) {
904 return deleteFileWindows(allocator, file_path);896 return deleteFileWindows(file_path);
905 } else {897 } else {
906 return deleteFilePosix(allocator, file_path);898 return deleteFilePosix(file_path);
907 }899 }
908}900}
909901
910pub fn deleteFileWindows(allocator: *Allocator, file_path: []const u8) !void {902pub fn deleteFileWindows(file_path: []const u8) !void {
911 const buf = try allocator.alloc(u8, file_path.len + 1);903 @compileError("TODO rewrite with DeleteFileW and no allocator");
912 defer allocator.free(buf);904}
913
914 mem.copy(u8, buf, file_path);
915 buf[file_path.len] = 0;
916905
917 if (windows.DeleteFileA(buf.ptr) == 0) {906pub fn deleteFilePosixC(file_path: [*]const u8) !void {
918 const err = windows.GetLastError();907 const err = posix.getErrno(posix.unlink(file_path));
919 return switch (err) {908 switch (err) {
920 windows.ERROR.FILE_NOT_FOUND => error.FileNotFound,909 0 => return,
921 windows.ERROR.ACCESS_DENIED => error.AccessDenied,910 posix.EACCES => return error.AccessDenied,
922 windows.ERROR.FILENAME_EXCED_RANGE, windows.ERROR.INVALID_PARAMETER => error.NameTooLong,911 posix.EPERM => return error.AccessDenied,
923 else => unexpectedErrorWindows(err),912 posix.EBUSY => return error.FileBusy,
924 };913 posix.EFAULT => unreachable,
914 posix.EINVAL => unreachable,
915 posix.EIO => return error.FileSystem,
916 posix.EISDIR => return error.IsDir,
917 posix.ELOOP => return error.SymLinkLoop,
918 posix.ENAMETOOLONG => return error.NameTooLong,
919 posix.ENOENT => return error.FileNotFound,
920 posix.ENOTDIR => return error.NotDir,
921 posix.ENOMEM => return error.SystemResources,
922 posix.EROFS => return error.ReadOnlyFileSystem,
923 else => return unexpectedErrorPosix(err),
925 }924 }
926}925}
927926
928pub fn deleteFilePosix(allocator: *Allocator, file_path: []const u8) !void {927pub fn deleteFilePosix(file_path: []const u8) !void {
929 const buf = try allocator.alloc(u8, file_path.len + 1);928 var path_with_null: [posix.PATH_MAX]u8 = undefined;
930 defer allocator.free(buf);929 if (file_path.len >= posix.PATH_MAX) return error.NameTooLong;
931930 mem.copy(u8, path_with_null[0..], file_path);
932 mem.copy(u8, buf, file_path);931 path_with_null[file_path.len] = 0;
933 buf[file_path.len] = 0;932 return deleteFilePosixC(&path_with_null);
934
935 const err = posix.getErrno(posix.unlink(buf.ptr));
936 if (err > 0) {
937 return switch (err) {
938 posix.EACCES, posix.EPERM => error.AccessDenied,
939 posix.EBUSY => error.FileBusy,
940 posix.EFAULT, posix.EINVAL => unreachable,
941 posix.EIO => error.FileSystem,
942 posix.EISDIR => error.IsDir,
943 posix.ELOOP => error.SymLinkLoop,
944 posix.ENAMETOOLONG => error.NameTooLong,
945 posix.ENOENT => error.FileNotFound,
946 posix.ENOTDIR => error.NotDir,
947 posix.ENOMEM => error.SystemResources,
948 posix.EROFS => error.ReadOnlyFileSystem,
949 else => unexpectedErrorPosix(err),
950 };
951 }
952}933}
953934
954/// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is935/// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is
...@@ -956,6 +937,7 @@ pub fn deleteFilePosix(allocator: *Allocator, file_path: []const u8) !void {...@@ -956,6 +937,7 @@ pub fn deleteFilePosix(allocator: *Allocator, file_path: []const u8) !void {
956/// there is a possibility of power loss or application termination leaving temporary files present937/// there is a possibility of power loss or application termination leaving temporary files present
957/// in the same directory as dest_path.938/// in the same directory as dest_path.
958/// Destination file will have the same mode as the source file.939/// Destination file will have the same mode as the source file.
940/// TODO investigate if this can work with no allocator
959pub fn copyFile(allocator: *Allocator, source_path: []const u8, dest_path: []const u8) !void {941pub fn copyFile(allocator: *Allocator, source_path: []const u8, dest_path: []const u8) !void {
960 var in_file = try os.File.openRead(source_path);942 var in_file = try os.File.openRead(source_path);
961 defer in_file.close();943 defer in_file.close();
...@@ -978,6 +960,7 @@ pub fn copyFile(allocator: *Allocator, source_path: []const u8, dest_path: []con...@@ -978,6 +960,7 @@ pub fn copyFile(allocator: *Allocator, source_path: []const u8, dest_path: []con
978/// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is960/// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is
979/// merged and readily available,961/// merged and readily available,
980/// there is a possibility of power loss or application termination leaving temporary files present962/// there is a possibility of power loss or application termination leaving temporary files present
963/// TODO investigate if this can work with no allocator
981pub fn copyFileMode(allocator: *Allocator, source_path: []const u8, dest_path: []const u8, mode: File.Mode) !void {964pub fn copyFileMode(allocator: *Allocator, source_path: []const u8, dest_path: []const u8, mode: File.Mode) !void {
982 var in_file = try os.File.openRead(source_path);965 var in_file = try os.File.openRead(source_path);
983 defer in_file.close();966 defer in_file.close();
...@@ -996,6 +979,7 @@ pub fn copyFileMode(allocator: *Allocator, source_path: []const u8, dest_path: [...@@ -996,6 +979,7 @@ pub fn copyFileMode(allocator: *Allocator, source_path: []const u8, dest_path: [
996}979}
997980
998pub const AtomicFile = struct {981pub const AtomicFile = struct {
982 /// TODO investigate if we can make this work with no allocator
999 allocator: *Allocator,983 allocator: *Allocator,
1000 file: os.File,984 file: os.File,
1001 tmp_path: []u8,985 tmp_path: []u8,
...@@ -1023,7 +1007,7 @@ pub const AtomicFile = struct {...@@ -1023,7 +1007,7 @@ pub const AtomicFile = struct {
1023 try getRandomBytes(rand_buf[0..]);1007 try getRandomBytes(rand_buf[0..]);
1024 b64_fs_encoder.encode(tmp_path[dirname_component_len..], rand_buf);1008 b64_fs_encoder.encode(tmp_path[dirname_component_len..], rand_buf);
10251009
1026 const file = os.File.openWriteNoClobber(allocator, tmp_path, mode) catch |err| switch (err) {1010 const file = os.File.openWriteNoClobber(tmp_path, mode) catch |err| switch (err) {
1027 error.PathAlreadyExists => continue,1011 error.PathAlreadyExists => continue,
1028 // TODO zig should figure out that this error set does not include PathAlreadyExists since1012 // TODO zig should figure out that this error set does not include PathAlreadyExists since
1029 // it is handled in the above switch1013 // it is handled in the above switch
...@@ -1059,56 +1043,59 @@ pub const AtomicFile = struct {...@@ -1059,56 +1043,59 @@ pub const AtomicFile = struct {
1059 }1043 }
1060};1044};
10611045
1062pub fn rename(allocator: *Allocator, old_path: []const u8, new_path: []const u8) !void {1046pub fn renameC(old_path: [*]const u8, new_path: [*]const u8) !void {
1063 const full_buf = try allocator.alloc(u8, old_path.len + new_path.len + 2);
1064 defer allocator.free(full_buf);
1065
1066 const old_buf = full_buf;
1067 mem.copy(u8, old_buf, old_path);
1068 old_buf[old_path.len] = 0;
1069
1070 const new_buf = full_buf[old_path.len + 1 ..];
1071 mem.copy(u8, new_buf, new_path);
1072 new_buf[new_path.len] = 0;
1073
1074 if (is_windows) {1047 if (is_windows) {
1075 const flags = windows.MOVEFILE_REPLACE_EXISTING | windows.MOVEFILE_WRITE_THROUGH;1048 @compileError("TODO implement for windows");
1076 if (windows.MoveFileExA(old_buf.ptr, new_buf.ptr, flags) == 0) {
1077 const err = windows.GetLastError();
1078 return switch (err) {
1079 else => unexpectedErrorWindows(err),
1080 };
1081 }
1082 } else {1049 } else {
1083 const err = posix.getErrno(posix.rename(old_buf.ptr, new_buf.ptr));1050 const err = posix.getErrno(posix.rename(old_path, new_path));
1084 if (err > 0) {1051 switch (err) {
1085 return switch (err) {1052 0 => return,
1086 posix.EACCES, posix.EPERM => error.AccessDenied,1053 posix.EACCES => return error.AccessDenied,
1087 posix.EBUSY => error.FileBusy,1054 posix.EPERM => return error.AccessDenied,
1088 posix.EDQUOT => error.DiskQuota,1055 posix.EBUSY => return error.FileBusy,
1089 posix.EFAULT, posix.EINVAL => unreachable,1056 posix.EDQUOT => return error.DiskQuota,
1090 posix.EISDIR => error.IsDir,1057 posix.EFAULT => unreachable,
1091 posix.ELOOP => error.SymLinkLoop,1058 posix.EINVAL => unreachable,
1092 posix.EMLINK => error.LinkQuotaExceeded,1059 posix.EISDIR => return error.IsDir,
1093 posix.ENAMETOOLONG => error.NameTooLong,1060 posix.ELOOP => return error.SymLinkLoop,
1094 posix.ENOENT => error.FileNotFound,1061 posix.EMLINK => return error.LinkQuotaExceeded,
1095 posix.ENOTDIR => error.NotDir,1062 posix.ENAMETOOLONG => return error.NameTooLong,
1096 posix.ENOMEM => error.SystemResources,1063 posix.ENOENT => return error.FileNotFound,
1097 posix.ENOSPC => error.NoSpaceLeft,1064 posix.ENOTDIR => return error.NotDir,
1098 posix.EEXIST, posix.ENOTEMPTY => error.PathAlreadyExists,1065 posix.ENOMEM => return error.SystemResources,
1099 posix.EROFS => error.ReadOnlyFileSystem,1066 posix.ENOSPC => return error.NoSpaceLeft,
1100 posix.EXDEV => error.RenameAcrossMountPoints,1067 posix.EEXIST => return error.PathAlreadyExists,
1101 else => unexpectedErrorPosix(err),1068 posix.ENOTEMPTY => return error.PathAlreadyExists,
1102 };1069 posix.EROFS => return error.ReadOnlyFileSystem,
1070 posix.EXDEV => return error.RenameAcrossMountPoints,
1071 else => return unexpectedErrorPosix(err),
1103 }1072 }
1104 }1073 }
1105}1074}
11061075
1107pub fn makeDir(allocator: *Allocator, dir_path: []const u8) !void {1076pub fn rename(old_path: []const u8, new_path: []const u8) !void {
1077 if (is_windows) {
1078 @compileError("TODO rewrite with MoveFileExW and no allocator");
1079 } else {
1080 var old_path_with_null: [posix.PATH_MAX]u8 = undefined;
1081 if (old_path.len >= posix.PATH_MAX) return error.NameTooLong;
1082 mem.copy(u8, old_path_with_null[0..], old_path);
1083 old_path_with_null[old_path.len] = 0;
1084
1085 var new_path_with_null: [posix.PATH_MAX]u8 = undefined;
1086 if (new_path.len >= posix.PATH_MAX) return error.NameTooLong;
1087 mem.copy(u8, new_path_with_null[0..], new_path);
1088 new_path_with_null[new_path.len] = 0;
1089
1090 return renameC(&old_path_with_null, &new_path_with_null);
1091 }
1092}
1093
1094pub fn makeDir(dir_path: []const u8) !void {
1108 if (is_windows) {1095 if (is_windows) {
1109 return makeDirWindows(allocator, dir_path);1096 return makeDirWindows(dir_path);
1110 } else {1097 } else {
1111 return makeDirPosix(allocator, dir_path);1098 return makeDirPosix(dir_path);
1112 }1099 }
1113}1100}
11141101
...@@ -1126,30 +1113,35 @@ pub fn makeDirWindows(allocator: *Allocator, dir_path: []const u8) !void {...@@ -1126,30 +1113,35 @@ pub fn makeDirWindows(allocator: *Allocator, dir_path: []const u8) !void {
1126 }1113 }
1127}1114}
11281115
1129pub fn makeDirPosix(allocator: *Allocator, dir_path: []const u8) !void {1116pub fn makeDirPosixC(dir_path: [*]const u8) !void {
1130 const path_buf = try cstr.addNullByte(allocator, dir_path);
1131 defer allocator.free(path_buf);
1132
1133 const err = posix.getErrno(posix.mkdir(path_buf.ptr, 0o755));1117 const err = posix.getErrno(posix.mkdir(path_buf.ptr, 0o755));
1134 if (err > 0) {1118 switch (err) {
1135 return switch (err) {1119 0 => return,
1136 posix.EACCES, posix.EPERM => error.AccessDenied,1120 posix.EACCES => return error.AccessDenied,
1137 posix.EDQUOT => error.DiskQuota,1121 posix.EPERM => return error.AccessDenied,
1138 posix.EEXIST => error.PathAlreadyExists,1122 posix.EDQUOT => return error.DiskQuota,
1139 posix.EFAULT => unreachable,1123 posix.EEXIST => return error.PathAlreadyExists,
1140 posix.ELOOP => error.SymLinkLoop,1124 posix.EFAULT => unreachable,
1141 posix.EMLINK => error.LinkQuotaExceeded,1125 posix.ELOOP => return error.SymLinkLoop,
1142 posix.ENAMETOOLONG => error.NameTooLong,1126 posix.EMLINK => return error.LinkQuotaExceeded,
1143 posix.ENOENT => error.FileNotFound,1127 posix.ENAMETOOLONG => return error.NameTooLong,
1144 posix.ENOMEM => error.SystemResources,1128 posix.ENOENT => return error.FileNotFound,
1145 posix.ENOSPC => error.NoSpaceLeft,1129 posix.ENOMEM => return error.SystemResources,
1146 posix.ENOTDIR => error.NotDir,1130 posix.ENOSPC => return error.NoSpaceLeft,
1147 posix.EROFS => error.ReadOnlyFileSystem,1131 posix.ENOTDIR => return error.NotDir,
1148 else => unexpectedErrorPosix(err),1132 posix.EROFS => return error.ReadOnlyFileSystem,
1149 };1133 else => return unexpectedErrorPosix(err),
1150 }1134 }
1151}1135}
11521136
1137pub fn makeDirPosix(dir_path: []const u8) !void {
1138 var path_with_null: [posix.PATH_MAX]u8 = undefined;
1139 if (dir_path.len >= posix.PATH_MAX) return error.NameTooLong;
1140 mem.copy(u8, path_with_null[0..], dir_path);
1141 path_with_null[dir_path.len] = 0;
1142 return makeDirPosixC(&path_with_null);
1143}
1144
1153/// Calls makeDir recursively to make an entire path. Returns success if the path1145/// Calls makeDir recursively to make an entire path. Returns success if the path
1154/// already exists and is a directory.1146/// already exists and is a directory.
1155pub fn makePath(allocator: *Allocator, full_path: []const u8) !void {1147pub fn makePath(allocator: *Allocator, full_path: []const u8) !void {
...@@ -1409,6 +1401,7 @@ pub const Dir = struct {...@@ -1409,6 +1401,7 @@ pub const Dir = struct {
1409 },1401 },
1410 Os.macosx, Os.ios => Handle{1402 Os.macosx, Os.ios => Handle{
1411 .fd = try posixOpen(1403 .fd = try posixOpen(
1404 allocator,
1412 dir_path,1405 dir_path,
1413 posix.O_RDONLY | posix.O_NONBLOCK | posix.O_DIRECTORY | posix.O_CLOEXEC,1406 posix.O_RDONLY | posix.O_NONBLOCK | posix.O_DIRECTORY | posix.O_CLOEXEC,
1414 0,1407 0,
...@@ -1420,6 +1413,7 @@ pub const Dir = struct {...@@ -1420,6 +1413,7 @@ pub const Dir = struct {
1420 },1413 },
1421 Os.linux => Handle{1414 Os.linux => Handle{
1422 .fd = try posixOpen(1415 .fd = try posixOpen(
1416 allocator,
1423 dir_path,1417 dir_path,
1424 posix.O_RDONLY | posix.O_DIRECTORY | posix.O_CLOEXEC,1418 posix.O_RDONLY | posix.O_DIRECTORY | posix.O_CLOEXEC,
1425 0,1419 0,
...@@ -1616,39 +1610,35 @@ pub fn changeCurDir(allocator: *Allocator, dir_path: []const u8) !void {...@@ -1616,39 +1610,35 @@ pub fn changeCurDir(allocator: *Allocator, dir_path: []const u8) !void {
1616}1610}
16171611
1618/// Read value of a symbolic link.1612/// Read value of a symbolic link.
1619pub fn readLink(allocator: *Allocator, file_path: []const u8) ![]u8 {1613/// The return value is a slice of out_buffer.
1620 var path_with_null: [PATH_MAX]u8 = undefined;1614pub fn readLinkC(pathname: [*]const u8, out_buffer: *[posix.PATH_MAX]u8) ![]u8 {
1621 if (file_path.len > PATH_MAX - 1)1615 const rc = posix.readlink(pathname, out_buffer, out_buffer.len);
1622 return error.NameTooLong;1616 const err = posix.getErrno(rc);
1623 mem.copy(u8, path_with_null[0..PATH_MAX - 1], file_path);1617 switch (err) {
1624 path_with_null[file_path.len] = '\x00';1618 0 => return out_buffer[0..rc],
16251619 posix.EACCES => error.AccessDenied,
1626 var result_buf = try allocator.alloc(u8, 1024);1620 posix.EFAULT => unreachable,
1627 errdefer allocator.free(result_buf);1621 posix.EINVAL => unreachable,
1628 while (true) {1622 posix.EIO => return error.FileSystem,
1629 const ret_val = posix.readlink(&path_with_null, result_buf.ptr, result_buf.len);1623 posix.ELOOP => return error.SymLinkLoop,
1630 const err = posix.getErrno(ret_val);1624 posix.ENAMETOOLONG => unreachable, // out_buffer is at least PATH_MAX
1631 if (err > 0) {1625 posix.ENOENT => return error.FileNotFound,
1632 return switch (err) {1626 posix.ENOMEM => return error.SystemResources,
1633 posix.EACCES => error.AccessDenied,1627 posix.ENOTDIR => return error.NotDir,
1634 posix.EFAULT, posix.EINVAL => unreachable,1628 else => return unexpectedErrorPosix(err),
1635 posix.EIO => error.FileSystem,
1636 posix.ELOOP => error.SymLinkLoop,
1637 posix.ENAMETOOLONG => error.NameTooLong,
1638 posix.ENOENT => error.FileNotFound,
1639 posix.ENOMEM => error.SystemResources,
1640 posix.ENOTDIR => error.NotDir,
1641 else => unexpectedErrorPosix(err),
1642 };
1643 }
1644 if (ret_val == result_buf.len) {
1645 result_buf = try allocator.realloc(u8, result_buf, result_buf.len * 2);
1646 continue;
1647 }
1648 return allocator.shrink(u8, result_buf, ret_val);
1649 }1629 }
1650}1630}
16511631
1632/// Read value of a symbolic link.
1633/// The return value is a slice of out_buffer.
1634pub fn readLink(file_path: []const u8, out_buffer: *[posix.PATH_MAX]u8) ![]u8 {
1635 var path_with_null: [posix.PATH_MAX]u8 = undefined;
1636 if (file_path.len >= posix.PATH_MAX) return error.NameTooLong;
1637 mem.copy(u8, path_with_null[0..], file_path);
1638 path_with_null[file_path.len] = 0;
1639 return readLinkC(&path_with_null, out_buffer);
1640}
1641
1652pub fn posix_setuid(uid: u32) !void {1642pub fn posix_setuid(uid: u32) !void {
1653 const err = posix.getErrno(posix.setuid(uid));1643 const err = posix.getErrno(posix.setuid(uid));
1654 if (err == 0) return;1644 if (err == 0) return;
...@@ -2035,13 +2025,13 @@ pub fn openSelfExe() !os.File {...@@ -2035,13 +2025,13 @@ pub fn openSelfExe() !os.File {
2035 const proc_file_path = "/proc/self/exe";2025 const proc_file_path = "/proc/self/exe";
2036 var fixed_buffer_mem: [proc_file_path.len + 1]u8 = undefined;2026 var fixed_buffer_mem: [proc_file_path.len + 1]u8 = undefined;
2037 var fixed_allocator = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]);2027 var fixed_allocator = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]);
2038 return os.File.openRead(proc_file_path);2028 return os.File.openRead(&fixed_allocator.allocator, proc_file_path);
2039 },2029 },
2040 Os.macosx, Os.ios => {2030 Os.macosx, Os.ios => {
2041 var fixed_buffer_mem: [darwin.PATH_MAX * 2]u8 = undefined;2031 var fixed_buffer_mem: [darwin.PATH_MAX * 2]u8 = undefined;
2042 var fixed_allocator = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]);2032 var fixed_allocator = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]);
2043 const self_exe_path = try selfExePath(&fixed_allocator.allocator);2033 const self_exe_path = try selfExePath(&fixed_allocator.allocator);
2044 return os.File.openRead(self_exe_path);2034 return os.File.openRead(&fixed_allocator.allocator, self_exe_path);
2045 },2035 },
2046 else => @compileError("Unsupported OS"),2036 else => @compileError("Unsupported OS"),
2047 }2037 }
std/os/path.zig+3-2
...@@ -573,7 +573,7 @@ pub fn resolvePosix(allocator: *Allocator, paths: []const []const u8) ![]u8 {...@@ -573,7 +573,7 @@ pub fn resolvePosix(allocator: *Allocator, paths: []const []const u8) ![]u8 {
573 result_index += 1;573 result_index += 1;
574 }574 }
575575
576 return result[0..result_index];576 return allocator.shrink(u8, result, result_index);
577}577}
578578
579test "os.path.resolve" {579test "os.path.resolve" {
...@@ -1077,6 +1077,7 @@ fn testRelativeWindows(from: []const u8, to: []const u8, expected_output: []cons...@@ -1077,6 +1077,7 @@ fn testRelativeWindows(from: []const u8, to: []const u8, expected_output: []cons
1077/// Expands all symbolic links and resolves references to `.`, `..`, and1077/// Expands all symbolic links and resolves references to `.`, `..`, and
1078/// extra `/` characters in ::pathname.1078/// extra `/` characters in ::pathname.
1079/// Caller must deallocate result.1079/// Caller must deallocate result.
1080/// TODO rename this to realAlloc and provide real with no allocator. See #1392
1080pub fn real(allocator: *Allocator, pathname: []const u8) ![]u8 {1081pub fn real(allocator: *Allocator, pathname: []const u8) ![]u8 {
1081 switch (builtin.os) {1082 switch (builtin.os) {
1082 Os.windows => {1083 Os.windows => {
...@@ -1166,7 +1167,7 @@ pub fn real(allocator: *Allocator, pathname: []const u8) ![]u8 {...@@ -1166,7 +1167,7 @@ pub fn real(allocator: *Allocator, pathname: []const u8) ![]u8 {
1166 return allocator.shrink(u8, result_buf, cstr.len(result_buf.ptr));1167 return allocator.shrink(u8, result_buf, cstr.len(result_buf.ptr));
1167 },1168 },
1168 Os.linux => {1169 Os.linux => {
1169 const fd = try os.posixOpen(pathname, posix.O_PATH | posix.O_NONBLOCK | posix.O_CLOEXEC, 0);1170 const fd = try os.posixOpen(allocator, pathname, posix.O_PATH | posix.O_NONBLOCK | posix.O_CLOEXEC, 0);
1170 defer os.close(fd);1171 defer os.close(fd);
11711172
1172 var buf: ["/proc/self/fd/-2147483648".len]u8 = undefined;1173 var buf: ["/proc/self/fd/-2147483648".len]u8 = undefined;
std/os/windows/kernel32.zig+3-5
...@@ -1,6 +1,5 @@...@@ -1,6 +1,5 @@
1use @import("index.zig");1use @import("index.zig");
22
3
4pub extern "kernel32" stdcallcc fn CancelIoEx(hFile: HANDLE, lpOverlapped: LPOVERLAPPED) BOOL;3pub extern "kernel32" stdcallcc fn CancelIoEx(hFile: HANDLE, lpOverlapped: LPOVERLAPPED) BOOL;
54
6pub extern "kernel32" stdcallcc fn CloseHandle(hObject: HANDLE) BOOL;5pub extern "kernel32" stdcallcc fn CloseHandle(hObject: HANDLE) BOOL;
...@@ -74,7 +73,8 @@ pub extern "kernel32" stdcallcc fn GetCommandLineA() LPSTR;...@@ -74,7 +73,8 @@ pub extern "kernel32" stdcallcc fn GetCommandLineA() LPSTR;
7473
75pub extern "kernel32" stdcallcc fn GetConsoleMode(in_hConsoleHandle: HANDLE, out_lpMode: *DWORD) BOOL;74pub extern "kernel32" stdcallcc fn GetConsoleMode(in_hConsoleHandle: HANDLE, out_lpMode: *DWORD) BOOL;
7675
77pub extern "kernel32" stdcallcc fn GetCurrentDirectoryA(nBufferLength: WORD, lpBuffer: ?LPSTR) DWORD;76pub extern "kernel32" stdcallcc fn GetCurrentDirectoryA(nBufferLength: WORD, lpBuffer: ?[*]CHAR) DWORD;
77pub extern "kernel32" stdcallcc fn GetCurrentDirectoryW(nBufferLength: WORD, lpBuffer: ?[*]WCHAR) DWORD;
7878
79pub extern "kernel32" stdcallcc fn GetCurrentThread() HANDLE;79pub extern "kernel32" stdcallcc fn GetCurrentThread() HANDLE;
80pub extern "kernel32" stdcallcc fn GetCurrentThreadId() DWORD;80pub extern "kernel32" stdcallcc fn GetCurrentThreadId() DWORD;
...@@ -107,7 +107,6 @@ pub extern "kernel32" stdcallcc fn GetFinalPathNameByHandleA(...@@ -107,7 +107,6 @@ pub extern "kernel32" stdcallcc fn GetFinalPathNameByHandleA(
107 dwFlags: DWORD,107 dwFlags: DWORD,
108) DWORD;108) DWORD;
109109
110
111pub extern "kernel32" stdcallcc fn GetOverlappedResult(hFile: HANDLE, lpOverlapped: *OVERLAPPED, lpNumberOfBytesTransferred: *DWORD, bWait: BOOL) BOOL;110pub extern "kernel32" stdcallcc fn GetOverlappedResult(hFile: HANDLE, lpOverlapped: *OVERLAPPED, lpNumberOfBytesTransferred: *DWORD, bWait: BOOL) BOOL;
112111
113pub extern "kernel32" stdcallcc fn GetProcessHeap() ?HANDLE;112pub extern "kernel32" stdcallcc fn GetProcessHeap() ?HANDLE;
...@@ -194,7 +193,6 @@ pub extern "kernel32" stdcallcc fn LoadLibraryA(lpLibFileName: LPCSTR) ?HMODULE;...@@ -194,7 +193,6 @@ pub extern "kernel32" stdcallcc fn LoadLibraryA(lpLibFileName: LPCSTR) ?HMODULE;
194193
195pub extern "kernel32" stdcallcc fn FreeLibrary(hModule: HMODULE) BOOL;194pub extern "kernel32" stdcallcc fn FreeLibrary(hModule: HMODULE) BOOL;
196195
197
198pub const FILE_NOTIFY_INFORMATION = extern struct {196pub const FILE_NOTIFY_INFORMATION = extern struct {
199 NextEntryOffset: DWORD,197 NextEntryOffset: DWORD,
200 Action: DWORD,198 Action: DWORD,
...@@ -208,7 +206,7 @@ pub const FILE_ACTION_MODIFIED = 0x00000003;...@@ -208,7 +206,7 @@ pub const FILE_ACTION_MODIFIED = 0x00000003;
208pub const FILE_ACTION_RENAMED_OLD_NAME = 0x00000004;206pub const FILE_ACTION_RENAMED_OLD_NAME = 0x00000004;
209pub const FILE_ACTION_RENAMED_NEW_NAME = 0x00000005;207pub const FILE_ACTION_RENAMED_NEW_NAME = 0x00000005;
210208
211pub const LPOVERLAPPED_COMPLETION_ROUTINE = ?extern fn(DWORD, DWORD, *OVERLAPPED) void;209pub const LPOVERLAPPED_COMPLETION_ROUTINE = ?extern fn (DWORD, DWORD, *OVERLAPPED) void;
212210
213pub const FILE_LIST_DIRECTORY = 1;211pub const FILE_LIST_DIRECTORY = 1;
214212
std/os/windows/util.zig+5-22
...@@ -7,6 +7,8 @@ const mem = std.mem;...@@ -7,6 +7,8 @@ const mem = std.mem;
7const BufMap = std.BufMap;7const BufMap = std.BufMap;
8const cstr = std.cstr;8const cstr = std.cstr;
99
10pub const PATH_MAX_UTF16 = 32767;
11
10pub const WaitError = error{12pub const WaitError = error{
11 WaitAbandoned,13 WaitAbandoned,
12 WaitTimeOut,14 WaitTimeOut,
...@@ -90,36 +92,17 @@ pub const OpenError = error{...@@ -90,36 +92,17 @@ pub const OpenError = error{
90 AccessDenied,92 AccessDenied,
91 PipeBusy,93 PipeBusy,
92 Unexpected,94 Unexpected,
93 OutOfMemory,
94};95};
9596
96/// `file_path` needs to be copied in memory to add a null terminating byte, hence the allocator.97/// `file_path` needs to be copied in memory to add a null terminating byte, hence the allocator.
97pub fn windowsOpen(98pub fn windowsOpen(
98 allocator: *mem.Allocator,
99 file_path: []const u8,99 file_path: []const u8,
100 desired_access: windows.DWORD,100 desired_access: windows.DWORD,
101 share_mode: windows.DWORD,101 share_mode: windows.DWORD,
102 creation_disposition: windows.DWORD,102 creation_disposition: windows.DWORD,
103 flags_and_attrs: windows.DWORD,103 flags_and_attrs: windows.DWORD,
104) OpenError!windows.HANDLE {104) OpenError!windows.HANDLE {
105 const path_with_null = try cstr.addNullByte(allocator, file_path);105 @compileError("TODO rewrite with CreateFileW and no allocator");
106 defer allocator.free(path_with_null);
107
108 const result = windows.CreateFileA(path_with_null.ptr, desired_access, share_mode, null, creation_disposition, flags_and_attrs, null);
109
110 if (result == windows.INVALID_HANDLE_VALUE) {
111 const err = windows.GetLastError();
112 return switch (err) {
113 windows.ERROR.SHARING_VIOLATION => OpenError.SharingViolation,
114 windows.ERROR.ALREADY_EXISTS, windows.ERROR.FILE_EXISTS => OpenError.PathAlreadyExists,
115 windows.ERROR.FILE_NOT_FOUND => OpenError.FileNotFound,
116 windows.ERROR.ACCESS_DENIED => OpenError.AccessDenied,
117 windows.ERROR.PIPE_BUSY => OpenError.PipeBusy,
118 else => os.unexpectedErrorWindows(err),
119 };
120 }
121
122 return result;
123}106}
124107
125/// Caller must free result.108/// Caller must free result.
...@@ -238,7 +221,7 @@ pub fn windowsPostQueuedCompletionStatus(completion_port: windows.HANDLE, bytes_...@@ -238,7 +221,7 @@ pub fn windowsPostQueuedCompletionStatus(completion_port: windows.HANDLE, bytes_
238 }221 }
239}222}
240223
241pub const WindowsWaitResult = enum{224pub const WindowsWaitResult = enum {
242 Normal,225 Normal,
243 Aborted,226 Aborted,
244 Cancelled,227 Cancelled,
...@@ -254,7 +237,7 @@ pub fn windowsGetQueuedCompletionStatus(completion_port: windows.HANDLE, bytes_t...@@ -254,7 +237,7 @@ pub fn windowsGetQueuedCompletionStatus(completion_port: windows.HANDLE, bytes_t
254 if (std.debug.runtime_safety) {237 if (std.debug.runtime_safety) {
255 std.debug.panic("unexpected error: {}\n", err);238 std.debug.panic("unexpected error: {}\n", err);
256 }239 }
257 }240 },
258 }241 }
259 }242 }
260 return WindowsWaitResult.Normal;243 return WindowsWaitResult.Normal;
std/unicode.zig+44-25
...@@ -218,7 +218,6 @@ const Utf8Iterator = struct {...@@ -218,7 +218,6 @@ const Utf8Iterator = struct {
218 }218 }
219219
220 const cp_len = utf8ByteSequenceLength(it.bytes[it.i]) catch unreachable;220 const cp_len = utf8ByteSequenceLength(it.bytes[it.i]) catch unreachable;
221
222 it.i += cp_len;221 it.i += cp_len;
223 return it.bytes[it.i - cp_len .. it.i];222 return it.bytes[it.i - cp_len .. it.i];
224 }223 }
...@@ -236,6 +235,34 @@ const Utf8Iterator = struct {...@@ -236,6 +235,34 @@ const Utf8Iterator = struct {
236 }235 }
237};236};
238237
238pub const Utf16LeIterator = struct {
239 bytes: []const u8,
240 i: usize,
241
242 pub fn init(s: []const u16) Utf16LeIterator {
243 return Utf16LeIterator{
244 .bytes = @sliceToBytes(s),
245 .i = 0,
246 };
247 }
248
249 pub fn nextCodepoint(it: *Utf16LeIterator) !?u32 {
250 const c0: u32 = mem.readIntLE(u16, it.bytes[it.i .. it.i + 2]);
251 if (c0 & ~u32(0x03ff) == 0xd800) {
252 // surrogate pair
253 it.i += 2;
254 if (it.i >= it.bytes.len) return error.DanglingSurrogateHalf;
255 const c1: u32 = mem.readIntLE(u16, it.bytes[it.i .. it.i + 2]);
256 if (c1 & ~u32(0x03ff) != 0xdc00) return error.ExpectedSecondSurrogateHalf;
257 return 0x10000 + (((c0 & 0x03ff) << 10) | (c1 & 0x03ff));
258 } else if (c0 & ~u32(0x03ff) == 0xdc00) {
259 return error.UnexpectedSecondSurrogateHalf;
260 } else {
261 return c0;
262 }
263 }
264};
265
239test "utf8 encode" {266test "utf8 encode" {
240 comptime testUtf8Encode() catch unreachable;267 comptime testUtf8Encode() catch unreachable;
241 try testUtf8Encode();268 try testUtf8Encode();
...@@ -446,42 +473,34 @@ fn testDecode(bytes: []const u8) !u32 {...@@ -446,42 +473,34 @@ fn testDecode(bytes: []const u8) !u32 {
446 return utf8Decode(bytes);473 return utf8Decode(bytes);
447}474}
448475
449// TODO: make this API on top of a non-allocating Utf16LeView476/// Caller must free returned memory.
450pub fn utf16leToUtf8(allocator: *mem.Allocator, utf16le: []const u16) ![]u8 {477pub fn utf16leToUtf8Alloc(allocator: *mem.Allocator, utf16le: []const u16) ![]u8 {
451 var result = std.ArrayList(u8).init(allocator);478 var result = std.ArrayList(u8).init(allocator);
452 // optimistically guess that it will all be ascii.479 // optimistically guess that it will all be ascii.
453 try result.ensureCapacity(utf16le.len);480 try result.ensureCapacity(utf16le.len);
454
455 const utf16le_as_bytes = @sliceToBytes(utf16le);
456 var i: usize = 0;
457 var out_index: usize = 0;481 var out_index: usize = 0;
458 while (i < utf16le_as_bytes.len) : (i += 2) {482 var it = Utf16LeIterator.init(utf16le);
459 // decode483 while (try it.nextCodepoint()) |codepoint| {
460 const c0: u32 = mem.readIntLE(u16, utf16le_as_bytes[i..i + 2]);
461 var codepoint: u32 = undefined;
462 if (c0 & ~u32(0x03ff) == 0xd800) {
463 // surrogate pair
464 i += 2;
465 if (i >= utf16le_as_bytes.len) return error.DanglingSurrogateHalf;
466 const c1: u32 = mem.readIntLE(u16, utf16le_as_bytes[i..i + 2]);
467 if (c1 & ~u32(0x03ff) != 0xdc00) return error.ExpectedSecondSurrogateHalf;
468 codepoint = 0x10000 + (((c0 & 0x03ff) << 10) | (c1 & 0x03ff));
469 } else if (c0 & ~u32(0x03ff) == 0xdc00) {
470 return error.UnexpectedSecondSurrogateHalf;
471 } else {
472 codepoint = c0;
473 }
474
475 // encode
476 const utf8_len = utf8CodepointSequenceLength(codepoint) catch unreachable;484 const utf8_len = utf8CodepointSequenceLength(codepoint) catch unreachable;
477 try result.resize(result.len + utf8_len);485 try result.resize(result.len + utf8_len);
478 _ = utf8Encode(codepoint, result.items[out_index..]) catch unreachable;486 assert((utf8Encode(codepoint, result.items[out_index..]) catch unreachable) == utf8_len);
479 out_index += utf8_len;487 out_index += utf8_len;
480 }488 }
481489
482 return result.toOwnedSlice();490 return result.toOwnedSlice();
483}491}
484492
493pub fn utf16leToUtf8(utf8: []u8, utf16le: []const u16) !void {
494 var out_index: usize = 0;
495 var it = Utf16LeIterator.init(utf16le);
496 while (try it.nextCodepoint()) |codepoint| {
497 const utf8_len = utf8CodepointSequenceLength(codepoint) catch unreachable;
498 try result.resize(result.len + utf8_len);
499 assert((utf8Encode(codepoint, result.items[out_index..]) catch unreachable) == utf8_len);
500 out_index += utf8_len;
501 }
502}
503
485test "utf16leToUtf8" {504test "utf16leToUtf8" {
486 var utf16le: [2]u16 = undefined;505 var utf16le: [2]u16 = undefined;
487 const utf16le_as_bytes = @sliceToBytes(utf16le[0..]);506 const utf16le_as_bytes = @sliceToBytes(utf16le[0..]);