| ... | @@ -235,6 +235,24 @@ const Thread = struct { | ... | @@ -235,6 +235,24 @@ const Thread = struct { |
| 235 | ) orelse return; | 235 | ) orelse return; |
| 236 | } | 236 | } |
| 237 | | 237 | |
| | 238 | fn endSyscallErrnoBug(thread: *Thread, err: posix.E) Io.UnexpectedError { |
| | 239 | @branchHint(.cold); |
| | 240 | thread.endSyscall(); |
| | 241 | return errnoBug(err); |
| | 242 | } |
| | 243 | |
| | 244 | fn endSyscallUnexpectedErrno(thread: *Thread, err: posix.E) Io.UnexpectedError { |
| | 245 | @branchHint(.cold); |
| | 246 | thread.endSyscall(); |
| | 247 | return posix.unexpectedErrno(err); |
| | 248 | } |
| | 249 | |
| | 250 | /// inline to make error return traces slightly shallower. |
| | 251 | inline fn endSyscallError(thread: *Thread, err: anytype) @TypeOf(err) { |
| | 252 | thread.endSyscall(); |
| | 253 | return err; |
| | 254 | } |
| | 255 | |
| 238 | fn currentSignalId() SignaleeId { | 256 | fn currentSignalId() SignaleeId { |
| 239 | return if (std.Thread.use_pthreads) std.c.pthread_self() else std.Thread.getCurrentId(); | 257 | return if (std.Thread.use_pthreads) std.c.pthread_self() else std.Thread.getCurrentId(); |
| 240 | } | 258 | } |
| ... | @@ -811,7 +829,6 @@ pub fn io(t: *Threaded) Io { | ... | @@ -811,7 +829,6 @@ pub fn io(t: *Threaded) Io { |
| 811 | .dirSetPermissions = dirSetPermissions, | 829 | .dirSetPermissions = dirSetPermissions, |
| 812 | .dirSetFilePermissions = dirSetFilePermissions, | 830 | .dirSetFilePermissions = dirSetFilePermissions, |
| 813 | .dirSetTimestamps = dirSetTimestamps, | 831 | .dirSetTimestamps = dirSetTimestamps, |
| 814 | .dirSetTimestampsNow = dirSetTimestampsNow, | | |
| 815 | .dirHardLink = dirHardLink, | 832 | .dirHardLink = dirHardLink, |
| 816 | | 833 | |
| 817 | .fileStat = fileStat, | 834 | .fileStat = fileStat, |
| ... | @@ -833,7 +850,6 @@ pub fn io(t: *Threaded) Io { | ... | @@ -833,7 +850,6 @@ pub fn io(t: *Threaded) Io { |
| 833 | .fileSetOwner = fileSetOwner, | 850 | .fileSetOwner = fileSetOwner, |
| 834 | .fileSetPermissions = fileSetPermissions, | 851 | .fileSetPermissions = fileSetPermissions, |
| 835 | .fileSetTimestamps = fileSetTimestamps, | 852 | .fileSetTimestamps = fileSetTimestamps, |
| 836 | .fileSetTimestampsNow = fileSetTimestampsNow, | | |
| 837 | .fileLock = fileLock, | 853 | .fileLock = fileLock, |
| 838 | .fileTryLock = fileTryLock, | 854 | .fileTryLock = fileTryLock, |
| 839 | .fileUnlock = fileUnlock, | 855 | .fileUnlock = fileUnlock, |
| ... | @@ -947,7 +963,6 @@ pub fn ioBasic(t: *Threaded) Io { | ... | @@ -947,7 +963,6 @@ pub fn ioBasic(t: *Threaded) Io { |
| 947 | .dirSetPermissions = dirSetPermissions, | 963 | .dirSetPermissions = dirSetPermissions, |
| 948 | .dirSetFilePermissions = dirSetFilePermissions, | 964 | .dirSetFilePermissions = dirSetFilePermissions, |
| 949 | .dirSetTimestamps = dirSetTimestamps, | 965 | .dirSetTimestamps = dirSetTimestamps, |
| 950 | .dirSetTimestampsNow = dirSetTimestampsNow, | | |
| 951 | .dirHardLink = dirHardLink, | 966 | .dirHardLink = dirHardLink, |
| 952 | | 967 | |
| 953 | .fileStat = fileStat, | 968 | .fileStat = fileStat, |
| ... | @@ -969,7 +984,6 @@ pub fn ioBasic(t: *Threaded) Io { | ... | @@ -969,7 +984,6 @@ pub fn ioBasic(t: *Threaded) Io { |
| 969 | .fileSetOwner = fileSetOwner, | 984 | .fileSetOwner = fileSetOwner, |
| 970 | .fileSetPermissions = fileSetPermissions, | 985 | .fileSetPermissions = fileSetPermissions, |
| 971 | .fileSetTimestamps = fileSetTimestamps, | 986 | .fileSetTimestamps = fileSetTimestamps, |
| 972 | .fileSetTimestampsNow = fileSetTimestampsNow, | | |
| 973 | .fileLock = fileLock, | 987 | .fileLock = fileLock, |
| 974 | .fileTryLock = fileTryLock, | 988 | .fileTryLock = fileTryLock, |
| 975 | .fileUnlock = fileUnlock, | 989 | .fileUnlock = fileUnlock, |
| ... | @@ -5977,8 +5991,6 @@ fn dirSetTimestamps( | ... | @@ -5977,8 +5991,6 @@ fn dirSetTimestamps( |
| 5977 | userdata: ?*anyopaque, | 5991 | userdata: ?*anyopaque, |
| 5978 | dir: Dir, | 5992 | dir: Dir, |
| 5979 | sub_path: []const u8, | 5993 | sub_path: []const u8, |
| 5980 | last_accessed: Io.Timestamp, | | |
| 5981 | last_modified: Io.Timestamp, | | |
| 5982 | options: Dir.SetTimestampsOptions, | 5994 | options: Dir.SetTimestampsOptions, |
| 5983 | ) Dir.SetTimestampsError!void { | 5995 | ) Dir.SetTimestampsError!void { |
| 5984 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | 5996 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| ... | @@ -5992,9 +6004,13 @@ fn dirSetTimestamps( | ... | @@ -5992,9 +6004,13 @@ fn dirSetTimestamps( |
| 5992 | @panic("TODO implement dirSetTimestamps wasi"); | 6004 | @panic("TODO implement dirSetTimestamps wasi"); |
| 5993 | } | 6005 | } |
| 5994 | | 6006 | |
| 5995 | const times: [2]posix.timespec = .{ | 6007 | var times_buffer: [2]posix.timespec = undefined; |
| 5996 | timestampToPosix(last_accessed.nanoseconds), | 6008 | const times = if (options.modify_timestamp == .now and options.access_timestamp == .now) null else p: { |
| 5997 | timestampToPosix(last_modified.nanoseconds), | 6009 | times_buffer = .{ |
| | 6010 | setTimestampToPosix(options.access_timestamp), |
| | 6011 | setTimestampToPosix(options.modify_timestamp), |
| | 6012 | }; |
| | 6013 | break :p &times_buffer; |
| 5998 | }; | 6014 | }; |
| 5999 | | 6015 | |
| 6000 | const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0; | 6016 | const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0; |
| ... | @@ -6003,80 +6019,26 @@ fn dirSetTimestamps( | ... | @@ -6003,80 +6019,26 @@ fn dirSetTimestamps( |
| 6003 | const sub_path_posix = try pathToPosix(sub_path, &path_buffer); | 6019 | const sub_path_posix = try pathToPosix(sub_path, &path_buffer); |
| 6004 | | 6020 | |
| 6005 | try current_thread.beginSyscall(); | 6021 | try current_thread.beginSyscall(); |
| 6006 | while (true) { | 6022 | while (true) switch (posix.errno(posix.system.utimensat(dir.handle, sub_path_posix, times, flags))) { |
| 6007 | switch (posix.errno(posix.system.utimensat(dir.handle, sub_path_posix, &times, flags))) { | 6023 | .SUCCESS => return current_thread.endSyscall(), |
| 6008 | .SUCCESS => return current_thread.endSyscall(), | 6024 | .INTR => { |
| 6009 | .INTR => { | 6025 | try current_thread.checkCancel(); |
| 6010 | try current_thread.checkCancel(); | 6026 | continue; |
| 6011 | continue; | 6027 | }, |
| 6012 | }, | 6028 | .BADF => |err| return current_thread.endSyscallErrnoBug(err), // always a race condition |
| 6013 | else => |e| { | 6029 | .FAULT => |err| return current_thread.endSyscallErrnoBug(err), |
| 6014 | current_thread.endSyscall(); | 6030 | .INVAL => |err| return current_thread.endSyscallErrnoBug(err), |
| 6015 | switch (e) { | 6031 | .ACCES => return current_thread.endSyscallError(error.AccessDenied), |
| 6016 | .ACCES => return error.AccessDenied, | 6032 | .PERM => return current_thread.endSyscallError(error.PermissionDenied), |
| 6017 | .PERM => return error.PermissionDenied, | 6033 | .ROFS => return current_thread.endSyscallError(error.ReadOnlyFileSystem), |
| 6018 | .BADF => |err| return errnoBug(err), // always a race condition | 6034 | else => |err| return current_thread.endSyscallUnexpectedErrno(err), |
| 6019 | .FAULT => |err| return errnoBug(err), | 6035 | }; |
| 6020 | .INVAL => |err| return errnoBug(err), | | |
| 6021 | .ROFS => return error.ReadOnlyFileSystem, | | |
| 6022 | else => |err| return posix.unexpectedErrno(err), | | |
| 6023 | } | | |
| 6024 | }, | | |
| 6025 | } | | |
| 6026 | } | | |
| 6027 | } | | |
| 6028 | | | |
| 6029 | fn dirSetTimestampsNow( | | |
| 6030 | userdata: ?*anyopaque, | | |
| 6031 | dir: Dir, | | |
| 6032 | sub_path: []const u8, | | |
| 6033 | options: Dir.SetTimestampsOptions, | | |
| 6034 | ) Dir.SetTimestampsError!void { | | |
| 6035 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | | |
| 6036 | const current_thread = Thread.getCurrent(t); | | |
| 6037 | | | |
| 6038 | if (is_windows) { | | |
| 6039 | @panic("TODO implement dirSetTimestampsNow windows"); | | |
| 6040 | } | | |
| 6041 | | | |
| 6042 | if (native_os == .wasi and !builtin.link_libc) { | | |
| 6043 | @panic("TODO implement dirSetTimestampsNow wasi"); | | |
| 6044 | } | | |
| 6045 | | | |
| 6046 | const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0; | | |
| 6047 | | | |
| 6048 | var path_buffer: [posix.PATH_MAX]u8 = undefined; | | |
| 6049 | const sub_path_posix = try pathToPosix(sub_path, &path_buffer); | | |
| 6050 | | | |
| 6051 | try current_thread.beginSyscall(); | | |
| 6052 | while (true) { | | |
| 6053 | switch (posix.errno(posix.system.utimensat(dir.handle, sub_path_posix, null, flags))) { | | |
| 6054 | .SUCCESS => return current_thread.endSyscall(), | | |
| 6055 | .INTR => { | | |
| 6056 | try current_thread.checkCancel(); | | |
| 6057 | continue; | | |
| 6058 | }, | | |
| 6059 | else => |e| { | | |
| 6060 | current_thread.endSyscall(); | | |
| 6061 | switch (e) { | | |
| 6062 | .ACCES => return error.AccessDenied, | | |
| 6063 | .PERM => return error.PermissionDenied, | | |
| 6064 | .BADF => |err| return errnoBug(err), // always a race condition | | |
| 6065 | .FAULT => |err| return errnoBug(err), | | |
| 6066 | .INVAL => |err| return errnoBug(err), | | |
| 6067 | .ROFS => return error.ReadOnlyFileSystem, | | |
| 6068 | else => |err| return posix.unexpectedErrno(err), | | |
| 6069 | } | | |
| 6070 | }, | | |
| 6071 | } | | |
| 6072 | } | | |
| 6073 | } | 6036 | } |
| 6074 | | 6037 | |
| 6075 | fn fileSetTimestamps( | 6038 | fn fileSetTimestamps( |
| 6076 | userdata: ?*anyopaque, | 6039 | userdata: ?*anyopaque, |
| 6077 | file: File, | 6040 | file: File, |
| 6078 | last_accessed: Io.Timestamp, | 6041 | options: File.SetTimestampsOptions, |
| 6079 | last_modified: Io.Timestamp, | | |
| 6080 | ) File.SetTimestampsError!void { | 6042 | ) File.SetTimestampsError!void { |
| 6081 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | 6043 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 6082 | const current_thread = Thread.getCurrent(t); | 6044 | const current_thread = Thread.getCurrent(t); |
| ... | @@ -6084,8 +6046,8 @@ fn fileSetTimestamps( | ... | @@ -6084,8 +6046,8 @@ fn fileSetTimestamps( |
| 6084 | if (is_windows) { | 6046 | if (is_windows) { |
| 6085 | try current_thread.checkCancel(); | 6047 | try current_thread.checkCancel(); |
| 6086 | | 6048 | |
| 6087 | const atime_ft = windows.nanoSecondsToFileTime(last_accessed); | 6049 | const atime_ft = windows.nanoSecondsToFileTime(options.access_time); |
| 6088 | const mtime_ft = windows.nanoSecondsToFileTime(last_modified); | 6050 | const mtime_ft = windows.nanoSecondsToFileTime(options.modify_time); |
| 6089 | | 6051 | |
| 6090 | // https://github.com/ziglang/zig/issues/1840 | 6052 | // https://github.com/ziglang/zig/issues/1840 |
| 6091 | const rc = windows.kernel32.SetFileTime(file.handle, null, &atime_ft, &mtime_ft); | 6053 | const rc = windows.kernel32.SetFileTime(file.handle, null, &atime_ft, &mtime_ft); |
| ... | @@ -6097,123 +6059,53 @@ fn fileSetTimestamps( | ... | @@ -6097,123 +6059,53 @@ fn fileSetTimestamps( |
| 6097 | return; | 6059 | return; |
| 6098 | } | 6060 | } |
| 6099 | | 6061 | |
| 6100 | const times: [2]posix.timespec = .{ | | |
| 6101 | timestampToPosix(last_accessed.nanoseconds), | | |
| 6102 | timestampToPosix(last_modified.nanoseconds), | | |
| 6103 | }; | | |
| 6104 | | | |
| 6105 | if (native_os == .wasi and !builtin.link_libc) { | 6062 | if (native_os == .wasi and !builtin.link_libc) { |
| 6106 | const atim = times[0].toTimestamp(); | 6063 | const atim = timestampToPosix(options.access_time.nanoseconds).toTimestamp(); |
| 6107 | const mtim = times[1].toTimestamp(); | 6064 | const mtim = timestampToPosix(options.modify_time.nanoseconds).toTimestamp(); |
| 6108 | try current_thread.beginSyscall(); | 6065 | try current_thread.beginSyscall(); |
| 6109 | while (true) { | 6066 | while (true) switch (std.os.wasi.fd_filestat_set_times(file.handle, atim, mtim, .{ |
| 6110 | switch (std.os.wasi.fd_filestat_set_times(file.handle, atim, mtim, .{ | 6067 | .ATIM = true, |
| 6111 | .ATIM = true, | 6068 | .MTIM = true, |
| 6112 | .MTIM = true, | 6069 | })) { |
| 6113 | })) { | | |
| 6114 | .SUCCESS => return current_thread.endSyscall(), | | |
| 6115 | .INTR => { | | |
| 6116 | try current_thread.checkCancel(); | | |
| 6117 | continue; | | |
| 6118 | }, | | |
| 6119 | else => |e| { | | |
| 6120 | current_thread.endSyscall(); | | |
| 6121 | switch (e) { | | |
| 6122 | .ACCES => return error.AccessDenied, | | |
| 6123 | .PERM => return error.PermissionDenied, | | |
| 6124 | .BADF => |err| return errnoBug(err), // File descriptor use-after-free. | | |
| 6125 | .FAULT => |err| return errnoBug(err), | | |
| 6126 | .INVAL => |err| return errnoBug(err), | | |
| 6127 | .ROFS => return error.ReadOnlyFileSystem, | | |
| 6128 | else => |err| return posix.unexpectedErrno(err), | | |
| 6129 | } | | |
| 6130 | }, | | |
| 6131 | } | | |
| 6132 | } | | |
| 6133 | } | | |
| 6134 | | | |
| 6135 | try current_thread.beginSyscall(); | | |
| 6136 | while (true) { | | |
| 6137 | switch (posix.errno(posix.system.futimens(file.handle, &times))) { | | |
| 6138 | .SUCCESS => return current_thread.endSyscall(), | 6070 | .SUCCESS => return current_thread.endSyscall(), |
| 6139 | .INTR => { | 6071 | .INTR => { |
| 6140 | try current_thread.checkCancel(); | 6072 | try current_thread.checkCancel(); |
| 6141 | continue; | 6073 | continue; |
| 6142 | }, | 6074 | }, |
| 6143 | else => |e| { | 6075 | .BADF => |err| return current_thread.endSyscallErrnoBug(err), // File descriptor use-after-free. |
| 6144 | current_thread.endSyscall(); | 6076 | .FAULT => |err| return current_thread.endSyscallErrnoBug(err), |
| 6145 | switch (e) { | 6077 | .INVAL => |err| return current_thread.endSyscallErrnoBug(err), |
| 6146 | .ACCES => return error.AccessDenied, | 6078 | .ACCES => return current_thread.endSyscallErrnoBug(error.AccessDenied), |
| 6147 | .PERM => return error.PermissionDenied, | 6079 | .PERM => return current_thread.endSyscallErrnoBug(error.PermissionDenied), |
| 6148 | .BADF => |err| return errnoBug(err), // always a race condition | 6080 | .ROFS => return current_thread.endSyscallErrnoBug(error.ReadOnlyFileSystem), |
| 6149 | .FAULT => |err| return errnoBug(err), | 6081 | else => |err| return current_thread.endSyscallUnexpectedErrno(err), |
| 6150 | .INVAL => |err| return errnoBug(err), | 6082 | }; |
| 6151 | .ROFS => return error.ReadOnlyFileSystem, | | |
| 6152 | else => |err| return posix.unexpectedErrno(err), | | |
| 6153 | } | | |
| 6154 | }, | | |
| 6155 | } | | |
| 6156 | } | | |
| 6157 | } | | |
| 6158 | | | |
| 6159 | fn fileSetTimestampsNow(userdata: ?*anyopaque, file: File) File.SetTimestampsError!void { | | |
| 6160 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | | |
| 6161 | const current_thread = Thread.getCurrent(t); | | |
| 6162 | | | |
| 6163 | if (is_windows) { | | |
| 6164 | @panic("TODO implement fileSetTimestampsNow windows"); | | |
| 6165 | } | 6083 | } |
| 6166 | | 6084 | |
| 6167 | if (native_os == .wasi and !builtin.link_libc) { | 6085 | var times_buffer: [2]posix.timespec = undefined; |
| 6168 | try current_thread.beginSyscall(); | 6086 | const times = if (options.modify_timestamp == .now and options.access_timestamp == .now) null else p: { |
| 6169 | while (true) { | 6087 | times_buffer = .{ |
| 6170 | switch (std.os.wasi.fd_filestat_set_times(file.handle, 0, 0, .{ | 6088 | setTimestampToPosix(options.access_timestamp), |
| 6171 | .ATIM_NOW = true, | 6089 | setTimestampToPosix(options.modify_timestamp), |
| 6172 | .MTIM_NOW = true, | 6090 | }; |
| 6173 | })) { | 6091 | break :p &times_buffer; |
| 6174 | .SUCCESS => return current_thread.endSyscall(), | 6092 | }; |
| 6175 | .INTR => { | | |
| 6176 | try current_thread.checkCancel(); | | |
| 6177 | continue; | | |
| 6178 | }, | | |
| 6179 | else => |e| { | | |
| 6180 | current_thread.endSyscall(); | | |
| 6181 | switch (e) { | | |
| 6182 | .ACCES => return error.AccessDenied, | | |
| 6183 | .PERM => return error.PermissionDenied, | | |
| 6184 | .BADF => |err| return errnoBug(err), // always a race condition | | |
| 6185 | .FAULT => |err| return errnoBug(err), | | |
| 6186 | .INVAL => |err| return errnoBug(err), | | |
| 6187 | .ROFS => return error.ReadOnlyFileSystem, | | |
| 6188 | else => |err| return posix.unexpectedErrno(err), | | |
| 6189 | } | | |
| 6190 | }, | | |
| 6191 | } | | |
| 6192 | } | | |
| 6193 | } | | |
| 6194 | | 6093 | |
| 6195 | try current_thread.beginSyscall(); | 6094 | try current_thread.beginSyscall(); |
| 6196 | while (true) { | 6095 | while (true) switch (posix.errno(posix.system.futimens(file.handle, times))) { |
| 6197 | switch (posix.errno(posix.system.futimens(file.handle, null))) { | 6096 | .SUCCESS => return current_thread.endSyscall(), |
| 6198 | .SUCCESS => return current_thread.endSyscall(), | 6097 | .INTR => { |
| 6199 | .INTR => { | 6098 | try current_thread.checkCancel(); |
| 6200 | try current_thread.checkCancel(); | 6099 | continue; |
| 6201 | continue; | 6100 | }, |
| 6202 | }, | 6101 | .BADF => |err| return current_thread.endSyscallErrnoBug(err), // always a race condition |
| 6203 | else => |e| { | 6102 | .FAULT => |err| return current_thread.endSyscallErrnoBug(err), |
| 6204 | current_thread.endSyscall(); | 6103 | .INVAL => |err| return current_thread.endSyscallErrnoBug(err), |
| 6205 | switch (e) { | 6104 | .ACCES => return current_thread.endSyscallError(error.AccessDenied), |
| 6206 | .ACCES => return error.AccessDenied, | 6105 | .PERM => return current_thread.endSyscallError(error.PermissionDenied), |
| 6207 | .PERM => return error.PermissionDenied, | 6106 | .ROFS => return current_thread.endSyscallError(error.ReadOnlyFileSystem), |
| 6208 | .BADF => |err| return errnoBug(err), // always a race condition | 6107 | else => |err| return current_thread.endSyscallUnexpectedErrno(err), |
| 6209 | .FAULT => |err| return errnoBug(err), | 6108 | }; |
| 6210 | .INVAL => |err| return errnoBug(err), | | |
| 6211 | .ROFS => return error.ReadOnlyFileSystem, | | |
| 6212 | else => |err| return posix.unexpectedErrno(err), | | |
| 6213 | } | | |
| 6214 | }, | | |
| 6215 | } | | |
| 6216 | } | | |
| 6217 | } | 6109 | } |
| 6218 | | 6110 | |
| 6219 | const windows_lock_range_off: windows.LARGE_INTEGER = 0; | 6111 | const windows_lock_range_off: windows.LARGE_INTEGER = 0; |
| ... | @@ -11283,6 +11175,14 @@ fn timestampToPosix(nanoseconds: i96) posix.timespec { | ... | @@ -11283,6 +11175,14 @@ fn timestampToPosix(nanoseconds: i96) posix.timespec { |
| 11283 | }; | 11175 | }; |
| 11284 | } | 11176 | } |
| 11285 | | 11177 | |
| | 11178 | fn setTimestampToPosix(set_ts: File.SetTimestamp) posix.timespec { |
| | 11179 | return switch (set_ts) { |
| | 11180 | .unchanged => .OMIT, |
| | 11181 | .now => .NOW, |
| | 11182 | .new => |t| timestampToPosix(t.nanoseconds), |
| | 11183 | }; |
| | 11184 | } |
| | 11185 | |
| 11286 | fn pathToPosix(file_path: []const u8, buffer: *[posix.PATH_MAX]u8) Dir.PathNameError![:0]u8 { | 11186 | fn pathToPosix(file_path: []const u8, buffer: *[posix.PATH_MAX]u8) Dir.PathNameError![:0]u8 { |
| 11287 | if (std.mem.containsAtLeastScalar2(u8, file_path, 0, 1)) return error.BadPathName; | 11187 | if (std.mem.containsAtLeastScalar2(u8, file_path, 0, 1)) return error.BadPathName; |
| 11288 | // >= rather than > to make room for the null byte | 11188 | // >= rather than > to make room for the null byte |