shellsim 0.1.15__tar.gz → 0.1.17__tar.gz

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 (184) hide show
  1. {shellsim-0.1.15 → shellsim-0.1.17}/Cargo.lock +1 -1
  2. {shellsim-0.1.15 → shellsim-0.1.17}/Cargo.toml +1 -1
  3. {shellsim-0.1.15 → shellsim-0.1.17}/PKG-INFO +1 -1
  4. {shellsim-0.1.15 → shellsim-0.1.17}/python/native/Cargo.lock +2 -2
  5. {shellsim-0.1.15 → shellsim-0.1.17}/python/native/Cargo.toml +1 -1
  6. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/builtins.rs +453 -13
  7. shellsim-0.1.17/src/commands/find.rs +619 -0
  8. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/fs.rs +479 -1
  9. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/system.rs +314 -13
  10. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/text.rs +1123 -28
  11. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/unavailable.rs +0 -3
  12. {shellsim-0.1.15 → shellsim-0.1.17}/src/exec.rs +308 -26
  13. {shellsim-0.1.15 → shellsim-0.1.17}/src/interp.rs +28 -0
  14. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/compiler.rs +0 -2
  15. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/heap.rs +6 -1
  16. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/source/asyncio.py +100 -25
  17. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/vm/calls.rs +1 -0
  18. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/vm/iteration.rs +43 -22
  19. {shellsim-0.1.15 → shellsim-0.1.17}/src/shell.rs +108 -11
  20. shellsim-0.1.15/src/commands/find.rs +0 -323
  21. {shellsim-0.1.15 → shellsim-0.1.17}/LICENSE +0 -0
  22. {shellsim-0.1.15 → shellsim-0.1.17}/README.md +0 -0
  23. {shellsim-0.1.15 → shellsim-0.1.17}/pyproject.toml +0 -0
  24. {shellsim-0.1.15 → shellsim-0.1.17}/python/native/src/lib.rs +0 -0
  25. {shellsim-0.1.15 → shellsim-0.1.17}/python/shellsim/__init__.py +0 -0
  26. {shellsim-0.1.15 → shellsim-0.1.17}/python/shellsim/__main__.py +0 -0
  27. {shellsim-0.1.15 → shellsim-0.1.17}/python/shellsim/_api.py +0 -0
  28. {shellsim-0.1.15 → shellsim-0.1.17}/python/shellsim/_cli.py +0 -0
  29. {shellsim-0.1.15 → shellsim-0.1.17}/python/shellsim/py.typed +0 -0
  30. {shellsim-0.1.15 → shellsim-0.1.17}/python/shellsim/python.py +0 -0
  31. {shellsim-0.1.15 → shellsim-0.1.17}/rust-toolchain.toml +0 -0
  32. {shellsim-0.1.15 → shellsim-0.1.17}/src/bin/shellsim-python.rs +0 -0
  33. {shellsim-0.1.15 → shellsim-0.1.17}/src/clock.rs +0 -0
  34. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/archives.rs +0 -0
  35. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/awk/ast.rs +0 -0
  36. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/awk/eval.rs +0 -0
  37. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/awk/lexer.rs +0 -0
  38. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/awk/parser.rs +0 -0
  39. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/awk.rs +0 -0
  40. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/dd.rs +0 -0
  41. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/echo.rs +0 -0
  42. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/git/apply.rs +0 -0
  43. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/git/commit.rs +0 -0
  44. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/git/compare.rs +0 -0
  45. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/git/config.rs +0 -0
  46. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/git/conflict.rs +0 -0
  47. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/git/diff.rs +0 -0
  48. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/git/ignore.rs +0 -0
  49. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/git/import.rs +0 -0
  50. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/git/log.rs +0 -0
  51. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/git/merge.rs +0 -0
  52. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/git/mod.rs +0 -0
  53. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/git/plumbing.rs +0 -0
  54. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/git/rebase.rs +0 -0
  55. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/git/refs.rs +0 -0
  56. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/git/repo.rs +0 -0
  57. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/git/stash.rs +0 -0
  58. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/git/switch.rs +0 -0
  59. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/git/worktree.rs +0 -0
  60. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/hashing.rs +0 -0
  61. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/makecmd.rs +0 -0
  62. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/mod.rs +0 -0
  63. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/net.rs +0 -0
  64. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/options.rs +0 -0
  65. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/patch.rs +0 -0
  66. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/pkg.rs +0 -0
  67. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/printf.rs +0 -0
  68. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/proc.rs +0 -0
  69. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/regex_compat.rs +0 -0
  70. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/rgcmd.rs +0 -0
  71. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/sed.rs +0 -0
  72. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/sort.rs +0 -0
  73. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/tarcmd.rs +0 -0
  74. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/util.rs +0 -0
  75. {shellsim-0.1.15 → shellsim-0.1.17}/src/commands/zipcmd.rs +0 -0
  76. {shellsim-0.1.15 → shellsim-0.1.17}/src/descriptors.rs +0 -0
  77. {shellsim-0.1.15 → shellsim-0.1.17}/src/expand.rs +0 -0
  78. {shellsim-0.1.15 → shellsim-0.1.17}/src/harness.rs +0 -0
  79. {shellsim-0.1.15 → shellsim-0.1.17}/src/harness_manager.rs +0 -0
  80. {shellsim-0.1.15 → shellsim-0.1.17}/src/hashes.rs +0 -0
  81. {shellsim-0.1.15 → shellsim-0.1.17}/src/host_ingest.rs +0 -0
  82. {shellsim-0.1.15 → shellsim-0.1.17}/src/jqcmd.rs +0 -0
  83. {shellsim-0.1.15 → shellsim-0.1.17}/src/lib.rs +0 -0
  84. {shellsim-0.1.15 → shellsim-0.1.17}/src/main.rs +0 -0
  85. {shellsim-0.1.15 → shellsim-0.1.17}/src/mcp.rs +0 -0
  86. {shellsim-0.1.15 → shellsim-0.1.17}/src/net.rs +0 -0
  87. {shellsim-0.1.15 → shellsim-0.1.17}/src/netcmd.rs +0 -0
  88. {shellsim-0.1.15 → shellsim-0.1.17}/src/process.rs +0 -0
  89. {shellsim-0.1.15 → shellsim-0.1.17}/src/pseudo_fs.rs +0 -0
  90. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/ast.rs +0 -0
  91. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/bytecode.rs +0 -0
  92. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/filesystem.rs +0 -0
  93. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/http.rs +0 -0
  94. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/lexer.rs +0 -0
  95. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/mapping.rs +0 -0
  96. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/mod.rs +0 -0
  97. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/native.rs +0 -0
  98. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/number.rs +0 -0
  99. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/object_model.rs +0 -0
  100. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/parser.rs +0 -0
  101. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/process.rs +0 -0
  102. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/protocol.rs +0 -0
  103. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/slice.rs +0 -0
  104. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/source.rs +0 -0
  105. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/argparse.rs +0 -0
  106. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/asyncio.rs +0 -0
  107. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/base64.rs +0 -0
  108. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/bisect.rs +0 -0
  109. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/collections.rs +0 -0
  110. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/core.rs +0 -0
  111. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/dataclasses.rs +0 -0
  112. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/enum.rs +0 -0
  113. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/frozen.rs +0 -0
  114. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/functools.rs +0 -0
  115. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/hashlib.rs +0 -0
  116. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/heapq.rs +0 -0
  117. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/http.rs +0 -0
  118. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/importlib.rs +0 -0
  119. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/itertools.rs +0 -0
  120. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/json.rs +0 -0
  121. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/math.rs +0 -0
  122. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/mod.rs +0 -0
  123. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/numpy.rs +0 -0
  124. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/os.rs +0 -0
  125. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/pytest.rs +0 -0
  126. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/re.rs +0 -0
  127. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/source/abc.py +0 -0
  128. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/source/base64.py +0 -0
  129. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/source/cmath.py +0 -0
  130. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/source/codecs.py +0 -0
  131. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/source/collections.py +0 -0
  132. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/source/complex.py +0 -0
  133. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/source/copy.py +0 -0
  134. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/source/csv.py +0 -0
  135. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/source/datetime.py +0 -0
  136. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/source/glob.py +0 -0
  137. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/source/hashlib.py +0 -0
  138. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/source/http.py +0 -0
  139. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/source/http_client.py +0 -0
  140. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/source/importlib.py +0 -0
  141. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/source/io.py +0 -0
  142. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/source/json.py +0 -0
  143. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/source/logging.py +0 -0
  144. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/source/numpy_random.py +0 -0
  145. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/source/os.py +0 -0
  146. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/source/pathlib.py +0 -0
  147. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/source/pytest.py +0 -0
  148. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/source/random.py +0 -0
  149. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/source/shutil.py +0 -0
  150. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/source/statistics.py +0 -0
  151. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/source/struct.py +0 -0
  152. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/source/subprocess.py +0 -0
  153. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/source/tempfile.py +0 -0
  154. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/source/textwrap.py +0 -0
  155. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/source/urllib.py +0 -0
  156. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/source/urllib_error.py +0 -0
  157. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/source/urllib_request.py +0 -0
  158. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/source/uuid.py +0 -0
  159. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/source/zipfile.py +0 -0
  160. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/source/zlib.py +0 -0
  161. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/string.rs +0 -0
  162. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/struct.rs +0 -0
  163. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/subprocess.rs +0 -0
  164. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/sys.rs +0 -0
  165. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/time.rs +0 -0
  166. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/typing.rs +0 -0
  167. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/unittest.rs +0 -0
  168. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/vfs.rs +0 -0
  169. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/stdlib/zlib.rs +0 -0
  170. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/string.rs +0 -0
  171. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/token.rs +0 -0
  172. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/vm/dispatch.rs +0 -0
  173. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/vm/host.rs +0 -0
  174. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/vm/namespace.rs +0 -0
  175. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/vm/native_runtime.rs +0 -0
  176. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/vm/objects.rs +0 -0
  177. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/vm/operations.rs +0 -0
  178. {shellsim-0.1.15 → shellsim-0.1.17}/src/python/vm.rs +0 -0
  179. {shellsim-0.1.15 → shellsim-0.1.17}/src/resources.rs +0 -0
  180. {shellsim-0.1.15 → shellsim-0.1.17}/src/sandbox.rs +0 -0
  181. {shellsim-0.1.15 → shellsim-0.1.17}/src/scenario.rs +0 -0
  182. {shellsim-0.1.15 → shellsim-0.1.17}/src/scheduler.rs +0 -0
  183. {shellsim-0.1.15 → shellsim-0.1.17}/src/telemetry.rs +0 -0
  184. {shellsim-0.1.15 → shellsim-0.1.17}/src/vfs.rs +0 -0
@@ -330,7 +330,7 @@ dependencies = [
330
330
 
331
331
  [[package]]
332
332
  name = "shellsim"
333
- version = "0.1.15"
333
+ version = "0.1.17"
334
334
  dependencies = [
335
335
  "base64",
336
336
  "crc32fast",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "shellsim"
3
- version = "0.1.15"
3
+ version = "0.1.17"
4
4
  edition = "2021"
5
5
  description = "In-process BusyBox-like environment for RL rollouts and agent tests"
6
6
  license = "Apache-2.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shellsim
3
- Version: 0.1.15
3
+ Version: 0.1.17
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: License :: OSI Approved :: Apache Software License
6
6
  Classifier: Operating System :: MacOS :: MacOS X
@@ -406,7 +406,7 @@ dependencies = [
406
406
 
407
407
  [[package]]
408
408
  name = "shellsim"
409
- version = "0.1.15"
409
+ version = "0.1.17"
410
410
  dependencies = [
411
411
  "base64",
412
412
  "crc32fast",
@@ -427,7 +427,7 @@ dependencies = [
427
427
 
428
428
  [[package]]
429
429
  name = "shellsim-python-bindings"
430
- version = "0.1.15"
430
+ version = "0.1.17"
431
431
  dependencies = [
432
432
  "pyo3",
433
433
  "serde",
@@ -2,7 +2,7 @@
2
2
 
3
3
  [package]
4
4
  name = "shellsim-python-bindings"
5
- version = "0.1.15"
5
+ version = "0.1.17"
6
6
  edition = "2021"
7
7
  description = "Python bindings for shellsim"
8
8
  license = "Apache-2.0"
@@ -17,7 +17,7 @@ pub fn register(m: &mut HashMap<&'static str, CommandSpec>) {
17
17
  reg(m, &["set"], Trust::Real, cmd_set);
18
18
  reg(m, &["declare", "typeset"], Trust::Real, cmd_declare);
19
19
  reg(m, &["local"], Trust::Real, cmd_local);
20
- reg_unsupported(m, &["readonly"]);
20
+ reg(m, &["readonly"], Trust::Real, cmd_readonly);
21
21
  reg_resumable(m, &["source", "."], Trust::Real, cmd_source, start_source);
22
22
  reg_resumable(m, &["eval"], Trust::Real, cmd_eval, start_eval);
23
23
  reg(m, &["exit"], Trust::Real, cmd_exit);
@@ -37,14 +37,16 @@ pub fn register(m: &mut HashMap<&'static str, CommandSpec>) {
37
37
  reg(m, &["bg"], Trust::Real, cmd_bg);
38
38
  reg(m, &["trap"], Trust::Real, cmd_trap);
39
39
  reg(m, &["flock"], Trust::Partial, cmd_flock);
40
- reg_unsupported(
41
- m,
42
- &[
43
- "disown", "umask", "ulimit", "hash", "complete", "shopt", "bind", "history", "exec",
44
- ],
45
- );
40
+ reg(m, &["disown"], Trust::Real, cmd_disown);
41
+ reg(m, &["umask"], Trust::Partial, cmd_umask);
42
+ reg(m, &["ulimit"], Trust::Partial, cmd_ulimit);
43
+ reg(m, &["hash"], Trust::Real, cmd_hash);
44
+ reg(m, &["shopt"], Trust::Partial, cmd_shopt);
45
+ reg(m, &["exec"], Trust::Partial, cmd_exec);
46
+ reg_unsupported(m, &["complete", "bind", "history"]);
46
47
  reg(m, &["kill"], Trust::Real, cmd_kill);
47
- reg_unsupported(m, &["killall", "pkill"]);
48
+ reg(m, &["killall"], Trust::Partial, cmd_killall);
49
+ reg(m, &["pkill"], Trust::Partial, cmd_pkill);
48
50
  reg(m, &["which"], Trust::Real, cmd_which);
49
51
  reg(m, &["type"], Trust::Real, cmd_type);
50
52
  reg_resumable(m, &["command"], Trust::Real, cmd_command, start_command);
@@ -58,6 +60,266 @@ pub fn register(m: &mut HashMap<&'static str, CommandSpec>) {
58
60
  reg(m, &["dirs"], Trust::Real, cmd_dirs);
59
61
  }
60
62
 
63
+ fn cmd_readonly(interp: &mut CommandContext<'_>, args: &[String], io: &mut Io) -> i32 {
64
+ if args.is_empty() || args == ["-p"] {
65
+ for name in &interp.readonly {
66
+ let value = interp.get_var(name).unwrap_or_default();
67
+ wln(io.out, &format!("declare -r {name}=\"{value}\""));
68
+ }
69
+ return 0;
70
+ }
71
+ let mut status = 0;
72
+ for argument in args.iter().filter(|argument| argument.as_str() != "--") {
73
+ if argument.starts_with('-') {
74
+ ewln(io.err, &format!("readonly: unsupported option {argument}"));
75
+ status = 2;
76
+ continue;
77
+ }
78
+ let name = declare_name_of(argument);
79
+ if !shell_identifier(name) {
80
+ ewln(io.err, &format!("readonly: {name}: not a valid identifier"));
81
+ status = 1;
82
+ continue;
83
+ }
84
+ if let Some((raw_key, raw_value)) = split_decl_assign(argument) {
85
+ if interp.readonly.contains(name) {
86
+ ewln(io.err, &format!("readonly: {name}: readonly variable"));
87
+ status = 1;
88
+ continue;
89
+ }
90
+ crate::exec::apply_assignment(interp, &raw_key, &raw_value);
91
+ } else if interp.get_var(name).is_none() && !interp.arrays.contains_key(name) {
92
+ interp.set_var(name, "");
93
+ }
94
+ interp.readonly.insert(name.to_string());
95
+ }
96
+ status
97
+ }
98
+
99
+ fn cmd_umask(interp: &mut CommandContext<'_>, args: &[String], io: &mut Io) -> i32 {
100
+ match args {
101
+ [] => {
102
+ wln(io.out, &format!("{:04o}", interp.umask));
103
+ 0
104
+ }
105
+ [flag] if flag == "-S" => {
106
+ let allowed = 0o777 & !interp.umask;
107
+ let triplet = |shift| {
108
+ let bits = (allowed >> shift) & 7_u16;
109
+ format!(
110
+ "{}{}{}",
111
+ if bits & 4_u16 != 0_u16 { "r" } else { "" },
112
+ if bits & 2_u16 != 0_u16 { "w" } else { "" },
113
+ if bits & 1_u16 != 0_u16 { "x" } else { "" }
114
+ )
115
+ };
116
+ wln(
117
+ io.out,
118
+ &format!("u={},g={},o={}", triplet(6), triplet(3), triplet(0)),
119
+ );
120
+ 0
121
+ }
122
+ [value] => match u16::from_str_radix(
123
+ match value.trim_start_matches('0') {
124
+ "" if !value.is_empty() => "0",
125
+ digits => digits,
126
+ },
127
+ 8,
128
+ ) {
129
+ Ok(mask) if mask <= 0o777 => {
130
+ interp.umask = mask;
131
+ 0
132
+ }
133
+ _ => {
134
+ ewln(io.err, &format!("umask: {value}: invalid octal mask"));
135
+ 1
136
+ }
137
+ },
138
+ _ => {
139
+ ewln(io.err, "umask: usage: umask [-S] [octal-mask]");
140
+ 2
141
+ }
142
+ }
143
+ }
144
+
145
+ fn cmd_ulimit(interp: &mut CommandContext<'_>, args: &[String], io: &mut Io) -> i32 {
146
+ let limits = interp.resources.limits();
147
+ let selected = args.first().map(String::as_str).unwrap_or("-f");
148
+ if args.len() > 1 || !matches!(selected, "-a" | "-f" | "-n" | "-t" | "-v") {
149
+ ewln(
150
+ io.err,
151
+ "ulimit: setting limits and this option are not supported",
152
+ );
153
+ return 2;
154
+ }
155
+ let blocks = |bytes: u64| {
156
+ if bytes == u64::MAX {
157
+ "unlimited".into()
158
+ } else {
159
+ (bytes / 1024).to_string()
160
+ }
161
+ };
162
+ match selected {
163
+ "-a" => {
164
+ wln(
165
+ io.out,
166
+ &format!(
167
+ "file size (blocks, -f) {}",
168
+ blocks(limits.disk)
169
+ ),
170
+ );
171
+ wln(io.out, "open files (-n) 1024");
172
+ wln(
173
+ io.out,
174
+ &format!("cpu time (seconds, -t) {}", limits.cpu),
175
+ );
176
+ wln(
177
+ io.out,
178
+ &format!(
179
+ "virtual memory (kbytes, -v) {}",
180
+ blocks(limits.memory)
181
+ ),
182
+ );
183
+ }
184
+ "-f" => wln(io.out, &blocks(limits.disk)),
185
+ "-n" => wln(io.out, "1024"),
186
+ "-t" => wln(io.out, &limits.cpu.to_string()),
187
+ "-v" => wln(io.out, &blocks(limits.memory)),
188
+ _ => unreachable!(),
189
+ }
190
+ 0
191
+ }
192
+
193
+ fn cmd_hash(interp: &mut CommandContext<'_>, args: &[String], io: &mut Io) -> i32 {
194
+ if args == ["-r"] {
195
+ interp.command_hash.clear();
196
+ return 0;
197
+ }
198
+ if args.first().map(String::as_str) == Some("-t") {
199
+ let mut status = 0;
200
+ for name in &args[1..] {
201
+ if let Some(path) = interp.command_hash.get(name) {
202
+ wln(io.out, path);
203
+ } else {
204
+ ewln(io.err, &format!("hash: {name}: not found"));
205
+ status = 1;
206
+ }
207
+ }
208
+ return status;
209
+ }
210
+ if args.is_empty() {
211
+ for (name, path) in &interp.command_hash {
212
+ wln(io.out, &format!("{path}\t{name}"));
213
+ }
214
+ return 0;
215
+ }
216
+ let mut status = 0;
217
+ for name in args {
218
+ let path = if crate::commands::is_registered(name) {
219
+ Some(format!("/usr/bin/{name}"))
220
+ } else if let crate::commands::util::ExecutableLookup::Found(path) =
221
+ crate::commands::util::resolve_executable(interp, name)
222
+ {
223
+ Some(path)
224
+ } else {
225
+ None
226
+ };
227
+ if let Some(path) = path {
228
+ interp.command_hash.insert(name.clone(), path);
229
+ } else {
230
+ ewln(io.err, &format!("hash: {name}: not found"));
231
+ status = 1;
232
+ }
233
+ }
234
+ status
235
+ }
236
+
237
+ const COMPAT_SHOPTS: &[&str] = &["expand_aliases", "sourcepath"];
238
+
239
+ fn cmd_shopt(interp: &mut CommandContext<'_>, args: &[String], io: &mut Io) -> i32 {
240
+ let (mode, names) = match args.first().map(String::as_str) {
241
+ Some("-s") => ('s', &args[1..]),
242
+ Some("-u") => ('u', &args[1..]),
243
+ Some("-q") => ('q', &args[1..]),
244
+ Some(option) if option.starts_with('-') => {
245
+ ewln(io.err, &format!("shopt: unsupported option {option}"));
246
+ return 2;
247
+ }
248
+ _ => ('p', args),
249
+ };
250
+ let selected = if names.is_empty() {
251
+ COMPAT_SHOPTS
252
+ .iter()
253
+ .map(|name| (*name).to_string())
254
+ .collect::<Vec<_>>()
255
+ } else {
256
+ names.to_vec()
257
+ };
258
+ let mut status = 0;
259
+ for name in selected {
260
+ if !COMPAT_SHOPTS.contains(&name.as_str()) {
261
+ ewln(io.err, &format!("shopt: {name}: invalid shell option name"));
262
+ status = 1;
263
+ continue;
264
+ }
265
+ match mode {
266
+ 's' => {
267
+ interp.shell_options.insert(name);
268
+ }
269
+ 'u' => {
270
+ interp.shell_options.remove(&name);
271
+ }
272
+ 'q' => status |= i32::from(!interp.shell_options.contains(&name)),
273
+ _ => wln(
274
+ io.out,
275
+ &format!(
276
+ "{}\t{}",
277
+ name,
278
+ if interp.shell_options.contains(&name) {
279
+ "on"
280
+ } else {
281
+ "off"
282
+ }
283
+ ),
284
+ ),
285
+ }
286
+ }
287
+ status
288
+ }
289
+
290
+ fn cmd_disown(interp: &mut CommandContext<'_>, args: &[String], io: &mut Io) -> i32 {
291
+ if args == ["-a"] {
292
+ interp.jobs.clear();
293
+ return 0;
294
+ }
295
+ let position = match job_position(interp, args, "disown") {
296
+ Ok(position) => position,
297
+ Err(error) => {
298
+ ewln(io.err, &format!("disown: {error}"));
299
+ return 1;
300
+ }
301
+ };
302
+ interp.jobs.remove(position);
303
+ 0
304
+ }
305
+
306
+ fn cmd_exec(interp: &mut CommandContext<'_>, args: &[String], io: &mut Io) -> i32 {
307
+ if args.is_empty() {
308
+ return 0;
309
+ }
310
+ let argv = if args.first().map(String::as_str) == Some("--") {
311
+ &args[1..]
312
+ } else {
313
+ args
314
+ };
315
+ if argv.is_empty() {
316
+ return 0;
317
+ }
318
+ let status = crate::commands::run(interp, argv, std::mem::take(&mut io.stdin), io.out, io.err);
319
+ interp.exiting = Some(status);
320
+ status
321
+ }
322
+
61
323
  const MAX_TRAP_STATE_BYTES: u64 = 1024 * 1024;
62
324
 
63
325
  /// Validate the common descriptor-lock form. A shellsim process has no ambient competing host
@@ -469,6 +731,155 @@ fn cmd_kill(interp: &mut CommandContext<'_>, args: &[String], io: &mut Io) -> i3
469
731
  status
470
732
  }
471
733
 
734
+ fn cmd_pkill(interp: &mut CommandContext<'_>, args: &[String], io: &mut Io) -> i32 {
735
+ let mut signal = crate::process::Signal::Terminate;
736
+ let mut full = false;
737
+ let mut exact = false;
738
+ let mut index = 0;
739
+ while let Some(argument) = args.get(index) {
740
+ match argument.as_str() {
741
+ "-f" => full = true,
742
+ "-x" => exact = true,
743
+ "-s" | "--signal" => {
744
+ index += 1;
745
+ let Some(value) = args.get(index) else {
746
+ ewln(io.err, "pkill: signal name required");
747
+ return 2;
748
+ };
749
+ let Ok(Some(parsed)) = parse_signal(value) else {
750
+ ewln(io.err, &format!("pkill: invalid signal: {value}"));
751
+ return 2;
752
+ };
753
+ signal = parsed;
754
+ }
755
+ value if value.starts_with('-') && value.len() > 1 => {
756
+ let Ok(Some(parsed)) = parse_signal(&value[1..]) else {
757
+ ewln(io.err, &format!("pkill: unsupported option {value}"));
758
+ return 2;
759
+ };
760
+ signal = parsed;
761
+ }
762
+ _ => break,
763
+ }
764
+ index += 1;
765
+ }
766
+ let Some(pattern) = args.get(index) else {
767
+ ewln(io.err, "pkill: pattern required");
768
+ return 2;
769
+ };
770
+ if index + 1 != args.len() {
771
+ ewln(io.err, "pkill: too many patterns");
772
+ return 2;
773
+ }
774
+ let regex = match regex::Regex::new(pattern) {
775
+ Ok(regex) => regex,
776
+ Err(error) => {
777
+ ewln(io.err, &format!("pkill: invalid pattern: {error}"));
778
+ return 2;
779
+ }
780
+ };
781
+ let current = interp.pid;
782
+ let targets = interp
783
+ .processes
784
+ .iter()
785
+ .filter(|record| {
786
+ record.pid != current
787
+ && !matches!(record.status, crate::process::ProcessStatus::Exited(_))
788
+ })
789
+ .filter(|record| {
790
+ let candidate = if full {
791
+ record.command.as_str()
792
+ } else {
793
+ record
794
+ .command
795
+ .split_whitespace()
796
+ .next()
797
+ .unwrap_or("")
798
+ .rsplit('/')
799
+ .next()
800
+ .unwrap_or("")
801
+ };
802
+ if exact {
803
+ regex
804
+ .find(candidate)
805
+ .is_some_and(|found| found.as_str() == candidate)
806
+ } else {
807
+ regex.is_match(candidate)
808
+ }
809
+ })
810
+ .map(|record| record.pid)
811
+ .collect::<Vec<_>>();
812
+ for pid in &targets {
813
+ let _ = interp.send_signal(*pid, signal);
814
+ }
815
+ i32::from(targets.is_empty())
816
+ }
817
+
818
+ fn cmd_killall(interp: &mut CommandContext<'_>, args: &[String], io: &mut Io) -> i32 {
819
+ let mut signal = crate::process::Signal::Terminate;
820
+ let mut names = Vec::new();
821
+ let mut index = 0;
822
+ while let Some(argument) = args.get(index) {
823
+ match argument.as_str() {
824
+ "-s" | "--signal" => {
825
+ index += 1;
826
+ let Some(value) = args.get(index) else {
827
+ ewln(io.err, "killall: signal name required");
828
+ return 2;
829
+ };
830
+ let Ok(Some(parsed)) = parse_signal(value) else {
831
+ ewln(io.err, &format!("killall: invalid signal: {value}"));
832
+ return 2;
833
+ };
834
+ signal = parsed;
835
+ }
836
+ value if value.starts_with('-') => {
837
+ let Ok(Some(parsed)) = parse_signal(&value[1..]) else {
838
+ ewln(io.err, &format!("killall: unsupported option {value}"));
839
+ return 2;
840
+ };
841
+ signal = parsed;
842
+ }
843
+ name => names.push(name.to_string()),
844
+ }
845
+ index += 1;
846
+ }
847
+ if names.is_empty() {
848
+ ewln(io.err, "killall: process name required");
849
+ return 2;
850
+ }
851
+ let current = interp.pid;
852
+ let targets = interp
853
+ .processes
854
+ .iter()
855
+ .filter(|record| {
856
+ let command = record
857
+ .command
858
+ .split_whitespace()
859
+ .next()
860
+ .unwrap_or("")
861
+ .rsplit('/')
862
+ .next()
863
+ .unwrap_or("");
864
+ record.pid != current
865
+ && !matches!(record.status, crate::process::ProcessStatus::Exited(_))
866
+ && names.iter().any(|name| name == command)
867
+ })
868
+ .map(|record| record.pid)
869
+ .collect::<Vec<_>>();
870
+ for pid in &targets {
871
+ let _ = interp.send_signal(*pid, signal);
872
+ }
873
+ if targets.is_empty() {
874
+ for name in names {
875
+ ewln(io.err, &format!("killall: {name}: no process found"));
876
+ }
877
+ 1
878
+ } else {
879
+ 0
880
+ }
881
+ }
882
+
472
883
  fn parse_signal(value: &str) -> Result<Option<crate::process::Signal>, String> {
473
884
  if value == "0" {
474
885
  Ok(None)
@@ -859,19 +1270,26 @@ fn print_directory_stack(interp: &CommandContext<'_>, one_per_line: bool, io: &m
859
1270
  }
860
1271
  }
861
1272
 
862
- fn cmd_export(interp: &mut CommandContext<'_>, args: &[String], _io: &mut Io) -> i32 {
1273
+ fn cmd_export(interp: &mut CommandContext<'_>, args: &[String], io: &mut Io) -> i32 {
1274
+ let mut status = 0;
863
1275
  for a in args {
864
1276
  if let Some((k, v)) = a.split_once('=') {
1277
+ if interp.readonly.contains(k) {
1278
+ ewln(io.err, &format!("export: {k}: readonly variable"));
1279
+ status = 1;
1280
+ continue;
1281
+ }
865
1282
  interp.set_var(k, v);
866
1283
  interp.export(k);
867
1284
  } else {
868
1285
  interp.export(a);
869
1286
  }
870
1287
  }
871
- 0
1288
+ status
872
1289
  }
873
1290
 
874
- fn cmd_unset(interp: &mut CommandContext<'_>, args: &[String], _io: &mut Io) -> i32 {
1291
+ fn cmd_unset(interp: &mut CommandContext<'_>, args: &[String], io: &mut Io) -> i32 {
1292
+ let mut status = 0;
875
1293
  for a in args {
876
1294
  if a == "-v" || a == "-f" {
877
1295
  continue;
@@ -880,18 +1298,28 @@ fn cmd_unset(interp: &mut CommandContext<'_>, args: &[String], _io: &mut Io) ->
880
1298
  if let Some(br) = a.find('[') {
881
1299
  if a.ends_with(']') {
882
1300
  let name = &a[..br];
1301
+ if interp.readonly.contains(name) {
1302
+ ewln(io.err, &format!("unset: {name}: readonly variable"));
1303
+ status = 1;
1304
+ continue;
1305
+ }
883
1306
  let key = &a[br + 1..a.len() - 1];
884
1307
  let key = key.trim_matches('"').trim_matches('\'');
885
1308
  interp.array_unset_elem(name, key);
886
1309
  continue;
887
1310
  }
888
1311
  }
1312
+ if interp.readonly.contains(a) {
1313
+ ewln(io.err, &format!("unset: {a}: readonly variable"));
1314
+ status = 1;
1315
+ continue;
1316
+ }
889
1317
  interp.vars.remove(a);
890
1318
  interp.arrays.remove(a);
891
1319
  interp.exported.remove(a);
892
1320
  interp.funcs.remove(a);
893
1321
  }
894
- 0
1322
+ status
895
1323
  }
896
1324
 
897
1325
  fn cmd_set(interp: &mut CommandContext<'_>, args: &[String], io: &mut Io) -> i32 {
@@ -979,6 +1407,7 @@ fn cmd_declare(interp: &mut CommandContext<'_>, args: &[String], io: &mut Io) ->
979
1407
  let mut assoc = false;
980
1408
  let mut indexed = false;
981
1409
  let mut print = false;
1410
+ let mut status = 0;
982
1411
  for a in args {
983
1412
  if a == "--" {
984
1413
  continue;
@@ -991,6 +1420,11 @@ fn cmd_declare(interp: &mut CommandContext<'_>, args: &[String], io: &mut Io) ->
991
1420
  }
992
1421
  // operand: NAME, NAME=value, NAME=( … ), NAME[sub]=value
993
1422
  let name = declare_name_of(a);
1423
+ if interp.readonly.contains(name) && split_decl_assign(a).is_some() {
1424
+ ewln(io.err, &format!("declare: {name}: readonly variable"));
1425
+ status = 1;
1426
+ continue;
1427
+ }
994
1428
  // Establish the array kind first so a following literal lands in the right store.
995
1429
  if assoc {
996
1430
  interp.declare_assoc(name);
@@ -1012,7 +1446,7 @@ fn cmd_declare(interp: &mut CommandContext<'_>, args: &[String], io: &mut Io) ->
1012
1446
  }
1013
1447
  }
1014
1448
  }
1015
- 0
1449
+ status
1016
1450
  }
1017
1451
 
1018
1452
  fn cmd_local(interp: &mut CommandContext<'_>, args: &[String], io: &mut Io) -> i32 {
@@ -1845,14 +2279,17 @@ fn is_shell_builtin_name(name: &str) -> bool {
1845
2279
  | "command"
1846
2280
  | "continue"
1847
2281
  | "declare"
2282
+ | "disown"
1848
2283
  | "dirs"
1849
2284
  | "echo"
1850
2285
  | "eval"
2286
+ | "exec"
1851
2287
  | "exit"
1852
2288
  | "export"
1853
2289
  | "false"
1854
2290
  | "fg"
1855
2291
  | "getopts"
2292
+ | "hash"
1856
2293
  | "jobs"
1857
2294
  | "kill"
1858
2295
  | "let"
@@ -1868,12 +2305,15 @@ fn is_shell_builtin_name(name: &str) -> bool {
1868
2305
  | "return"
1869
2306
  | "set"
1870
2307
  | "shift"
2308
+ | "shopt"
1871
2309
  | "source"
1872
2310
  | "test"
1873
2311
  | "trap"
1874
2312
  | "true"
1875
2313
  | "type"
1876
2314
  | "typeset"
2315
+ | "ulimit"
2316
+ | "umask"
1877
2317
  | "unalias"
1878
2318
  | "unset"
1879
2319
  | "wait"