dsh-ops 0.0.0-stage → 0.2.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 (139) hide show
  1. package/CHANGELOG.md +189 -0
  2. package/LICENSE +30 -0
  3. package/NOTICE +106 -0
  4. package/PROVENANCE.md +417 -0
  5. package/README.en.md +121 -0
  6. package/README.md +113 -2
  7. package/README.zh.md +114 -0
  8. package/bin/dsh-ops.mjs +1216 -0
  9. package/cordis.patch.yml +160 -0
  10. package/docs/manual-validation.md +53 -0
  11. package/docs/schema-baseline.json +64 -0
  12. package/docs/schema-current.json +84 -0
  13. package/docs/schema-measurement.md +17 -0
  14. package/dsh-plugin.json +88 -0
  15. package/icon.svg +12 -0
  16. package/lib/binary.js +409 -0
  17. package/lib/config.js +198 -0
  18. package/lib/handshake.js +252 -0
  19. package/lib/index.js +108 -0
  20. package/lib/jobs.js +42 -0
  21. package/lib/policy.js +64 -0
  22. package/lib/presentation.js +63 -0
  23. package/lib/profile-install.js +61 -0
  24. package/lib/rust.js +194 -0
  25. package/lib/session-shells.js +78 -0
  26. package/lib/shells.js +993 -0
  27. package/lib/tools.js +657 -0
  28. package/locale/en.json +6 -0
  29. package/locale/zh.json +6 -0
  30. package/package.json +114 -4
  31. package/vendor/fastctx/Cargo.lock +3210 -0
  32. package/vendor/fastctx/Cargo.toml +94 -0
  33. package/vendor/fastctx/FORK.md +119 -0
  34. package/vendor/fastctx/LICENSE-APACHE +201 -0
  35. package/vendor/fastctx/NOTICE +40 -0
  36. package/vendor/fastctx/README.md +439 -0
  37. package/vendor/fastctx/THIRD_PARTY_LICENSES.md +17 -0
  38. package/vendor/fastctx/THIRD_PARTY_LICENSES_RUST.md +7914 -0
  39. package/vendor/fastctx/UPSTREAM.md +49 -0
  40. package/vendor/fastctx/build.rs +413 -0
  41. package/vendor/fastctx/src/background_status.rs +403 -0
  42. package/vendor/fastctx/src/binary.rs +75 -0
  43. package/vendor/fastctx/src/bounded_sort.rs +500 -0
  44. package/vendor/fastctx/src/budget.rs +781 -0
  45. package/vendor/fastctx/src/cli/mod.rs +110 -0
  46. package/vendor/fastctx/src/context_guard.rs +289 -0
  47. package/vendor/fastctx/src/control/mod.rs +6 -0
  48. package/vendor/fastctx/src/control/paths.rs +49 -0
  49. package/vendor/fastctx/src/control/settings.rs +753 -0
  50. package/vendor/fastctx/src/control/transaction.rs +531 -0
  51. package/vendor/fastctx/src/edit/document.rs +535 -0
  52. package/vendor/fastctx/src/edit/locks.rs +371 -0
  53. package/vendor/fastctx/src/edit/mod.rs +213 -0
  54. package/vendor/fastctx/src/edit/private_storage/unix.rs +315 -0
  55. package/vendor/fastctx/src/edit/private_storage/windows.rs +793 -0
  56. package/vendor/fastctx/src/edit/private_storage.rs +234 -0
  57. package/vendor/fastctx/src/edit/replace.rs +1030 -0
  58. package/vendor/fastctx/src/edit_server.rs +53 -0
  59. package/vendor/fastctx/src/encoding/reference_v011.rs +587 -0
  60. package/vendor/fastctx/src/encoding/snapshot_pipeline.rs +1678 -0
  61. package/vendor/fastctx/src/encoding.rs +1118 -0
  62. package/vendor/fastctx/src/file_executor.rs +1151 -0
  63. package/vendor/fastctx/src/file_snapshot.rs +1491 -0
  64. package/vendor/fastctx/src/glob_filter.rs +98 -0
  65. package/vendor/fastctx/src/glob_tool.rs +653 -0
  66. package/vendor/fastctx/src/grep_sink.rs +1162 -0
  67. package/vendor/fastctx/src/grep_tool.rs +2449 -0
  68. package/vendor/fastctx/src/lib.rs +45 -0
  69. package/vendor/fastctx/src/main.rs +15 -0
  70. package/vendor/fastctx/src/model.rs +51 -0
  71. package/vendor/fastctx/src/model_guidance.rs +62 -0
  72. package/vendor/fastctx/src/operation.rs +356 -0
  73. package/vendor/fastctx/src/ordered_window.rs +1235 -0
  74. package/vendor/fastctx/src/os_environment.rs +414 -0
  75. package/vendor/fastctx/src/path_codec.rs +850 -0
  76. package/vendor/fastctx/src/paths.rs +244 -0
  77. package/vendor/fastctx/src/process_identity.rs +763 -0
  78. package/vendor/fastctx/src/process_policy.rs +74 -0
  79. package/vendor/fastctx/src/read_tool/batch.rs +496 -0
  80. package/vendor/fastctx/src/read_tool/hex_file.rs +141 -0
  81. package/vendor/fastctx/src/read_tool/image_file.rs +88 -0
  82. package/vendor/fastctx/src/read_tool/mod.rs +245 -0
  83. package/vendor/fastctx/src/read_tool/pdf.rs +470 -0
  84. package/vendor/fastctx/src/read_tool/pdf_disabled.rs +47 -0
  85. package/vendor/fastctx/src/read_tool/pdf_engine.rs +664 -0
  86. package/vendor/fastctx/src/read_tool/text_file.rs +351 -0
  87. package/vendor/fastctx/src/render_plan.rs +468 -0
  88. package/vendor/fastctx/src/runtime/activity.rs +159 -0
  89. package/vendor/fastctx/src/runtime/hosts.rs +99 -0
  90. package/vendor/fastctx/src/runtime/journal.rs +556 -0
  91. package/vendor/fastctx/src/runtime/local_ipc.rs +186 -0
  92. package/vendor/fastctx/src/runtime/mod.rs +746 -0
  93. package/vendor/fastctx/src/runtime/protocol.rs +296 -0
  94. package/vendor/fastctx/src/runtime/session.rs +536 -0
  95. package/vendor/fastctx/src/runtime/windows_process.rs +66 -0
  96. package/vendor/fastctx/src/search_parallelism.rs +106 -0
  97. package/vendor/fastctx/src/search_text.rs +227 -0
  98. package/vendor/fastctx/src/server.rs +359 -0
  99. package/vendor/fastctx/src/server_manifest.rs +468 -0
  100. package/vendor/fastctx/src/server_support.rs +826 -0
  101. package/vendor/fastctx/src/session.rs +629 -0
  102. package/vendor/fastctx/src/shell/apply_patch_hint.rs +41 -0
  103. package/vendor/fastctx/src/shell/bash.rs +263 -0
  104. package/vendor/fastctx/src/shell/buffer.rs +108 -0
  105. package/vendor/fastctx/src/shell/encoding.rs +403 -0
  106. package/vendor/fastctx/src/shell/foreground.rs +115 -0
  107. package/vendor/fastctx/src/shell/jobs/admission.rs +91 -0
  108. package/vendor/fastctx/src/shell/jobs/background.rs +146 -0
  109. package/vendor/fastctx/src/shell/jobs/host.rs +830 -0
  110. package/vendor/fastctx/src/shell/jobs/identity.rs +29 -0
  111. package/vendor/fastctx/src/shell/jobs/mod.rs +1513 -0
  112. package/vendor/fastctx/src/shell/jobs/model.rs +244 -0
  113. package/vendor/fastctx/src/shell/jobs/output_log.rs +1148 -0
  114. package/vendor/fastctx/src/shell/jobs/store.rs +1300 -0
  115. package/vendor/fastctx/src/shell/mod.rs +345 -0
  116. package/vendor/fastctx/src/shell/normalize.rs +389 -0
  117. package/vendor/fastctx/src/shell/output.rs +406 -0
  118. package/vendor/fastctx/src/shell/process.rs +493 -0
  119. package/vendor/fastctx/src/shell_server.rs +156 -0
  120. package/vendor/fastctx/src/skip_report.rs +83 -0
  121. package/vendor/fastctx/src/stdio_transport.rs +177 -0
  122. package/vendor/fastctx/src/tool_schema.rs +204 -0
  123. package/vendor/fastctx/src/traversal.rs +846 -0
  124. package/vendor/fastctx/third-party/pdfium-7763/LICENSE +9 -0
  125. package/vendor/fastctx/third-party/pdfium-7763/licenses/abseil.txt +202 -0
  126. package/vendor/fastctx/third-party/pdfium-7763/licenses/agg23.txt +14 -0
  127. package/vendor/fastctx/third-party/pdfium-7763/licenses/fast_float.txt +27 -0
  128. package/vendor/fastctx/third-party/pdfium-7763/licenses/freetype.txt +169 -0
  129. package/vendor/fastctx/third-party/pdfium-7763/licenses/icu.txt +542 -0
  130. package/vendor/fastctx/third-party/pdfium-7763/licenses/lcms.txt +27 -0
  131. package/vendor/fastctx/third-party/pdfium-7763/licenses/libjpeg_turbo.ijg +260 -0
  132. package/vendor/fastctx/third-party/pdfium-7763/licenses/libjpeg_turbo.md +135 -0
  133. package/vendor/fastctx/third-party/pdfium-7763/licenses/libopenjpeg.txt +32 -0
  134. package/vendor/fastctx/third-party/pdfium-7763/licenses/libpng.txt +134 -0
  135. package/vendor/fastctx/third-party/pdfium-7763/licenses/libtiff.txt +21 -0
  136. package/vendor/fastctx/third-party/pdfium-7763/licenses/llvm-libc.txt +278 -0
  137. package/vendor/fastctx/third-party/pdfium-7763/licenses/pdfium.txt +230 -0
  138. package/vendor/fastctx/third-party/pdfium-7763/licenses/simdutf.txt +18 -0
  139. package/vendor/fastctx/third-party/pdfium-7763/licenses/zlib.txt +29 -0
@@ -0,0 +1,315 @@
1
+ //! Unix private-directory creation and no-follow enforcement.
2
+
3
+ use std::ffi::{CString, OsString};
4
+ use std::fs::{self, File, OpenOptions};
5
+ use std::os::fd::{AsRawFd, FromRawFd};
6
+ use std::os::unix::ffi::OsStrExt;
7
+ use std::os::unix::fs::{MetadataExt, OpenOptionsExt, PermissionsExt};
8
+ use std::path::{Path, PathBuf};
9
+
10
+ pub(super) fn runtime_component(component: &str) -> PathBuf {
11
+ resolve_runtime_component(
12
+ component,
13
+ std::env::var_os("XDG_RUNTIME_DIR"),
14
+ std::env::temp_dir(),
15
+ )
16
+ }
17
+
18
+ pub(super) fn short_control_center_directory() -> PathBuf {
19
+ let uid = unsafe { libc::geteuid() };
20
+ PathBuf::from("/tmp").join(format!("fastctx-engine-{uid}"))
21
+ }
22
+
23
+ fn resolve_runtime_component(
24
+ component: &str,
25
+ xdg_runtime_dir: Option<OsString>,
26
+ temp_dir: PathBuf,
27
+ ) -> PathBuf {
28
+ if let Some(runtime) = xdg_runtime_dir {
29
+ let runtime = PathBuf::from(runtime);
30
+ if runtime.is_absolute() {
31
+ return runtime.join(format!("fastctx-{component}"));
32
+ }
33
+ }
34
+ let base = if temp_dir.is_absolute() {
35
+ temp_dir
36
+ } else {
37
+ PathBuf::from("/tmp")
38
+ };
39
+ let uid = unsafe { libc::geteuid() };
40
+ base.join(format!("fastctx-{component}-{uid}"))
41
+ }
42
+
43
+ pub(super) fn ensure_private_directory(path: &Path, label: &str) -> Result<(), String> {
44
+ let chain = managed_chain(path);
45
+ let root = chain.first().expect("managed chains are never empty");
46
+ let parent_path = root.parent().ok_or_else(|| {
47
+ format!(
48
+ "Cannot create the {label} directory {} because its managed root has no parent.",
49
+ crate::paths::display_path(path)
50
+ )
51
+ })?;
52
+
53
+ let mut parent_options = OpenOptions::new();
54
+ parent_options
55
+ .read(true)
56
+ .custom_flags(libc::O_DIRECTORY | libc::O_CLOEXEC);
57
+ let root_parent = parent_options.open(parent_path).map_err(|error| {
58
+ format!(
59
+ "Cannot open the parent of the {label} directory {}: {error}",
60
+ crate::paths::display_path(parent_path)
61
+ )
62
+ })?;
63
+
64
+ let mut opened_chain: Vec<File> = Vec::with_capacity(chain.len());
65
+ for directory_path in &chain {
66
+ let name = directory_path.file_name().ok_or_else(|| {
67
+ format!(
68
+ "Cannot create the {label} directory {} because a managed component has no name.",
69
+ crate::paths::display_path(directory_path)
70
+ )
71
+ })?;
72
+ let name = CString::new(name.as_bytes()).map_err(|_| {
73
+ format!(
74
+ "Cannot create the {label} directory {} because a path component contains a NUL byte.",
75
+ crate::paths::display_path(directory_path)
76
+ )
77
+ })?;
78
+ let parent_fd = opened_chain
79
+ .last()
80
+ .map_or(root_parent.as_raw_fd(), AsRawFd::as_raw_fd);
81
+ let created = unsafe { libc::mkdirat(parent_fd, name.as_ptr(), 0o700) };
82
+ if created != 0 {
83
+ let error = std::io::Error::last_os_error();
84
+ if error.kind() != std::io::ErrorKind::AlreadyExists {
85
+ return Err(format!(
86
+ "Cannot create the {label} directory {}: {error}",
87
+ crate::paths::display_path(directory_path)
88
+ ));
89
+ }
90
+ }
91
+
92
+ super::verify_directory(directory_path, label)?;
93
+ let fd = unsafe {
94
+ libc::openat(
95
+ parent_fd,
96
+ name.as_ptr(),
97
+ libc::O_RDONLY | libc::O_DIRECTORY | libc::O_NOFOLLOW | libc::O_CLOEXEC,
98
+ )
99
+ };
100
+ if fd < 0 {
101
+ return Err(format!(
102
+ "Cannot securely open the {label} directory {}: {}",
103
+ crate::paths::display_path(directory_path),
104
+ std::io::Error::last_os_error()
105
+ ));
106
+ }
107
+ let directory = unsafe { File::from_raw_fd(fd) };
108
+ let opened = directory.metadata().map_err(|error| {
109
+ format!(
110
+ "Cannot inspect the open {label} directory {}: {error}",
111
+ crate::paths::display_path(directory_path)
112
+ )
113
+ })?;
114
+ if !opened.is_dir() {
115
+ return Err(format!(
116
+ "Cannot use the {label} path {} because it is not a private directory. Remove it and retry.",
117
+ crate::paths::display_path(directory_path)
118
+ ));
119
+ }
120
+ directory
121
+ .set_permissions(fs::Permissions::from_mode(0o700))
122
+ .map_err(|error| {
123
+ format!(
124
+ "Cannot secure the {label} directory {}: {error}",
125
+ crate::paths::display_path(directory_path)
126
+ )
127
+ })?;
128
+
129
+ let secured = directory.metadata().map_err(|error| {
130
+ format!(
131
+ "Cannot verify the secured {label} directory {}: {error}",
132
+ crate::paths::display_path(directory_path)
133
+ )
134
+ })?;
135
+ if secured.permissions().mode() & 0o777 != 0o700 {
136
+ return Err(format!(
137
+ "Cannot secure the {label} directory {} because its permissions are not owner-only after chmod.",
138
+ crate::paths::display_path(directory_path)
139
+ ));
140
+ }
141
+
142
+ let current = fs::symlink_metadata(directory_path).map_err(|error| {
143
+ format!(
144
+ "Cannot re-inspect the {label} directory {} after securing it: {error}",
145
+ crate::paths::display_path(directory_path)
146
+ )
147
+ })?;
148
+ super::validate_directory_metadata(directory_path, label, &current)?;
149
+ if opened.dev() != secured.dev()
150
+ || opened.ino() != secured.ino()
151
+ || secured.dev() != current.dev()
152
+ || secured.ino() != current.ino()
153
+ {
154
+ return Err(format!(
155
+ "Cannot secure the {label} directory {} because it changed while permissions were applied. Retry after the path stops changing.",
156
+ crate::paths::display_path(directory_path)
157
+ ));
158
+ }
159
+ opened_chain.push(directory);
160
+ }
161
+ Ok(())
162
+ }
163
+
164
+ fn managed_chain(path: &Path) -> Vec<PathBuf> {
165
+ let root = path
166
+ .ancestors()
167
+ .find_map(|ancestor| {
168
+ let parent = ancestor.parent()?;
169
+ if ancestor.file_name().is_some_and(|name| name == "runtime")
170
+ && parent.file_name().is_some_and(|name| name == "fastctx")
171
+ {
172
+ Some(parent)
173
+ } else if ancestor.file_name().is_some_and(|name| name == ".fastctx") {
174
+ Some(ancestor)
175
+ } else {
176
+ None
177
+ }
178
+ })
179
+ .unwrap_or(path);
180
+ let mut chain = Vec::new();
181
+ for ancestor in path.ancestors() {
182
+ chain.push(ancestor.to_path_buf());
183
+ if ancestor == root {
184
+ break;
185
+ }
186
+ }
187
+ chain.reverse();
188
+ chain
189
+ }
190
+
191
+ pub(super) fn configure_no_follow(options: &mut OpenOptions) {
192
+ options.custom_flags(libc::O_NOFOLLOW | libc::O_CLOEXEC);
193
+ }
194
+
195
+ pub(super) fn is_reparse_or_symlink(metadata: &fs::Metadata) -> bool {
196
+ metadata.file_type().is_symlink()
197
+ }
198
+
199
+ #[cfg(test)]
200
+ mod tests {
201
+ use super::{ensure_private_directory, resolve_runtime_component};
202
+ use std::ffi::OsString;
203
+ use std::os::unix::fs::{PermissionsExt, symlink};
204
+ use std::path::PathBuf;
205
+
206
+ #[test]
207
+ fn private_directory_mode_is_owner_only() {
208
+ let temp = tempfile::tempdir().unwrap();
209
+ let directory = temp.path().join("edit-locks");
210
+ ensure_private_directory(&directory, "edit-lock").unwrap();
211
+ let mode = std::fs::metadata(directory).unwrap().permissions().mode() & 0o777;
212
+ assert_eq!(mode, 0o700);
213
+ }
214
+
215
+ #[test]
216
+ fn every_new_directory_in_the_managed_chain_starts_owner_only() {
217
+ let temp = tempfile::tempdir().unwrap();
218
+ let first = temp.path().join("fastctx");
219
+ let second = first.join("runtime");
220
+ let directory = second.join("edit-locks");
221
+ ensure_private_directory(&directory, "edit-lock").unwrap();
222
+
223
+ for managed in [first, second, directory] {
224
+ let mode = std::fs::metadata(managed).unwrap().permissions().mode() & 0o777;
225
+ assert_eq!(mode, 0o700);
226
+ }
227
+ }
228
+
229
+ #[test]
230
+ fn existing_managed_chain_is_converged_to_owner_only() {
231
+ let temp = tempfile::tempdir().unwrap();
232
+ let parent = temp.path().join(".fastctx");
233
+ let directory = parent.join("jobs");
234
+ std::fs::create_dir_all(&directory).unwrap();
235
+ for managed in [&parent, &directory] {
236
+ std::fs::set_permissions(managed, std::fs::Permissions::from_mode(0o755)).unwrap();
237
+ }
238
+
239
+ ensure_private_directory(&directory, "background job registry").unwrap();
240
+
241
+ for managed in [parent, directory] {
242
+ let mode = std::fs::metadata(managed).unwrap().permissions().mode() & 0o777;
243
+ assert_eq!(mode, 0o700);
244
+ }
245
+ }
246
+
247
+ #[test]
248
+ fn managed_symlink_anchor_is_rejected_before_creating_children() {
249
+ let temp = tempfile::tempdir().unwrap();
250
+ let home = temp.path().join("home");
251
+ let outside = temp.path().join("outside");
252
+ std::fs::create_dir(&home).unwrap();
253
+ std::fs::create_dir(&outside).unwrap();
254
+ let anchor = home.join(".fastctx");
255
+ symlink(&outside, &anchor).unwrap();
256
+
257
+ let error =
258
+ ensure_private_directory(&anchor.join("jobs"), "background job registry").unwrap_err();
259
+ assert!(error.contains("not a private directory"), "{error}");
260
+ assert!(!outside.join("jobs").exists());
261
+ }
262
+
263
+ #[test]
264
+ fn symlink_in_the_trusted_runtime_base_remains_supported() {
265
+ let temp = tempfile::tempdir().unwrap();
266
+ let real_base = temp.path().join("real-runtime");
267
+ std::fs::create_dir(&real_base).unwrap();
268
+ let linked_base = temp.path().join("runtime-link");
269
+ symlink(&real_base, &linked_base).unwrap();
270
+ let directory = linked_base.join("fastctx-edit-locks");
271
+
272
+ ensure_private_directory(&directory, "edit-lock").unwrap();
273
+
274
+ let actual = real_base.join("fastctx-edit-locks");
275
+ assert!(actual.is_dir());
276
+ let mode = std::fs::metadata(actual).unwrap().permissions().mode() & 0o777;
277
+ assert_eq!(mode, 0o700);
278
+ }
279
+
280
+ #[test]
281
+ fn directory_symlink_is_rejected_without_chmodding_the_target() {
282
+ let temp = tempfile::tempdir().unwrap();
283
+ let target = temp.path().join("outside");
284
+ std::fs::create_dir(&target).unwrap();
285
+ std::fs::set_permissions(&target, std::fs::Permissions::from_mode(0o755)).unwrap();
286
+ let link = temp.path().join("edit-locks");
287
+ symlink(&target, &link).unwrap();
288
+
289
+ let error = ensure_private_directory(&link, "edit-lock").unwrap_err();
290
+ assert!(error.contains("not a private directory"), "{error}");
291
+ let target_mode = std::fs::metadata(target).unwrap().permissions().mode() & 0o777;
292
+ assert_eq!(target_mode, 0o755);
293
+ }
294
+
295
+ #[test]
296
+ fn relative_runtime_environment_never_produces_a_relative_lock_path() {
297
+ let path = resolve_runtime_component(
298
+ "edit-locks",
299
+ Some(OsString::from("relative-xdg")),
300
+ PathBuf::from("relative-temp"),
301
+ );
302
+ assert!(path.is_absolute(), "{}", path.display());
303
+ assert!(path.starts_with("/tmp"), "{}", path.display());
304
+ }
305
+
306
+ #[test]
307
+ fn absolute_xdg_runtime_directory_is_used_without_an_architecture_split() {
308
+ let path = resolve_runtime_component(
309
+ "edit-locks",
310
+ Some(OsString::from("/run/user/1234")),
311
+ PathBuf::from("/ignored"),
312
+ );
313
+ assert_eq!(path, PathBuf::from("/run/user/1234/fastctx-edit-locks"));
314
+ }
315
+ }