koffi 2.14.1 → 2.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/build/koffi/darwin_arm64/koffi.node +0 -0
  3. package/build/koffi/darwin_x64/koffi.node +0 -0
  4. package/build/koffi/freebsd_arm64/koffi.node +0 -0
  5. package/build/koffi/freebsd_ia32/koffi.node +0 -0
  6. package/build/koffi/freebsd_x64/koffi.node +0 -0
  7. package/build/koffi/linux_arm64/koffi.node +0 -0
  8. package/build/koffi/linux_armhf/koffi.node +0 -0
  9. package/build/koffi/linux_ia32/koffi.node +0 -0
  10. package/build/koffi/linux_loong64/koffi.node +0 -0
  11. package/build/koffi/linux_riscv64d/koffi.node +0 -0
  12. package/build/koffi/linux_x64/koffi.node +0 -0
  13. package/build/koffi/musl_arm64/koffi.node +0 -0
  14. package/build/koffi/musl_x64/koffi.node +0 -0
  15. package/build/koffi/openbsd_ia32/koffi.node +0 -0
  16. package/build/koffi/openbsd_x64/koffi.node +0 -0
  17. package/build/koffi/win32_arm64/koffi.node +0 -0
  18. package/build/koffi/win32_ia32/koffi.node +0 -0
  19. package/build/koffi/win32_x64/koffi.node +0 -0
  20. package/doc/assets.ini +2 -1
  21. package/doc/build.sh +9 -0
  22. package/doc/pages/404.md +17 -0
  23. package/doc/pages/index.md +1 -1
  24. package/doc/pages/misc.md +16 -11
  25. package/doc/pages.ini +4 -0
  26. package/doc/static/highlight.js +2 -14
  27. package/doc/static/koffi.css +3 -15
  28. package/doc/static/print.css +2 -14
  29. package/index.d.ts +29 -24
  30. package/index.js +8 -8
  31. package/indirect.js +8 -8
  32. package/{src/core → lib/native}/base/base.cc +1058 -630
  33. package/{src/core → lib/native}/base/base.hh +334 -165
  34. package/{src/core → lib/native}/base/crc.inc +2 -20
  35. package/lib/native/base/crc_gen.py +72 -0
  36. package/{src/core → lib/native}/base/mimetypes.inc +2 -20
  37. package/{src/core → lib/native}/base/mimetypes_gen.py +2 -21
  38. package/lib/native/base/tower.cc +821 -0
  39. package/lib/native/base/tower.hh +81 -0
  40. package/{src/core → lib/native}/base/unicode.inc +2 -20
  41. package/{src/core → lib/native}/base/unicode_gen.py +4 -41
  42. package/package.json +1 -1
  43. package/src/cnoke/assets/FindCNoke.cmake +8 -20
  44. package/src/cnoke/assets/win_delay_hook.c +2 -20
  45. package/src/cnoke/cnoke.js +2 -21
  46. package/src/cnoke/src/builder.js +2 -20
  47. package/src/cnoke/src/index.js +2 -20
  48. package/src/cnoke/src/tools.js +2 -20
  49. package/src/koffi/CMakeLists.txt +19 -22
  50. package/src/koffi/cmake/raylib.cmake +5 -22
  51. package/src/koffi/cmake/sqlite3.cmake +2 -20
  52. package/src/koffi/src/abi_arm32.cc +7 -25
  53. package/src/koffi/src/abi_arm32_asm.S +2 -20
  54. package/src/koffi/src/abi_arm64.cc +7 -25
  55. package/src/koffi/src/abi_arm64_asm.S +2 -20
  56. package/src/koffi/src/abi_arm64_asm.asm +2 -20
  57. package/src/koffi/src/abi_loong64.cc +2 -20
  58. package/src/koffi/src/abi_loong64_asm.S +2 -20
  59. package/src/koffi/src/abi_riscv64.cc +7 -25
  60. package/src/koffi/src/abi_riscv64_asm.S +2 -20
  61. package/src/koffi/src/abi_x64_sysv.cc +7 -25
  62. package/src/koffi/src/abi_x64_sysv_asm.S +2 -20
  63. package/src/koffi/src/abi_x64_win.cc +12 -30
  64. package/src/koffi/src/abi_x64_win_asm.asm +2 -20
  65. package/src/koffi/src/abi_x86.cc +7 -25
  66. package/src/koffi/src/abi_x86_asm.S +2 -20
  67. package/src/koffi/src/abi_x86_asm.asm +2 -20
  68. package/src/koffi/src/call.cc +25 -45
  69. package/src/koffi/src/call.hh +3 -21
  70. package/src/koffi/src/errno.inc +2 -20
  71. package/src/koffi/src/ffi.cc +62 -62
  72. package/src/koffi/src/ffi.hh +14 -29
  73. package/src/koffi/src/init.js +2 -20
  74. package/src/koffi/src/parser.cc +13 -27
  75. package/src/koffi/src/parser.hh +3 -21
  76. package/src/koffi/src/trampolines/armasm.inc +0 -21
  77. package/src/koffi/src/trampolines/gnu.inc +0 -21
  78. package/src/koffi/src/trampolines/masm32.inc +0 -21
  79. package/src/koffi/src/trampolines/masm64.inc +0 -21
  80. package/src/koffi/src/trampolines/prototypes.inc +0 -21
  81. package/src/koffi/src/util.cc +44 -59
  82. package/src/koffi/src/util.hh +6 -24
  83. package/src/koffi/src/uv.cc +193 -0
  84. package/src/koffi/src/uv.def +10 -0
  85. package/src/koffi/src/uv.hh +40 -0
  86. package/src/koffi/src/win32.cc +2 -20
  87. package/src/koffi/src/win32.hh +3 -21
  88. package/vendor/node-api-headers/include/uv/aix.h +32 -0
  89. package/vendor/node-api-headers/include/uv/bsd.h +34 -0
  90. package/vendor/node-api-headers/include/uv/darwin.h +61 -0
  91. package/vendor/node-api-headers/include/uv/errno.h +483 -0
  92. package/vendor/node-api-headers/include/uv/linux.h +34 -0
  93. package/vendor/node-api-headers/include/uv/os390.h +33 -0
  94. package/vendor/node-api-headers/include/uv/posix.h +31 -0
  95. package/vendor/node-api-headers/include/uv/sunos.h +44 -0
  96. package/vendor/node-api-headers/include/uv/threadpool.h +37 -0
  97. package/vendor/node-api-headers/include/uv/tree.h +521 -0
  98. package/vendor/node-api-headers/include/uv/unix.h +512 -0
  99. package/vendor/node-api-headers/include/uv/version.h +43 -0
  100. package/vendor/node-api-headers/include/uv/win.h +698 -0
  101. package/vendor/node-api-headers/include/uv.h +1990 -0
  102. package/src/core/base/crc_gen.py +0 -109
@@ -1,23 +1,5 @@
1
- // Copyright (C) 2025 Niels Martignène <niels.martignene@protonmail.com>
2
-
3
- // Permission is hereby granted, free of charge, to any person obtaining a copy of
4
- // this software and associated documentation files (the “Software”), to deal in
5
- // the Software without restriction, including without limitation the rights to use,
6
- // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
7
- // Software, and to permit persons to whom the Software is furnished to do so,
8
- // subject to the following conditions:
9
-
10
- // The above copyright notice and this permission notice shall be included in all
11
- // copies or substantial portions of the Software.
12
-
13
- // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
14
- // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
15
- // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
16
- // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
17
- // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
18
- // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19
- // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
20
- // OTHER DEALINGS IN THE SOFTWARE.
1
+ // SPDX-License-Identifier: MIT
2
+ // SPDX-FileCopyrightText: 2025 Niels Martignène <niels.martignene@protonmail.com>
21
3
 
22
4
  #include "base.hh"
23
5
  #include "crc.inc"
@@ -115,6 +97,7 @@
115
97
  #if defined(__linux__)
116
98
  #include <sys/syscall.h>
117
99
  #include <sys/sendfile.h>
100
+ #include <sys/eventfd.h>
118
101
  #endif
119
102
  #if defined(__APPLE__)
120
103
  #include <sys/random.h>
@@ -932,6 +915,75 @@ Span<char> DuplicateString(Span<const char> str, Allocator *alloc)
932
915
  return MakeSpan(new_str, str.len);
933
916
  }
934
917
 
918
+ template <typename CompareFunc>
919
+ static inline int NaturalCmp(Span<const char> str1, Span<const char> str2, CompareFunc cmp)
920
+ {
921
+ Size i = 0;
922
+ Size j = 0;
923
+
924
+ while (i < str1.len && j < str2.len) {
925
+ int delta = cmp(str1[i], str2[j]);
926
+
927
+ if (delta) {
928
+ if (IsAsciiDigit(str1[i]) && IsAsciiDigit(str2[i])) {
929
+ while (i < str1.len && str1[i] == '0') {
930
+ i++;
931
+ }
932
+ while (j < str2.len && str2[j] == '0') {
933
+ j++;
934
+ }
935
+
936
+ bool digit1 = false;
937
+ bool digit2 = false;
938
+ int bias = 0;
939
+
940
+ for (;;) {
941
+ digit1 = (i < str1.len) && IsAsciiDigit(str1[i]);
942
+ digit2 = (j < str2.len) && IsAsciiDigit(str2[j]);
943
+
944
+ if (!digit1 || !digit2)
945
+ break;
946
+
947
+ bias = bias ? bias : cmp(str1[i], str2[j]);
948
+ i++;
949
+ j++;
950
+ }
951
+
952
+ if (!digit1 && !digit2 && bias) {
953
+ return bias;
954
+ } else if (digit1 || digit2) {
955
+ return digit1 ? 1 : -1;
956
+ }
957
+ } else {
958
+ return delta;
959
+ }
960
+ } else {
961
+ i++;
962
+ j++;
963
+ }
964
+ }
965
+
966
+ if (i == str1.len && j < str2.len) {
967
+ return -1;
968
+ } else if (i < str1.len) {
969
+ return 1;
970
+ } else {
971
+ return 0;
972
+ }
973
+ }
974
+
975
+ int CmpNatural(Span<const char> str1, Span<const char> str2)
976
+ {
977
+ auto cmp = [](int a, int b) { return a - b; };
978
+ return NaturalCmp(str1, str2, cmp);
979
+ }
980
+
981
+ int CmpNaturalI(Span<const char> str1, Span<const char> str2)
982
+ {
983
+ auto cmp = [](int a, int b) { return LowerAscii(a) - LowerAscii(b); };
984
+ return NaturalCmp(str1, str2, cmp);
985
+ }
986
+
935
987
  // ------------------------------------------------------------------------
936
988
  // Format
937
989
  // ------------------------------------------------------------------------
@@ -940,6 +992,8 @@ static const char DigitPairs[201] = "0001020304050607080910111213141516171819202
940
992
  "25262728293031323334353637383940414243444546474849"
941
993
  "50515253545556575859606162636465666768697071727374"
942
994
  "75767778798081828384858687888990919293949596979899";
995
+ static const char BigHexLiterals[] = "0123456789ABCDEF";
996
+ static const char SmallHexLiterals[] = "0123456789abcdef";
943
997
 
944
998
  static Span<char> FormatUnsignedToDecimal(uint64_t value, char out_buf[32])
945
999
  {
@@ -975,13 +1029,11 @@ static Span<char> FormatUnsignedToBinary(uint64_t value, char out_buf[64])
975
1029
 
976
1030
  static Span<char> FormatUnsignedToOctal(uint64_t value, char out_buf[64])
977
1031
  {
978
- static const char literals[] = "012345678";
979
-
980
1032
  Size offset = 64;
981
1033
  do {
982
1034
  uint64_t digit = value & 0x7;
983
1035
  value >>= 3;
984
- out_buf[--offset] = literals[digit];
1036
+ out_buf[--offset] = BigHexLiterals[digit];
985
1037
  } while (value);
986
1038
 
987
1039
  return MakeSpan(out_buf + offset, 64 - offset);
@@ -989,13 +1041,11 @@ static Span<char> FormatUnsignedToOctal(uint64_t value, char out_buf[64])
989
1041
 
990
1042
  static Span<char> FormatUnsignedToBigHex(uint64_t value, char out_buf[32])
991
1043
  {
992
- static const char literals[] = "0123456789ABCDEF";
993
-
994
1044
  Size offset = 32;
995
1045
  do {
996
1046
  uint64_t digit = value & 0xF;
997
1047
  value >>= 4;
998
- out_buf[--offset] = literals[digit];
1048
+ out_buf[--offset] = BigHexLiterals[digit];
999
1049
  } while (value);
1000
1050
 
1001
1051
  return MakeSpan(out_buf + offset, 32 - offset);
@@ -1003,13 +1053,11 @@ static Span<char> FormatUnsignedToBigHex(uint64_t value, char out_buf[32])
1003
1053
 
1004
1054
  static Span<char> FormatUnsignedToSmallHex(uint64_t value, char out_buf[32])
1005
1055
  {
1006
- static const char literals[] = "0123456789abcdef";
1007
-
1008
1056
  Size offset = 32;
1009
1057
  do {
1010
1058
  uint64_t digit = value & 0xF;
1011
1059
  value >>= 4;
1012
- out_buf[--offset] = literals[digit];
1060
+ out_buf[--offset] = SmallHexLiterals[digit];
1013
1061
  } while (value);
1014
1062
 
1015
1063
  return MakeSpan(out_buf + offset, 32 - offset);
@@ -1185,406 +1233,429 @@ Span<const char> FormatFloatingPoint(T value, bool non_zero, int min_prec, int m
1185
1233
  #endif
1186
1234
  }
1187
1235
 
1236
+ template <typename AppendFunc>
1237
+ static inline void AppendPad(Size pad, char padding, AppendFunc append)
1238
+ {
1239
+ for (Size i = 0; i < pad; i++) {
1240
+ append(padding);
1241
+ }
1242
+ }
1243
+
1244
+ template <typename AppendFunc>
1245
+ static inline void AppendSafe(char c, AppendFunc append)
1246
+ {
1247
+ if (IsAsciiControl(c))
1248
+ return;
1249
+
1250
+ append(c);
1251
+ }
1252
+
1188
1253
  template <typename AppendFunc>
1189
1254
  static inline void ProcessArg(const FmtArg &arg, AppendFunc append)
1190
1255
  {
1191
- for (int i = 0; i < arg.repeat; i++) {
1192
- LocalArray<char, 2048> out_buf;
1193
- char num_buf[128];
1194
- Span<const char> out = {};
1256
+ switch (arg.type) {
1257
+ case FmtType::Str: { append(arg.u.str); } break;
1195
1258
 
1196
- Size pad_len = arg.pad_len;
1259
+ case FmtType::PadStr: {
1260
+ append(arg.u.str);
1261
+ AppendPad(arg.pad - arg.u.str.len, arg.padding, append);
1262
+ } break;
1263
+ case FmtType::RepeatStr: {
1264
+ Span<const char> str = arg.u.repeat.str;
1197
1265
 
1198
- switch (arg.type) {
1199
- case FmtType::Str1: { out = arg.u.str1; } break;
1200
- case FmtType::Str2: { out = arg.u.str2; } break;
1201
- case FmtType::Buffer: { out = arg.u.buf; } break;
1202
- case FmtType::Char: { out = MakeSpan(&arg.u.ch, 1); } break;
1266
+ for (int i = 0; i < arg.u.repeat.count; i++) {
1267
+ append(str);
1268
+ }
1269
+ } break;
1203
1270
 
1204
- case FmtType::Custom: { arg.u.custom.Format(append); } break;
1271
+ case FmtType::Char: { append(MakeSpan(&arg.u.ch, 1)); } break;
1272
+ case FmtType::Buffer: {
1273
+ Span<const char> str = arg.u.buf;
1274
+ append(str);
1275
+ } break;
1276
+ case FmtType::Custom: { arg.u.custom.Format(append); } break;
1205
1277
 
1206
- case FmtType::Bool: {
1207
- if (arg.u.b) {
1208
- out = "true";
1209
- } else {
1210
- out = "false";
1211
- }
1212
- } break;
1278
+ case FmtType::Bool: { append(arg.u.b ? "true" : "false"); } break;
1213
1279
 
1214
- case FmtType::Integer: {
1215
- if (arg.u.i < 0) {
1216
- if (arg.pad_len < 0 && arg.pad_char == '0') {
1280
+ case FmtType::Integer: {
1281
+ if (arg.u.i < 0) {
1282
+ char buf[128];
1283
+ Span<const char> str = FormatUnsignedToDecimal((uint64_t)-arg.u.i, buf);
1284
+
1285
+ if (arg.pad) {
1286
+ if (arg.padding == '0') {
1217
1287
  append('-');
1288
+ AppendPad((Size)arg.pad - str.len - 1, arg.padding, append);
1218
1289
  } else {
1219
- out_buf.Append('-');
1290
+ AppendPad((Size)arg.pad - str.len - 1, arg.padding, append);
1291
+ append('-');
1220
1292
  }
1221
-
1222
- out_buf.Append(FormatUnsignedToDecimal((uint64_t)-arg.u.i, num_buf));
1223
- out = out_buf;
1224
1293
  } else {
1225
- out = FormatUnsignedToDecimal((uint64_t)arg.u.i, num_buf);
1294
+ append('-');
1226
1295
  }
1227
- } break;
1228
- case FmtType::Unsigned: {
1229
- out = FormatUnsignedToDecimal(arg.u.u, num_buf);
1230
- } break;
1231
- case FmtType::Float: {
1232
- static const uint32_t ExponentMask = 0x7f800000u;
1233
- static const uint32_t MantissaMask = 0x007fffffu;
1234
- static const uint32_t SignMask = 0x80000000u;
1235
1296
 
1236
- union { float f; uint32_t u32; } u;
1237
- u.f = arg.u.f.value;
1297
+ append(str);
1298
+ } else {
1299
+ char buf[128];
1300
+ Span<const char> str = FormatUnsignedToDecimal((uint64_t)arg.u.i, buf);
1238
1301
 
1239
- if ((u.u32 & ExponentMask) == ExponentMask) {
1240
- uint32_t mantissa = u.u32 & MantissaMask;
1302
+ AppendPad((Size)arg.pad - str.len, arg.padding, append);
1303
+ append(str);
1304
+ }
1305
+ } break;
1306
+ case FmtType::Unsigned: {
1307
+ char buf[128];
1308
+ Span<const char> str = FormatUnsignedToDecimal(arg.u.u, buf);
1241
1309
 
1242
- if (mantissa) {
1243
- out = "NaN";
1244
- } else {
1245
- out = (u.u32 & SignMask) ? "-Inf" : "Inf";
1246
- }
1247
- } else {
1248
- if (u.u32 & SignMask) {
1249
- if (arg.pad_len < 0 && arg.pad_char == '0') {
1250
- append('-');
1251
- } else {
1252
- out_buf.Append('-');
1253
- }
1310
+ AppendPad((Size)arg.pad - str.len, arg.padding, append);
1311
+ append(str);
1312
+ } break;
1254
1313
 
1255
- out_buf.Append(FormatFloatingPoint(-u.f, true, arg.u.f.min_prec, arg.u.f.max_prec, num_buf));
1256
- out = out_buf;
1257
- } else {
1258
- out = FormatFloatingPoint(u.f, u.u32, arg.u.f.min_prec, arg.u.f.max_prec, num_buf);
1259
- }
1260
- }
1261
- } break;
1262
- case FmtType::Double: {
1263
- static const uint64_t ExponentMask = 0x7FF0000000000000ull;
1264
- static const uint64_t MantissaMask = 0x000FFFFFFFFFFFFFull;
1265
- static const uint64_t SignMask = 0x8000000000000000ull;
1314
+ case FmtType::Float: {
1315
+ static const uint32_t ExponentMask = 0x7f800000u;
1316
+ static const uint32_t MantissaMask = 0x007fffffu;
1317
+ static const uint32_t SignMask = 0x80000000u;
1266
1318
 
1267
- union { double d; uint64_t u64; } u;
1268
- u.d = arg.u.d.value;
1319
+ union { float f; uint32_t u32; } u;
1320
+ u.f = arg.u.f.value;
1269
1321
 
1270
- if ((u.u64 & ExponentMask) == ExponentMask) {
1271
- uint64_t mantissa = u.u64 & MantissaMask;
1322
+ if ((u.u32 & ExponentMask) == ExponentMask) {
1323
+ uint32_t mantissa = u.u32 & MantissaMask;
1272
1324
 
1273
- if (mantissa) {
1274
- out = "NaN";
1275
- } else {
1276
- out = (u.u64 & SignMask) ? "-Inf" : "Inf";
1277
- }
1325
+ if (mantissa) {
1326
+ append("NaN");
1278
1327
  } else {
1279
- if (u.u64 & SignMask) {
1280
- if (arg.pad_len < 0 && arg.pad_char == '0') {
1281
- append('-');
1282
- } else {
1283
- out_buf.Append('-');
1284
- }
1285
-
1286
- out_buf.Append(FormatFloatingPoint(-u.d, true, arg.u.d.min_prec, arg.u.d.max_prec, num_buf));
1287
- out = out_buf;
1288
- } else {
1289
- out = FormatFloatingPoint(u.d, u.u64, arg.u.d.min_prec, arg.u.d.max_prec, num_buf);
1290
- }
1328
+ append((u.u32 & SignMask) ? "-Inf" : "Inf");
1291
1329
  }
1292
- } break;
1293
- case FmtType::Binary: {
1294
- out = FormatUnsignedToBinary(arg.u.u, num_buf);
1295
- } break;
1296
- case FmtType::Octal: {
1297
- out = FormatUnsignedToOctal(arg.u.u, num_buf);
1298
- } break;
1299
- case FmtType::BigHex: {
1300
- out = FormatUnsignedToBigHex(arg.u.u, num_buf);
1301
- } break;
1302
- case FmtType::SmallHex: {
1303
- out = FormatUnsignedToSmallHex(arg.u.u, num_buf);
1304
- } break;
1330
+ } else {
1331
+ char buf[128];
1305
1332
 
1306
- case FmtType::MemorySize: {
1307
- double size;
1308
- if (arg.u.i < 0) {
1309
- size = (double)-arg.u.i;
1310
- if (arg.pad_len < 0 && arg.pad_char == '0') {
1311
- append('-');
1312
- } else {
1313
- out_buf.Append('-');
1314
- }
1333
+ if (u.u32 & SignMask) {
1334
+ append('-');
1335
+ append(FormatFloatingPoint(-u.f, true, arg.u.f.min_prec, arg.u.f.max_prec, buf));
1315
1336
  } else {
1316
- size = (double)arg.u.i;
1337
+ append(FormatFloatingPoint(u.f, u.u32, arg.u.f.min_prec, arg.u.f.max_prec, buf));
1317
1338
  }
1339
+ }
1340
+ } break;
1341
+ case FmtType::Double: {
1342
+ static const uint64_t ExponentMask = 0x7FF0000000000000ull;
1343
+ static const uint64_t MantissaMask = 0x000FFFFFFFFFFFFFull;
1344
+ static const uint64_t SignMask = 0x8000000000000000ull;
1318
1345
 
1319
- if (size >= 1073688137.0) {
1320
- size /= 1073741824.0;
1321
-
1322
- int prec = 1 + (size < 9.9995) + (size < 99.995);
1323
- out_buf.Append(FormatFloatingPoint(size, true, prec, prec, num_buf));
1324
- out_buf.Append(" GiB");
1325
- } else if (size >= 1048524.0) {
1326
- size /= 1048576.0;
1346
+ union { double d; uint64_t u64; } u;
1347
+ u.d = arg.u.d.value;
1327
1348
 
1328
- int prec = 1 + (size < 9.9995) + (size < 99.995);
1329
- out_buf.Append(FormatFloatingPoint(size, true, prec, prec, num_buf));
1330
- out_buf.Append(" MiB");
1331
- } else if (size >= 1023.95) {
1332
- size /= 1024.0;
1349
+ if ((u.u64 & ExponentMask) == ExponentMask) {
1350
+ uint64_t mantissa = u.u64 & MantissaMask;
1333
1351
 
1334
- int prec = 1 + (size < 9.9995) + (size < 99.995);
1335
- out_buf.Append(FormatFloatingPoint(size, true, prec, prec, num_buf));
1336
- out_buf.Append(" kiB");
1352
+ if (mantissa) {
1353
+ append("NaN");
1337
1354
  } else {
1338
- out_buf.Append(FormatFloatingPoint(size, arg.u.i, 0, 0, num_buf));
1339
- out_buf.Append(" B");
1355
+ append((u.u64 & SignMask) ? "-Inf" : "Inf");
1340
1356
  }
1357
+ } else {
1358
+ char buf[128];
1341
1359
 
1342
- out = out_buf;
1343
- } break;
1344
- case FmtType::DiskSize: {
1345
- double size;
1346
- if (arg.u.i < 0) {
1347
- size = (double)-arg.u.i;
1348
- if (arg.pad_len < 0 && arg.pad_char == '0') {
1349
- append('-');
1350
- } else {
1351
- out_buf.Append('-');
1352
- }
1360
+ if (u.u64 & SignMask) {
1361
+ append('-');
1362
+ append(FormatFloatingPoint(-u.d, true, arg.u.d.min_prec, arg.u.d.max_prec, buf));
1353
1363
  } else {
1354
- size = (double)arg.u.i;
1364
+ append(FormatFloatingPoint(u.d, u.u64, arg.u.d.min_prec, arg.u.d.max_prec, buf));
1355
1365
  }
1366
+ }
1367
+ } break;
1356
1368
 
1357
- if (size >= 999950000.0) {
1358
- size /= 1000000000.0;
1369
+ case FmtType::Binary: {
1370
+ char buf[128];
1371
+ Span<const char> str = FormatUnsignedToBinary(arg.u.u, buf);
1359
1372
 
1360
- int prec = 1 + (size < 9.9995) + (size < 99.995);
1361
- out_buf.Append(FormatFloatingPoint(size, true, prec, prec, num_buf));
1362
- out_buf.Append(" GB");
1363
- } else if (size >= 999950.0) {
1364
- size /= 1000000.0;
1373
+ AppendPad((Size)arg.pad - str.len, arg.padding, append);
1374
+ append(str);
1375
+ } break;
1376
+ case FmtType::Octal: {
1377
+ char buf[128];
1378
+ Span<const char> str = FormatUnsignedToOctal(arg.u.u, buf);
1365
1379
 
1366
- int prec = 1 + (size < 9.9995) + (size < 99.995);
1367
- out_buf.Append(FormatFloatingPoint(size, true, prec, prec, num_buf));
1368
- out_buf.Append(" MB");
1369
- } else if (size >= 999.95) {
1370
- size /= 1000.0;
1380
+ AppendPad((Size)arg.pad - str.len, arg.padding, append);
1381
+ append(str);
1382
+ } break;
1383
+ case FmtType::BigHex: {
1384
+ char buf[128];
1385
+ Span<const char> str = FormatUnsignedToBigHex(arg.u.u, buf);
1371
1386
 
1372
- int prec = 1 + (size < 9.9995) + (size < 99.995);
1373
- out_buf.Append(FormatFloatingPoint(size, true, prec, prec, num_buf));
1374
- out_buf.Append(" kB");
1375
- } else {
1376
- out_buf.Append(FormatFloatingPoint(size, arg.u.i, 0, 0, num_buf));
1377
- out_buf.Append(" B");
1378
- }
1387
+ AppendPad((Size)arg.pad - str.len, arg.padding, append);
1388
+ append(str);
1389
+ } break;
1390
+ case FmtType::SmallHex: {
1391
+ char buf[128];
1392
+ Span<const char> str = FormatUnsignedToSmallHex(arg.u.u, buf);
1379
1393
 
1380
- out = out_buf;
1381
- } break;
1394
+ AppendPad((Size)arg.pad - str.len, arg.padding, append);
1395
+ append(str);
1396
+ } break;
1382
1397
 
1383
- case FmtType::Date: {
1384
- K_ASSERT(!arg.u.date.value || arg.u.date.IsValid());
1398
+ case FmtType::BigBytes: {
1399
+ for (uint8_t c: arg.u.hex) {
1400
+ char encoded[2];
1385
1401
 
1386
- int year = arg.u.date.st.year;
1387
- if (year < 0) {
1388
- out_buf.Append('-');
1389
- year = -year;
1390
- }
1391
- if (year < 10) {
1392
- out_buf.Append("000");
1393
- } else if (year < 100) {
1394
- out_buf.Append("00");
1395
- } else if (year < 1000) {
1396
- out_buf.Append('0');
1397
- }
1398
- out_buf.Append(FormatUnsignedToDecimal((uint64_t)year, num_buf));
1399
- out_buf.Append('-');
1400
- if (arg.u.date.st.month < 10) {
1401
- out_buf.Append('0');
1402
- }
1403
- out_buf.Append(FormatUnsignedToDecimal((uint64_t)arg.u.date.st.month, num_buf));
1404
- out_buf.Append('-');
1405
- if (arg.u.date.st.day < 10) {
1406
- out_buf.Append('0');
1407
- }
1408
- out_buf.Append(FormatUnsignedToDecimal((uint64_t)arg.u.date.st.day, num_buf));
1409
- out = out_buf;
1410
- } break;
1402
+ encoded[0] = BigHexLiterals[((uint8_t)c >> 4) & 0xF];
1403
+ encoded[1] = BigHexLiterals[((uint8_t)c >> 0) & 0xF];
1411
1404
 
1412
- case FmtType::TimeISO: {
1413
- const TimeSpec &spec = arg.u.time.spec;
1414
-
1415
- if (spec.offset && arg.u.time.ms) {
1416
- int offset_h = spec.offset / 60;
1417
- int offset_m = spec.offset % 60;
1418
-
1419
- out_buf.len = Fmt(out_buf.data, "%1%2%3T%4%5%6.%7%8%9%10",
1420
- FmtArg(spec.year).Pad0(-2), FmtArg(spec.month).Pad0(-2),
1421
- FmtArg(spec.day).Pad0(-2), FmtArg(spec.hour).Pad0(-2),
1422
- FmtArg(spec.min).Pad0(-2), FmtArg(spec.sec).Pad0(-2), FmtArg(spec.msec).Pad0(-3),
1423
- offset_h >= 0 ? "+" : "", FmtArg(offset_h).Pad0(-2), FmtArg(offset_m).Pad0(-2)).len;
1424
- } else if (spec.offset) {
1425
- int offset_h = spec.offset / 60;
1426
- int offset_m = spec.offset % 60;
1427
-
1428
- out_buf.len = Fmt(out_buf.data, "%1%2%3T%4%5%6%7%8%9",
1429
- FmtArg(spec.year).Pad0(-2), FmtArg(spec.month).Pad0(-2),
1430
- FmtArg(spec.day).Pad0(-2), FmtArg(spec.hour).Pad0(-2),
1431
- FmtArg(spec.min).Pad0(-2), FmtArg(spec.sec).Pad0(-2),
1432
- offset_h >= 0 ? "+" : "", FmtArg(offset_h).Pad0(-2), FmtArg(offset_m).Pad0(-2)).len;
1433
- } else if (arg.u.time.ms) {
1434
- out_buf.len = Fmt(out_buf.data, "%1%2%3T%4%5%6.%7Z",
1435
- FmtArg(spec.year).Pad0(-2), FmtArg(spec.month).Pad0(-2),
1436
- FmtArg(spec.day).Pad0(-2), FmtArg(spec.hour).Pad0(-2),
1437
- FmtArg(spec.min).Pad0(-2), FmtArg(spec.sec).Pad0(-2), FmtArg(spec.msec).Pad0(-3)).len;
1438
- } else {
1439
- out_buf.len = Fmt(out_buf.data, "%1%2%3T%4%5%6Z",
1440
- FmtArg(spec.year).Pad0(-2), FmtArg(spec.month).Pad0(-2),
1441
- FmtArg(spec.day).Pad0(-2), FmtArg(spec.hour).Pad0(-2),
1442
- FmtArg(spec.min).Pad0(-2), FmtArg(spec.sec).Pad0(-2)).len;
1443
- }
1444
- out = out_buf;
1445
- } break;
1446
- case FmtType::TimeNice: {
1447
- const TimeSpec &spec = arg.u.time.spec;
1405
+ Span<const char> buf = MakeSpan(encoded, 2);
1406
+ append(buf);
1407
+ }
1408
+ } break;
1409
+ case FmtType::SmallBytes: {
1410
+ for (uint8_t c: arg.u.hex) {
1411
+ char encoded[2];
1412
+
1413
+ encoded[0] = SmallHexLiterals[((uint8_t)c >> 4) & 0xF];
1414
+ encoded[1] = SmallHexLiterals[((uint8_t)c >> 0) & 0xF];
1448
1415
 
1416
+ Span<const char> buf = MakeSpan(encoded, 2);
1417
+ append(buf);
1418
+ }
1419
+ } break;
1420
+
1421
+ case FmtType::MemorySize: {
1422
+ char buf[128];
1423
+
1424
+ double size;
1425
+ if (arg.u.i < 0) {
1426
+ append('-');
1427
+ size = (double)-arg.u.i;
1428
+ } else {
1429
+ size = (double)arg.u.i;
1430
+ }
1431
+
1432
+ if (size >= 1073688137.0) {
1433
+ size /= 1073741824.0;
1434
+
1435
+ int prec = 1 + (size < 9.9995) + (size < 99.995);
1436
+ append(FormatFloatingPoint(size, true, prec, prec, buf));
1437
+ append(" GiB");
1438
+ } else if (size >= 1048524.0) {
1439
+ size /= 1048576.0;
1440
+
1441
+ int prec = 1 + (size < 9.9995) + (size < 99.995);
1442
+ append(FormatFloatingPoint(size, true, prec, prec, buf));
1443
+ append(" MiB");
1444
+ } else if (size >= 1023.95) {
1445
+ size /= 1024.0;
1446
+
1447
+ int prec = 1 + (size < 9.9995) + (size < 99.995);
1448
+ append(FormatFloatingPoint(size, true, prec, prec, buf));
1449
+ append(" kiB");
1450
+ } else {
1451
+ append(FormatFloatingPoint(size, arg.u.i, 0, 0, buf));
1452
+ append(" B");
1453
+ }
1454
+ } break;
1455
+ case FmtType::DiskSize: {
1456
+ char buf[128];
1457
+
1458
+ double size;
1459
+ if (arg.u.i < 0) {
1460
+ append('-');
1461
+ size = (double)-arg.u.i;
1462
+ } else {
1463
+ size = (double)arg.u.i;
1464
+ }
1465
+
1466
+ if (size >= 999950000.0) {
1467
+ size /= 1000000000.0;
1468
+
1469
+ int prec = 1 + (size < 9.9995) + (size < 99.995);
1470
+ append(FormatFloatingPoint(size, true, prec, prec, buf));
1471
+ append(" GB");
1472
+ } else if (size >= 999950.0) {
1473
+ size /= 1000000.0;
1474
+
1475
+ int prec = 1 + (size < 9.9995) + (size < 99.995);
1476
+ append(FormatFloatingPoint(size, true, prec, prec, buf));
1477
+ append(" MB");
1478
+ } else if (size >= 999.95) {
1479
+ size /= 1000.0;
1480
+
1481
+ int prec = 1 + (size < 9.9995) + (size < 99.995);
1482
+ append(FormatFloatingPoint(size, true, prec, prec, buf));
1483
+ append(" kB");
1484
+ } else {
1485
+ append(FormatFloatingPoint(size, arg.u.i, 0, 0, buf));
1486
+ append(" B");
1487
+ }
1488
+ } break;
1489
+
1490
+ case FmtType::Date: {
1491
+ K_ASSERT(!arg.u.date.value || arg.u.date.IsValid());
1492
+
1493
+ char buf[128];
1494
+
1495
+ int year = arg.u.date.st.year;
1496
+ if (year < 0) {
1497
+ append('-');
1498
+ year = -year;
1499
+ }
1500
+ if (year < 10) {
1501
+ append("000");
1502
+ } else if (year < 100) {
1503
+ append("00");
1504
+ } else if (year < 1000) {
1505
+ append('0');
1506
+ }
1507
+ append(FormatUnsignedToDecimal((uint64_t)year, buf));
1508
+ append('-');
1509
+ if (arg.u.date.st.month < 10) {
1510
+ append('0');
1511
+ }
1512
+ append(FormatUnsignedToDecimal((uint64_t)arg.u.date.st.month, buf));
1513
+ append('-');
1514
+ if (arg.u.date.st.day < 10) {
1515
+ append('0');
1516
+ }
1517
+ append(FormatUnsignedToDecimal((uint64_t)arg.u.date.st.day, buf));
1518
+ } break;
1519
+
1520
+ case FmtType::TimeISO: {
1521
+ const TimeSpec &spec = arg.u.time.spec;
1522
+
1523
+ LocalArray<char, 128> buf;
1524
+
1525
+ if (spec.offset && arg.u.time.ms) {
1449
1526
  int offset_h = spec.offset / 60;
1450
1527
  int offset_m = spec.offset % 60;
1451
1528
 
1452
- if (arg.u.time.ms) {
1453
- out_buf.len = Fmt(out_buf.data, "%1-%2-%3 %4:%5:%6.%7 %8%9%10",
1454
- FmtArg(spec.year).Pad0(-2), FmtArg(spec.month).Pad0(-2),
1455
- FmtArg(spec.day).Pad0(-2), FmtArg(spec.hour).Pad0(-2),
1456
- FmtArg(spec.min).Pad0(-2), FmtArg(spec.sec).Pad0(-2), FmtArg(spec.msec).Pad0(-3),
1457
- offset_h >= 0 ? "+" : "", FmtArg(offset_h).Pad0(-2), FmtArg(offset_m).Pad0(-2)).len;
1458
- } else {
1459
- out_buf.len = Fmt(out_buf.data, "%1-%2-%3 %4:%5:%6 %7%8%9",
1460
- FmtArg(spec.year).Pad0(-2), FmtArg(spec.month).Pad0(-2),
1461
- FmtArg(spec.day).Pad0(-2), FmtArg(spec.hour).Pad0(-2),
1462
- FmtArg(spec.min).Pad0(-2), FmtArg(spec.sec).Pad0(-2),
1463
- offset_h >= 0 ? "+" : "", FmtArg(offset_h).Pad0(-2), FmtArg(offset_m).Pad0(-2)).len;
1464
- }
1465
- out = out_buf;
1466
- } break;
1529
+ buf.len = Fmt(buf.data, "%1%2%3T%4%5%6.%7%8%9%10",
1530
+ FmtInt(spec.year, 2), FmtInt(spec.month, 2),
1531
+ FmtInt(spec.day, 2), FmtInt(spec.hour, 2),
1532
+ FmtInt(spec.min, 2), FmtInt(spec.sec, 2), FmtInt(spec.msec, 3),
1533
+ offset_h >= 0 ? "+" : "", FmtInt(offset_h, 2), FmtInt(offset_m, 2)).len;
1534
+ } else if (spec.offset) {
1535
+ int offset_h = spec.offset / 60;
1536
+ int offset_m = spec.offset % 60;
1467
1537
 
1468
- case FmtType::Random: {
1469
- static const char *const DefaultChars = "abcdefghijklmnopqrstuvwxyz0123456789";
1470
- Span<const char> chars = arg.u.random.chars ? arg.u.random.chars : DefaultChars;
1538
+ buf.len = Fmt(buf.data, "%1%2%3T%4%5%6%7%8%9",
1539
+ FmtInt(spec.year, 2), FmtInt(spec.month, 2),
1540
+ FmtInt(spec.day, 2), FmtInt(spec.hour, 2),
1541
+ FmtInt(spec.min, 2), FmtInt(spec.sec, 2),
1542
+ offset_h >= 0 ? "+" : "", FmtInt(offset_h, 2), FmtInt(offset_m, 2)).len;
1543
+ } else if (arg.u.time.ms) {
1544
+ buf.len = Fmt(buf.data, "%1%2%3T%4%5%6.%7Z",
1545
+ FmtInt(spec.year, 2), FmtInt(spec.month, 2),
1546
+ FmtInt(spec.day, 2), FmtInt(spec.hour, 2),
1547
+ FmtInt(spec.min, 2), FmtInt(spec.sec, 2), FmtInt(spec.msec, 3)).len;
1548
+ } else {
1549
+ buf.len = Fmt(buf.data, "%1%2%3T%4%5%6Z",
1550
+ FmtInt(spec.year, 2), FmtInt(spec.month, 2),
1551
+ FmtInt(spec.day, 2), FmtInt(spec.hour, 2),
1552
+ FmtInt(spec.min, 2), FmtInt(spec.sec, 2)).len;
1553
+ }
1471
1554
 
1472
- K_ASSERT(arg.u.random.len <= K_SIZE(out_buf.data));
1555
+ append(buf);
1556
+ } break;
1557
+ case FmtType::TimeNice: {
1558
+ const TimeSpec &spec = arg.u.time.spec;
1473
1559
 
1474
- for (Size j = 0; j < arg.u.random.len; j++) {
1475
- int rnd = GetRandomInt(0, (int)chars.len);
1476
- out_buf.Append(chars[rnd]);
1477
- }
1560
+ LocalArray<char, 128> buf;
1478
1561
 
1479
- out = out_buf;
1480
- } break;
1562
+ if (arg.u.time.ms) {
1563
+ int offset_h = spec.offset / 60;
1564
+ int offset_m = spec.offset % 60;
1481
1565
 
1482
- case FmtType::FlagNames: {
1483
- if (arg.u.flags.flags) {
1484
- Span<const char> sep = arg.u.flags.separator;
1485
- for (Size j = 0; j < arg.u.flags.u.names.len; j++) {
1486
- if (arg.u.flags.flags & (1ull << j)) {
1487
- out_buf.Append(arg.u.flags.u.names[j]);
1488
- out_buf.Append(sep);
1489
- }
1490
- }
1491
- out = out_buf.Take(0, out_buf.len - sep.len);
1492
- } else {
1493
- out = "None";
1566
+ buf.len = Fmt(buf.data, "%1-%2-%3 %4:%5:%6.%7 %8%9%10",
1567
+ FmtInt(spec.year, 2), FmtInt(spec.month, 2),
1568
+ FmtInt(spec.day, 2), FmtInt(spec.hour, 2),
1569
+ FmtInt(spec.min, 2), FmtInt(spec.sec, 2), FmtInt(spec.msec, 3),
1570
+ offset_h >= 0 ? "+" : "", FmtInt(offset_h, 2), FmtInt(offset_m, 2)).len;
1571
+ } else {
1572
+ int offset_h = spec.offset / 60;
1573
+ int offset_m = spec.offset % 60;
1574
+
1575
+ buf.len = Fmt(buf.data, "%1-%2-%3 %4:%5:%6 %7%8%9",
1576
+ FmtInt(spec.year, 2), FmtInt(spec.month, 2),
1577
+ FmtInt(spec.day, 2), FmtInt(spec.hour, 2),
1578
+ FmtInt(spec.min, 2), FmtInt(spec.sec, 2),
1579
+ offset_h >= 0 ? "+" : "", FmtInt(offset_h, 2), FmtInt(offset_m, 2)).len;
1580
+ }
1581
+
1582
+ append(buf);
1583
+ } break;
1584
+
1585
+ case FmtType::List: {
1586
+ Span<const char> separator = arg.u.list.separator;
1587
+
1588
+ if (arg.u.list.u.names.len) {
1589
+ append(arg.u.list.u.names[0]);
1590
+
1591
+ for (Size i = 1; i < arg.u.list.u.names.len; i++) {
1592
+ append(separator);
1593
+ append(arg.u.list.u.names[i]);
1494
1594
  }
1495
- } break;
1496
- case FmtType::FlagOptions: {
1497
- if (arg.u.flags.flags) {
1498
- Span<const char> sep = arg.u.flags.separator;
1499
- for (Size j = 0; j < arg.u.flags.u.options.len; j++) {
1500
- if (arg.u.flags.flags & (1ull << j)) {
1501
- out_buf.Append(arg.u.flags.u.options[j].name);
1502
- out_buf.Append(sep);
1503
- }
1504
- }
1505
- out = out_buf.Take(0, out_buf.len - sep.len);
1506
- } else {
1507
- out = "None";
1595
+ } else {
1596
+ append(T("None"));
1597
+ }
1598
+ } break;
1599
+ case FmtType::FlagNames: {
1600
+ uint64_t flags = arg.u.list.flags;
1601
+ Span<const char> separator = arg.u.list.separator;
1602
+
1603
+ if (flags) {
1604
+ for (;;) {
1605
+ int idx = CountTrailingZeros(flags);
1606
+ flags &= ~(1ull << idx);
1607
+
1608
+ append(arg.u.list.u.names[idx]);
1609
+ if (!flags)
1610
+ break;
1611
+ append(separator);
1508
1612
  }
1509
- } break;
1613
+ } else {
1614
+ append(T("None"));
1615
+ }
1616
+ } break;
1617
+ case FmtType::FlagOptions: {
1618
+ uint64_t flags = arg.u.list.flags;
1619
+ Span<const char> separator = arg.u.list.separator;
1510
1620
 
1511
- case FmtType::Span: {
1512
- FmtArg arg2;
1513
- arg2.type = arg.u.span.type;
1514
- arg2.repeat = arg.repeat;
1515
- arg2.pad_len = arg.pad_len;
1516
- arg2.pad_char = arg.pad_char;
1517
-
1518
- const uint8_t *ptr = (const uint8_t *)arg.u.span.ptr;
1519
- for (Size j = 0; j < arg.u.span.len; j++) {
1520
- switch (arg.u.span.type) {
1521
- case FmtType::Str1: { arg2.u.str1 = *(const char **)ptr; } break;
1522
- case FmtType::Str2: { arg2.u.str2 = *(const Span<const char> *)ptr; } break;
1523
- case FmtType::Buffer: { K_UNREACHABLE(); } break;
1524
- case FmtType::Char: { arg2.u.ch = *(const char *)ptr; } break;
1525
- case FmtType::Custom: { K_UNREACHABLE(); } break;
1526
- case FmtType::Bool: { arg2.u.b = *(const bool *)ptr; } break;
1527
- case FmtType::Integer:
1528
- case FmtType::Unsigned:
1529
- case FmtType::Binary:
1530
- case FmtType::Octal:
1531
- case FmtType::BigHex:
1532
- case FmtType::SmallHex: {
1533
- switch (arg.u.span.type_len) {
1534
- case 8: { arg2.u.u = *(const uint64_t *)ptr; } break;
1535
- case 4: { arg2.u.u = *(const uint32_t *)ptr; } break;
1536
- case 2: { arg2.u.u = *(const uint16_t *)ptr; } break;
1537
- case 1: { arg2.u.u = *(const uint8_t *)ptr; } break;
1538
- default: { K_UNREACHABLE(); } break;
1539
- }
1540
- } break;
1541
- case FmtType::Float: {
1542
- arg2.u.f.value = *(const float *)ptr;
1543
- arg2.u.d.min_prec = 0;
1544
- arg2.u.d.max_prec = INT_MAX;
1545
- } break;
1546
- case FmtType::Double: {
1547
- arg2.u.d.value = *(const double *)ptr;
1548
- arg2.u.d.min_prec = 0;
1549
- arg2.u.d.max_prec = INT_MAX;
1550
- } break;
1551
- case FmtType::MemorySize:
1552
- case FmtType::DiskSize: { arg2.u.i = *(const int64_t *)ptr; } break;
1553
- case FmtType::Date: { arg2.u.date = *(const LocalDate *)ptr; } break;
1554
- case FmtType::TimeISO:
1555
- case FmtType::TimeNice: { arg2.u.time = *(decltype(FmtArg::u.time) *)ptr; } break;
1556
- case FmtType::Random: { K_UNREACHABLE(); } break;
1557
- case FmtType::FlagNames: { K_UNREACHABLE(); } break;
1558
- case FmtType::FlagOptions: { K_UNREACHABLE(); } break;
1559
- case FmtType::Span: { K_UNREACHABLE(); } break;
1560
- }
1561
- ptr += arg.u.span.type_len;
1621
+ if (arg.u.list.flags) {
1622
+ for (;;) {
1623
+ int idx = CountTrailingZeros(flags);
1624
+ flags &= ~(1ull << idx);
1562
1625
 
1563
- if (j) {
1564
- append(arg.u.span.separator);
1565
- }
1566
- ProcessArg(arg2, append);
1626
+ append(arg.u.list.u.options[idx].name);
1627
+ if (!flags)
1628
+ break;
1629
+ append(separator);
1567
1630
  }
1631
+ } else {
1632
+ append(T("None"));
1633
+ }
1634
+ } break;
1568
1635
 
1569
- continue;
1570
- } break;
1571
- }
1636
+ case FmtType::Random: {
1637
+ LocalArray<char, 512> buf;
1638
+
1639
+ static const char *const DefaultChars = "abcdefghijklmnopqrstuvwxyz0123456789";
1640
+ Span<const char> chars = arg.u.random.chars ? arg.u.random.chars : DefaultChars;
1641
+
1642
+ K_ASSERT(arg.u.random.len <= K_SIZE(buf.data));
1643
+ buf.len = arg.u.random.len;
1572
1644
 
1573
- if (pad_len < 0) {
1574
- pad_len = (-pad_len) - out.len;
1575
- for (Size j = 0; j < pad_len; j++) {
1576
- append(arg.pad_char);
1645
+ for (Size j = 0; j < arg.u.random.len; j++) {
1646
+ int rnd = GetRandomInt(0, (int)chars.len);
1647
+ buf[j] = chars[rnd];
1577
1648
  }
1578
- append(out);
1579
- } else if (pad_len > 0) {
1580
- append(out);
1581
- pad_len -= out.len;
1582
- for (Size j = 0; j < pad_len; j++) {
1583
- append(arg.pad_char);
1649
+
1650
+ append(buf);
1651
+ } break;
1652
+
1653
+ case FmtType::SafeStr: {
1654
+ for (char c: arg.u.str) {
1655
+ AppendSafe(c, append);
1584
1656
  }
1585
- } else {
1586
- append(out);
1587
- }
1657
+ } break;
1658
+ case FmtType::SafeChar: { AppendSafe(arg.u.ch, append); } break;
1588
1659
  }
1589
1660
  }
1590
1661
 
@@ -1864,17 +1935,48 @@ void FmtLowerAscii::Format(FunctionRef<void(Span<const char>)> append) const
1864
1935
  }
1865
1936
  }
1866
1937
 
1938
+ void FmtUrlSafe::Format(FunctionRef<void(Span<const char>)> append) const
1939
+ {
1940
+ for (char c: str) {
1941
+ if (IsAsciiAlphaOrDigit(c) || strchr(passthrough, c)) {
1942
+ append((char)c);
1943
+ } else {
1944
+ char encoded[3];
1945
+
1946
+ encoded[0] = '%';
1947
+ encoded[1] = BigHexLiterals[((uint8_t)c >> 4) & 0xF];
1948
+ encoded[2] = BigHexLiterals[((uint8_t)c >> 0) & 0xF];
1949
+
1950
+ Span<const char> buf = MakeSpan(encoded, 3);
1951
+ append(buf);
1952
+ }
1953
+ }
1954
+ }
1955
+
1956
+ void FmtHtmlSafe::Format(FunctionRef<void(Span<const char>)> append) const
1957
+ {
1958
+ for (char c: str) {
1959
+ switch (c) {
1960
+ case '<': { append("&lt;"); } break;
1961
+ case '>': { append("&gt;"); } break;
1962
+ case '"': { append("&quot;"); } break;
1963
+ case '\'': { append("&apos;"); } break;
1964
+ case '&': { append("&amp;"); } break;
1965
+
1966
+ default: { append(c); } break;
1967
+ }
1968
+ }
1969
+ }
1970
+
1867
1971
  void FmtEscape::Format(FunctionRef<void(Span<const char>)> append) const
1868
1972
  {
1869
1973
  for (char c: str) {
1870
- if (c == '"') {
1871
- append("\\\"");
1872
- } else if (c == '\'') {
1873
- append("\\'");
1874
- } else if (c == '\r') {
1974
+ if (c == '\r') {
1875
1975
  append("\\r");
1876
1976
  } else if (c == '\n') {
1877
1977
  append("\\n");
1978
+ } else if (c == '\\') {
1979
+ append("\\\\");
1878
1980
  } else if ((unsigned int)c < 32) {
1879
1981
  char encoded[4];
1880
1982
 
@@ -1885,32 +1987,15 @@ void FmtEscape::Format(FunctionRef<void(Span<const char>)> append) const
1885
1987
 
1886
1988
  Span<const char> buf = MakeSpan(encoded, 4);
1887
1989
  append(buf);
1990
+ } else if (c == quote) {
1991
+ append('\\');
1992
+ append(quote);
1888
1993
  } else {
1889
1994
  append(c);
1890
1995
  }
1891
1996
  }
1892
1997
  }
1893
1998
 
1894
- void FmtUrlSafe::Format(FunctionRef<void(Span<const char>)> append) const
1895
- {
1896
- static const char literals[] = "0123456789ABCDEF";
1897
-
1898
- for (char c: str) {
1899
- if (IsAsciiAlphaOrDigit(c) || strchr(passthrough, c)) {
1900
- append((char)c);
1901
- } else {
1902
- char encoded[3];
1903
-
1904
- encoded[0] = '%';
1905
- encoded[1] = literals[((uint8_t)c >> 4) & 0xF];
1906
- encoded[2] = literals[((uint8_t)c >> 0) & 0xF];
1907
-
1908
- Span<const char> buf = MakeSpan(encoded, 3);
1909
- append(buf);
1910
- }
1911
- }
1912
- }
1913
-
1914
1999
  FmtArg FmtVersion(int64_t version, int parts, int by)
1915
2000
  {
1916
2001
  K_ASSERT(version >= 0);
@@ -1963,7 +2048,7 @@ const char *GetEnv(const char *name)
1963
2048
  static HashMap<const char *, const char *> values;
1964
2049
 
1965
2050
  bool inserted;
1966
- auto bucket = values.TrySetDefault(name, &inserted);
2051
+ auto bucket = values.InsertOrGetDefault(name, &inserted);
1967
2052
 
1968
2053
  if (inserted) {
1969
2054
  const char *str = (const char *)EM_ASM_INT({
@@ -2046,7 +2131,7 @@ void LogFmt(LogLevel level, const char *ctx, const char *fmt, Span<const FmtArg>
2046
2131
  char ctx_buf[512];
2047
2132
  if (log_times) {
2048
2133
  double time = (double)(GetMonotonicTime() - start_time) / 1000;
2049
- Fmt(ctx_buf, "[%1] %2", FmtDouble(time, 3).Pad(-8), ctx ? ctx : "");
2134
+ Fmt(ctx_buf, "[%1] %2", FmtDouble(time, 3, 8), ctx ? ctx : "");
2050
2135
 
2051
2136
  ctx = ctx_buf;
2052
2137
  }
@@ -2383,13 +2468,13 @@ void DefaultProgressHandler(Span<const ProgressInfo> bars)
2383
2468
  int progress = (int)(100 * delta / range);
2384
2469
  int size = progress / 4;
2385
2470
 
2386
- buf.len += Fmt(buf.TakeAvailable(), true, "%!..+[%1%2]%!0 %3\n", FmtArg('=').Repeat(size), FmtArg(' ').Repeat(25 - size), bar.text).len;
2471
+ buf.len += Fmt(buf.TakeAvailable(), true, "%!..+[%1%2]%!0 %3\n", FmtRepeat("=", size), FmtRepeat(" ", 25 - size), bar.text).len;
2387
2472
  } else {
2388
2473
  int progress = (int)(frame % 44);
2389
2474
  int before = (progress > 22) ? (44 - progress) : progress;
2390
2475
  int after = std::max(22 - before, 0);
2391
2476
 
2392
- buf.len += Fmt(buf.TakeAvailable(), true, "%!..+[%1===%2]%!0 %3\n", FmtArg(' ').Repeat(before), FmtArg(' ').Repeat(after), bar.text).len;
2477
+ buf.len += Fmt(buf.TakeAvailable(), true, "%!..+[%1===%2]%!0 %3\n", FmtRepeat(" ", before), FmtRepeat(" ", after), bar.text).len;
2393
2478
  }
2394
2479
  }
2395
2480
 
@@ -2668,7 +2753,7 @@ bool ResizeFile(int fd, const char *filename, int64_t len)
2668
2753
  return true;
2669
2754
  }
2670
2755
 
2671
- bool SetFileMetaData(int fd, const char *filename, int64_t mtime, int64_t btime, uint32_t)
2756
+ bool SetFileTimes(int fd, const char *filename, int64_t mtime, int64_t btime)
2672
2757
  {
2673
2758
  HANDLE h = (HANDLE)_get_osfhandle(fd);
2674
2759
 
@@ -3117,25 +3202,61 @@ bool ResizeFile(int fd, const char *filename, int64_t len)
3117
3202
  return true;
3118
3203
  }
3119
3204
 
3120
- bool SetFileMetaData(int fd, const char *filename, int64_t mtime, int64_t, uint32_t mode)
3205
+ bool SetFileMode(int fd, const char *filename, uint32_t mode)
3206
+ {
3207
+ if (fd >= 0) {
3208
+ if (fchmod(fd, (mode_t)mode) < 0) {
3209
+ LogError("Failed to set permissions of '%1': %2", filename, strerror(errno));
3210
+ return false;
3211
+ }
3212
+ } else {
3213
+ if (fchmodat(AT_FDCWD, filename, (mode_t)mode, AT_SYMLINK_NOFOLLOW) < 0) {
3214
+ LogError("Failed to set permissions of '%1': %2", filename, strerror(errno));
3215
+ return false;
3216
+ }
3217
+ }
3218
+
3219
+ return true;
3220
+ }
3221
+
3222
+ bool SetFileOwner(int fd, const char *filename, uint32_t uid, uint32_t gid)
3223
+ {
3224
+ if (fd >= 0) {
3225
+ if (fchown(fd, (uid_t)uid, (gid_t)gid) < 0) {
3226
+ LogError("Failed to change owner of '%1': %2", filename, strerror(errno));
3227
+ return false;
3228
+ }
3229
+ } else {
3230
+ if (lchown(filename, (uid_t)uid, (gid_t)gid) < 0) {
3231
+ LogError("Failed to change owner of '%1': %2", filename, strerror(errno));
3232
+ return false;
3233
+ }
3234
+ }
3235
+
3236
+ return true;
3237
+ }
3238
+
3239
+ bool SetFileTimes(int fd, const char *filename, int64_t mtime, int64_t)
3121
3240
  {
3122
3241
  struct timespec times[2] = {};
3123
- bool valid = true;
3124
3242
 
3125
3243
  times[0].tv_nsec = UTIME_OMIT;
3126
3244
  times[1].tv_sec = mtime / 1000;
3127
3245
  times[1].tv_nsec = (mtime % 1000) * 1000000;
3128
3246
 
3129
- if (futimens(fd, times) < 0) {
3130
- LogError("Failed to set modification time of '%1': %2", filename, filename);
3131
- valid = false;
3132
- }
3133
- if (fchmod(fd, (mode_t)mode) < 0) {
3134
- LogError("Failed to set permissions of '%1'", filename);
3135
- valid = false;
3247
+ if (fd >= 0) {
3248
+ if (futimens(fd, times) < 0) {
3249
+ LogError("Failed to set modification time of '%1': %2", filename, strerror(errno));
3250
+ return false;
3251
+ }
3252
+ } else {
3253
+ if (utimensat(AT_FDCWD, filename, times, AT_SYMLINK_NOFOLLOW) < 0) {
3254
+ LogError("Failed to set modification time of '%1': %2", filename, strerror(errno));
3255
+ return false;
3256
+ }
3136
3257
  }
3137
3258
 
3138
- return valid;
3259
+ return true;
3139
3260
  }
3140
3261
 
3141
3262
  #if !defined(__wasm__)
@@ -3748,7 +3869,7 @@ const char *GetApplicationExecutable()
3748
3869
 
3749
3870
  size_t argc;
3750
3871
  {
3751
- int ret = sysctl(name, K_LEN(name), nullptr, &argc, NULL, 0);
3872
+ int ret = sysctl(name, K_LEN(name), nullptr, &argc, nullptr, 0);
3752
3873
  K_ASSERT(ret >= 0);
3753
3874
  K_ASSERT(argc >= 1);
3754
3875
  }
@@ -3783,7 +3904,7 @@ const char *GetApplicationExecutable()
3783
3904
  int name[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 };
3784
3905
  size_t len = sizeof(executable_path);
3785
3906
 
3786
- int ret = sysctl(name, K_LEN(name), executable_path, &len, NULL, 0);
3907
+ int ret = sysctl(name, K_LEN(name), executable_path, &len, nullptr, 0);
3787
3908
  K_ASSERT(ret >= 0);
3788
3909
  K_ASSERT(len < K_SIZE(executable_path));
3789
3910
  }
@@ -3852,13 +3973,6 @@ Span<const char> GetPathExtension(Span<const char> filename, CompressionType *ou
3852
3973
  return extension;
3853
3974
  }
3854
3975
 
3855
- CompressionType GetPathCompression(Span<const char> filename)
3856
- {
3857
- CompressionType compression_type;
3858
- GetPathExtension(filename, &compression_type);
3859
- return compression_type;
3860
- }
3861
-
3862
3976
  Span<char> NormalizePath(Span<const char> path, Span<const char> root_directory, unsigned int flags, Allocator *alloc)
3863
3977
  {
3864
3978
  K_ASSERT(alloc);
@@ -3866,6 +3980,21 @@ Span<char> NormalizePath(Span<const char> path, Span<const char> root_directory,
3866
3980
  if (!path.len && !root_directory.len)
3867
3981
  return Fmt(alloc, "");
3868
3982
 
3983
+ #if !defined(_WIN32)
3984
+ if (!(flags & (int)NormalizeFlag::NoExpansion)) {
3985
+ Span<const char> prefix = SplitStrAny(path, K_PATH_SEPARATORS);
3986
+
3987
+ if (prefix == "~") {
3988
+ const char *home = GetEnv("HOME");
3989
+
3990
+ if (home) {
3991
+ root_directory = home;
3992
+ path = TrimStrLeft(path.Take(1, path.len - 1), K_PATH_SEPARATORS);
3993
+ }
3994
+ }
3995
+ }
3996
+ #endif
3997
+
3869
3998
  HeapArray<char> buf(alloc);
3870
3999
  Size parts_count = 0;
3871
4000
 
@@ -3906,15 +4035,17 @@ Span<char> NormalizePath(Span<const char> path, Span<const char> root_directory,
3906
4035
 
3907
4036
  if (!buf.len) {
3908
4037
  buf.Append('.');
4038
+
4039
+ if (flags & (int)NormalizeFlag::EndWithSeparator) {
4040
+ buf.Append(separator);
4041
+ }
3909
4042
  } else if (buf.len == 1 && IsPathSeparator(buf[0])) {
3910
- // Root '/', keep as-is
3911
- } else {
4043
+ // Root '/', keep as-is or almost
4044
+ buf[0] = separator;
4045
+ } else if (!(flags & (int)NormalizeFlag::EndWithSeparator)) {
3912
4046
  // Strip last separator
3913
4047
  buf.len--;
3914
4048
  }
3915
- if (flags & (int)NormalizeFlag::EndWithSeparator) {
3916
- buf.Append(separator);
3917
- }
3918
4049
 
3919
4050
  #if defined(_WIN32)
3920
4051
  if (buf.len >= 2 && IsAsciiAlpha(buf[0]) && buf[1] == ':') {
@@ -3961,6 +4092,20 @@ bool PathContainsDotDot(const char *path)
3961
4092
  return false;
3962
4093
  }
3963
4094
 
4095
+ bool PathContainsDotDot(Span<const char> path)
4096
+ {
4097
+ const char *ptr = path.ptr;
4098
+ const char *end = path.end();
4099
+
4100
+ while ((ptr = (const char *)MemMem(ptr, end - ptr, "..", 2))) {
4101
+ if ((ptr == path.ptr || IsPathSeparator(ptr[-1])) && (ptr + 2 == end || IsPathSeparator(ptr[2])))
4102
+ return true;
4103
+ ptr += 2;
4104
+ }
4105
+
4106
+ return false;
4107
+ }
4108
+
3964
4109
  static bool CheckForDumbTerm()
3965
4110
  {
3966
4111
  static bool dumb = ([]() {
@@ -4777,6 +4922,7 @@ bool EnsureDirectoryExists(const char *filename)
4777
4922
 
4778
4923
  #if defined(_WIN32)
4779
4924
 
4925
+ static const DWORD main_thread = GetCurrentThreadId();
4780
4926
  static HANDLE console_ctrl_event = CreateEvent(nullptr, TRUE, FALSE, nullptr);
4781
4927
  static bool ignore_ctrl_event = false;
4782
4928
 
@@ -4811,7 +4957,7 @@ bool CreateOverlappedPipe(bool overlap0, bool overlap1, PipeMode mode, HANDLE ou
4811
4957
 
4812
4958
  char pipe_name[128];
4813
4959
  do {
4814
- Fmt(pipe_name, "\\\\.\\Pipe\\libcc.%1.%2",
4960
+ Fmt(pipe_name, "\\\\.\\pipe\\kcc.%1.%2",
4815
4961
  GetCurrentProcessId(), InterlockedIncrement(&pipe_idx));
4816
4962
 
4817
4963
  DWORD open_mode = PIPE_ACCESS_INBOUND | FILE_FLAG_FIRST_PIPE_INSTANCE | (overlap0 ? FILE_FLAG_OVERLAPPED : 0);
@@ -5138,12 +5284,11 @@ bool ExecuteCommandLine(const char *cmd_line, const ExecuteInfo &info,
5138
5284
 
5139
5285
  #else
5140
5286
 
5141
- #if defined(__OpenBSD__) || defined(__FreeBSD__)
5142
5287
  static const pthread_t main_thread = pthread_self();
5143
- #endif
5288
+
5144
5289
  static std::atomic_bool flag_signal { false };
5145
5290
  static std::atomic_int explicit_signal { 0 };
5146
- static int interrupt_pfd[2] = {-1, -1};
5291
+ static std::atomic_int interrupt_pfd[2] { -1, -1 };
5147
5292
 
5148
5293
  void SetSignalHandler(int signal, void (*func)(int), struct sigaction *prev)
5149
5294
  {
@@ -5158,19 +5303,17 @@ void SetSignalHandler(int signal, void (*func)(int), struct sigaction *prev)
5158
5303
 
5159
5304
  static void DefaultSignalHandler(int signal)
5160
5305
  {
5161
- #if defined(__OpenBSD__) || defined(__FreeBSD__)
5162
- if (!pthread_main_np()) {
5306
+ if (pthread_self() != main_thread) {
5163
5307
  pthread_kill(main_thread, signal);
5164
5308
  return;
5165
5309
  }
5166
- #endif
5167
5310
 
5168
5311
  pid_t pid = getpid();
5169
5312
  K_ASSERT(pid > 1);
5170
5313
 
5171
- if (interrupt_pfd[1] >= 0) {
5314
+ if (int fd = interrupt_pfd[1].load(); fd >= 0) {
5172
5315
  char dummy = 0;
5173
- K_IGNORE write(interrupt_pfd[1], &dummy, 1);
5316
+ K_IGNORE write(fd, &dummy, 1);
5174
5317
  }
5175
5318
 
5176
5319
  if (flag_signal) {
@@ -5181,26 +5324,30 @@ static void DefaultSignalHandler(int signal)
5181
5324
  }
5182
5325
  }
5183
5326
 
5184
- bool CreatePipe(int pfd[2])
5327
+ bool CreatePipe(bool block, int out_pfd[2])
5185
5328
  {
5186
5329
  #if defined(__APPLE__)
5187
- if (pipe(pfd) < 0) {
5330
+ if (pipe(out_pfd) < 0) {
5188
5331
  LogError("Failed to create pipe: %1", strerror(errno));
5189
5332
  return false;
5190
5333
  }
5191
5334
 
5192
- if (fcntl(pfd[0], F_SETFD, FD_CLOEXEC) < 0 || fcntl(pfd[1], F_SETFD, FD_CLOEXEC) < 0) {
5335
+ if (fcntl(out_pfd[0], F_SETFD, FD_CLOEXEC) < 0 || fcntl(out_pfd[1], F_SETFD, FD_CLOEXEC) < 0) {
5193
5336
  LogError("Failed to set FD_CLOEXEC on pipe: %1", strerror(errno));
5194
5337
  return false;
5195
5338
  }
5196
- if (fcntl(pfd[0], F_SETFL, O_NONBLOCK) < 0 || fcntl(pfd[1], F_SETFL, O_NONBLOCK) < 0) {
5197
- LogError("Failed to set O_NONBLOCK on pipe: %1", strerror(errno));
5198
- return false;
5339
+ if (!block) {
5340
+ if (fcntl(out_pfd[0], F_SETFL, O_NONBLOCK) < 0 || fcntl(out_pfd[1], F_SETFL, O_NONBLOCK) < 0) {
5341
+ LogError("Failed to set O_NONBLOCK on pipe: %1", strerror(errno));
5342
+ return false;
5343
+ }
5199
5344
  }
5200
5345
 
5201
5346
  return true;
5202
5347
  #else
5203
- if (pipe2(pfd, O_CLOEXEC | O_NONBLOCK) < 0) {
5348
+ int flags = O_CLOEXEC | (block ? 0 : O_NONBLOCK);
5349
+
5350
+ if (pipe2(out_pfd, flags) < 0) {
5204
5351
  LogError("Failed to create pipe: %1", strerror(errno));
5205
5352
  return false;
5206
5353
  }
@@ -5218,6 +5365,28 @@ void CloseDescriptorSafe(int *fd_ptr)
5218
5365
  *fd_ptr = -1;
5219
5366
  }
5220
5367
 
5368
+ static void InitInterruptPipe()
5369
+ {
5370
+ static bool success = ([]() {
5371
+ static int pfd[2];
5372
+
5373
+ if (!CreatePipe(false, pfd))
5374
+ return false;
5375
+
5376
+ atexit([]() {
5377
+ CloseDescriptor(pfd[0]);
5378
+ CloseDescriptor(pfd[1]);
5379
+ });
5380
+
5381
+ interrupt_pfd[0] = pfd[0];
5382
+ interrupt_pfd[1] = pfd[1];
5383
+
5384
+ return true;
5385
+ })();
5386
+
5387
+ K_CRITICAL(success, "Failed to create interrupt pipe");
5388
+ }
5389
+
5221
5390
  bool ExecuteCommandLine(const char *cmd_line, const ExecuteInfo &info,
5222
5391
  FunctionRef<Span<const uint8_t>()> in_func,
5223
5392
  FunctionRef<void(Span<uint8_t> buf)> out_func, int *out_code)
@@ -5231,12 +5400,8 @@ bool ExecuteCommandLine(const char *cmd_line, const ExecuteInfo &info,
5231
5400
  CloseDescriptorSafe(&in_pfd[1]);
5232
5401
  };
5233
5402
  if (in_func.IsValid()) {
5234
- if (!CreatePipe(in_pfd))
5235
- return false;
5236
- if (fcntl(in_pfd[1], F_SETFL, O_NONBLOCK) < 0) {
5237
- LogError("Failed to set O_NONBLOCK on pipe: %1", strerror(errno));
5403
+ if (!CreatePipe(false, in_pfd))
5238
5404
  return false;
5239
- }
5240
5405
  }
5241
5406
 
5242
5407
  // Create write pipes
@@ -5246,33 +5411,11 @@ bool ExecuteCommandLine(const char *cmd_line, const ExecuteInfo &info,
5246
5411
  CloseDescriptorSafe(&out_pfd[1]);
5247
5412
  };
5248
5413
  if (out_func.IsValid()) {
5249
- if (!CreatePipe(out_pfd))
5250
- return false;
5251
- if (fcntl(out_pfd[0], F_SETFL, O_NONBLOCK) < 0) {
5252
- LogError("Failed to set O_NONBLOCK on pipe: %1", strerror(errno));
5414
+ if (!CreatePipe(false, out_pfd))
5253
5415
  return false;
5254
- }
5255
5416
  }
5256
5417
 
5257
- // Create child termination pipe
5258
- {
5259
- static bool success = ([]() {
5260
- if (!CreatePipe(interrupt_pfd))
5261
- return false;
5262
-
5263
- atexit([]() {
5264
- CloseDescriptorSafe(&interrupt_pfd[0]);
5265
- CloseDescriptorSafe(&interrupt_pfd[1]);
5266
- });
5267
-
5268
- return true;
5269
- })();
5270
-
5271
- if (!success) {
5272
- LogError("Failed to create termination pipe");
5273
- return false;
5274
- }
5275
- }
5418
+ InitInterruptPipe();
5276
5419
 
5277
5420
  // Prepare new environment (if needed)
5278
5421
  HeapArray<char *> new_env;
@@ -5345,9 +5488,9 @@ bool ExecuteCommandLine(const char *cmd_line, const ExecuteInfo &info,
5345
5488
  out_idx = pfds.len;
5346
5489
  pfds.Append({ out_pfd[0], POLLIN, 0 });
5347
5490
  }
5348
- if (interrupt_pfd[0] >= 0) {
5491
+ if (int fd = interrupt_pfd[0].load(); fd >= 0) {
5349
5492
  term_idx = pfds.len;
5350
- pfds.Append({ interrupt_pfd[0], POLLIN, 0 });
5493
+ pfds.Append({ fd, POLLIN, 0 });
5351
5494
  }
5352
5495
 
5353
5496
  if (K_RESTART_EINTR(poll(pfds.data, (nfds_t)pfds.len, -1), < 0) < 0) {
@@ -5575,9 +5718,9 @@ WaitResult WaitEvents(Span<const WaitSource> sources, int64_t timeout, uint64_t
5575
5718
 
5576
5719
  LocalArray<HANDLE, 64> events;
5577
5720
  DWORD wake = 0;
5721
+ DWORD wait_ret = 0;
5578
5722
 
5579
5723
  events.Append(console_ctrl_event);
5580
- events.Append(wait_msg_event);
5581
5724
 
5582
5725
  // There is no way to get a waitable HANDLE for the Win32 GUI message pump.
5583
5726
  // Instead, waitable sources (such as the system tray code) return NULL to indicate that
@@ -5592,6 +5735,11 @@ WaitResult WaitEvents(Span<const WaitSource> sources, int64_t timeout, uint64_t
5592
5735
  timeout = (int64_t)std::min((uint64_t)timeout, (uint64_t)src.timeout);
5593
5736
  }
5594
5737
 
5738
+ if (main_thread == GetCurrentThreadId()) {
5739
+ wait_ret = WAIT_OBJECT_0 + (DWORD)events.len;
5740
+ events.Append(wait_msg_event);
5741
+ }
5742
+
5595
5743
  DWORD ret;
5596
5744
  if (timeout >= 0) {
5597
5745
  do {
@@ -5604,34 +5752,31 @@ WaitResult WaitEvents(Span<const WaitSource> sources, int64_t timeout, uint64_t
5604
5752
  ret = MsgWaitForMultipleObjects((DWORD)events.len, events.data, FALSE, INFINITE, wake);
5605
5753
  }
5606
5754
 
5607
- switch (ret) {
5608
- case WAIT_OBJECT_0: return WaitResult::Interrupt;
5609
- case WAIT_OBJECT_0 + 1: {
5610
- ResetEvent(wait_msg_event);
5611
- return WaitResult::Message;
5612
- } break;
5613
- default: {
5614
- if (ret == WAIT_OBJECT_0 + events.len) {
5615
- // Mark all sources with an interest in the message pump as ready
5616
- if (out_ready) {
5617
- uint64_t flags = 0;
5618
- for (Size i = 0; i < sources.len; i++) {
5619
- flags |= !sources[i].handle ? (1ull << i) : 0;
5620
- }
5621
- *out_ready = flags;
5622
- }
5623
- return WaitResult::Ready;
5755
+ if (ret == WAIT_TIMEOUT) {
5756
+ return WaitResult::Timeout;
5757
+ } else if (ret == WAIT_OBJECT_0) {
5758
+ return WaitResult::Interrupt;
5759
+ } else if (ret == wait_ret) {
5760
+ ResetEvent(wait_msg_event);
5761
+ return WaitResult::Message;
5762
+ } else if (ret == WAIT_OBJECT_0 + events.len) {
5763
+ // Mark all sources with an interest in the message pump as ready
5764
+ if (out_ready) {
5765
+ uint64_t flags = 0;
5766
+ for (Size i = 0; i < sources.len; i++) {
5767
+ flags |= !sources[i].handle ? (1ull << i) : 0;
5624
5768
  }
5769
+ *out_ready = flags;
5770
+ }
5771
+ return WaitResult::Ready;
5772
+ } else {
5773
+ Size idx = (Size)ret - WAIT_OBJECT_0 - 1;
5774
+ K_ASSERT(idx >= 0 && idx < sources.len);
5625
5775
 
5626
- Size idx = (Size)ret - WAIT_OBJECT_0 - 2;
5627
- K_ASSERT(idx >= 0 && idx < sources.len);
5628
-
5629
- if (out_ready) {
5630
- *out_ready |= 1ull << idx;
5631
- }
5632
- return WaitResult::Ready;
5633
- } break;
5634
- case WAIT_TIMEOUT: return WaitResult::Timeout;
5776
+ if (out_ready) {
5777
+ *out_ready |= 1ull << idx;
5778
+ }
5779
+ return WaitResult::Ready;
5635
5780
  }
5636
5781
  }
5637
5782
 
@@ -5641,11 +5786,16 @@ WaitResult WaitEvents(int64_t timeout)
5641
5786
  return WaitEvents(sources, timeout);
5642
5787
  }
5643
5788
 
5644
- void InterruptWait()
5789
+ void PostWaitMessage()
5645
5790
  {
5646
5791
  SetEvent(wait_msg_event);
5647
5792
  }
5648
5793
 
5794
+ void PostTerminate()
5795
+ {
5796
+ SetEvent(console_ctrl_event);
5797
+ }
5798
+
5649
5799
  #else
5650
5800
 
5651
5801
  void WaitDelay(int64_t delay)
@@ -5669,18 +5819,24 @@ void WaitDelay(int64_t delay)
5669
5819
  WaitResult WaitEvents(Span<const WaitSource> sources, int64_t timeout, uint64_t *out_ready)
5670
5820
  {
5671
5821
  LocalArray<struct pollfd, 64> pfds;
5672
- K_ASSERT(sources.len <= K_LEN(pfds.data));
5673
-
5674
- static std::atomic_bool message { false };
5822
+ K_ASSERT(sources.len <= K_LEN(pfds.data) - 1);
5675
5823
 
5824
+ // Don't exit after SIGINT/SIGTERM, just signal us
5676
5825
  flag_signal = true;
5826
+
5827
+ static std::atomic_bool message { false };
5677
5828
  SetSignalHandler(SIGUSR1, [](int) { message = true; });
5678
5829
 
5679
5830
  for (const WaitSource &src: sources) {
5680
- pfds.Append({ src.fd, (short)src.events, 0 });
5831
+ short events = src.events ? (short)src.events : POLLIN;
5832
+ pfds.Append({ src.fd, events, 0 });
5833
+
5681
5834
  timeout = (int64_t)std::min((uint64_t)timeout, (uint64_t)src.timeout);
5682
5835
  }
5683
5836
 
5837
+ InitInterruptPipe();
5838
+ pfds.Append({ interrupt_pfd[0], POLLIN, 0 });
5839
+
5684
5840
  int64_t start = (timeout >= 0) ? GetMonotonicTime() : 0;
5685
5841
  int64_t until = start + timeout;
5686
5842
  int timeout32 = (int)std::min(until - start, (int64_t)INT_MAX);
@@ -5690,7 +5846,7 @@ WaitResult WaitEvents(Span<const WaitSource> sources, int64_t timeout, uint64_t
5690
5846
  return WaitResult::Exit;
5691
5847
  } else if (explicit_signal) {
5692
5848
  return WaitResult::Interrupt;
5693
- } else if (message) {
5849
+ } else if (message && pthread_self() == main_thread) {
5694
5850
  message = false;
5695
5851
  return WaitResult::Message;
5696
5852
  }
@@ -5702,16 +5858,19 @@ WaitResult WaitEvents(Span<const WaitSource> sources, int64_t timeout, uint64_t
5702
5858
  continue;
5703
5859
 
5704
5860
  LogError("Failed to poll for events: %1", strerror(errno));
5705
- return WaitResult::Error;
5861
+ abort();
5706
5862
  } else if (ready > 0) {
5707
- if (out_ready) {
5708
- uint64_t flags = 0;
5709
- for (Size i = 0; i < pfds.len; i++) {
5710
- flags |= pfds[i].revents ? (1ull << i) : 0;
5863
+ uint64_t flags = 0;
5864
+ for (Size i = 0; i < pfds.len - 1; i++) {
5865
+ flags |= pfds[i].revents ? (1ull << i) : 0;
5866
+ }
5867
+
5868
+ if (flags) {
5869
+ if (out_ready) {
5870
+ *out_ready = flags;
5711
5871
  }
5712
- *out_ready = flags;
5872
+ return WaitResult::Ready;
5713
5873
  }
5714
- return WaitResult::Ready;
5715
5874
  }
5716
5875
 
5717
5876
  if (timeout >= 0) {
@@ -5731,12 +5890,20 @@ WaitResult WaitEvents(int64_t timeout)
5731
5890
  return WaitEvents(sources, timeout);
5732
5891
  }
5733
5892
 
5734
- void InterruptWait()
5893
+ void PostWaitMessage()
5735
5894
  {
5736
5895
  pid_t pid = getpid();
5737
5896
  kill(pid, SIGUSR1);
5738
5897
  }
5739
5898
 
5899
+ void PostTerminate()
5900
+ {
5901
+ InitInterruptPipe();
5902
+
5903
+ char dummy = 0;
5904
+ K_IGNORE write(interrupt_pfd[1], &dummy, 1);
5905
+ }
5906
+
5740
5907
  #endif
5741
5908
 
5742
5909
  #endif
@@ -5837,30 +6004,30 @@ error:
5837
6004
 
5838
6005
  bool NotifySystemd()
5839
6006
  {
5840
- const char *addr_str = GetEnv("NOTIFY_SOCKET");
5841
- if (!addr_str)
6007
+ const char *addr = GetEnv("NOTIFY_SOCKET");
6008
+ if (!addr)
5842
6009
  return true;
5843
6010
 
5844
- struct sockaddr_un addr;
5845
- if (addr_str[0] == '@') {
5846
- addr_str++;
6011
+ struct sockaddr_un sa;
6012
+ if (addr[0] == '@') {
6013
+ addr++;
5847
6014
 
5848
- if (strlen(addr_str) >= sizeof(addr.sun_path) - 1) {
6015
+ if (strlen(addr) >= sizeof(sa.sun_path) - 1) {
5849
6016
  LogError("Abstract socket address in NOTIFY_SOCKET is too long");
5850
6017
  return false;
5851
6018
  }
5852
6019
 
5853
- addr.sun_family = AF_UNIX;
5854
- addr.sun_path[0] = 0;
5855
- CopyString(addr_str, MakeSpan(addr.sun_path + 1, K_SIZE(addr.sun_path) - 1));
5856
- } else if (addr_str[0] == '/') {
5857
- if (strlen(addr_str) >= sizeof(addr.sun_path)) {
6020
+ sa.sun_family = AF_UNIX;
6021
+ sa.sun_path[0] = 0;
6022
+ CopyString(addr, MakeSpan(sa.sun_path + 1, K_SIZE(sa.sun_path) - 1));
6023
+ } else if (addr[0] == '/') {
6024
+ if (strlen(addr) >= sizeof(sa.sun_path)) {
5858
6025
  LogError("Socket pathname in NOTIFY_SOCKET is too long");
5859
6026
  return false;
5860
6027
  }
5861
6028
 
5862
- addr.sun_family = AF_UNIX;
5863
- CopyString(addr_str, addr.sun_path);
6029
+ sa.sun_family = AF_UNIX;
6030
+ CopyString(addr, sa.sun_path);
5864
6031
  } else {
5865
6032
  LogError("Invalid socket address in NOTIFY_SOCKET");
5866
6033
  return false;
@@ -5877,8 +6044,8 @@ bool NotifySystemd()
5877
6044
  struct msghdr msg = {};
5878
6045
  iov.iov_base = (void *)"READY=1";
5879
6046
  iov.iov_len = strlen("READY=1");
5880
- msg.msg_name = &addr;
5881
- msg.msg_namelen = offsetof(struct sockaddr_un, sun_path) + strlen(addr_str);
6047
+ msg.msg_name = &sa;
6048
+ msg.msg_namelen = offsetof(struct sockaddr_un, sun_path) + strlen(addr);
5882
6049
  msg.msg_iov = &iov;
5883
6050
  msg.msg_iovlen = 1;
5884
6051
 
@@ -5927,25 +6094,13 @@ void InitApp()
5927
6094
  #endif
5928
6095
 
5929
6096
  #if !defined(_WIN32) && !defined(__wasi__)
5930
- // Best effort
5931
- setpgid(0, 0);
5932
-
5933
6097
  // Setup default signal handlers
5934
6098
  SetSignalHandler(SIGINT, DefaultSignalHandler);
5935
6099
  SetSignalHandler(SIGTERM, DefaultSignalHandler);
5936
6100
  SetSignalHandler(SIGHUP, DefaultSignalHandler);
5937
6101
  SetSignalHandler(SIGPIPE, [](int) {});
5938
6102
 
5939
- // Kill children on exit
5940
- atexit([]() {
5941
- if (interrupt_pfd[1] >= 0) {
5942
- pid_t pid = getpid();
5943
- K_ASSERT(pid > 1);
5944
-
5945
- SetSignalHandler(SIGTERM, [](int) {});
5946
- kill(-pid, SIGTERM);
5947
- }
5948
- });
6103
+ InitInterruptPipe();
5949
6104
 
5950
6105
  // Make sure timezone information is in place, which is useful if some kind of sandbox runs later and
5951
6106
  // the timezone information is not available (seccomp, namespace, landlock, whatever).
@@ -6981,39 +7136,65 @@ int CreateSocket(SocketType type, int flags)
6981
7136
 
6982
7137
  #endif
6983
7138
 
6984
- bool BindIPSocket(int sock, SocketType type, int port)
7139
+ bool BindIPSocket(int sock, SocketType type, const char *addr, int port)
6985
7140
  {
6986
7141
  K_ASSERT(type == SocketType::Dual || type == SocketType::IPv4 || type == SocketType::IPv6);
6987
7142
 
6988
7143
  if (type == SocketType::IPv4) {
6989
- struct sockaddr_in addr = {};
7144
+ struct sockaddr_in sa = {};
7145
+
7146
+ sa.sin_family = AF_INET;
7147
+ sa.sin_port = htons((uint16_t)port);
6990
7148
 
6991
- addr.sin_family = AF_INET;
6992
- addr.sin_port = htons((uint16_t)port);
6993
- addr.sin_addr.s_addr = htonl(INADDR_ANY);
7149
+ if (addr) {
7150
+ if (inet_pton(AF_INET, addr, &sa.sin_addr) <= 0) {
7151
+ LogError("Invalid IPv4 address '%1'", addr);
7152
+ return false;
7153
+ }
7154
+ } else {
7155
+ sa.sin_addr.s_addr = htonl(INADDR_ANY);
7156
+ }
6994
7157
 
6995
- if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
7158
+ if (bind(sock, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
6996
7159
  #if defined(_WIN32)
6997
- LogError("Failed to bind to port %1: %2", port, GetWin32ErrorString());
7160
+ LogError("Failed to bind to '%1:%2': %3", addr ? addr : "*", port, GetWin32ErrorString());
6998
7161
  return false;
6999
7162
  #else
7000
- LogError("Failed to bind to port %1: %2", port, strerror(errno));
7163
+ LogError("Failed to bind to '%1:%2': %3", addr ? addr : "*", port, strerror(errno));
7001
7164
  return false;
7002
7165
  #endif
7003
7166
  }
7004
7167
  } else {
7005
- struct sockaddr_in6 addr = {};
7168
+ struct sockaddr_in6 sa = {};
7006
7169
 
7007
- addr.sin6_family = AF_INET6;
7008
- addr.sin6_port = htons((uint16_t)port);
7009
- addr.sin6_addr = IN6ADDR_ANY_INIT;
7170
+ sa.sin6_family = AF_INET6;
7171
+ sa.sin6_port = htons((uint16_t)port);
7010
7172
 
7011
- if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
7173
+ if (addr) {
7174
+ if (!strchr(addr, ':')) {
7175
+ char buf[512];
7176
+ Fmt(buf, "::FFFF:%1", addr);
7177
+
7178
+ if (inet_pton(AF_INET6, buf, &sa.sin6_addr) <= 0) {
7179
+ LogError("Invalid IPv4 or IPv6 address '%1'", addr);
7180
+ return false;
7181
+ }
7182
+ } else {
7183
+ if (inet_pton(AF_INET6, addr, &sa.sin6_addr) <= 0) {
7184
+ LogError("Invalid IPv6 address '%1'", addr);
7185
+ return false;
7186
+ }
7187
+ }
7188
+ } else {
7189
+ sa.sin6_addr = IN6ADDR_ANY_INIT;
7190
+ }
7191
+
7192
+ if (bind(sock, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
7012
7193
  #if defined(_WIN32)
7013
- LogError("Failed to bind to port %1: %2", port, GetWin32ErrorString());
7194
+ LogError("Failed to bind to '%1:%2': %3", addr ? addr : "*", port, GetWin32ErrorString());
7014
7195
  return false;
7015
7196
  #else
7016
- LogError("Failed to bind to port %1: %2", port, strerror(errno));
7197
+ LogError("Failed to bind to '%1:%2': %3", addr ? addr : "*", port, strerror(errno));
7017
7198
  return false;
7018
7199
  #endif
7019
7200
  }
@@ -7024,16 +7205,32 @@ bool BindIPSocket(int sock, SocketType type, int port)
7024
7205
 
7025
7206
  bool BindUnixSocket(int sock, const char *path)
7026
7207
  {
7027
- struct sockaddr_un addr = {};
7208
+ struct sockaddr_un sa = {};
7209
+
7210
+ // Protect against abtract Unix sockets on Linux
7211
+ if (!path[0]) {
7212
+ LogError("Cannot open empty UNIX socket");
7213
+ return false;
7214
+ }
7028
7215
 
7029
- addr.sun_family = AF_UNIX;
7030
- if (!CopyString(path, addr.sun_path)) {
7216
+ sa.sun_family = AF_UNIX;
7217
+ if (!CopyString(path, sa.sun_path)) {
7031
7218
  LogError("Excessive UNIX socket path length");
7032
7219
  return false;
7033
7220
  }
7034
7221
 
7035
- unlink(path);
7036
- if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
7222
+ #if !defined(_WIN32)
7223
+ // Remove existing socket (if any)
7224
+ {
7225
+ struct stat sb;
7226
+ if (!stat(path, &sb) && S_ISSOCK(sb.st_mode)) {
7227
+ LogDebug("Removing existing socket '%1'", path);
7228
+ unlink(path);
7229
+ }
7230
+ }
7231
+ #endif
7232
+
7233
+ if (bind(sock, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
7037
7234
  #if defined(_WIN32)
7038
7235
  LogError("Failed to bind socket to '%1': %2", path, GetWin32ErrorString());
7039
7236
  return false;
@@ -7042,68 +7239,82 @@ bool BindUnixSocket(int sock, const char *path)
7042
7239
  return false;
7043
7240
  #endif
7044
7241
  }
7242
+
7243
+ #if !defined(_WIN32)
7045
7244
  chmod(path, 0666);
7245
+ #endif
7046
7246
 
7047
7247
  return true;
7048
7248
  }
7049
7249
 
7050
- int OpenIPSocket(SocketType type, int port, int flags)
7250
+ bool ConnectIPSocket(int sock, const char *addr, int port)
7051
7251
  {
7052
- K_ASSERT(type == SocketType::Dual || type == SocketType::IPv4 || type == SocketType::IPv6);
7252
+ if (strchr(addr, ':')) {
7253
+ struct sockaddr_in6 sa = {};
7053
7254
 
7054
- int sock = CreateSocket(type, flags);
7055
- if (sock < 0)
7056
- return -1;
7057
- K_DEFER_N(err_guard) { CloseSocket(sock); };
7255
+ sa.sin6_family = AF_INET6;
7256
+ sa.sin6_port = htons((unsigned short)port);
7058
7257
 
7059
- if (!BindIPSocket(sock, type, port))
7060
- return -1;
7258
+ if (inet_pton(AF_INET6, addr, &sa.sin6_addr) <= 0) {
7259
+ LogError("Invalid IPv6 address '%1'", addr);
7260
+ return false;
7261
+ }
7061
7262
 
7062
- err_guard.Disable();
7063
- return sock;
7064
- }
7263
+ if (connect(sock, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
7264
+ #if defined(_WIN32)
7265
+ LogError("Failed to connect to '%1' (%2): %3", addr, port, GetWin32ErrorString());
7266
+ return false;
7267
+ #else
7268
+ LogError("Failed to connect to '%1' (%2): %3", addr, port, strerror(errno));
7269
+ return false;
7270
+ #endif
7271
+ }
7272
+ } else {
7273
+ struct sockaddr_in sa = {};
7065
7274
 
7066
- int OpenUnixSocket(const char *path, int flags)
7067
- {
7068
- int sock = CreateSocket(SocketType::Unix, flags);
7069
- if (sock < 0)
7070
- return -1;
7071
- K_DEFER_N(err_guard) { CloseSocket(sock); };
7275
+ sa.sin_family = AF_INET;
7276
+ sa.sin_port = htons((unsigned short)port);
7072
7277
 
7073
- if (!BindUnixSocket(sock, path))
7074
- return -1;
7278
+ if (inet_pton(AF_INET, addr, &sa.sin_addr) <= 0) {
7279
+ LogError("Invalid IPv4 address '%1'", addr);
7280
+ return false;
7281
+ }
7075
7282
 
7076
- err_guard.Disable();
7077
- return sock;
7283
+ if (connect(sock, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
7284
+ #if defined(_WIN32)
7285
+ LogError("Failed to connect to '%1' (%2): %3", addr, port, GetWin32ErrorString());
7286
+ return false;
7287
+ #else
7288
+ LogError("Failed to connect to '%1' (%2): %3", addr, port, strerror(errno));
7289
+ return false;
7290
+ #endif
7291
+ }
7292
+ }
7293
+
7294
+ return true;
7078
7295
  }
7079
7296
 
7080
- int ConnectToUnixSocket(const char *path, int flags)
7297
+ bool ConnectUnixSocket(int sock, const char *path)
7081
7298
  {
7082
- struct sockaddr_un addr = {};
7299
+ struct sockaddr_un sa = {};
7083
7300
 
7084
- addr.sun_family = AF_UNIX;
7085
- if (!CopyString(path, addr.sun_path)) {
7301
+ sa.sun_family = AF_UNIX;
7302
+ if (!CopyString(path, sa.sun_path)) {
7086
7303
  LogError("Excessive UNIX socket path length");
7087
- return -1;
7304
+ return false;
7088
7305
  }
7089
7306
 
7090
- int sock = CreateSocket(SocketType::Unix, flags);
7091
- if (sock < 0)
7092
- return -1;
7093
- K_DEFER_N(err_guard) { CloseSocket(sock); };
7094
-
7095
- if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
7307
+ if (connect(sock, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
7096
7308
  #if defined(_WIN32)
7097
- LogError("Failed to connect to '%1': %2", path, GetWin32ErrorString());
7098
- return -1;
7309
+ LogError("Failed to connect to UNIX socket '%1': %2", path, GetWin32ErrorString());
7310
+ return false;
7099
7311
  #else
7100
- LogError("Failed to connect to '%1': %2", path, strerror(errno));
7101
- return -1;
7312
+ LogError("Failed to connect to UNIX socket '%1': %2", path, strerror(errno));
7313
+ return false;
7102
7314
  #endif
7103
7315
  }
7104
7316
 
7105
- err_guard.Disable();
7106
- return sock;
7317
+ return true;
7107
7318
  }
7108
7319
 
7109
7320
  void SetDescriptorNonBlock(int fd, bool enable)
@@ -7613,14 +7824,12 @@ void StreamReader::SetDecoder(StreamDecoder *decoder)
7613
7824
  this->decoder = decoder;
7614
7825
  }
7615
7826
 
7616
- bool StreamReader::Open(Span<const uint8_t> buf, const char *filename, unsigned int flags,
7617
- CompressionType compression_type)
7827
+ bool StreamReader::Open(Span<const uint8_t> buf, const char *filename, CompressionType compression_type)
7618
7828
  {
7619
7829
  Close(true);
7620
7830
 
7621
7831
  K_DEFER_N(err_guard) { error = true; };
7622
7832
 
7623
- lazy = flags & (int)StreamReaderFlag::LazyFill;
7624
7833
  error = false;
7625
7834
  raw_read = 0;
7626
7835
  read_total = 0;
@@ -7640,13 +7849,12 @@ bool StreamReader::Open(Span<const uint8_t> buf, const char *filename, unsigned
7640
7849
  return true;
7641
7850
  }
7642
7851
 
7643
- bool StreamReader::Open(int fd, const char *filename, unsigned int flags, CompressionType compression_type)
7852
+ bool StreamReader::Open(int fd, const char *filename, CompressionType compression_type)
7644
7853
  {
7645
7854
  Close(true);
7646
7855
 
7647
7856
  K_DEFER_N(err_guard) { error = true; };
7648
7857
 
7649
- lazy = flags & (int)StreamReaderFlag::LazyFill;
7650
7858
  error = false;
7651
7859
  raw_read = 0;
7652
7860
  read_total = 0;
@@ -7667,13 +7875,12 @@ bool StreamReader::Open(int fd, const char *filename, unsigned int flags, Compre
7667
7875
  return true;
7668
7876
  }
7669
7877
 
7670
- OpenResult StreamReader::Open(const char *filename, unsigned int flags, CompressionType compression_type)
7878
+ OpenResult StreamReader::Open(const char *filename, CompressionType compression_type)
7671
7879
  {
7672
7880
  Close(true);
7673
7881
 
7674
7882
  K_DEFER_N(err_guard) { error = true; };
7675
7883
 
7676
- lazy = flags & (int)StreamReaderFlag::LazyFill;
7677
7884
  error = false;
7678
7885
  raw_read = 0;
7679
7886
  read_total = 0;
@@ -7697,14 +7904,12 @@ OpenResult StreamReader::Open(const char *filename, unsigned int flags, Compress
7697
7904
  return OpenResult::Success;
7698
7905
  }
7699
7906
 
7700
- bool StreamReader::Open(const std::function<Size(Span<uint8_t>)> &func, const char *filename, unsigned int flags,
7701
- CompressionType compression_type)
7907
+ bool StreamReader::Open(const std::function<Size(Span<uint8_t>)> &func, const char *filename, CompressionType compression_type)
7702
7908
  {
7703
7909
  Close(true);
7704
7910
 
7705
7911
  K_DEFER_N(err_guard) { error = true; };
7706
7912
 
7707
- lazy = flags & (int)StreamReaderFlag::LazyFill;
7708
7913
  error = false;
7709
7914
  raw_read = 0;
7710
7915
  read_total = 0;
@@ -7748,7 +7953,6 @@ bool StreamReader::Close(bool implicit)
7748
7953
  bool ret = !filename || !error;
7749
7954
 
7750
7955
  filename = nullptr;
7751
- lazy = false;
7752
7956
  error = true;
7753
7957
  source.type = SourceType::Memory;
7754
7958
  source.eof = false;
@@ -7807,6 +8011,40 @@ void StreamReader::SetDescriptorOwned(bool owned)
7807
8011
 
7808
8012
  Size StreamReader::Read(Span<uint8_t> out_buf)
7809
8013
  {
8014
+ #if !defined(__wasm__)
8015
+ std::lock_guard<std::mutex> lock(mutex);
8016
+ #endif
8017
+
8018
+ if (error) [[unlikely]]
8019
+ return -1;
8020
+
8021
+ Size len = 0;
8022
+
8023
+ if (decoder) {
8024
+ len = decoder->Read(out_buf.len, out_buf.ptr);
8025
+ if (len < 0) [[unlikely]] {
8026
+ error = true;
8027
+ return -1;
8028
+ }
8029
+ } else {
8030
+ len = ReadRaw(out_buf.len, out_buf.ptr);
8031
+ if (len < 0) [[unlikely]]
8032
+ return -1;
8033
+ eof = source.eof;
8034
+ }
8035
+
8036
+ if (!error && read_max >= 0 && len > read_max - read_total) [[unlikely]] {
8037
+ LogError("Exceeded max stream size of %1", FmtDiskSize(read_max));
8038
+ error = true;
8039
+ return -1;
8040
+ }
8041
+
8042
+ read_total += len;
8043
+ return len;
8044
+ }
8045
+
8046
+ Size StreamReader::ReadFill(Span<uint8_t> out_buf)
8047
+ {
7810
8048
  #if !defined(__wasm__)
7811
8049
  std::lock_guard<std::mutex> lock(mutex);
7812
8050
  #endif
@@ -7842,7 +8080,7 @@ Size StreamReader::Read(Span<uint8_t> out_buf)
7842
8080
  return -1;
7843
8081
  }
7844
8082
 
7845
- if (lazy || eof)
8083
+ if (eof)
7846
8084
  break;
7847
8085
  }
7848
8086
 
@@ -7883,10 +8121,10 @@ Size StreamReader::ReadAll(Size max_len, HeapArray<uint8_t> *out_buf)
7883
8121
  // who need/want to append a NUL character.
7884
8122
  out_buf->Grow((Size)raw_len + 1);
7885
8123
 
7886
- Size read_len = Read((Size)raw_len, out_buf->end());
8124
+ Size read_len = ReadFill(out_buf->TakeAvailable());
7887
8125
  if (read_len < 0)
7888
8126
  return -1;
7889
- out_buf->len += read_len;
8127
+ out_buf->len += (Size)std::min(raw_len, (int64_t)read_len);
7890
8128
 
7891
8129
  buf_guard.Disable();
7892
8130
  return read_len;
@@ -7897,7 +8135,7 @@ Size StreamReader::ReadAll(Size max_len, HeapArray<uint8_t> *out_buf)
7897
8135
  Size grow = std::min(total_len ? Megabytes(1) : Kibibytes(64), K_SIZE_MAX - out_buf->len);
7898
8136
  out_buf->Grow(grow);
7899
8137
 
7900
- Size read_len = Read(out_buf->Available(), out_buf->end());
8138
+ Size read_len = Read(out_buf->TakeAvailable());
7901
8139
  if (read_len < 0)
7902
8140
  return -1;
7903
8141
 
@@ -8032,7 +8270,9 @@ bool LineReader::Next(Span<char> *out_line)
8032
8270
  if (!view.len) {
8033
8271
  buf.Grow(K_LINE_READER_STEP_SIZE + 1);
8034
8272
 
8035
- Size read_len = st->Read(K_LINE_READER_STEP_SIZE, buf.end());
8273
+ Span<char> available = MakeSpan(buf.end(), K_LINE_READER_STEP_SIZE);
8274
+
8275
+ Size read_len = st->Read(available);
8036
8276
  if (read_len < 0) {
8037
8277
  error = true;
8038
8278
  return false;
@@ -8960,7 +9200,7 @@ bool PatchFile(Span<const uint8_t> data, StreamWriter *writer,
8960
9200
  bool PatchFile(const AssetInfo &asset, StreamWriter *writer,
8961
9201
  FunctionRef<void(Span<const char>, StreamWriter *)> func)
8962
9202
  {
8963
- StreamReader reader(asset.data, "<asset>", 0, asset.compression_type);
9203
+ StreamReader reader(asset.data, "<asset>", asset.compression_type);
8964
9204
 
8965
9205
  if (!PatchFile(&reader, writer, func)) {
8966
9206
  K_ASSERT(reader.IsValid());
@@ -9509,6 +9749,10 @@ bool ConsolePrompter::ReadRaw(Span<const char> *out_str)
9509
9749
  Delete(str_offset, SkipForward(str_offset, 1));
9510
9750
  RenderRaw();
9511
9751
  }
9752
+ } else if (match_escape("\x1B")) { // Double escape
9753
+ StdErr->Write("\r\n");
9754
+ StdErr->Flush();
9755
+ return false;
9512
9756
  } else if (match_escape("\x7F")) { // Alt-Backspace
9513
9757
  Delete(FindBackward(str_offset, " \t\r\n"), str_offset);
9514
9758
  RenderRaw();
@@ -9651,11 +9895,54 @@ bool ConsolePrompter::ReadRaw(Span<const char> *out_str)
9651
9895
  return true;
9652
9896
  } break;
9653
9897
 
9898
+ case '\t': {
9899
+ if (complete) {
9900
+ BlockAllocator temp_alloc;
9901
+ HeapArray<CompleteChoice> choices;
9902
+
9903
+ PushLogFilter([](LogLevel, const char *, const char *, FunctionRef<LogFunc>) {});
9904
+ K_DEFER_N(log_guard) { PopLogFilter(); };
9905
+
9906
+ CompleteResult ret = complete(str, &temp_alloc, &choices);
9907
+
9908
+ switch (ret) {
9909
+ case CompleteResult::Success: {
9910
+ if (choices.len == 1) {
9911
+ const CompleteChoice &choice = choices[0];
9912
+
9913
+ str.RemoveFrom(0);
9914
+ str.Append(choice.value);
9915
+ str_offset = str.len;
9916
+ RenderRaw();
9917
+ } else if (choices.len) {
9918
+ for (const CompleteChoice &choice: choices) {
9919
+ Print(StdErr, "\r\n %!0%!Y..%1%!0", choice.name);
9920
+ }
9921
+ StdErr->Write("\r\n");
9922
+
9923
+ RenderRaw();
9924
+ }
9925
+ } break;
9926
+
9927
+ case CompleteResult::TooMany: {
9928
+ Print(StdErr, "\r\n %!0%!Y..%1%!0\r\n", T("Too many possibilities to show"));
9929
+ RenderRaw();
9930
+ } break;
9931
+ case CompleteResult::Error: {
9932
+ Print(StdErr, "\r\n %!0%!Y..%1%!0\r\n", T("Autocompletion error"));
9933
+ RenderRaw();
9934
+ } break;
9935
+ }
9936
+
9937
+ break;
9938
+ }
9939
+ } [[fallthrough]];
9940
+
9654
9941
  default: {
9655
9942
  LocalArray<char, 16> frag;
9656
9943
  if (uc == '\t') {
9657
9944
  frag.Append(" ");
9658
- } else if (uc >= 32) {
9945
+ } else if (!IsAsciiControl(uc)) {
9659
9946
  frag.len = EncodeUtf8(uc, frag.data);
9660
9947
  } else {
9661
9948
  continue;
@@ -9731,6 +10018,14 @@ Size ConsolePrompter::ReadRawEnum(Span<const PromptChoice> choices, Size value)
9731
10018
  fake_input = "\x10";
9732
10019
  } else if (match_escape("[B")) { // Down
9733
10020
  fake_input = "\x0E";
10021
+ } else if (match_escape("\x1B")) { // Double escape
10022
+ if (rows > y) {
10023
+ Print(StdErr, "\x1B[%1B", rows - y);
10024
+ }
10025
+ StdErr->Write("\r");
10026
+ StdErr->Flush();
10027
+
10028
+ return -1;
9734
10029
  }
9735
10030
  } break;
9736
10031
 
@@ -9792,7 +10087,7 @@ bool ConsolePrompter::ReadBuffered(Span<const char> *out_str)
9792
10087
 
9793
10088
  do {
9794
10089
  uint8_t c = 0;
9795
- if (StdIn->Read(1, &c) < 0)
10090
+ if (StdIn->Read(MakeSpan(&c, 1)) < 0)
9796
10091
  return false;
9797
10092
 
9798
10093
  if (c == '\n') {
@@ -9801,7 +10096,7 @@ bool ConsolePrompter::ReadBuffered(Span<const char> *out_str)
9801
10096
  *out_str = str;
9802
10097
  }
9803
10098
  return true;
9804
- } else if (c >= 32 || c == '\t') {
10099
+ } else if (!IsAsciiControl(c)) {
9805
10100
  str.Append((char)c);
9806
10101
  }
9807
10102
  } while (!StdIn->IsEOF());
@@ -9823,7 +10118,7 @@ Size ConsolePrompter::ReadBufferedEnum(Span<const PromptChoice> choices)
9823
10118
 
9824
10119
  do {
9825
10120
  uint8_t c = 0;
9826
- if (StdIn->Read(1, &c) < 0)
10121
+ if (StdIn->Read(MakeSpan(&c, 1)) < 0)
9827
10122
  return -1;
9828
10123
 
9829
10124
  if (c == '\n') {
@@ -9840,7 +10135,7 @@ Size ConsolePrompter::ReadBufferedEnum(Span<const PromptChoice> choices)
9840
10135
 
9841
10136
  StdErr->Write(prefix);
9842
10137
  StdErr->Flush();
9843
- } else if (c >= 32 || c == '\t') {
10138
+ } else if (!IsAsciiControl(c)) {
9844
10139
  str.Append((char)c);
9845
10140
  }
9846
10141
  } while (!StdIn->IsEOF());
@@ -9944,7 +10239,11 @@ void ConsolePrompter::FormatChoices(Span<const PromptChoice> choices, Size value
9944
10239
  const PromptChoice &choice = choices[i];
9945
10240
  int pad = align - ComputeUnicodeWidth(choice.str);
9946
10241
 
9947
- Fmt(&str, " [%1] %2%3 ", choice.c, choice.str, FmtArg(' ').Repeat(pad));
10242
+ if (choice.c) {
10243
+ Fmt(&str, " [%1] %2%3 ", choice.c, choice.str, FmtRepeat(" ", pad));
10244
+ } else {
10245
+ Fmt(&str, " %1%2 ", choice.str, FmtRepeat(" ", pad));
10246
+ }
9948
10247
  if (i == value) {
9949
10248
  str_offset = str.len;
9950
10249
  }
@@ -9984,7 +10283,7 @@ void ConsolePrompter::RenderRaw()
9984
10283
  int width = mask ? mask_columns : ComputeUnicodeWidth(str.Take(i, bytes));
9985
10284
 
9986
10285
  if (x2 + width >= columns || str[i] == '\n') {
9987
- FmtArg prefix = FmtArg(' ').Repeat(prompt_columns - 1);
10286
+ FmtArg prefix = FmtRepeat(" ", prompt_columns - 1);
9988
10287
  Print(StdErr, "\x1B[0K\r\n%!D.+%1%!0 %!..+", prefix);
9989
10288
 
9990
10289
  x2 = prompt_columns;
@@ -10028,7 +10327,7 @@ void ConsolePrompter::RenderBuffered()
10028
10327
  Print(StdErr, "%1 %2", prompt, line);
10029
10328
  while (remain.len) {
10030
10329
  line = SplitStr(remain, '\n', &remain);
10031
- Print(StdErr, "\n%1%2", FmtArg(' ').Repeat(prompt_columns), line);
10330
+ Print(StdErr, "\n%1%2", FmtRepeat(" ", prompt_columns), line);
10032
10331
  }
10033
10332
 
10034
10333
  StdErr->Flush();
@@ -10206,6 +10505,7 @@ const char *Prompt(const char *prompt, const char *default_value, const char *ma
10206
10505
 
10207
10506
  prompter.prompt = prompt;
10208
10507
  prompter.mask = mask;
10508
+
10209
10509
  prompter.str.allocator = alloc;
10210
10510
  if (default_value) {
10211
10511
  prompter.str.Append(default_value);
@@ -10225,11 +10525,12 @@ Size PromptEnum(const char *prompt, Span<const PromptChoice> choices, Size value
10225
10525
  HashSet<char> keys;
10226
10526
 
10227
10527
  for (const PromptChoice &choice: choices) {
10228
- keys.Set(choice.c);
10229
- }
10528
+ if (!choice.c)
10529
+ continue;
10230
10530
 
10231
- bool duplicates = (keys.table.count < choices.len);
10232
- K_ASSERT(!duplicates);
10531
+ bool duplicates = !keys.InsertOrFail(choice.c);
10532
+ K_ASSERT(!duplicates);
10533
+ }
10233
10534
  }
10234
10535
  #endif
10235
10536
 
@@ -10242,13 +10543,12 @@ Size PromptEnum(const char *prompt, Span<const PromptChoice> choices, Size value
10242
10543
  Size PromptEnum(const char *prompt, Span<const char *const> strings, Size value)
10243
10544
  {
10244
10545
  static const char literals[] = "123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
10245
- K_ASSERT(strings.len <= K_LEN(literals));
10246
10546
 
10247
10547
  HeapArray<PromptChoice> choices;
10248
10548
 
10249
10549
  for (Size i = 0; i < strings.len; i++) {
10250
10550
  const char *str = strings[i];
10251
- PromptChoice choice = { literals[i], str };
10551
+ PromptChoice choice = { str, i < K_LEN(literals) ? literals[i] : (char)0 };
10252
10552
 
10253
10553
  choices.Append(choice);
10254
10554
  }
@@ -10256,6 +10556,134 @@ Size PromptEnum(const char *prompt, Span<const char *const> strings, Size value)
10256
10556
  return PromptEnum(prompt, choices, value);
10257
10557
  }
10258
10558
 
10559
+ int PromptYN(const char *prompt)
10560
+ {
10561
+ const char *yes = T("Yes");
10562
+ const char *no = T("No");
10563
+
10564
+ const char *shortcuts = T("yn");
10565
+ K_ASSERT(strlen(shortcuts) == 2);
10566
+
10567
+ Size ret = PromptEnum(prompt, {{ yes, shortcuts[0] }, { no, shortcuts[1] }});
10568
+ if (ret < 0)
10569
+ return -1;
10570
+
10571
+ return !ret;
10572
+ }
10573
+
10574
+ const char *PromptPath(const char *prompt, const char *default_path, Span<const char> root_directory, Allocator *alloc)
10575
+ {
10576
+ K_ASSERT(alloc);
10577
+
10578
+ ConsolePrompter prompter;
10579
+
10580
+ prompter.prompt = prompt;
10581
+ prompter.complete = [&](Span<const char> str, Allocator *alloc, HeapArray<CompleteChoice> *out_choices) {
10582
+ Size start_len = out_choices->len;
10583
+ K_DEFER_N(err_guard) { out_choices->RemoveFrom(start_len); };
10584
+
10585
+ Span<const char> path = TrimStrRight(str, K_PATH_SEPARATORS);
10586
+ bool separator = (path.len < str.len);
10587
+
10588
+ // If the value points to a directory, append separator and return
10589
+ if (str.len && !separator) {
10590
+ const char *filename = NormalizePath(path, root_directory, alloc).ptr;
10591
+
10592
+ FileInfo file_info;
10593
+ StatResult ret = StatFile(filename, (int)StatFlag::SilentMissing | (int)StatFlag::FollowSymlink, &file_info);
10594
+
10595
+ if (ret == StatResult::Success && file_info.type == FileType::Directory) {
10596
+ const char *value = Fmt(alloc, "%1%/", path).ptr;
10597
+ out_choices->Append({ value, value });
10598
+
10599
+ err_guard.Disable();
10600
+ return CompleteResult::Success;
10601
+ }
10602
+ }
10603
+
10604
+ Span<const char> directory = path;
10605
+ Span<const char> prefix = separator ? "" : SplitStrReverseAny(path, K_PATH_SEPARATORS, &directory);
10606
+
10607
+ // EnumerateDirectory takes a C string, so we need the NUL terminator,
10608
+ // and we also need to take root_dir into account.
10609
+ const char *dirname = nullptr;
10610
+
10611
+ if (PathIsAbsolute(directory)) {
10612
+ dirname = DuplicateString(directory, alloc).ptr;
10613
+ } else {
10614
+ if (!root_directory.len)
10615
+ return CompleteResult::Success;
10616
+
10617
+ dirname = NormalizePath(directory, root_directory, alloc).ptr;
10618
+ dirname = dirname[0] ? dirname : ".";
10619
+ }
10620
+
10621
+ EnumResult ret = EnumerateDirectory(dirname, nullptr, -1, [&](const char *basename, FileType file_type) {
10622
+ #if defined(_WIN32)
10623
+ if (!StartsWithI(basename, prefix))
10624
+ return true;
10625
+ #else
10626
+ if (!StartsWith(basename, prefix))
10627
+ return true;
10628
+ #endif
10629
+
10630
+ if (out_choices->len - start_len >= K_COMPLETE_PATH_LIMIT)
10631
+ return false;
10632
+
10633
+ CompleteChoice choice;
10634
+ {
10635
+ HeapArray<char> buf(alloc);
10636
+
10637
+ // Make directory part
10638
+ buf.Append(directory);
10639
+ if (directory.len && !IsPathSeparator(directory[directory.len - 1])) {
10640
+ buf.Append(*K_PATH_SEPARATORS);
10641
+ }
10642
+
10643
+ Size name_offset = buf.len;
10644
+
10645
+ // Append name
10646
+ buf.Append(basename);
10647
+ if (file_type == FileType::Directory) {
10648
+ buf.Append(*K_PATH_SEPARATORS);
10649
+ }
10650
+ buf.Append(0);
10651
+ buf.Trim();
10652
+
10653
+ choice.value = buf.Leak().ptr;
10654
+ choice.name = choice.value + name_offset;
10655
+ }
10656
+
10657
+ out_choices->Append(choice);
10658
+ return true;
10659
+ });
10660
+
10661
+ if (ret == EnumResult::CallbackFail) {
10662
+ return CompleteResult::TooMany;
10663
+ } else if (ret != EnumResult::Success) {
10664
+ // Just ignore it and don't print anything
10665
+ return CompleteResult::Success;
10666
+ }
10667
+
10668
+ std::sort(out_choices->ptr + start_len, out_choices->end(),
10669
+ [](const CompleteChoice &choice1, const CompleteChoice &choice2) { return CmpNaturalI(choice1.name, choice2.name) < 0; });
10670
+
10671
+ err_guard.Disable();
10672
+ return CompleteResult::Success;
10673
+ };
10674
+
10675
+ prompter.str.allocator = alloc;
10676
+ if (default_path) {
10677
+ prompter.str.Append(default_path);
10678
+ }
10679
+
10680
+ if (!prompter.Read())
10681
+ return nullptr;
10682
+
10683
+ const char *str = NormalizePath(prompter.str, alloc).ptr;
10684
+ return str;
10685
+ }
10686
+
10259
10687
  // ------------------------------------------------------------------------
10260
10688
  // Mime types
10261
10689
  // ------------------------------------------------------------------------
@@ -10373,7 +10801,7 @@ static inline int ComputeCharacterWidth(int32_t uc)
10373
10801
  {
10374
10802
  // Fast path
10375
10803
  if (uc < 128)
10376
- return (uc >= 32) ? 1 : 0;
10804
+ return IsAsciiControl(uc) ? 0 : 1;
10377
10805
 
10378
10806
  if (TestUnicodeTable(WcWidthNull, uc))
10379
10807
  return 0;