koffi 2.14.1 → 2.15.1

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 (113) hide show
  1. package/CHANGELOG.md +26 -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 +5 -3
  24. package/doc/pages/misc.md +18 -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/doc/templates/code.html +1 -2
  30. package/doc/templates/page.html +1 -2
  31. package/index.d.ts +29 -24
  32. package/index.js +9 -9
  33. package/indirect.js +9 -9
  34. package/{src/core → lib/native}/base/base.cc +1137 -674
  35. package/{src/core → lib/native}/base/base.hh +362 -195
  36. package/{src/core → lib/native}/base/crc.inc +2 -20
  37. package/lib/native/base/crc_gen.py +72 -0
  38. package/{src/core → lib/native}/base/mimetypes.inc +2 -20
  39. package/{src/core → lib/native}/base/mimetypes_gen.py +2 -21
  40. package/lib/native/base/tower.cc +821 -0
  41. package/lib/native/base/tower.hh +81 -0
  42. package/{src/core → lib/native}/base/unicode.inc +2 -20
  43. package/{src/core → lib/native}/base/unicode_gen.py +4 -41
  44. package/package.json +2 -2
  45. package/src/cnoke/assets/FindCNoke.cmake +24 -30
  46. package/src/cnoke/assets/win_delay_hook.c +6 -20
  47. package/src/cnoke/cnoke.js +2 -21
  48. package/src/cnoke/src/builder.js +51 -66
  49. package/src/cnoke/src/index.js +2 -20
  50. package/src/cnoke/src/tools.js +2 -20
  51. package/src/koffi/CMakeLists.txt +30 -23
  52. package/src/koffi/cmake/raylib.cmake +5 -22
  53. package/src/koffi/cmake/sqlite3.cmake +2 -20
  54. package/src/koffi/src/abi_arm32.cc +7 -25
  55. package/src/koffi/src/abi_arm32_asm.S +2 -20
  56. package/src/koffi/src/abi_arm64.cc +7 -25
  57. package/src/koffi/src/abi_arm64_asm.S +2 -20
  58. package/src/koffi/src/abi_arm64_asm.asm +2 -20
  59. package/src/koffi/src/abi_loong64.cc +2 -20
  60. package/src/koffi/src/abi_loong64_asm.S +2 -20
  61. package/src/koffi/src/abi_riscv64.cc +7 -25
  62. package/src/koffi/src/abi_riscv64_asm.S +2 -20
  63. package/src/koffi/src/abi_x64_sysv.cc +7 -25
  64. package/src/koffi/src/abi_x64_sysv_asm.S +2 -20
  65. package/src/koffi/src/abi_x64_win.cc +12 -30
  66. package/src/koffi/src/abi_x64_win_asm.S +162 -0
  67. package/src/koffi/src/abi_x64_win_asm.asm +2 -20
  68. package/src/koffi/src/abi_x86.cc +7 -25
  69. package/src/koffi/src/abi_x86_asm.S +2 -20
  70. package/src/koffi/src/abi_x86_asm.asm +2 -20
  71. package/src/koffi/src/call.cc +25 -45
  72. package/src/koffi/src/call.hh +3 -21
  73. package/src/koffi/src/errno.inc +2 -20
  74. package/src/koffi/src/ffi.cc +64 -63
  75. package/src/koffi/src/ffi.hh +15 -30
  76. package/src/koffi/src/init.js +2 -20
  77. package/src/koffi/src/parser.cc +13 -27
  78. package/src/koffi/src/parser.hh +3 -21
  79. package/src/koffi/src/trampolines/armasm.inc +0 -21
  80. package/src/koffi/src/trampolines/gnu.inc +0 -21
  81. package/src/koffi/src/trampolines/masm32.inc +0 -21
  82. package/src/koffi/src/trampolines/masm64.inc +0 -21
  83. package/src/koffi/src/trampolines/prototypes.inc +0 -21
  84. package/src/koffi/src/util.cc +50 -64
  85. package/src/koffi/src/util.hh +8 -25
  86. package/src/koffi/src/uv.cc +193 -0
  87. package/src/koffi/src/uv.def +10 -0
  88. package/src/koffi/src/uv.hh +40 -0
  89. package/src/koffi/src/win32.cc +2 -20
  90. package/src/koffi/src/win32.hh +3 -21
  91. package/vendor/node-api-headers/CHANGELOG.md +22 -0
  92. package/vendor/node-api-headers/README.md +6 -17
  93. package/vendor/node-api-headers/include/js_native_api.h +3 -13
  94. package/vendor/node-api-headers/include/js_native_api_types.h +15 -0
  95. package/vendor/node-api-headers/include/node_api.h +0 -4
  96. package/vendor/node-api-headers/include/node_api_types.h +6 -0
  97. package/vendor/node-api-headers/include/uv/aix.h +32 -0
  98. package/vendor/node-api-headers/include/uv/bsd.h +34 -0
  99. package/vendor/node-api-headers/include/uv/darwin.h +61 -0
  100. package/vendor/node-api-headers/include/uv/errno.h +483 -0
  101. package/vendor/node-api-headers/include/uv/linux.h +34 -0
  102. package/vendor/node-api-headers/include/uv/os390.h +33 -0
  103. package/vendor/node-api-headers/include/uv/posix.h +31 -0
  104. package/vendor/node-api-headers/include/uv/sunos.h +44 -0
  105. package/vendor/node-api-headers/include/uv/threadpool.h +37 -0
  106. package/vendor/node-api-headers/include/uv/tree.h +521 -0
  107. package/vendor/node-api-headers/include/uv/unix.h +512 -0
  108. package/vendor/node-api-headers/include/uv/version.h +43 -0
  109. package/vendor/node-api-headers/include/uv/win.h +698 -0
  110. package/vendor/node-api-headers/include/uv.h +1990 -0
  111. package/vendor/node-api-headers/package.json +1 -1
  112. package/vendor/node-api-headers/scripts/update-headers.js +6 -0
  113. 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>
@@ -767,25 +750,6 @@ int64_t GetUnixTime()
767
750
  #endif
768
751
  }
769
752
 
770
- int64_t GetMonotonicTime()
771
- {
772
- #if defined(_WIN32)
773
- return (int64_t)GetTickCount64();
774
- #elif defined(__EMSCRIPTEN__)
775
- return (int64_t)emscripten_get_now();
776
- #elif defined(CLOCK_MONOTONIC_COARSE)
777
- struct timespec ts;
778
- K_CRITICAL(clock_gettime(CLOCK_MONOTONIC_COARSE, &ts) == 0, "clock_gettime(CLOCK_MONOTONIC_COARSE) failed: %1", strerror(errno));
779
-
780
- return (int64_t)ts.tv_sec * 1000 + (int64_t)ts.tv_nsec / 1000000;
781
- #else
782
- struct timespec ts;
783
- K_CRITICAL(clock_gettime(CLOCK_MONOTONIC, &ts) == 0, "clock_gettime(CLOCK_MONOTONIC) failed: %1", strerror(errno));
784
-
785
- return (int64_t)ts.tv_sec * 1000 + (int64_t)ts.tv_nsec / 1000000;
786
- #endif
787
- }
788
-
789
753
  TimeSpec DecomposeTimeUTC(int64_t time)
790
754
  {
791
755
  TimeSpec spec = {};
@@ -879,6 +843,39 @@ int64_t ComposeTimeUTC(const TimeSpec &spec)
879
843
  return time;
880
844
  }
881
845
 
846
+ // ------------------------------------------------------------------------
847
+ // Clock
848
+ // ------------------------------------------------------------------------
849
+
850
+ int64_t GetMonotonicClock()
851
+ {
852
+ static std::atomic_int64_t memory;
853
+
854
+ #if defined(_WIN32)
855
+ int64_t clock = (int64_t)GetTickCount64();
856
+ #elif defined(__EMSCRIPTEN__)
857
+ int64_t clock = emscripten_get_now();
858
+ #elif defined(CLOCK_MONOTONIC_COARSE)
859
+ struct timespec ts;
860
+ K_CRITICAL(clock_gettime(CLOCK_MONOTONIC_COARSE, &ts) == 0, "clock_gettime(CLOCK_MONOTONIC_COARSE) failed: %1", strerror(errno));
861
+
862
+ int64_t clock = (int64_t)ts.tv_sec * 1000 + (int64_t)ts.tv_nsec / 1000000;
863
+ #else
864
+ struct timespec ts;
865
+ K_CRITICAL(clock_gettime(CLOCK_MONOTONIC, &ts) == 0, "clock_gettime(CLOCK_MONOTONIC) failed: %1", strerror(errno));
866
+
867
+ int64_t clock = (int64_t)ts.tv_sec * 1000 + (int64_t)ts.tv_nsec / 1000000;
868
+ #endif
869
+
870
+ // Protect against clock going backwards
871
+ int64_t prev = memory.load(std::memory_order_relaxed);
872
+ if (clock < prev) [[unlikely]]
873
+ return prev;
874
+ memory.compare_exchange_weak(prev, clock, std::memory_order_relaxed, std::memory_order_relaxed);
875
+
876
+ return clock;
877
+ }
878
+
882
879
  // ------------------------------------------------------------------------
883
880
  // Strings
884
881
  // ------------------------------------------------------------------------
@@ -932,6 +929,75 @@ Span<char> DuplicateString(Span<const char> str, Allocator *alloc)
932
929
  return MakeSpan(new_str, str.len);
933
930
  }
934
931
 
932
+ template <typename CompareFunc>
933
+ static inline int NaturalCmp(Span<const char> str1, Span<const char> str2, CompareFunc cmp)
934
+ {
935
+ Size i = 0;
936
+ Size j = 0;
937
+
938
+ while (i < str1.len && j < str2.len) {
939
+ int delta = cmp(str1[i], str2[j]);
940
+
941
+ if (delta) {
942
+ if (IsAsciiDigit(str1[i]) && IsAsciiDigit(str2[i])) {
943
+ while (i < str1.len && str1[i] == '0') {
944
+ i++;
945
+ }
946
+ while (j < str2.len && str2[j] == '0') {
947
+ j++;
948
+ }
949
+
950
+ bool digit1 = false;
951
+ bool digit2 = false;
952
+ int bias = 0;
953
+
954
+ for (;;) {
955
+ digit1 = (i < str1.len) && IsAsciiDigit(str1[i]);
956
+ digit2 = (j < str2.len) && IsAsciiDigit(str2[j]);
957
+
958
+ if (!digit1 || !digit2)
959
+ break;
960
+
961
+ bias = bias ? bias : cmp(str1[i], str2[j]);
962
+ i++;
963
+ j++;
964
+ }
965
+
966
+ if (!digit1 && !digit2 && bias) {
967
+ return bias;
968
+ } else if (digit1 || digit2) {
969
+ return digit1 ? 1 : -1;
970
+ }
971
+ } else {
972
+ return delta;
973
+ }
974
+ } else {
975
+ i++;
976
+ j++;
977
+ }
978
+ }
979
+
980
+ if (i == str1.len && j < str2.len) {
981
+ return -1;
982
+ } else if (i < str1.len) {
983
+ return 1;
984
+ } else {
985
+ return 0;
986
+ }
987
+ }
988
+
989
+ int CmpNatural(Span<const char> str1, Span<const char> str2)
990
+ {
991
+ auto cmp = [](int a, int b) { return a - b; };
992
+ return NaturalCmp(str1, str2, cmp);
993
+ }
994
+
995
+ int CmpNaturalI(Span<const char> str1, Span<const char> str2)
996
+ {
997
+ auto cmp = [](int a, int b) { return LowerAscii(a) - LowerAscii(b); };
998
+ return NaturalCmp(str1, str2, cmp);
999
+ }
1000
+
935
1001
  // ------------------------------------------------------------------------
936
1002
  // Format
937
1003
  // ------------------------------------------------------------------------
@@ -940,6 +1006,8 @@ static const char DigitPairs[201] = "0001020304050607080910111213141516171819202
940
1006
  "25262728293031323334353637383940414243444546474849"
941
1007
  "50515253545556575859606162636465666768697071727374"
942
1008
  "75767778798081828384858687888990919293949596979899";
1009
+ static const char BigHexLiterals[] = "0123456789ABCDEF";
1010
+ static const char SmallHexLiterals[] = "0123456789abcdef";
943
1011
 
944
1012
  static Span<char> FormatUnsignedToDecimal(uint64_t value, char out_buf[32])
945
1013
  {
@@ -975,13 +1043,11 @@ static Span<char> FormatUnsignedToBinary(uint64_t value, char out_buf[64])
975
1043
 
976
1044
  static Span<char> FormatUnsignedToOctal(uint64_t value, char out_buf[64])
977
1045
  {
978
- static const char literals[] = "012345678";
979
-
980
1046
  Size offset = 64;
981
1047
  do {
982
1048
  uint64_t digit = value & 0x7;
983
1049
  value >>= 3;
984
- out_buf[--offset] = literals[digit];
1050
+ out_buf[--offset] = BigHexLiterals[digit];
985
1051
  } while (value);
986
1052
 
987
1053
  return MakeSpan(out_buf + offset, 64 - offset);
@@ -989,13 +1055,11 @@ static Span<char> FormatUnsignedToOctal(uint64_t value, char out_buf[64])
989
1055
 
990
1056
  static Span<char> FormatUnsignedToBigHex(uint64_t value, char out_buf[32])
991
1057
  {
992
- static const char literals[] = "0123456789ABCDEF";
993
-
994
1058
  Size offset = 32;
995
1059
  do {
996
1060
  uint64_t digit = value & 0xF;
997
1061
  value >>= 4;
998
- out_buf[--offset] = literals[digit];
1062
+ out_buf[--offset] = BigHexLiterals[digit];
999
1063
  } while (value);
1000
1064
 
1001
1065
  return MakeSpan(out_buf + offset, 32 - offset);
@@ -1003,13 +1067,11 @@ static Span<char> FormatUnsignedToBigHex(uint64_t value, char out_buf[32])
1003
1067
 
1004
1068
  static Span<char> FormatUnsignedToSmallHex(uint64_t value, char out_buf[32])
1005
1069
  {
1006
- static const char literals[] = "0123456789abcdef";
1007
-
1008
1070
  Size offset = 32;
1009
1071
  do {
1010
1072
  uint64_t digit = value & 0xF;
1011
1073
  value >>= 4;
1012
- out_buf[--offset] = literals[digit];
1074
+ out_buf[--offset] = SmallHexLiterals[digit];
1013
1075
  } while (value);
1014
1076
 
1015
1077
  return MakeSpan(out_buf + offset, 32 - offset);
@@ -1185,406 +1247,429 @@ Span<const char> FormatFloatingPoint(T value, bool non_zero, int min_prec, int m
1185
1247
  #endif
1186
1248
  }
1187
1249
 
1250
+ template <typename AppendFunc>
1251
+ static inline void AppendPad(Size pad, char padding, AppendFunc append)
1252
+ {
1253
+ for (Size i = 0; i < pad; i++) {
1254
+ append(padding);
1255
+ }
1256
+ }
1257
+
1258
+ template <typename AppendFunc>
1259
+ static inline void AppendSafe(char c, AppendFunc append)
1260
+ {
1261
+ if (IsAsciiControl(c))
1262
+ return;
1263
+
1264
+ append(c);
1265
+ }
1266
+
1188
1267
  template <typename AppendFunc>
1189
1268
  static inline void ProcessArg(const FmtArg &arg, AppendFunc append)
1190
1269
  {
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 = {};
1270
+ switch (arg.type) {
1271
+ case FmtType::Str: { append(arg.u.str); } break;
1195
1272
 
1196
- Size pad_len = arg.pad_len;
1273
+ case FmtType::PadStr: {
1274
+ append(arg.u.str);
1275
+ AppendPad(arg.pad - arg.u.str.len, arg.padding, append);
1276
+ } break;
1277
+ case FmtType::RepeatStr: {
1278
+ Span<const char> str = arg.u.repeat.str;
1197
1279
 
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;
1280
+ for (int i = 0; i < arg.u.repeat.count; i++) {
1281
+ append(str);
1282
+ }
1283
+ } break;
1203
1284
 
1204
- case FmtType::Custom: { arg.u.custom.Format(append); } break;
1285
+ case FmtType::Char: { append(MakeSpan(&arg.u.ch, 1)); } break;
1286
+ case FmtType::Buffer: {
1287
+ Span<const char> str = arg.u.buf;
1288
+ append(str);
1289
+ } break;
1290
+ case FmtType::Custom: { arg.u.custom.Format(append); } break;
1205
1291
 
1206
- case FmtType::Bool: {
1207
- if (arg.u.b) {
1208
- out = "true";
1209
- } else {
1210
- out = "false";
1211
- }
1212
- } break;
1292
+ case FmtType::Bool: { append(arg.u.b ? "true" : "false"); } break;
1293
+
1294
+ case FmtType::Integer: {
1295
+ if (arg.u.i < 0) {
1296
+ char buf[128];
1297
+ Span<const char> str = FormatUnsignedToDecimal((uint64_t)-arg.u.i, buf);
1213
1298
 
1214
- case FmtType::Integer: {
1215
- if (arg.u.i < 0) {
1216
- if (arg.pad_len < 0 && arg.pad_char == '0') {
1299
+ if (arg.pad) {
1300
+ if (arg.padding == '0') {
1217
1301
  append('-');
1302
+ AppendPad((Size)arg.pad - str.len - 1, arg.padding, append);
1218
1303
  } else {
1219
- out_buf.Append('-');
1304
+ AppendPad((Size)arg.pad - str.len - 1, arg.padding, append);
1305
+ append('-');
1220
1306
  }
1221
-
1222
- out_buf.Append(FormatUnsignedToDecimal((uint64_t)-arg.u.i, num_buf));
1223
- out = out_buf;
1224
1307
  } else {
1225
- out = FormatUnsignedToDecimal((uint64_t)arg.u.i, num_buf);
1308
+ append('-');
1226
1309
  }
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
1310
 
1236
- union { float f; uint32_t u32; } u;
1237
- u.f = arg.u.f.value;
1311
+ append(str);
1312
+ } else {
1313
+ char buf[128];
1314
+ Span<const char> str = FormatUnsignedToDecimal((uint64_t)arg.u.i, buf);
1238
1315
 
1239
- if ((u.u32 & ExponentMask) == ExponentMask) {
1240
- uint32_t mantissa = u.u32 & MantissaMask;
1316
+ AppendPad((Size)arg.pad - str.len, arg.padding, append);
1317
+ append(str);
1318
+ }
1319
+ } break;
1320
+ case FmtType::Unsigned: {
1321
+ char buf[128];
1322
+ Span<const char> str = FormatUnsignedToDecimal(arg.u.u, buf);
1241
1323
 
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
- }
1324
+ AppendPad((Size)arg.pad - str.len, arg.padding, append);
1325
+ append(str);
1326
+ } break;
1254
1327
 
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;
1328
+ case FmtType::Float: {
1329
+ static const uint32_t ExponentMask = 0x7f800000u;
1330
+ static const uint32_t MantissaMask = 0x007fffffu;
1331
+ static const uint32_t SignMask = 0x80000000u;
1266
1332
 
1267
- union { double d; uint64_t u64; } u;
1268
- u.d = arg.u.d.value;
1333
+ union { float f; uint32_t u32; } u;
1334
+ u.f = arg.u.f.value;
1269
1335
 
1270
- if ((u.u64 & ExponentMask) == ExponentMask) {
1271
- uint64_t mantissa = u.u64 & MantissaMask;
1336
+ if ((u.u32 & ExponentMask) == ExponentMask) {
1337
+ uint32_t mantissa = u.u32 & MantissaMask;
1272
1338
 
1273
- if (mantissa) {
1274
- out = "NaN";
1275
- } else {
1276
- out = (u.u64 & SignMask) ? "-Inf" : "Inf";
1277
- }
1339
+ if (mantissa) {
1340
+ append("NaN");
1278
1341
  } 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
- }
1342
+ append((u.u32 & SignMask) ? "-Inf" : "Inf");
1291
1343
  }
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;
1344
+ } else {
1345
+ char buf[128];
1305
1346
 
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
- }
1347
+ if (u.u32 & SignMask) {
1348
+ append('-');
1349
+ append(FormatFloatingPoint(-u.f, true, arg.u.f.min_prec, arg.u.f.max_prec, buf));
1315
1350
  } else {
1316
- size = (double)arg.u.i;
1351
+ append(FormatFloatingPoint(u.f, u.u32, arg.u.f.min_prec, arg.u.f.max_prec, buf));
1317
1352
  }
1353
+ }
1354
+ } break;
1355
+ case FmtType::Double: {
1356
+ static const uint64_t ExponentMask = 0x7FF0000000000000ull;
1357
+ static const uint64_t MantissaMask = 0x000FFFFFFFFFFFFFull;
1358
+ static const uint64_t SignMask = 0x8000000000000000ull;
1318
1359
 
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;
1360
+ union { double d; uint64_t u64; } u;
1361
+ u.d = arg.u.d.value;
1327
1362
 
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;
1363
+ if ((u.u64 & ExponentMask) == ExponentMask) {
1364
+ uint64_t mantissa = u.u64 & MantissaMask;
1333
1365
 
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");
1366
+ if (mantissa) {
1367
+ append("NaN");
1337
1368
  } else {
1338
- out_buf.Append(FormatFloatingPoint(size, arg.u.i, 0, 0, num_buf));
1339
- out_buf.Append(" B");
1369
+ append((u.u64 & SignMask) ? "-Inf" : "Inf");
1340
1370
  }
1371
+ } else {
1372
+ char buf[128];
1341
1373
 
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
- }
1374
+ if (u.u64 & SignMask) {
1375
+ append('-');
1376
+ append(FormatFloatingPoint(-u.d, true, arg.u.d.min_prec, arg.u.d.max_prec, buf));
1353
1377
  } else {
1354
- size = (double)arg.u.i;
1378
+ append(FormatFloatingPoint(u.d, u.u64, arg.u.d.min_prec, arg.u.d.max_prec, buf));
1355
1379
  }
1380
+ }
1381
+ } break;
1356
1382
 
1357
- if (size >= 999950000.0) {
1358
- size /= 1000000000.0;
1383
+ case FmtType::Binary: {
1384
+ char buf[128];
1385
+ Span<const char> str = FormatUnsignedToBinary(arg.u.u, buf);
1359
1386
 
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;
1387
+ AppendPad((Size)arg.pad - str.len, arg.padding, append);
1388
+ append(str);
1389
+ } break;
1390
+ case FmtType::Octal: {
1391
+ char buf[128];
1392
+ Span<const char> str = FormatUnsignedToOctal(arg.u.u, buf);
1365
1393
 
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;
1394
+ AppendPad((Size)arg.pad - str.len, arg.padding, append);
1395
+ append(str);
1396
+ } break;
1397
+ case FmtType::BigHex: {
1398
+ char buf[128];
1399
+ Span<const char> str = FormatUnsignedToBigHex(arg.u.u, buf);
1371
1400
 
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
- }
1401
+ AppendPad((Size)arg.pad - str.len, arg.padding, append);
1402
+ append(str);
1403
+ } break;
1404
+ case FmtType::SmallHex: {
1405
+ char buf[128];
1406
+ Span<const char> str = FormatUnsignedToSmallHex(arg.u.u, buf);
1379
1407
 
1380
- out = out_buf;
1381
- } break;
1408
+ AppendPad((Size)arg.pad - str.len, arg.padding, append);
1409
+ append(str);
1410
+ } break;
1382
1411
 
1383
- case FmtType::Date: {
1384
- K_ASSERT(!arg.u.date.value || arg.u.date.IsValid());
1412
+ case FmtType::BigBytes: {
1413
+ for (uint8_t c: arg.u.hex) {
1414
+ char encoded[2];
1385
1415
 
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;
1416
+ encoded[0] = BigHexLiterals[((uint8_t)c >> 4) & 0xF];
1417
+ encoded[1] = BigHexLiterals[((uint8_t)c >> 0) & 0xF];
1411
1418
 
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;
1419
+ Span<const char> buf = MakeSpan(encoded, 2);
1420
+ append(buf);
1421
+ }
1422
+ } break;
1423
+ case FmtType::SmallBytes: {
1424
+ for (uint8_t c: arg.u.hex) {
1425
+ char encoded[2];
1426
+
1427
+ encoded[0] = SmallHexLiterals[((uint8_t)c >> 4) & 0xF];
1428
+ encoded[1] = SmallHexLiterals[((uint8_t)c >> 0) & 0xF];
1429
+
1430
+ Span<const char> buf = MakeSpan(encoded, 2);
1431
+ append(buf);
1432
+ }
1433
+ } break;
1434
+
1435
+ case FmtType::MemorySize: {
1436
+ char buf[128];
1437
+
1438
+ double size;
1439
+ if (arg.u.i < 0) {
1440
+ append('-');
1441
+ size = (double)-arg.u.i;
1442
+ } else {
1443
+ size = (double)arg.u.i;
1444
+ }
1445
+
1446
+ if (size >= 1073688137.0) {
1447
+ size /= 1073741824.0;
1448
+
1449
+ int prec = 1 + (size < 9.9995) + (size < 99.995);
1450
+ append(FormatFloatingPoint(size, true, prec, prec, buf));
1451
+ append(" GiB");
1452
+ } else if (size >= 1048524.0) {
1453
+ size /= 1048576.0;
1454
+
1455
+ int prec = 1 + (size < 9.9995) + (size < 99.995);
1456
+ append(FormatFloatingPoint(size, true, prec, prec, buf));
1457
+ append(" MiB");
1458
+ } else if (size >= 1023.95) {
1459
+ size /= 1024.0;
1460
+
1461
+ int prec = 1 + (size < 9.9995) + (size < 99.995);
1462
+ append(FormatFloatingPoint(size, true, prec, prec, buf));
1463
+ append(" kiB");
1464
+ } else {
1465
+ append(FormatFloatingPoint(size, arg.u.i, 0, 0, buf));
1466
+ append(" B");
1467
+ }
1468
+ } break;
1469
+ case FmtType::DiskSize: {
1470
+ char buf[128];
1471
+
1472
+ double size;
1473
+ if (arg.u.i < 0) {
1474
+ append('-');
1475
+ size = (double)-arg.u.i;
1476
+ } else {
1477
+ size = (double)arg.u.i;
1478
+ }
1479
+
1480
+ if (size >= 999950000.0) {
1481
+ size /= 1000000000.0;
1482
+
1483
+ int prec = 1 + (size < 9.9995) + (size < 99.995);
1484
+ append(FormatFloatingPoint(size, true, prec, prec, buf));
1485
+ append(" GB");
1486
+ } else if (size >= 999950.0) {
1487
+ size /= 1000000.0;
1488
+
1489
+ int prec = 1 + (size < 9.9995) + (size < 99.995);
1490
+ append(FormatFloatingPoint(size, true, prec, prec, buf));
1491
+ append(" MB");
1492
+ } else if (size >= 999.95) {
1493
+ size /= 1000.0;
1494
+
1495
+ int prec = 1 + (size < 9.9995) + (size < 99.995);
1496
+ append(FormatFloatingPoint(size, true, prec, prec, buf));
1497
+ append(" kB");
1498
+ } else {
1499
+ append(FormatFloatingPoint(size, arg.u.i, 0, 0, buf));
1500
+ append(" B");
1501
+ }
1502
+ } break;
1503
+
1504
+ case FmtType::Date: {
1505
+ K_ASSERT(!arg.u.date.value || arg.u.date.IsValid());
1448
1506
 
1507
+ char buf[128];
1508
+
1509
+ int year = arg.u.date.st.year;
1510
+ if (year < 0) {
1511
+ append('-');
1512
+ year = -year;
1513
+ }
1514
+ if (year < 10) {
1515
+ append("000");
1516
+ } else if (year < 100) {
1517
+ append("00");
1518
+ } else if (year < 1000) {
1519
+ append('0');
1520
+ }
1521
+ append(FormatUnsignedToDecimal((uint64_t)year, buf));
1522
+ append('-');
1523
+ if (arg.u.date.st.month < 10) {
1524
+ append('0');
1525
+ }
1526
+ append(FormatUnsignedToDecimal((uint64_t)arg.u.date.st.month, buf));
1527
+ append('-');
1528
+ if (arg.u.date.st.day < 10) {
1529
+ append('0');
1530
+ }
1531
+ append(FormatUnsignedToDecimal((uint64_t)arg.u.date.st.day, buf));
1532
+ } break;
1533
+
1534
+ case FmtType::TimeISO: {
1535
+ const TimeSpec &spec = arg.u.time.spec;
1536
+
1537
+ LocalArray<char, 128> buf;
1538
+
1539
+ if (spec.offset && arg.u.time.ms) {
1449
1540
  int offset_h = spec.offset / 60;
1450
1541
  int offset_m = spec.offset % 60;
1451
1542
 
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;
1543
+ buf.len = Fmt(buf.data, "%1%2%3T%4%5%6.%7%8%9%10",
1544
+ FmtInt(spec.year, 2), FmtInt(spec.month, 2),
1545
+ FmtInt(spec.day, 2), FmtInt(spec.hour, 2),
1546
+ FmtInt(spec.min, 2), FmtInt(spec.sec, 2), FmtInt(spec.msec, 3),
1547
+ offset_h >= 0 ? "+" : "", FmtInt(offset_h, 2), FmtInt(offset_m, 2)).len;
1548
+ } else if (spec.offset) {
1549
+ int offset_h = spec.offset / 60;
1550
+ int offset_m = spec.offset % 60;
1467
1551
 
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;
1552
+ buf.len = Fmt(buf.data, "%1%2%3T%4%5%6%7%8%9",
1553
+ FmtInt(spec.year, 2), FmtInt(spec.month, 2),
1554
+ FmtInt(spec.day, 2), FmtInt(spec.hour, 2),
1555
+ FmtInt(spec.min, 2), FmtInt(spec.sec, 2),
1556
+ offset_h >= 0 ? "+" : "", FmtInt(offset_h, 2), FmtInt(offset_m, 2)).len;
1557
+ } else if (arg.u.time.ms) {
1558
+ buf.len = Fmt(buf.data, "%1%2%3T%4%5%6.%7Z",
1559
+ FmtInt(spec.year, 2), FmtInt(spec.month, 2),
1560
+ FmtInt(spec.day, 2), FmtInt(spec.hour, 2),
1561
+ FmtInt(spec.min, 2), FmtInt(spec.sec, 2), FmtInt(spec.msec, 3)).len;
1562
+ } else {
1563
+ buf.len = Fmt(buf.data, "%1%2%3T%4%5%6Z",
1564
+ FmtInt(spec.year, 2), FmtInt(spec.month, 2),
1565
+ FmtInt(spec.day, 2), FmtInt(spec.hour, 2),
1566
+ FmtInt(spec.min, 2), FmtInt(spec.sec, 2)).len;
1567
+ }
1471
1568
 
1472
- K_ASSERT(arg.u.random.len <= K_SIZE(out_buf.data));
1569
+ append(buf);
1570
+ } break;
1571
+ case FmtType::TimeNice: {
1572
+ const TimeSpec &spec = arg.u.time.spec;
1473
1573
 
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
- }
1574
+ LocalArray<char, 128> buf;
1478
1575
 
1479
- out = out_buf;
1480
- } break;
1576
+ if (arg.u.time.ms) {
1577
+ int offset_h = spec.offset / 60;
1578
+ int offset_m = spec.offset % 60;
1481
1579
 
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";
1580
+ buf.len = Fmt(buf.data, "%1-%2-%3 %4:%5:%6.%7 %8%9%10",
1581
+ FmtInt(spec.year, 2), FmtInt(spec.month, 2),
1582
+ FmtInt(spec.day, 2), FmtInt(spec.hour, 2),
1583
+ FmtInt(spec.min, 2), FmtInt(spec.sec, 2), FmtInt(spec.msec, 3),
1584
+ offset_h >= 0 ? "+" : "", FmtInt(offset_h, 2), FmtInt(offset_m, 2)).len;
1585
+ } else {
1586
+ int offset_h = spec.offset / 60;
1587
+ int offset_m = spec.offset % 60;
1588
+
1589
+ buf.len = Fmt(buf.data, "%1-%2-%3 %4:%5:%6 %7%8%9",
1590
+ FmtInt(spec.year, 2), FmtInt(spec.month, 2),
1591
+ FmtInt(spec.day, 2), FmtInt(spec.hour, 2),
1592
+ FmtInt(spec.min, 2), FmtInt(spec.sec, 2),
1593
+ offset_h >= 0 ? "+" : "", FmtInt(offset_h, 2), FmtInt(offset_m, 2)).len;
1594
+ }
1595
+
1596
+ append(buf);
1597
+ } break;
1598
+
1599
+ case FmtType::List: {
1600
+ Span<const char> separator = arg.u.list.separator;
1601
+
1602
+ if (arg.u.list.u.names.len) {
1603
+ append(arg.u.list.u.names[0]);
1604
+
1605
+ for (Size i = 1; i < arg.u.list.u.names.len; i++) {
1606
+ append(separator);
1607
+ append(arg.u.list.u.names[i]);
1494
1608
  }
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";
1609
+ } else {
1610
+ append(T("None"));
1611
+ }
1612
+ } break;
1613
+ case FmtType::FlagNames: {
1614
+ uint64_t flags = arg.u.list.flags;
1615
+ Span<const char> separator = arg.u.list.separator;
1616
+
1617
+ if (flags) {
1618
+ for (;;) {
1619
+ int idx = CountTrailingZeros(flags);
1620
+ flags &= ~(1ull << idx);
1621
+
1622
+ append(arg.u.list.u.names[idx]);
1623
+ if (!flags)
1624
+ break;
1625
+ append(separator);
1508
1626
  }
1509
- } break;
1627
+ } else {
1628
+ append(T("None"));
1629
+ }
1630
+ } break;
1631
+ case FmtType::FlagOptions: {
1632
+ uint64_t flags = arg.u.list.flags;
1633
+ Span<const char> separator = arg.u.list.separator;
1510
1634
 
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;
1635
+ if (arg.u.list.flags) {
1636
+ for (;;) {
1637
+ int idx = CountTrailingZeros(flags);
1638
+ flags &= ~(1ull << idx);
1562
1639
 
1563
- if (j) {
1564
- append(arg.u.span.separator);
1565
- }
1566
- ProcessArg(arg2, append);
1640
+ append(arg.u.list.u.options[idx].name);
1641
+ if (!flags)
1642
+ break;
1643
+ append(separator);
1567
1644
  }
1645
+ } else {
1646
+ append(T("None"));
1647
+ }
1648
+ } break;
1568
1649
 
1569
- continue;
1570
- } break;
1571
- }
1650
+ case FmtType::Random: {
1651
+ LocalArray<char, 512> buf;
1652
+
1653
+ static const char *const DefaultChars = "abcdefghijklmnopqrstuvwxyz0123456789";
1654
+ Span<const char> chars = arg.u.random.chars ? arg.u.random.chars : DefaultChars;
1572
1655
 
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);
1656
+ K_ASSERT(arg.u.random.len <= K_SIZE(buf.data));
1657
+ buf.len = arg.u.random.len;
1658
+
1659
+ for (Size j = 0; j < arg.u.random.len; j++) {
1660
+ int rnd = GetRandomInt(0, (int)chars.len);
1661
+ buf[j] = chars[rnd];
1577
1662
  }
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);
1663
+
1664
+ append(buf);
1665
+ } break;
1666
+
1667
+ case FmtType::SafeStr: {
1668
+ for (char c: arg.u.str) {
1669
+ AppendSafe(c, append);
1584
1670
  }
1585
- } else {
1586
- append(out);
1587
- }
1671
+ } break;
1672
+ case FmtType::SafeChar: { AppendSafe(arg.u.ch, append); } break;
1588
1673
  }
1589
1674
  }
1590
1675
 
@@ -1864,17 +1949,48 @@ void FmtLowerAscii::Format(FunctionRef<void(Span<const char>)> append) const
1864
1949
  }
1865
1950
  }
1866
1951
 
1952
+ void FmtUrlSafe::Format(FunctionRef<void(Span<const char>)> append) const
1953
+ {
1954
+ for (char c: str) {
1955
+ if (IsAsciiAlphaOrDigit(c) || strchr(passthrough, c)) {
1956
+ append((char)c);
1957
+ } else {
1958
+ char encoded[3];
1959
+
1960
+ encoded[0] = '%';
1961
+ encoded[1] = BigHexLiterals[((uint8_t)c >> 4) & 0xF];
1962
+ encoded[2] = BigHexLiterals[((uint8_t)c >> 0) & 0xF];
1963
+
1964
+ Span<const char> buf = MakeSpan(encoded, 3);
1965
+ append(buf);
1966
+ }
1967
+ }
1968
+ }
1969
+
1970
+ void FmtHtmlSafe::Format(FunctionRef<void(Span<const char>)> append) const
1971
+ {
1972
+ for (char c: str) {
1973
+ switch (c) {
1974
+ case '<': { append("&lt;"); } break;
1975
+ case '>': { append("&gt;"); } break;
1976
+ case '"': { append("&quot;"); } break;
1977
+ case '\'': { append("&apos;"); } break;
1978
+ case '&': { append("&amp;"); } break;
1979
+
1980
+ default: { append(c); } break;
1981
+ }
1982
+ }
1983
+ }
1984
+
1867
1985
  void FmtEscape::Format(FunctionRef<void(Span<const char>)> append) const
1868
1986
  {
1869
1987
  for (char c: str) {
1870
- if (c == '"') {
1871
- append("\\\"");
1872
- } else if (c == '\'') {
1873
- append("\\'");
1874
- } else if (c == '\r') {
1988
+ if (c == '\r') {
1875
1989
  append("\\r");
1876
1990
  } else if (c == '\n') {
1877
1991
  append("\\n");
1992
+ } else if (c == '\\') {
1993
+ append("\\\\");
1878
1994
  } else if ((unsigned int)c < 32) {
1879
1995
  char encoded[4];
1880
1996
 
@@ -1885,32 +2001,15 @@ void FmtEscape::Format(FunctionRef<void(Span<const char>)> append) const
1885
2001
 
1886
2002
  Span<const char> buf = MakeSpan(encoded, 4);
1887
2003
  append(buf);
2004
+ } else if (c == quote) {
2005
+ append('\\');
2006
+ append(quote);
1888
2007
  } else {
1889
2008
  append(c);
1890
2009
  }
1891
2010
  }
1892
2011
  }
1893
2012
 
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
2013
  FmtArg FmtVersion(int64_t version, int parts, int by)
1915
2014
  {
1916
2015
  K_ASSERT(version >= 0);
@@ -1946,7 +2045,7 @@ FmtArg FmtVersion(int64_t version, int parts, int by)
1946
2045
  // Debug and errors
1947
2046
  // ------------------------------------------------------------------------
1948
2047
 
1949
- static int64_t start_time = GetMonotonicTime();
2048
+ static int64_t start_clock = GetMonotonicClock();
1950
2049
 
1951
2050
  static std::function<LogFunc> log_handler = DefaultLogHandler;
1952
2051
  static bool log_vt100 = FileIsVt100(STDERR_FILENO);
@@ -1963,7 +2062,7 @@ const char *GetEnv(const char *name)
1963
2062
  static HashMap<const char *, const char *> values;
1964
2063
 
1965
2064
  bool inserted;
1966
- auto bucket = values.TrySetDefault(name, &inserted);
2065
+ auto bucket = values.InsertOrGetDefault(name, &inserted);
1967
2066
 
1968
2067
  if (inserted) {
1969
2068
  const char *str = (const char *)EM_ASM_INT({
@@ -2045,8 +2144,8 @@ void LogFmt(LogLevel level, const char *ctx, const char *fmt, Span<const FmtArg>
2045
2144
 
2046
2145
  char ctx_buf[512];
2047
2146
  if (log_times) {
2048
- double time = (double)(GetMonotonicTime() - start_time) / 1000;
2049
- Fmt(ctx_buf, "[%1] %2", FmtDouble(time, 3).Pad(-8), ctx ? ctx : "");
2147
+ double time = (double)(GetMonotonicClock() - start_clock) / 1000;
2148
+ Fmt(ctx_buf, "[%1] %2", FmtDouble(time, 3, 8), ctx ? ctx : "");
2050
2149
 
2051
2150
  ctx = ctx_buf;
2052
2151
  }
@@ -2383,13 +2482,13 @@ void DefaultProgressHandler(Span<const ProgressInfo> bars)
2383
2482
  int progress = (int)(100 * delta / range);
2384
2483
  int size = progress / 4;
2385
2484
 
2386
- buf.len += Fmt(buf.TakeAvailable(), true, "%!..+[%1%2]%!0 %3\n", FmtArg('=').Repeat(size), FmtArg(' ').Repeat(25 - size), bar.text).len;
2485
+ buf.len += Fmt(buf.TakeAvailable(), true, "%!..+[%1%2]%!0 %3\n", FmtRepeat("=", size), FmtRepeat(" ", 25 - size), bar.text).len;
2387
2486
  } else {
2388
2487
  int progress = (int)(frame % 44);
2389
2488
  int before = (progress > 22) ? (44 - progress) : progress;
2390
2489
  int after = std::max(22 - before, 0);
2391
2490
 
2392
- buf.len += Fmt(buf.TakeAvailable(), true, "%!..+[%1===%2]%!0 %3\n", FmtArg(' ').Repeat(before), FmtArg(' ').Repeat(after), bar.text).len;
2491
+ buf.len += Fmt(buf.TakeAvailable(), true, "%!..+[%1===%2]%!0 %3\n", FmtRepeat(" ", before), FmtRepeat(" ", after), bar.text).len;
2393
2492
  }
2394
2493
  }
2395
2494
 
@@ -2668,7 +2767,7 @@ bool ResizeFile(int fd, const char *filename, int64_t len)
2668
2767
  return true;
2669
2768
  }
2670
2769
 
2671
- bool SetFileMetaData(int fd, const char *filename, int64_t mtime, int64_t btime, uint32_t)
2770
+ bool SetFileTimes(int fd, const char *filename, int64_t mtime, int64_t btime)
2672
2771
  {
2673
2772
  HANDLE h = (HANDLE)_get_osfhandle(fd);
2674
2773
 
@@ -3117,25 +3216,61 @@ bool ResizeFile(int fd, const char *filename, int64_t len)
3117
3216
  return true;
3118
3217
  }
3119
3218
 
3120
- bool SetFileMetaData(int fd, const char *filename, int64_t mtime, int64_t, uint32_t mode)
3219
+ bool SetFileMode(int fd, const char *filename, uint32_t mode)
3220
+ {
3221
+ if (fd >= 0) {
3222
+ if (fchmod(fd, (mode_t)mode) < 0) {
3223
+ LogError("Failed to set permissions of '%1': %2", filename, strerror(errno));
3224
+ return false;
3225
+ }
3226
+ } else {
3227
+ if (fchmodat(AT_FDCWD, filename, (mode_t)mode, AT_SYMLINK_NOFOLLOW) < 0) {
3228
+ LogError("Failed to set permissions of '%1': %2", filename, strerror(errno));
3229
+ return false;
3230
+ }
3231
+ }
3232
+
3233
+ return true;
3234
+ }
3235
+
3236
+ bool SetFileOwner(int fd, const char *filename, uint32_t uid, uint32_t gid)
3237
+ {
3238
+ if (fd >= 0) {
3239
+ if (fchown(fd, (uid_t)uid, (gid_t)gid) < 0) {
3240
+ LogError("Failed to change owner of '%1': %2", filename, strerror(errno));
3241
+ return false;
3242
+ }
3243
+ } else {
3244
+ if (lchown(filename, (uid_t)uid, (gid_t)gid) < 0) {
3245
+ LogError("Failed to change owner of '%1': %2", filename, strerror(errno));
3246
+ return false;
3247
+ }
3248
+ }
3249
+
3250
+ return true;
3251
+ }
3252
+
3253
+ bool SetFileTimes(int fd, const char *filename, int64_t mtime, int64_t)
3121
3254
  {
3122
3255
  struct timespec times[2] = {};
3123
- bool valid = true;
3124
3256
 
3125
3257
  times[0].tv_nsec = UTIME_OMIT;
3126
3258
  times[1].tv_sec = mtime / 1000;
3127
3259
  times[1].tv_nsec = (mtime % 1000) * 1000000;
3128
3260
 
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;
3261
+ if (fd >= 0) {
3262
+ if (futimens(fd, times) < 0) {
3263
+ LogError("Failed to set modification time of '%1': %2", filename, strerror(errno));
3264
+ return false;
3265
+ }
3266
+ } else {
3267
+ if (utimensat(AT_FDCWD, filename, times, AT_SYMLINK_NOFOLLOW) < 0) {
3268
+ LogError("Failed to set modification time of '%1': %2", filename, strerror(errno));
3269
+ return false;
3270
+ }
3136
3271
  }
3137
3272
 
3138
- return valid;
3273
+ return true;
3139
3274
  }
3140
3275
 
3141
3276
  #if !defined(__wasm__)
@@ -3748,7 +3883,7 @@ const char *GetApplicationExecutable()
3748
3883
 
3749
3884
  size_t argc;
3750
3885
  {
3751
- int ret = sysctl(name, K_LEN(name), nullptr, &argc, NULL, 0);
3886
+ int ret = sysctl(name, K_LEN(name), nullptr, &argc, nullptr, 0);
3752
3887
  K_ASSERT(ret >= 0);
3753
3888
  K_ASSERT(argc >= 1);
3754
3889
  }
@@ -3783,7 +3918,7 @@ const char *GetApplicationExecutable()
3783
3918
  int name[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 };
3784
3919
  size_t len = sizeof(executable_path);
3785
3920
 
3786
- int ret = sysctl(name, K_LEN(name), executable_path, &len, NULL, 0);
3921
+ int ret = sysctl(name, K_LEN(name), executable_path, &len, nullptr, 0);
3787
3922
  K_ASSERT(ret >= 0);
3788
3923
  K_ASSERT(len < K_SIZE(executable_path));
3789
3924
  }
@@ -3852,13 +3987,6 @@ Span<const char> GetPathExtension(Span<const char> filename, CompressionType *ou
3852
3987
  return extension;
3853
3988
  }
3854
3989
 
3855
- CompressionType GetPathCompression(Span<const char> filename)
3856
- {
3857
- CompressionType compression_type;
3858
- GetPathExtension(filename, &compression_type);
3859
- return compression_type;
3860
- }
3861
-
3862
3990
  Span<char> NormalizePath(Span<const char> path, Span<const char> root_directory, unsigned int flags, Allocator *alloc)
3863
3991
  {
3864
3992
  K_ASSERT(alloc);
@@ -3866,6 +3994,21 @@ Span<char> NormalizePath(Span<const char> path, Span<const char> root_directory,
3866
3994
  if (!path.len && !root_directory.len)
3867
3995
  return Fmt(alloc, "");
3868
3996
 
3997
+ #if !defined(_WIN32)
3998
+ if (!(flags & (int)NormalizeFlag::NoExpansion)) {
3999
+ Span<const char> prefix = SplitStrAny(path, K_PATH_SEPARATORS);
4000
+
4001
+ if (prefix == "~") {
4002
+ const char *home = GetEnv("HOME");
4003
+
4004
+ if (home) {
4005
+ root_directory = home;
4006
+ path = TrimStrLeft(path.Take(1, path.len - 1), K_PATH_SEPARATORS);
4007
+ }
4008
+ }
4009
+ }
4010
+ #endif
4011
+
3869
4012
  HeapArray<char> buf(alloc);
3870
4013
  Size parts_count = 0;
3871
4014
 
@@ -3906,15 +4049,17 @@ Span<char> NormalizePath(Span<const char> path, Span<const char> root_directory,
3906
4049
 
3907
4050
  if (!buf.len) {
3908
4051
  buf.Append('.');
4052
+
4053
+ if (flags & (int)NormalizeFlag::EndWithSeparator) {
4054
+ buf.Append(separator);
4055
+ }
3909
4056
  } else if (buf.len == 1 && IsPathSeparator(buf[0])) {
3910
- // Root '/', keep as-is
3911
- } else {
4057
+ // Root '/', keep as-is or almost
4058
+ buf[0] = separator;
4059
+ } else if (!(flags & (int)NormalizeFlag::EndWithSeparator)) {
3912
4060
  // Strip last separator
3913
4061
  buf.len--;
3914
4062
  }
3915
- if (flags & (int)NormalizeFlag::EndWithSeparator) {
3916
- buf.Append(separator);
3917
- }
3918
4063
 
3919
4064
  #if defined(_WIN32)
3920
4065
  if (buf.len >= 2 && IsAsciiAlpha(buf[0]) && buf[1] == ':') {
@@ -3961,6 +4106,20 @@ bool PathContainsDotDot(const char *path)
3961
4106
  return false;
3962
4107
  }
3963
4108
 
4109
+ bool PathContainsDotDot(Span<const char> path)
4110
+ {
4111
+ const char *ptr = path.ptr;
4112
+ const char *end = path.end();
4113
+
4114
+ while ((ptr = (const char *)MemMem(ptr, end - ptr, "..", 2))) {
4115
+ if ((ptr == path.ptr || IsPathSeparator(ptr[-1])) && (ptr + 2 == end || IsPathSeparator(ptr[2])))
4116
+ return true;
4117
+ ptr += 2;
4118
+ }
4119
+
4120
+ return false;
4121
+ }
4122
+
3964
4123
  static bool CheckForDumbTerm()
3965
4124
  {
3966
4125
  static bool dumb = ([]() {
@@ -4777,6 +4936,7 @@ bool EnsureDirectoryExists(const char *filename)
4777
4936
 
4778
4937
  #if defined(_WIN32)
4779
4938
 
4939
+ static const DWORD main_thread = GetCurrentThreadId();
4780
4940
  static HANDLE console_ctrl_event = CreateEvent(nullptr, TRUE, FALSE, nullptr);
4781
4941
  static bool ignore_ctrl_event = false;
4782
4942
 
@@ -4811,7 +4971,7 @@ bool CreateOverlappedPipe(bool overlap0, bool overlap1, PipeMode mode, HANDLE ou
4811
4971
 
4812
4972
  char pipe_name[128];
4813
4973
  do {
4814
- Fmt(pipe_name, "\\\\.\\Pipe\\libcc.%1.%2",
4974
+ Fmt(pipe_name, "\\\\.\\pipe\\kcc.%1.%2",
4815
4975
  GetCurrentProcessId(), InterlockedIncrement(&pipe_idx));
4816
4976
 
4817
4977
  DWORD open_mode = PIPE_ACCESS_INBOUND | FILE_FLAG_FIRST_PIPE_INSTANCE | (overlap0 ? FILE_FLAG_OVERLAPPED : 0);
@@ -5138,12 +5298,11 @@ bool ExecuteCommandLine(const char *cmd_line, const ExecuteInfo &info,
5138
5298
 
5139
5299
  #else
5140
5300
 
5141
- #if defined(__OpenBSD__) || defined(__FreeBSD__)
5142
5301
  static const pthread_t main_thread = pthread_self();
5143
- #endif
5302
+
5144
5303
  static std::atomic_bool flag_signal { false };
5145
5304
  static std::atomic_int explicit_signal { 0 };
5146
- static int interrupt_pfd[2] = {-1, -1};
5305
+ static std::atomic_int interrupt_pfd[2] { -1, -1 };
5147
5306
 
5148
5307
  void SetSignalHandler(int signal, void (*func)(int), struct sigaction *prev)
5149
5308
  {
@@ -5158,19 +5317,17 @@ void SetSignalHandler(int signal, void (*func)(int), struct sigaction *prev)
5158
5317
 
5159
5318
  static void DefaultSignalHandler(int signal)
5160
5319
  {
5161
- #if defined(__OpenBSD__) || defined(__FreeBSD__)
5162
- if (!pthread_main_np()) {
5320
+ if (pthread_self() != main_thread) {
5163
5321
  pthread_kill(main_thread, signal);
5164
5322
  return;
5165
5323
  }
5166
- #endif
5167
5324
 
5168
5325
  pid_t pid = getpid();
5169
5326
  K_ASSERT(pid > 1);
5170
5327
 
5171
- if (interrupt_pfd[1] >= 0) {
5328
+ if (int fd = interrupt_pfd[1].load(); fd >= 0) {
5172
5329
  char dummy = 0;
5173
- K_IGNORE write(interrupt_pfd[1], &dummy, 1);
5330
+ K_IGNORE write(fd, &dummy, 1);
5174
5331
  }
5175
5332
 
5176
5333
  if (flag_signal) {
@@ -5181,26 +5338,30 @@ static void DefaultSignalHandler(int signal)
5181
5338
  }
5182
5339
  }
5183
5340
 
5184
- bool CreatePipe(int pfd[2])
5341
+ bool CreatePipe(bool block, int out_pfd[2])
5185
5342
  {
5186
5343
  #if defined(__APPLE__)
5187
- if (pipe(pfd) < 0) {
5344
+ if (pipe(out_pfd) < 0) {
5188
5345
  LogError("Failed to create pipe: %1", strerror(errno));
5189
5346
  return false;
5190
5347
  }
5191
5348
 
5192
- if (fcntl(pfd[0], F_SETFD, FD_CLOEXEC) < 0 || fcntl(pfd[1], F_SETFD, FD_CLOEXEC) < 0) {
5349
+ if (fcntl(out_pfd[0], F_SETFD, FD_CLOEXEC) < 0 || fcntl(out_pfd[1], F_SETFD, FD_CLOEXEC) < 0) {
5193
5350
  LogError("Failed to set FD_CLOEXEC on pipe: %1", strerror(errno));
5194
5351
  return false;
5195
5352
  }
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;
5353
+ if (!block) {
5354
+ if (fcntl(out_pfd[0], F_SETFL, O_NONBLOCK) < 0 || fcntl(out_pfd[1], F_SETFL, O_NONBLOCK) < 0) {
5355
+ LogError("Failed to set O_NONBLOCK on pipe: %1", strerror(errno));
5356
+ return false;
5357
+ }
5199
5358
  }
5200
5359
 
5201
5360
  return true;
5202
5361
  #else
5203
- if (pipe2(pfd, O_CLOEXEC | O_NONBLOCK) < 0) {
5362
+ int flags = O_CLOEXEC | (block ? 0 : O_NONBLOCK);
5363
+
5364
+ if (pipe2(out_pfd, flags) < 0) {
5204
5365
  LogError("Failed to create pipe: %1", strerror(errno));
5205
5366
  return false;
5206
5367
  }
@@ -5218,6 +5379,28 @@ void CloseDescriptorSafe(int *fd_ptr)
5218
5379
  *fd_ptr = -1;
5219
5380
  }
5220
5381
 
5382
+ static void InitInterruptPipe()
5383
+ {
5384
+ static bool success = ([]() {
5385
+ static int pfd[2];
5386
+
5387
+ if (!CreatePipe(false, pfd))
5388
+ return false;
5389
+
5390
+ atexit([]() {
5391
+ CloseDescriptor(pfd[0]);
5392
+ CloseDescriptor(pfd[1]);
5393
+ });
5394
+
5395
+ interrupt_pfd[0] = pfd[0];
5396
+ interrupt_pfd[1] = pfd[1];
5397
+
5398
+ return true;
5399
+ })();
5400
+
5401
+ K_CRITICAL(success, "Failed to create interrupt pipe");
5402
+ }
5403
+
5221
5404
  bool ExecuteCommandLine(const char *cmd_line, const ExecuteInfo &info,
5222
5405
  FunctionRef<Span<const uint8_t>()> in_func,
5223
5406
  FunctionRef<void(Span<uint8_t> buf)> out_func, int *out_code)
@@ -5231,12 +5414,8 @@ bool ExecuteCommandLine(const char *cmd_line, const ExecuteInfo &info,
5231
5414
  CloseDescriptorSafe(&in_pfd[1]);
5232
5415
  };
5233
5416
  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));
5417
+ if (!CreatePipe(false, in_pfd))
5238
5418
  return false;
5239
- }
5240
5419
  }
5241
5420
 
5242
5421
  // Create write pipes
@@ -5246,33 +5425,11 @@ bool ExecuteCommandLine(const char *cmd_line, const ExecuteInfo &info,
5246
5425
  CloseDescriptorSafe(&out_pfd[1]);
5247
5426
  };
5248
5427
  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));
5428
+ if (!CreatePipe(false, out_pfd))
5253
5429
  return false;
5254
- }
5255
5430
  }
5256
5431
 
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
- }
5432
+ InitInterruptPipe();
5276
5433
 
5277
5434
  // Prepare new environment (if needed)
5278
5435
  HeapArray<char *> new_env;
@@ -5345,9 +5502,9 @@ bool ExecuteCommandLine(const char *cmd_line, const ExecuteInfo &info,
5345
5502
  out_idx = pfds.len;
5346
5503
  pfds.Append({ out_pfd[0], POLLIN, 0 });
5347
5504
  }
5348
- if (interrupt_pfd[0] >= 0) {
5505
+ if (int fd = interrupt_pfd[0].load(); fd >= 0) {
5349
5506
  term_idx = pfds.len;
5350
- pfds.Append({ interrupt_pfd[0], POLLIN, 0 });
5507
+ pfds.Append({ fd, POLLIN, 0 });
5351
5508
  }
5352
5509
 
5353
5510
  if (K_RESTART_EINTR(poll(pfds.data, (nfds_t)pfds.len, -1), < 0) < 0) {
@@ -5422,7 +5579,7 @@ bool ExecuteCommandLine(const char *cmd_line, const ExecuteInfo &info,
5422
5579
  // Wait for process exit
5423
5580
  int status;
5424
5581
  {
5425
- int64_t start = GetMonotonicTime();
5582
+ int64_t start = GetMonotonicClock();
5426
5583
 
5427
5584
  for (;;) {
5428
5585
  int ret = K_RESTART_EINTR(waitpid(pid, &status, terminate ? WNOHANG : 0), < 0);
@@ -5431,7 +5588,7 @@ bool ExecuteCommandLine(const char *cmd_line, const ExecuteInfo &info,
5431
5588
  LogError("Failed to wait for process exit: %1", strerror(errno));
5432
5589
  return false;
5433
5590
  } else if (!ret) {
5434
- int64_t delay = GetMonotonicTime() - start;
5591
+ int64_t delay = GetMonotonicClock() - start;
5435
5592
 
5436
5593
  if (delay < 2000) {
5437
5594
  // A timeout on waitpid would be better, but... sigh
@@ -5575,9 +5732,9 @@ WaitResult WaitEvents(Span<const WaitSource> sources, int64_t timeout, uint64_t
5575
5732
 
5576
5733
  LocalArray<HANDLE, 64> events;
5577
5734
  DWORD wake = 0;
5735
+ DWORD wait_ret = 0;
5578
5736
 
5579
5737
  events.Append(console_ctrl_event);
5580
- events.Append(wait_msg_event);
5581
5738
 
5582
5739
  // There is no way to get a waitable HANDLE for the Win32 GUI message pump.
5583
5740
  // Instead, waitable sources (such as the system tray code) return NULL to indicate that
@@ -5592,6 +5749,11 @@ WaitResult WaitEvents(Span<const WaitSource> sources, int64_t timeout, uint64_t
5592
5749
  timeout = (int64_t)std::min((uint64_t)timeout, (uint64_t)src.timeout);
5593
5750
  }
5594
5751
 
5752
+ if (main_thread == GetCurrentThreadId()) {
5753
+ wait_ret = WAIT_OBJECT_0 + (DWORD)events.len;
5754
+ events.Append(wait_msg_event);
5755
+ }
5756
+
5595
5757
  DWORD ret;
5596
5758
  if (timeout >= 0) {
5597
5759
  do {
@@ -5604,34 +5766,31 @@ WaitResult WaitEvents(Span<const WaitSource> sources, int64_t timeout, uint64_t
5604
5766
  ret = MsgWaitForMultipleObjects((DWORD)events.len, events.data, FALSE, INFINITE, wake);
5605
5767
  }
5606
5768
 
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;
5769
+ if (ret == WAIT_TIMEOUT) {
5770
+ return WaitResult::Timeout;
5771
+ } else if (ret == WAIT_OBJECT_0) {
5772
+ return WaitResult::Interrupt;
5773
+ } else if (ret == wait_ret) {
5774
+ ResetEvent(wait_msg_event);
5775
+ return WaitResult::Message;
5776
+ } else if (ret == WAIT_OBJECT_0 + events.len) {
5777
+ // Mark all sources with an interest in the message pump as ready
5778
+ if (out_ready) {
5779
+ uint64_t flags = 0;
5780
+ for (Size i = 0; i < sources.len; i++) {
5781
+ flags |= !sources[i].handle ? (1ull << i) : 0;
5624
5782
  }
5783
+ *out_ready = flags;
5784
+ }
5785
+ return WaitResult::Ready;
5786
+ } else {
5787
+ Size idx = (Size)ret - WAIT_OBJECT_0 - 1;
5788
+ K_ASSERT(idx >= 0 && idx < sources.len);
5625
5789
 
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;
5790
+ if (out_ready) {
5791
+ *out_ready |= 1ull << idx;
5792
+ }
5793
+ return WaitResult::Ready;
5635
5794
  }
5636
5795
  }
5637
5796
 
@@ -5641,11 +5800,16 @@ WaitResult WaitEvents(int64_t timeout)
5641
5800
  return WaitEvents(sources, timeout);
5642
5801
  }
5643
5802
 
5644
- void InterruptWait()
5803
+ void PostWaitMessage()
5645
5804
  {
5646
5805
  SetEvent(wait_msg_event);
5647
5806
  }
5648
5807
 
5808
+ void PostTerminate()
5809
+ {
5810
+ SetEvent(console_ctrl_event);
5811
+ }
5812
+
5649
5813
  #else
5650
5814
 
5651
5815
  void WaitDelay(int64_t delay)
@@ -5669,19 +5833,25 @@ void WaitDelay(int64_t delay)
5669
5833
  WaitResult WaitEvents(Span<const WaitSource> sources, int64_t timeout, uint64_t *out_ready)
5670
5834
  {
5671
5835
  LocalArray<struct pollfd, 64> pfds;
5672
- K_ASSERT(sources.len <= K_LEN(pfds.data));
5673
-
5674
- static std::atomic_bool message { false };
5836
+ K_ASSERT(sources.len <= K_LEN(pfds.data) - 1);
5675
5837
 
5838
+ // Don't exit after SIGINT/SIGTERM, just signal us
5676
5839
  flag_signal = true;
5840
+
5841
+ static std::atomic_bool message { false };
5677
5842
  SetSignalHandler(SIGUSR1, [](int) { message = true; });
5678
5843
 
5679
5844
  for (const WaitSource &src: sources) {
5680
- pfds.Append({ src.fd, (short)src.events, 0 });
5845
+ short events = src.events ? (short)src.events : POLLIN;
5846
+ pfds.Append({ src.fd, events, 0 });
5847
+
5681
5848
  timeout = (int64_t)std::min((uint64_t)timeout, (uint64_t)src.timeout);
5682
5849
  }
5683
5850
 
5684
- int64_t start = (timeout >= 0) ? GetMonotonicTime() : 0;
5851
+ InitInterruptPipe();
5852
+ pfds.Append({ interrupt_pfd[0], POLLIN, 0 });
5853
+
5854
+ int64_t start = (timeout >= 0) ? GetMonotonicClock() : 0;
5685
5855
  int64_t until = start + timeout;
5686
5856
  int timeout32 = (int)std::min(until - start, (int64_t)INT_MAX);
5687
5857
 
@@ -5690,7 +5860,7 @@ WaitResult WaitEvents(Span<const WaitSource> sources, int64_t timeout, uint64_t
5690
5860
  return WaitResult::Exit;
5691
5861
  } else if (explicit_signal) {
5692
5862
  return WaitResult::Interrupt;
5693
- } else if (message) {
5863
+ } else if (message && pthread_self() == main_thread) {
5694
5864
  message = false;
5695
5865
  return WaitResult::Message;
5696
5866
  }
@@ -5702,23 +5872,26 @@ WaitResult WaitEvents(Span<const WaitSource> sources, int64_t timeout, uint64_t
5702
5872
  continue;
5703
5873
 
5704
5874
  LogError("Failed to poll for events: %1", strerror(errno));
5705
- return WaitResult::Error;
5875
+ abort();
5706
5876
  } 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;
5877
+ uint64_t flags = 0;
5878
+ for (Size i = 0; i < pfds.len - 1; i++) {
5879
+ flags |= pfds[i].revents ? (1ull << i) : 0;
5880
+ }
5881
+
5882
+ if (flags) {
5883
+ if (out_ready) {
5884
+ *out_ready = flags;
5711
5885
  }
5712
- *out_ready = flags;
5886
+ return WaitResult::Ready;
5713
5887
  }
5714
- return WaitResult::Ready;
5715
5888
  }
5716
5889
 
5717
5890
  if (timeout >= 0) {
5718
- int64_t now = GetMonotonicTime();
5719
- if (now >= until)
5891
+ int64_t clock = GetMonotonicClock();
5892
+ if (clock >= until)
5720
5893
  break;
5721
- timeout32 = (int)std::min(until - now, (int64_t)INT_MAX);
5894
+ timeout32 = (int)std::min(until - clock, (int64_t)INT_MAX);
5722
5895
  }
5723
5896
  }
5724
5897
 
@@ -5731,12 +5904,20 @@ WaitResult WaitEvents(int64_t timeout)
5731
5904
  return WaitEvents(sources, timeout);
5732
5905
  }
5733
5906
 
5734
- void InterruptWait()
5907
+ void PostWaitMessage()
5735
5908
  {
5736
5909
  pid_t pid = getpid();
5737
5910
  kill(pid, SIGUSR1);
5738
5911
  }
5739
5912
 
5913
+ void PostTerminate()
5914
+ {
5915
+ InitInterruptPipe();
5916
+
5917
+ char dummy = 0;
5918
+ K_IGNORE write(interrupt_pfd[1], &dummy, 1);
5919
+ }
5920
+
5740
5921
  #endif
5741
5922
 
5742
5923
  #endif
@@ -5837,30 +6018,30 @@ error:
5837
6018
 
5838
6019
  bool NotifySystemd()
5839
6020
  {
5840
- const char *addr_str = GetEnv("NOTIFY_SOCKET");
5841
- if (!addr_str)
6021
+ const char *addr = GetEnv("NOTIFY_SOCKET");
6022
+ if (!addr)
5842
6023
  return true;
5843
6024
 
5844
- struct sockaddr_un addr;
5845
- if (addr_str[0] == '@') {
5846
- addr_str++;
6025
+ struct sockaddr_un sa;
6026
+ if (addr[0] == '@') {
6027
+ addr++;
5847
6028
 
5848
- if (strlen(addr_str) >= sizeof(addr.sun_path) - 1) {
6029
+ if (strlen(addr) >= sizeof(sa.sun_path) - 1) {
5849
6030
  LogError("Abstract socket address in NOTIFY_SOCKET is too long");
5850
6031
  return false;
5851
6032
  }
5852
6033
 
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)) {
6034
+ sa.sun_family = AF_UNIX;
6035
+ sa.sun_path[0] = 0;
6036
+ CopyString(addr, MakeSpan(sa.sun_path + 1, K_SIZE(sa.sun_path) - 1));
6037
+ } else if (addr[0] == '/') {
6038
+ if (strlen(addr) >= sizeof(sa.sun_path)) {
5858
6039
  LogError("Socket pathname in NOTIFY_SOCKET is too long");
5859
6040
  return false;
5860
6041
  }
5861
6042
 
5862
- addr.sun_family = AF_UNIX;
5863
- CopyString(addr_str, addr.sun_path);
6043
+ sa.sun_family = AF_UNIX;
6044
+ CopyString(addr, sa.sun_path);
5864
6045
  } else {
5865
6046
  LogError("Invalid socket address in NOTIFY_SOCKET");
5866
6047
  return false;
@@ -5877,8 +6058,8 @@ bool NotifySystemd()
5877
6058
  struct msghdr msg = {};
5878
6059
  iov.iov_base = (void *)"READY=1";
5879
6060
  iov.iov_len = strlen("READY=1");
5880
- msg.msg_name = &addr;
5881
- msg.msg_namelen = offsetof(struct sockaddr_un, sun_path) + strlen(addr_str);
6061
+ msg.msg_name = &sa;
6062
+ msg.msg_namelen = offsetof(struct sockaddr_un, sun_path) + strlen(addr);
5882
6063
  msg.msg_iov = &iov;
5883
6064
  msg.msg_iovlen = 1;
5884
6065
 
@@ -5927,25 +6108,13 @@ void InitApp()
5927
6108
  #endif
5928
6109
 
5929
6110
  #if !defined(_WIN32) && !defined(__wasi__)
5930
- // Best effort
5931
- setpgid(0, 0);
5932
-
5933
6111
  // Setup default signal handlers
5934
6112
  SetSignalHandler(SIGINT, DefaultSignalHandler);
5935
6113
  SetSignalHandler(SIGTERM, DefaultSignalHandler);
5936
6114
  SetSignalHandler(SIGHUP, DefaultSignalHandler);
5937
6115
  SetSignalHandler(SIGPIPE, [](int) {});
5938
6116
 
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
- });
6117
+ InitInterruptPipe();
5949
6118
 
5950
6119
  // Make sure timezone information is in place, which is useful if some kind of sandbox runs later and
5951
6120
  // the timezone information is not available (seccomp, namespace, landlock, whatever).
@@ -6586,7 +6755,7 @@ bool ParseVersion(Span<const char> str, int parts, int multiplier,
6586
6755
  // ------------------------------------------------------------------------
6587
6756
 
6588
6757
  static thread_local Size rnd_remain;
6589
- static thread_local int64_t rnd_time;
6758
+ static thread_local int64_t rnd_clock;
6590
6759
  #if !defined(_WIN32)
6591
6760
  static thread_local pid_t rnd_pid;
6592
6761
  #endif
@@ -6699,7 +6868,7 @@ void FillRandomSafe(void *out_buf, Size len)
6699
6868
 
6700
6869
  // Reseed every 4 megabytes, or every hour, or after a fork
6701
6870
  reseed |= (rnd_remain <= 0);
6702
- reseed |= (GetMonotonicTime() - rnd_time > 3600 * 1000);
6871
+ reseed |= (GetMonotonicClock() - rnd_clock > 3600 * 1000);
6703
6872
  #if !defined(_WIN32)
6704
6873
  reseed |= (getpid() != rnd_pid);
6705
6874
  #endif
@@ -6727,7 +6896,7 @@ restart:
6727
6896
  ZeroSafe(&buf, K_SIZE(buf));
6728
6897
 
6729
6898
  rnd_remain = Mebibytes(4);
6730
- rnd_time = GetMonotonicTime();
6899
+ rnd_clock = GetMonotonicClock();
6731
6900
  #if !defined(_WIN32)
6732
6901
  rnd_pid = getpid();
6733
6902
  #endif
@@ -6981,39 +7150,65 @@ int CreateSocket(SocketType type, int flags)
6981
7150
 
6982
7151
  #endif
6983
7152
 
6984
- bool BindIPSocket(int sock, SocketType type, int port)
7153
+ bool BindIPSocket(int sock, SocketType type, const char *addr, int port)
6985
7154
  {
6986
7155
  K_ASSERT(type == SocketType::Dual || type == SocketType::IPv4 || type == SocketType::IPv6);
6987
7156
 
6988
7157
  if (type == SocketType::IPv4) {
6989
- struct sockaddr_in addr = {};
7158
+ struct sockaddr_in sa = {};
7159
+
7160
+ sa.sin_family = AF_INET;
7161
+ sa.sin_port = htons((uint16_t)port);
6990
7162
 
6991
- addr.sin_family = AF_INET;
6992
- addr.sin_port = htons((uint16_t)port);
6993
- addr.sin_addr.s_addr = htonl(INADDR_ANY);
7163
+ if (addr) {
7164
+ if (inet_pton(AF_INET, addr, &sa.sin_addr) <= 0) {
7165
+ LogError("Invalid IPv4 address '%1'", addr);
7166
+ return false;
7167
+ }
7168
+ } else {
7169
+ sa.sin_addr.s_addr = htonl(INADDR_ANY);
7170
+ }
6994
7171
 
6995
- if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
7172
+ if (bind(sock, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
6996
7173
  #if defined(_WIN32)
6997
- LogError("Failed to bind to port %1: %2", port, GetWin32ErrorString());
7174
+ LogError("Failed to bind to '%1:%2': %3", addr ? addr : "*", port, GetWin32ErrorString());
6998
7175
  return false;
6999
7176
  #else
7000
- LogError("Failed to bind to port %1: %2", port, strerror(errno));
7177
+ LogError("Failed to bind to '%1:%2': %3", addr ? addr : "*", port, strerror(errno));
7001
7178
  return false;
7002
7179
  #endif
7003
7180
  }
7004
7181
  } else {
7005
- struct sockaddr_in6 addr = {};
7182
+ struct sockaddr_in6 sa = {};
7006
7183
 
7007
- addr.sin6_family = AF_INET6;
7008
- addr.sin6_port = htons((uint16_t)port);
7009
- addr.sin6_addr = IN6ADDR_ANY_INIT;
7184
+ sa.sin6_family = AF_INET6;
7185
+ sa.sin6_port = htons((uint16_t)port);
7186
+
7187
+ if (addr) {
7188
+ if (!strchr(addr, ':')) {
7189
+ char buf[512];
7190
+ Fmt(buf, "::FFFF:%1", addr);
7191
+
7192
+ if (inet_pton(AF_INET6, buf, &sa.sin6_addr) <= 0) {
7193
+ LogError("Invalid IPv4 or IPv6 address '%1'", addr);
7194
+ return false;
7195
+ }
7196
+ } else {
7197
+ if (inet_pton(AF_INET6, addr, &sa.sin6_addr) <= 0) {
7198
+ LogError("Invalid IPv6 address '%1'", addr);
7199
+ return false;
7200
+ }
7201
+ }
7202
+ } else {
7203
+ sa.sin6_addr = IN6ADDR_ANY_INIT;
7204
+ }
7010
7205
 
7011
- if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
7206
+ if (bind(sock, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
7012
7207
  #if defined(_WIN32)
7013
- LogError("Failed to bind to port %1: %2", port, GetWin32ErrorString());
7208
+ LogError("Failed to bind to '%1:%2': %3", addr ? addr : "*", port, GetWin32ErrorString());
7014
7209
  return false;
7015
7210
  #else
7016
- LogError("Failed to bind to port %1: %2", port, strerror(errno));
7211
+ LogError("Failed to bind to '%1:%2': %3", addr ? addr : "*", port, strerror(errno));
7017
7212
  return false;
7018
7213
  #endif
7019
7214
  }
@@ -7024,16 +7219,32 @@ bool BindIPSocket(int sock, SocketType type, int port)
7024
7219
 
7025
7220
  bool BindUnixSocket(int sock, const char *path)
7026
7221
  {
7027
- struct sockaddr_un addr = {};
7222
+ struct sockaddr_un sa = {};
7223
+
7224
+ // Protect against abtract Unix sockets on Linux
7225
+ if (!path[0]) {
7226
+ LogError("Cannot open empty UNIX socket");
7227
+ return false;
7228
+ }
7028
7229
 
7029
- addr.sun_family = AF_UNIX;
7030
- if (!CopyString(path, addr.sun_path)) {
7230
+ sa.sun_family = AF_UNIX;
7231
+ if (!CopyString(path, sa.sun_path)) {
7031
7232
  LogError("Excessive UNIX socket path length");
7032
7233
  return false;
7033
7234
  }
7034
7235
 
7035
- unlink(path);
7036
- if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
7236
+ #if !defined(_WIN32)
7237
+ // Remove existing socket (if any)
7238
+ {
7239
+ struct stat sb;
7240
+ if (!stat(path, &sb) && S_ISSOCK(sb.st_mode)) {
7241
+ LogDebug("Removing existing socket '%1'", path);
7242
+ unlink(path);
7243
+ }
7244
+ }
7245
+ #endif
7246
+
7247
+ if (bind(sock, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
7037
7248
  #if defined(_WIN32)
7038
7249
  LogError("Failed to bind socket to '%1': %2", path, GetWin32ErrorString());
7039
7250
  return false;
@@ -7042,68 +7253,82 @@ bool BindUnixSocket(int sock, const char *path)
7042
7253
  return false;
7043
7254
  #endif
7044
7255
  }
7256
+
7257
+ #if !defined(_WIN32)
7045
7258
  chmod(path, 0666);
7259
+ #endif
7046
7260
 
7047
7261
  return true;
7048
7262
  }
7049
7263
 
7050
- int OpenIPSocket(SocketType type, int port, int flags)
7264
+ bool ConnectIPSocket(int sock, const char *addr, int port)
7051
7265
  {
7052
- K_ASSERT(type == SocketType::Dual || type == SocketType::IPv4 || type == SocketType::IPv6);
7266
+ if (strchr(addr, ':')) {
7267
+ struct sockaddr_in6 sa = {};
7053
7268
 
7054
- int sock = CreateSocket(type, flags);
7055
- if (sock < 0)
7056
- return -1;
7057
- K_DEFER_N(err_guard) { CloseSocket(sock); };
7269
+ sa.sin6_family = AF_INET6;
7270
+ sa.sin6_port = htons((unsigned short)port);
7058
7271
 
7059
- if (!BindIPSocket(sock, type, port))
7060
- return -1;
7272
+ if (inet_pton(AF_INET6, addr, &sa.sin6_addr) <= 0) {
7273
+ LogError("Invalid IPv6 address '%1'", addr);
7274
+ return false;
7275
+ }
7061
7276
 
7062
- err_guard.Disable();
7063
- return sock;
7064
- }
7277
+ if (connect(sock, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
7278
+ #if defined(_WIN32)
7279
+ LogError("Failed to connect to '%1' (%2): %3", addr, port, GetWin32ErrorString());
7280
+ return false;
7281
+ #else
7282
+ LogError("Failed to connect to '%1' (%2): %3", addr, port, strerror(errno));
7283
+ return false;
7284
+ #endif
7285
+ }
7286
+ } else {
7287
+ struct sockaddr_in sa = {};
7065
7288
 
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); };
7289
+ sa.sin_family = AF_INET;
7290
+ sa.sin_port = htons((unsigned short)port);
7072
7291
 
7073
- if (!BindUnixSocket(sock, path))
7074
- return -1;
7292
+ if (inet_pton(AF_INET, addr, &sa.sin_addr) <= 0) {
7293
+ LogError("Invalid IPv4 address '%1'", addr);
7294
+ return false;
7295
+ }
7075
7296
 
7076
- err_guard.Disable();
7077
- return sock;
7297
+ if (connect(sock, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
7298
+ #if defined(_WIN32)
7299
+ LogError("Failed to connect to '%1' (%2): %3", addr, port, GetWin32ErrorString());
7300
+ return false;
7301
+ #else
7302
+ LogError("Failed to connect to '%1' (%2): %3", addr, port, strerror(errno));
7303
+ return false;
7304
+ #endif
7305
+ }
7306
+ }
7307
+
7308
+ return true;
7078
7309
  }
7079
7310
 
7080
- int ConnectToUnixSocket(const char *path, int flags)
7311
+ bool ConnectUnixSocket(int sock, const char *path)
7081
7312
  {
7082
- struct sockaddr_un addr = {};
7313
+ struct sockaddr_un sa = {};
7083
7314
 
7084
- addr.sun_family = AF_UNIX;
7085
- if (!CopyString(path, addr.sun_path)) {
7315
+ sa.sun_family = AF_UNIX;
7316
+ if (!CopyString(path, sa.sun_path)) {
7086
7317
  LogError("Excessive UNIX socket path length");
7087
- return -1;
7318
+ return false;
7088
7319
  }
7089
7320
 
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) {
7321
+ if (connect(sock, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
7096
7322
  #if defined(_WIN32)
7097
- LogError("Failed to connect to '%1': %2", path, GetWin32ErrorString());
7098
- return -1;
7323
+ LogError("Failed to connect to UNIX socket '%1': %2", path, GetWin32ErrorString());
7324
+ return false;
7099
7325
  #else
7100
- LogError("Failed to connect to '%1': %2", path, strerror(errno));
7101
- return -1;
7326
+ LogError("Failed to connect to UNIX socket '%1': %2", path, strerror(errno));
7327
+ return false;
7102
7328
  #endif
7103
7329
  }
7104
7330
 
7105
- err_guard.Disable();
7106
- return sock;
7331
+ return true;
7107
7332
  }
7108
7333
 
7109
7334
  void SetDescriptorNonBlock(int fd, bool enable)
@@ -7613,14 +7838,12 @@ void StreamReader::SetDecoder(StreamDecoder *decoder)
7613
7838
  this->decoder = decoder;
7614
7839
  }
7615
7840
 
7616
- bool StreamReader::Open(Span<const uint8_t> buf, const char *filename, unsigned int flags,
7617
- CompressionType compression_type)
7841
+ bool StreamReader::Open(Span<const uint8_t> buf, const char *filename, CompressionType compression_type)
7618
7842
  {
7619
7843
  Close(true);
7620
7844
 
7621
7845
  K_DEFER_N(err_guard) { error = true; };
7622
7846
 
7623
- lazy = flags & (int)StreamReaderFlag::LazyFill;
7624
7847
  error = false;
7625
7848
  raw_read = 0;
7626
7849
  read_total = 0;
@@ -7640,13 +7863,12 @@ bool StreamReader::Open(Span<const uint8_t> buf, const char *filename, unsigned
7640
7863
  return true;
7641
7864
  }
7642
7865
 
7643
- bool StreamReader::Open(int fd, const char *filename, unsigned int flags, CompressionType compression_type)
7866
+ bool StreamReader::Open(int fd, const char *filename, CompressionType compression_type)
7644
7867
  {
7645
7868
  Close(true);
7646
7869
 
7647
7870
  K_DEFER_N(err_guard) { error = true; };
7648
7871
 
7649
- lazy = flags & (int)StreamReaderFlag::LazyFill;
7650
7872
  error = false;
7651
7873
  raw_read = 0;
7652
7874
  read_total = 0;
@@ -7667,13 +7889,12 @@ bool StreamReader::Open(int fd, const char *filename, unsigned int flags, Compre
7667
7889
  return true;
7668
7890
  }
7669
7891
 
7670
- OpenResult StreamReader::Open(const char *filename, unsigned int flags, CompressionType compression_type)
7892
+ OpenResult StreamReader::Open(const char *filename, CompressionType compression_type)
7671
7893
  {
7672
7894
  Close(true);
7673
7895
 
7674
7896
  K_DEFER_N(err_guard) { error = true; };
7675
7897
 
7676
- lazy = flags & (int)StreamReaderFlag::LazyFill;
7677
7898
  error = false;
7678
7899
  raw_read = 0;
7679
7900
  read_total = 0;
@@ -7697,14 +7918,12 @@ OpenResult StreamReader::Open(const char *filename, unsigned int flags, Compress
7697
7918
  return OpenResult::Success;
7698
7919
  }
7699
7920
 
7700
- bool StreamReader::Open(const std::function<Size(Span<uint8_t>)> &func, const char *filename, unsigned int flags,
7701
- CompressionType compression_type)
7921
+ bool StreamReader::Open(const std::function<Size(Span<uint8_t>)> &func, const char *filename, CompressionType compression_type)
7702
7922
  {
7703
7923
  Close(true);
7704
7924
 
7705
7925
  K_DEFER_N(err_guard) { error = true; };
7706
7926
 
7707
- lazy = flags & (int)StreamReaderFlag::LazyFill;
7708
7927
  error = false;
7709
7928
  raw_read = 0;
7710
7929
  read_total = 0;
@@ -7748,7 +7967,6 @@ bool StreamReader::Close(bool implicit)
7748
7967
  bool ret = !filename || !error;
7749
7968
 
7750
7969
  filename = nullptr;
7751
- lazy = false;
7752
7970
  error = true;
7753
7971
  source.type = SourceType::Memory;
7754
7972
  source.eof = false;
@@ -7807,6 +8025,40 @@ void StreamReader::SetDescriptorOwned(bool owned)
7807
8025
 
7808
8026
  Size StreamReader::Read(Span<uint8_t> out_buf)
7809
8027
  {
8028
+ #if !defined(__wasm__)
8029
+ std::lock_guard<std::mutex> lock(mutex);
8030
+ #endif
8031
+
8032
+ if (error) [[unlikely]]
8033
+ return -1;
8034
+
8035
+ Size len = 0;
8036
+
8037
+ if (decoder) {
8038
+ len = decoder->Read(out_buf.len, out_buf.ptr);
8039
+ if (len < 0) [[unlikely]] {
8040
+ error = true;
8041
+ return -1;
8042
+ }
8043
+ } else {
8044
+ len = ReadRaw(out_buf.len, out_buf.ptr);
8045
+ if (len < 0) [[unlikely]]
8046
+ return -1;
8047
+ eof = source.eof;
8048
+ }
8049
+
8050
+ if (!error && read_max >= 0 && len > read_max - read_total) [[unlikely]] {
8051
+ LogError("Exceeded max stream size of %1", FmtDiskSize(read_max));
8052
+ error = true;
8053
+ return -1;
8054
+ }
8055
+
8056
+ read_total += len;
8057
+ return len;
8058
+ }
8059
+
8060
+ Size StreamReader::ReadFill(Span<uint8_t> out_buf)
8061
+ {
7810
8062
  #if !defined(__wasm__)
7811
8063
  std::lock_guard<std::mutex> lock(mutex);
7812
8064
  #endif
@@ -7842,7 +8094,7 @@ Size StreamReader::Read(Span<uint8_t> out_buf)
7842
8094
  return -1;
7843
8095
  }
7844
8096
 
7845
- if (lazy || eof)
8097
+ if (eof)
7846
8098
  break;
7847
8099
  }
7848
8100
 
@@ -7883,10 +8135,10 @@ Size StreamReader::ReadAll(Size max_len, HeapArray<uint8_t> *out_buf)
7883
8135
  // who need/want to append a NUL character.
7884
8136
  out_buf->Grow((Size)raw_len + 1);
7885
8137
 
7886
- Size read_len = Read((Size)raw_len, out_buf->end());
8138
+ Size read_len = ReadFill(out_buf->TakeAvailable());
7887
8139
  if (read_len < 0)
7888
8140
  return -1;
7889
- out_buf->len += read_len;
8141
+ out_buf->len += (Size)std::min(raw_len, (int64_t)read_len);
7890
8142
 
7891
8143
  buf_guard.Disable();
7892
8144
  return read_len;
@@ -7897,7 +8149,7 @@ Size StreamReader::ReadAll(Size max_len, HeapArray<uint8_t> *out_buf)
7897
8149
  Size grow = std::min(total_len ? Megabytes(1) : Kibibytes(64), K_SIZE_MAX - out_buf->len);
7898
8150
  out_buf->Grow(grow);
7899
8151
 
7900
- Size read_len = Read(out_buf->Available(), out_buf->end());
8152
+ Size read_len = Read(out_buf->TakeAvailable());
7901
8153
  if (read_len < 0)
7902
8154
  return -1;
7903
8155
 
@@ -8032,7 +8284,9 @@ bool LineReader::Next(Span<char> *out_line)
8032
8284
  if (!view.len) {
8033
8285
  buf.Grow(K_LINE_READER_STEP_SIZE + 1);
8034
8286
 
8035
- Size read_len = st->Read(K_LINE_READER_STEP_SIZE, buf.end());
8287
+ Span<char> available = MakeSpan(buf.end(), K_LINE_READER_STEP_SIZE);
8288
+
8289
+ Size read_len = st->Read(available);
8036
8290
  if (read_len < 0) {
8037
8291
  error = true;
8038
8292
  return false;
@@ -8960,7 +9214,7 @@ bool PatchFile(Span<const uint8_t> data, StreamWriter *writer,
8960
9214
  bool PatchFile(const AssetInfo &asset, StreamWriter *writer,
8961
9215
  FunctionRef<void(Span<const char>, StreamWriter *)> func)
8962
9216
  {
8963
- StreamReader reader(asset.data, "<asset>", 0, asset.compression_type);
9217
+ StreamReader reader(asset.data, "<asset>", asset.compression_type);
8964
9218
 
8965
9219
  if (!PatchFile(&reader, writer, func)) {
8966
9220
  K_ASSERT(reader.IsValid());
@@ -9025,12 +9279,14 @@ static HeapArray<TranslationTable> i18n_tables;
9025
9279
  static NoDestroy<HeapArray<TranslationMap>> i18n_maps;
9026
9280
  static HashMap<Span<const char> , const TranslationTable *> i18n_locales;
9027
9281
 
9028
- static const TranslationMap *i18n_default;
9029
- static thread_local const TranslationMap *i18n_thread = i18n_default;
9282
+ static const TranslationTable *i18n_default_table;
9283
+ static const TranslationMap *i18n_default_map;
9284
+ static thread_local const TranslationTable *i18n_thread_table = i18n_default_table;
9285
+ static thread_local const TranslationMap *i18n_thread_map = i18n_default_map;
9030
9286
 
9031
- static void SetDefaultLocale()
9287
+ static void SetDefaultLocale(const char *default_lang)
9032
9288
  {
9033
- if (i18n_default)
9289
+ if (i18n_default_table)
9034
9290
  return;
9035
9291
 
9036
9292
  // Obey environment settings, even on Windows, for easy override
@@ -9043,9 +9299,11 @@ static void SetDefaultLocale()
9043
9299
 
9044
9300
  if (env) {
9045
9301
  ChangeThreadLocale(env);
9046
- i18n_default = i18n_thread;
9047
9302
 
9048
- if (i18n_default)
9303
+ i18n_default_table = i18n_thread_table;
9304
+ i18n_default_map = i18n_thread_map;
9305
+
9306
+ if (i18n_default_table)
9049
9307
  return;
9050
9308
  }
9051
9309
  }
@@ -9063,9 +9321,11 @@ static void SetDefaultLocale()
9063
9321
  ConvertWin32WideToUtf8(buffer, lang);
9064
9322
 
9065
9323
  ChangeThreadLocale(lang);
9066
- i18n_default = i18n_thread;
9067
9324
 
9068
- if (i18n_default)
9325
+ i18n_default_table = i18n_thread_table;
9326
+ i18n_default_map = i18n_thread_map;
9327
+
9328
+ if (i18n_default_table)
9069
9329
  return;
9070
9330
  }
9071
9331
  } else {
@@ -9073,9 +9333,15 @@ static void SetDefaultLocale()
9073
9333
  }
9074
9334
  }
9075
9335
  #endif
9336
+
9337
+ ChangeThreadLocale(default_lang);
9338
+ K_CRITICAL(i18n_thread_table, "Missing default locale");
9339
+
9340
+ i18n_default_table = i18n_thread_table;
9341
+ i18n_default_map = i18n_thread_map;
9076
9342
  }
9077
9343
 
9078
- void InitLocales(Span<const TranslationTable> tables)
9344
+ void InitLocales(Span<const TranslationTable> tables, const char *default_lang)
9079
9345
  {
9080
9346
  K_ASSERT(!i18n_tables.len);
9081
9347
 
@@ -9092,7 +9358,7 @@ void InitLocales(Span<const TranslationTable> tables)
9092
9358
  i18n_locales.Set(table.language, &table);
9093
9359
  }
9094
9360
 
9095
- SetDefaultLocale();
9361
+ SetDefaultLocale(default_lang);
9096
9362
  }
9097
9363
 
9098
9364
  void ChangeThreadLocale(const char *name)
@@ -9102,18 +9368,27 @@ void ChangeThreadLocale(const char *name)
9102
9368
 
9103
9369
  if (table) {
9104
9370
  Size idx = table - i18n_tables.ptr;
9105
- i18n_thread = &(*i18n_maps)[idx];
9371
+
9372
+ i18n_thread_table = table;
9373
+ i18n_thread_map = &(*i18n_maps)[idx];
9106
9374
  } else {
9107
- i18n_thread = i18n_default;
9375
+ i18n_thread_table = i18n_default_table;
9376
+ i18n_thread_map = i18n_default_map;
9108
9377
  }
9109
9378
  }
9110
9379
 
9380
+ const char *GetThreadLocale()
9381
+ {
9382
+ K_ASSERT(i18n_thread_table);
9383
+ return i18n_thread_table->language;
9384
+ }
9385
+
9111
9386
  const char *T(const char *key)
9112
9387
  {
9113
- if (!i18n_thread)
9388
+ if (!i18n_thread_map)
9114
9389
  return key;
9115
9390
 
9116
- return i18n_thread->FindValue(key, key);
9391
+ return i18n_thread_map->FindValue(key, key);
9117
9392
  }
9118
9393
 
9119
9394
  // ------------------------------------------------------------------------
@@ -9509,6 +9784,10 @@ bool ConsolePrompter::ReadRaw(Span<const char> *out_str)
9509
9784
  Delete(str_offset, SkipForward(str_offset, 1));
9510
9785
  RenderRaw();
9511
9786
  }
9787
+ } else if (match_escape("\x1B")) { // Double escape
9788
+ StdErr->Write("\r\n");
9789
+ StdErr->Flush();
9790
+ return false;
9512
9791
  } else if (match_escape("\x7F")) { // Alt-Backspace
9513
9792
  Delete(FindBackward(str_offset, " \t\r\n"), str_offset);
9514
9793
  RenderRaw();
@@ -9651,11 +9930,54 @@ bool ConsolePrompter::ReadRaw(Span<const char> *out_str)
9651
9930
  return true;
9652
9931
  } break;
9653
9932
 
9933
+ case '\t': {
9934
+ if (complete) {
9935
+ BlockAllocator temp_alloc;
9936
+ HeapArray<CompleteChoice> choices;
9937
+
9938
+ PushLogFilter([](LogLevel, const char *, const char *, FunctionRef<LogFunc>) {});
9939
+ K_DEFER_N(log_guard) { PopLogFilter(); };
9940
+
9941
+ CompleteResult ret = complete(str, &temp_alloc, &choices);
9942
+
9943
+ switch (ret) {
9944
+ case CompleteResult::Success: {
9945
+ if (choices.len == 1) {
9946
+ const CompleteChoice &choice = choices[0];
9947
+
9948
+ str.RemoveFrom(0);
9949
+ str.Append(choice.value);
9950
+ str_offset = str.len;
9951
+ RenderRaw();
9952
+ } else if (choices.len) {
9953
+ for (const CompleteChoice &choice: choices) {
9954
+ Print(StdErr, "\r\n %!0%!Y..%1%!0", choice.name);
9955
+ }
9956
+ StdErr->Write("\r\n");
9957
+
9958
+ RenderRaw();
9959
+ }
9960
+ } break;
9961
+
9962
+ case CompleteResult::TooMany: {
9963
+ Print(StdErr, "\r\n %!0%!Y..%1%!0\r\n", T("Too many possibilities to show"));
9964
+ RenderRaw();
9965
+ } break;
9966
+ case CompleteResult::Error: {
9967
+ Print(StdErr, "\r\n %!0%!Y..%1%!0\r\n", T("Autocompletion error"));
9968
+ RenderRaw();
9969
+ } break;
9970
+ }
9971
+
9972
+ break;
9973
+ }
9974
+ } [[fallthrough]];
9975
+
9654
9976
  default: {
9655
9977
  LocalArray<char, 16> frag;
9656
9978
  if (uc == '\t') {
9657
9979
  frag.Append(" ");
9658
- } else if (uc >= 32) {
9980
+ } else if (!IsAsciiControl(uc)) {
9659
9981
  frag.len = EncodeUtf8(uc, frag.data);
9660
9982
  } else {
9661
9983
  continue;
@@ -9731,6 +10053,14 @@ Size ConsolePrompter::ReadRawEnum(Span<const PromptChoice> choices, Size value)
9731
10053
  fake_input = "\x10";
9732
10054
  } else if (match_escape("[B")) { // Down
9733
10055
  fake_input = "\x0E";
10056
+ } else if (match_escape("\x1B")) { // Double escape
10057
+ if (rows > y) {
10058
+ Print(StdErr, "\x1B[%1B", rows - y);
10059
+ }
10060
+ StdErr->Write("\r");
10061
+ StdErr->Flush();
10062
+
10063
+ return -1;
9734
10064
  }
9735
10065
  } break;
9736
10066
 
@@ -9792,7 +10122,7 @@ bool ConsolePrompter::ReadBuffered(Span<const char> *out_str)
9792
10122
 
9793
10123
  do {
9794
10124
  uint8_t c = 0;
9795
- if (StdIn->Read(1, &c) < 0)
10125
+ if (StdIn->Read(MakeSpan(&c, 1)) < 0)
9796
10126
  return false;
9797
10127
 
9798
10128
  if (c == '\n') {
@@ -9801,7 +10131,7 @@ bool ConsolePrompter::ReadBuffered(Span<const char> *out_str)
9801
10131
  *out_str = str;
9802
10132
  }
9803
10133
  return true;
9804
- } else if (c >= 32 || c == '\t') {
10134
+ } else if (!IsAsciiControl(c)) {
9805
10135
  str.Append((char)c);
9806
10136
  }
9807
10137
  } while (!StdIn->IsEOF());
@@ -9823,7 +10153,7 @@ Size ConsolePrompter::ReadBufferedEnum(Span<const PromptChoice> choices)
9823
10153
 
9824
10154
  do {
9825
10155
  uint8_t c = 0;
9826
- if (StdIn->Read(1, &c) < 0)
10156
+ if (StdIn->Read(MakeSpan(&c, 1)) < 0)
9827
10157
  return -1;
9828
10158
 
9829
10159
  if (c == '\n') {
@@ -9840,7 +10170,7 @@ Size ConsolePrompter::ReadBufferedEnum(Span<const PromptChoice> choices)
9840
10170
 
9841
10171
  StdErr->Write(prefix);
9842
10172
  StdErr->Flush();
9843
- } else if (c >= 32 || c == '\t') {
10173
+ } else if (!IsAsciiControl(c)) {
9844
10174
  str.Append((char)c);
9845
10175
  }
9846
10176
  } while (!StdIn->IsEOF());
@@ -9944,7 +10274,11 @@ void ConsolePrompter::FormatChoices(Span<const PromptChoice> choices, Size value
9944
10274
  const PromptChoice &choice = choices[i];
9945
10275
  int pad = align - ComputeUnicodeWidth(choice.str);
9946
10276
 
9947
- Fmt(&str, " [%1] %2%3 ", choice.c, choice.str, FmtArg(' ').Repeat(pad));
10277
+ if (choice.c) {
10278
+ Fmt(&str, " [%1] %2%3 ", choice.c, choice.str, FmtRepeat(" ", pad));
10279
+ } else {
10280
+ Fmt(&str, " %1%2 ", choice.str, FmtRepeat(" ", pad));
10281
+ }
9948
10282
  if (i == value) {
9949
10283
  str_offset = str.len;
9950
10284
  }
@@ -9984,7 +10318,7 @@ void ConsolePrompter::RenderRaw()
9984
10318
  int width = mask ? mask_columns : ComputeUnicodeWidth(str.Take(i, bytes));
9985
10319
 
9986
10320
  if (x2 + width >= columns || str[i] == '\n') {
9987
- FmtArg prefix = FmtArg(' ').Repeat(prompt_columns - 1);
10321
+ FmtArg prefix = FmtRepeat(" ", prompt_columns - 1);
9988
10322
  Print(StdErr, "\x1B[0K\r\n%!D.+%1%!0 %!..+", prefix);
9989
10323
 
9990
10324
  x2 = prompt_columns;
@@ -10028,7 +10362,7 @@ void ConsolePrompter::RenderBuffered()
10028
10362
  Print(StdErr, "%1 %2", prompt, line);
10029
10363
  while (remain.len) {
10030
10364
  line = SplitStr(remain, '\n', &remain);
10031
- Print(StdErr, "\n%1%2", FmtArg(' ').Repeat(prompt_columns), line);
10365
+ Print(StdErr, "\n%1%2", FmtRepeat(" ", prompt_columns), line);
10032
10366
  }
10033
10367
 
10034
10368
  StdErr->Flush();
@@ -10206,6 +10540,7 @@ const char *Prompt(const char *prompt, const char *default_value, const char *ma
10206
10540
 
10207
10541
  prompter.prompt = prompt;
10208
10542
  prompter.mask = mask;
10543
+
10209
10544
  prompter.str.allocator = alloc;
10210
10545
  if (default_value) {
10211
10546
  prompter.str.Append(default_value);
@@ -10225,11 +10560,12 @@ Size PromptEnum(const char *prompt, Span<const PromptChoice> choices, Size value
10225
10560
  HashSet<char> keys;
10226
10561
 
10227
10562
  for (const PromptChoice &choice: choices) {
10228
- keys.Set(choice.c);
10229
- }
10563
+ if (!choice.c)
10564
+ continue;
10230
10565
 
10231
- bool duplicates = (keys.table.count < choices.len);
10232
- K_ASSERT(!duplicates);
10566
+ bool duplicates = !keys.InsertOrFail(choice.c);
10567
+ K_ASSERT(!duplicates);
10568
+ }
10233
10569
  }
10234
10570
  #endif
10235
10571
 
@@ -10242,13 +10578,12 @@ Size PromptEnum(const char *prompt, Span<const PromptChoice> choices, Size value
10242
10578
  Size PromptEnum(const char *prompt, Span<const char *const> strings, Size value)
10243
10579
  {
10244
10580
  static const char literals[] = "123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
10245
- K_ASSERT(strings.len <= K_LEN(literals));
10246
10581
 
10247
10582
  HeapArray<PromptChoice> choices;
10248
10583
 
10249
10584
  for (Size i = 0; i < strings.len; i++) {
10250
10585
  const char *str = strings[i];
10251
- PromptChoice choice = { literals[i], str };
10586
+ PromptChoice choice = { str, i < K_LEN(literals) ? literals[i] : (char)0 };
10252
10587
 
10253
10588
  choices.Append(choice);
10254
10589
  }
@@ -10256,6 +10591,134 @@ Size PromptEnum(const char *prompt, Span<const char *const> strings, Size value)
10256
10591
  return PromptEnum(prompt, choices, value);
10257
10592
  }
10258
10593
 
10594
+ int PromptYN(const char *prompt)
10595
+ {
10596
+ const char *yes = T("Yes");
10597
+ const char *no = T("No");
10598
+
10599
+ const char *shortcuts = T("yn");
10600
+ K_ASSERT(strlen(shortcuts) == 2);
10601
+
10602
+ Size ret = PromptEnum(prompt, {{ yes, shortcuts[0] }, { no, shortcuts[1] }});
10603
+ if (ret < 0)
10604
+ return -1;
10605
+
10606
+ return !ret;
10607
+ }
10608
+
10609
+ const char *PromptPath(const char *prompt, const char *default_path, Span<const char> root_directory, Allocator *alloc)
10610
+ {
10611
+ K_ASSERT(alloc);
10612
+
10613
+ ConsolePrompter prompter;
10614
+
10615
+ prompter.prompt = prompt;
10616
+ prompter.complete = [&](Span<const char> str, Allocator *alloc, HeapArray<CompleteChoice> *out_choices) {
10617
+ Size start_len = out_choices->len;
10618
+ K_DEFER_N(err_guard) { out_choices->RemoveFrom(start_len); };
10619
+
10620
+ Span<const char> path = TrimStrRight(str, K_PATH_SEPARATORS);
10621
+ bool separator = (path.len < str.len);
10622
+
10623
+ // If the value points to a directory, append separator and return
10624
+ if (str.len && !separator) {
10625
+ const char *filename = NormalizePath(path, root_directory, alloc).ptr;
10626
+
10627
+ FileInfo file_info;
10628
+ StatResult ret = StatFile(filename, (int)StatFlag::SilentMissing | (int)StatFlag::FollowSymlink, &file_info);
10629
+
10630
+ if (ret == StatResult::Success && file_info.type == FileType::Directory) {
10631
+ const char *value = Fmt(alloc, "%1%/", path).ptr;
10632
+ out_choices->Append({ value, value });
10633
+
10634
+ err_guard.Disable();
10635
+ return CompleteResult::Success;
10636
+ }
10637
+ }
10638
+
10639
+ Span<const char> directory = path;
10640
+ Span<const char> prefix = separator ? "" : SplitStrReverseAny(path, K_PATH_SEPARATORS, &directory);
10641
+
10642
+ // EnumerateDirectory takes a C string, so we need the NUL terminator,
10643
+ // and we also need to take root_dir into account.
10644
+ const char *dirname = nullptr;
10645
+
10646
+ if (PathIsAbsolute(directory)) {
10647
+ dirname = DuplicateString(directory, alloc).ptr;
10648
+ } else {
10649
+ if (!root_directory.len)
10650
+ return CompleteResult::Success;
10651
+
10652
+ dirname = NormalizePath(directory, root_directory, alloc).ptr;
10653
+ dirname = dirname[0] ? dirname : ".";
10654
+ }
10655
+
10656
+ EnumResult ret = EnumerateDirectory(dirname, nullptr, -1, [&](const char *basename, FileType file_type) {
10657
+ #if defined(_WIN32)
10658
+ if (!StartsWithI(basename, prefix))
10659
+ return true;
10660
+ #else
10661
+ if (!StartsWith(basename, prefix))
10662
+ return true;
10663
+ #endif
10664
+
10665
+ if (out_choices->len - start_len >= K_COMPLETE_PATH_LIMIT)
10666
+ return false;
10667
+
10668
+ CompleteChoice choice;
10669
+ {
10670
+ HeapArray<char> buf(alloc);
10671
+
10672
+ // Make directory part
10673
+ buf.Append(directory);
10674
+ if (directory.len && !IsPathSeparator(directory[directory.len - 1])) {
10675
+ buf.Append(*K_PATH_SEPARATORS);
10676
+ }
10677
+
10678
+ Size name_offset = buf.len;
10679
+
10680
+ // Append name
10681
+ buf.Append(basename);
10682
+ if (file_type == FileType::Directory) {
10683
+ buf.Append(*K_PATH_SEPARATORS);
10684
+ }
10685
+ buf.Append(0);
10686
+ buf.Trim();
10687
+
10688
+ choice.value = buf.Leak().ptr;
10689
+ choice.name = choice.value + name_offset;
10690
+ }
10691
+
10692
+ out_choices->Append(choice);
10693
+ return true;
10694
+ });
10695
+
10696
+ if (ret == EnumResult::CallbackFail) {
10697
+ return CompleteResult::TooMany;
10698
+ } else if (ret != EnumResult::Success) {
10699
+ // Just ignore it and don't print anything
10700
+ return CompleteResult::Success;
10701
+ }
10702
+
10703
+ std::sort(out_choices->ptr + start_len, out_choices->end(),
10704
+ [](const CompleteChoice &choice1, const CompleteChoice &choice2) { return CmpNaturalI(choice1.name, choice2.name) < 0; });
10705
+
10706
+ err_guard.Disable();
10707
+ return CompleteResult::Success;
10708
+ };
10709
+
10710
+ prompter.str.allocator = alloc;
10711
+ if (default_path) {
10712
+ prompter.str.Append(default_path);
10713
+ }
10714
+
10715
+ if (!prompter.Read())
10716
+ return nullptr;
10717
+
10718
+ const char *str = NormalizePath(prompter.str, alloc).ptr;
10719
+ return str;
10720
+ }
10721
+
10259
10722
  // ------------------------------------------------------------------------
10260
10723
  // Mime types
10261
10724
  // ------------------------------------------------------------------------
@@ -10373,7 +10836,7 @@ static inline int ComputeCharacterWidth(int32_t uc)
10373
10836
  {
10374
10837
  // Fast path
10375
10838
  if (uc < 128)
10376
- return (uc >= 32) ? 1 : 0;
10839
+ return IsAsciiControl(uc) ? 0 : 1;
10377
10840
 
10378
10841
  if (TestUnicodeTable(WcWidthNull, uc))
10379
10842
  return 0;