tensorfs 0.3.0__tar.gz → 0.3.1__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.
- {tensorfs-0.3.0 → tensorfs-0.3.1}/Cargo.lock +2 -2
- {tensorfs-0.3.0 → tensorfs-0.3.1}/Cargo.toml +1 -1
- {tensorfs-0.3.0 → tensorfs-0.3.1}/PKG-INFO +1 -1
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/bin/tfs/ingest.rs +1 -1
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/bin/tfs/main.rs +221 -0
- tensorfs-0.3.1/crates/tensorfs-core/src/home.rs +195 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/lib.rs +2 -0
- tensorfs-0.3.1/crates/tensorfs-core/src/repo_cache.rs +337 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/store.rs +261 -20
- tensorfs-0.3.1/crates/tensorfs-core/tests/home.rs +195 -0
- tensorfs-0.3.1/crates/tensorfs-core/tests/repo_cache.rs +373 -0
- tensorfs-0.3.1/crates/tensorfs-core/tests/store_boundaries.rs +268 -0
- tensorfs-0.3.1/crates/tensorfs-core/tests/version.rs +34 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-py/src/lib.rs +9 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-py/src/store.rs +91 -11
- {tensorfs-0.3.0 → tensorfs-0.3.1}/pyproject.toml +7 -1
- {tensorfs-0.3.0 → tensorfs-0.3.1}/python/tensorfs/__init__.py +94 -9
- tensorfs-0.3.1/python/tensorfs/_ext.pyi +41 -0
- tensorfs-0.3.1/python/tensorfs/py.typed +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/Cargo.toml +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/b64.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/bin/tfs/ckpt.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/bin/tfs/conform.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/bin/tfs/fetch.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/bin/tfs/rd.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/bin/tfs/repo.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/bin/tfs/snap.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/bin/tfs/watch.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/canon.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/capability.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/catalog.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/cbor.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/checkpoint.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/corpus.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/derived.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/dtype.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/err.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/fetch.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/fill.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/fit.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/gc.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/header.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/ids.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/ingest/carrier.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/ingest/convert.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/ingest/fingerprint.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/ingest/gguf.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/ingest/mod.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/ingest/source.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/ingest/stamp.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/ingest/transaction.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/jcs.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/limits.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/machine.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/manifest.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/meta.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/project.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/read.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/receipt.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/registry.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/relation.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/repository.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/sha256.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/spec.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/storage.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/vectors.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/tests/fit.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/tests/fixtures/repo_apply/blobs.jsonl +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_list.jsonl +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_repository.json +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_rows.jsonl +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/tests/fixtures/repo_apply/header.cbor +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/tests/fixtures/repo_apply/manifest.json +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/tests/fixtures/repo_apply/mutation.json +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/tests/liveness.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/tests/repo_store.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/tests/source_plan.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/tests/tensor_schema.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-py/Cargo.toml +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-py/src/docs.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-py/src/errors.rs +0 -0
- {tensorfs-0.3.0 → tensorfs-0.3.1}/python/tensorfs/errors.py +0 -0
|
@@ -249,7 +249,7 @@ checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
|
|
|
249
249
|
|
|
250
250
|
[[package]]
|
|
251
251
|
name = "tensorfs-core"
|
|
252
|
-
version = "0.3.
|
|
252
|
+
version = "0.3.1"
|
|
253
253
|
dependencies = [
|
|
254
254
|
"fs2",
|
|
255
255
|
"minicbor",
|
|
@@ -258,7 +258,7 @@ dependencies = [
|
|
|
258
258
|
|
|
259
259
|
[[package]]
|
|
260
260
|
name = "tensorfs-py"
|
|
261
|
-
version = "0.3.
|
|
261
|
+
version = "0.3.1"
|
|
262
262
|
dependencies = [
|
|
263
263
|
"pyo3",
|
|
264
264
|
"tensorfs-core",
|
|
@@ -1282,7 +1282,7 @@ pub fn cmd_worker(root: &Path, target: &str, f: &Flags) -> ExitCode {
|
|
|
1282
1282
|
}
|
|
1283
1283
|
let session = session_of(f, &p);
|
|
1284
1284
|
let tenant = flag(f, "tenant").unwrap_or("dev").to_string();
|
|
1285
|
-
let mut store = ok!(Store::
|
|
1285
|
+
let mut store = ok!(Store::open(root));
|
|
1286
1286
|
if flag_on(f, "progress") {
|
|
1287
1287
|
store = store.observed(Arc::new(Reporter::default()));
|
|
1288
1288
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
//! tfs — TensorFS store and CozyTensors inspection tool.
|
|
2
2
|
//!
|
|
3
|
+
//! tfs version release version + sha256 of this binary, the skew observable
|
|
3
4
|
//! tfs registry print the seeded platform namespace (alias -> digest)
|
|
4
5
|
//! tfs id <file> object id (+ derived ids) of a stored document
|
|
5
6
|
//! tfs cbor encode|decode|check <file> header conformance seam; JSON is diagnostic only
|
|
@@ -10,7 +11,15 @@
|
|
|
10
11
|
//! --size <bytes> --small <n>, plus the dev-only kill points --fault <stage> --ready <path>.
|
|
11
12
|
//!
|
|
12
13
|
//! tfs store init|info <root>
|
|
14
|
+
//! tfs store ensure [<root>]
|
|
15
|
+
//! tfs store prepare-readers [<root>]
|
|
16
|
+
//! tfs store complete-cozytensors [<root>]
|
|
17
|
+
//! tfs home print TENSORFS_HOME or ~/.tensorfs
|
|
18
|
+
//! tfs home migrate <old-root> [--to <new-root>]
|
|
13
19
|
//! tfs store rebuild <census> --rows <jsonl> [--sqlite <tensorfs.sqlite>]
|
|
20
|
+
//! tfs store admit-file <store> blob|manifest <id> <length> <source>
|
|
21
|
+
//! tfs repo-cache admit <cache> <store> blob|manifest <id> <length>
|
|
22
|
+
//! tfs repo-cache backfill <cache> <store> blob|manifest <id> <length>
|
|
14
23
|
//! tfs put <root> <file> stream one object in (any shape)
|
|
15
24
|
//! tfs get <root> <hex> verified read, whole or ranged
|
|
16
25
|
//! tfs contains <root> <hex> the presence HINT, and what it is worth
|
|
@@ -94,6 +103,7 @@ use tensorfs_core::ingest::stamp::Stamp;
|
|
|
94
103
|
use tensorfs_core::ingest::{Evidence, IngestProfile, IngestSubject, IngestVerificationReceipt};
|
|
95
104
|
use tensorfs_core::manifest::{Draft, Entry, Manifest};
|
|
96
105
|
use tensorfs_core::registry;
|
|
106
|
+
use tensorfs_core::repo_cache::{CacheKind, RepoObjectCache};
|
|
97
107
|
use tensorfs_core::spec::EncodingSpec;
|
|
98
108
|
use tensorfs_core::store::{Fault, Store, Verdict, FAULT_STAGES};
|
|
99
109
|
|
|
@@ -123,9 +133,30 @@ fn main() -> ExitCode {
|
|
|
123
133
|
let args = accept_printed_ids(args);
|
|
124
134
|
let a: Vec<&str> = args.iter().map(|s| s.as_str()).collect();
|
|
125
135
|
match a.as_slice() {
|
|
136
|
+
["version"] => cmd_version(),
|
|
137
|
+
["home"] => cmd_home(None),
|
|
138
|
+
["home", "migrate", source] => cmd_home_migrate(Path::new(source), &flags),
|
|
139
|
+
["home", r] => cmd_home(Some(Path::new(r))),
|
|
140
|
+
["store", "init"] => cmd_store_default("init"),
|
|
141
|
+
["store", "info"] => cmd_store_default("info"),
|
|
142
|
+
["store", "ensure"] => cmd_store_default("ensure"),
|
|
143
|
+
["store", "prepare-readers"] => cmd_store_prepare_readers(None),
|
|
144
|
+
["store", "complete-cozytensors"] => cmd_store_complete_cozytensors(None),
|
|
126
145
|
["store", "init", r] => cmd_store(Path::new(r), "init"),
|
|
127
146
|
["store", "info", r] => cmd_store(Path::new(r), "info"),
|
|
147
|
+
["store", "ensure", r] => cmd_store(Path::new(r), "ensure"),
|
|
148
|
+
["store", "prepare-readers", r] => cmd_store_prepare_readers(Some(Path::new(r))),
|
|
149
|
+
["store", "complete-cozytensors", r] => cmd_store_complete_cozytensors(Some(Path::new(r))),
|
|
128
150
|
["store", "rebuild", r] => repo::cmd_rebuild(Path::new(r), &flags),
|
|
151
|
+
["store", "admit-file", store, kind, object, length, source] => {
|
|
152
|
+
cmd_store_admit_file(Path::new(store), kind, object, length, Path::new(source))
|
|
153
|
+
}
|
|
154
|
+
["repo-cache", "admit", cache, store, kind, object, length] => {
|
|
155
|
+
cmd_repo_cache_admit(Path::new(cache), Path::new(store), kind, object, length)
|
|
156
|
+
}
|
|
157
|
+
["repo-cache", "backfill", cache, store, kind, object, length] => {
|
|
158
|
+
cmd_repo_cache_backfill(Path::new(cache), Path::new(store), kind, object, length)
|
|
159
|
+
}
|
|
129
160
|
["put", r, f] => cmd_put(Path::new(r), Path::new(f), &flags),
|
|
130
161
|
["get", r, h] => cmd_get(Path::new(r), h, &flags),
|
|
131
162
|
["contains", r, h] => cmd_contains(Path::new(r), h),
|
|
@@ -255,6 +286,157 @@ fn main() -> ExitCode {
|
|
|
255
286
|
}
|
|
256
287
|
}
|
|
257
288
|
|
|
289
|
+
/// `tfs <version> sha256:<digest of this binary's own bytes>` — one line, machine-parsed by
|
|
290
|
+
/// tensorhub and cozy-creator at startup (tfs-051). Byte-identical builds print the same
|
|
291
|
+
/// digest; two builds stamped with one version cannot hide behind it.
|
|
292
|
+
fn cmd_version() -> ExitCode {
|
|
293
|
+
let bytes = match std::env::current_exe().and_then(fs::read) {
|
|
294
|
+
Ok(bytes) => bytes,
|
|
295
|
+
Err(error) => {
|
|
296
|
+
eprintln!("REFUSED IO_FAILED: read own binary: {error}");
|
|
297
|
+
return ExitCode::from(1);
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
println!(
|
|
301
|
+
"tfs {} sha256:{}",
|
|
302
|
+
env!("CARGO_PKG_VERSION"),
|
|
303
|
+
tensorfs_core::sha256::hex_digest(&bytes)
|
|
304
|
+
);
|
|
305
|
+
ExitCode::SUCCESS
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
fn cache_args(kind: &str, object: &str, length: &str) -> TfsResult<(CacheKind, ObjectRef)> {
|
|
309
|
+
let kind = CacheKind::parse(kind).ok_or_else(|| Refusal {
|
|
310
|
+
code: tensorfs_core::err::Code::WRONG_TYPE,
|
|
311
|
+
detail: format!("repo cache kind {kind:?} is not blob or manifest"),
|
|
312
|
+
})?;
|
|
313
|
+
let sha256 = tensorfs_core::ids::hex64("repo cache object", object)?;
|
|
314
|
+
let length = length.parse::<u64>().map_err(|_| Refusal {
|
|
315
|
+
code: tensorfs_core::err::Code::NUMBER_RANGE,
|
|
316
|
+
detail: format!("repo cache length {length:?} is not an unsigned integer"),
|
|
317
|
+
})?;
|
|
318
|
+
Ok((kind, ObjectRef { sha256, length }))
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
fn cmd_repo_cache_admit(
|
|
322
|
+
cache: &Path,
|
|
323
|
+
store: &Path,
|
|
324
|
+
kind: &str,
|
|
325
|
+
object: &str,
|
|
326
|
+
length: &str,
|
|
327
|
+
) -> ExitCode {
|
|
328
|
+
let (kind, object) = match cache_args(kind, object, length) {
|
|
329
|
+
Ok(value) => value,
|
|
330
|
+
Err(error) => return bail(error),
|
|
331
|
+
};
|
|
332
|
+
let store = match Store::open(store) {
|
|
333
|
+
Ok(value) => value,
|
|
334
|
+
Err(error) => return bail(error),
|
|
335
|
+
};
|
|
336
|
+
let status = match RepoObjectCache::new(cache).admit(&store, kind, &object) {
|
|
337
|
+
Ok(value) => value,
|
|
338
|
+
Err(error) => return bail(error),
|
|
339
|
+
};
|
|
340
|
+
println!("{{\"status\":\"{}\"}}", status.as_str());
|
|
341
|
+
ExitCode::SUCCESS
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
fn cmd_repo_cache_backfill(
|
|
345
|
+
cache: &Path,
|
|
346
|
+
store: &Path,
|
|
347
|
+
kind: &str,
|
|
348
|
+
object: &str,
|
|
349
|
+
length: &str,
|
|
350
|
+
) -> ExitCode {
|
|
351
|
+
let (kind, object) = match cache_args(kind, object, length) {
|
|
352
|
+
Ok(value) => value,
|
|
353
|
+
Err(error) => return bail(error),
|
|
354
|
+
};
|
|
355
|
+
let store = match Store::open(store) {
|
|
356
|
+
Ok(value) => value,
|
|
357
|
+
Err(error) => return bail(error),
|
|
358
|
+
};
|
|
359
|
+
let status = match RepoObjectCache::new(cache).backfill_store(&store, kind, &object) {
|
|
360
|
+
Ok(value) => value,
|
|
361
|
+
Err(error) => return bail(error),
|
|
362
|
+
};
|
|
363
|
+
println!("{{\"status\":\"{}\"}}", status.as_str());
|
|
364
|
+
ExitCode::SUCCESS
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
fn cmd_store_admit_file(
|
|
368
|
+
store: &Path,
|
|
369
|
+
kind: &str,
|
|
370
|
+
object: &str,
|
|
371
|
+
length: &str,
|
|
372
|
+
source: &Path,
|
|
373
|
+
) -> ExitCode {
|
|
374
|
+
let (kind, object) = match cache_args(kind, object, length) {
|
|
375
|
+
Ok(value) => value,
|
|
376
|
+
Err(error) => return bail(error),
|
|
377
|
+
};
|
|
378
|
+
let store = match Store::open(store) {
|
|
379
|
+
Ok(value) => value,
|
|
380
|
+
Err(error) => return bail(error),
|
|
381
|
+
};
|
|
382
|
+
let admitted = match kind {
|
|
383
|
+
CacheKind::Blob => match store.put_file(source, Some(&object), &Fault::default()) {
|
|
384
|
+
Ok(value) => value.admitted,
|
|
385
|
+
Err(error) => return bail(error),
|
|
386
|
+
},
|
|
387
|
+
CacheKind::Manifest => {
|
|
388
|
+
let metadata = match fs::metadata(source) {
|
|
389
|
+
Ok(metadata) => metadata,
|
|
390
|
+
Err(error) => {
|
|
391
|
+
return bail(Refusal {
|
|
392
|
+
code: tensorfs_core::err::Code::IO_FAILED,
|
|
393
|
+
detail: format!("stat {}: {error}", source.display()),
|
|
394
|
+
})
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
if metadata.len() != object.length || object.length > Manifest::MAX_BYTES as u64 {
|
|
398
|
+
return bail(Refusal {
|
|
399
|
+
code: tensorfs_core::err::Code::LENGTH_MISMATCH,
|
|
400
|
+
detail: format!(
|
|
401
|
+
"{} is {} bytes, expected {}",
|
|
402
|
+
source.display(),
|
|
403
|
+
metadata.len(),
|
|
404
|
+
object.length
|
|
405
|
+
),
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
let bytes = match fs::read(source) {
|
|
409
|
+
Ok(bytes) => bytes,
|
|
410
|
+
Err(error) => {
|
|
411
|
+
return bail(Refusal {
|
|
412
|
+
code: tensorfs_core::err::Code::IO_FAILED,
|
|
413
|
+
detail: format!("read {}: {error}", source.display()),
|
|
414
|
+
})
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
let manifest = match Manifest::parse(&bytes) {
|
|
418
|
+
Ok(value) => value,
|
|
419
|
+
Err(error) => return bail(error),
|
|
420
|
+
};
|
|
421
|
+
if ObjectRef::of(&bytes) != object {
|
|
422
|
+
return bail(Refusal {
|
|
423
|
+
code: tensorfs_core::err::Code::OBJECT_ID_MISMATCH,
|
|
424
|
+
detail: format!("{} bytes disagree with {}", source.display(), object.id()),
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
match store.put_manifest(&manifest) {
|
|
428
|
+
Ok(value) => value.admitted,
|
|
429
|
+
Err(error) => return bail(error),
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
println!(
|
|
434
|
+
"{{\"status\":\"{}\"}}",
|
|
435
|
+
if admitted { "stored" } else { "present" }
|
|
436
|
+
);
|
|
437
|
+
ExitCode::SUCCESS
|
|
438
|
+
}
|
|
439
|
+
|
|
258
440
|
fn cmd_config_canonicalize(path: &Path, flags: &Flags) -> ExitCode {
|
|
259
441
|
let Some(output) = flag(flags, "out") else {
|
|
260
442
|
eprintln!("--out <canonical.json> is required");
|
|
@@ -336,9 +518,30 @@ mod repo;
|
|
|
336
518
|
mod snap;
|
|
337
519
|
mod watch;
|
|
338
520
|
|
|
521
|
+
fn cmd_home(explicit: Option<&Path>) -> ExitCode {
|
|
522
|
+
println!("{}", ok!(tensorfs_core::home::resolve(explicit)).display());
|
|
523
|
+
ExitCode::SUCCESS
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
fn cmd_home_migrate(source: &Path, flags: &Flags) -> ExitCode {
|
|
527
|
+
let destination = flag(flags, "to").map(Path::new);
|
|
528
|
+
let report = ok!(tensorfs_core::home::move_store(source, destination));
|
|
529
|
+
println!(
|
|
530
|
+
"moved {} -> {} (device/inode/mtime preserved by same-filesystem rename)",
|
|
531
|
+
report.source.display(),
|
|
532
|
+
report.destination.display(),
|
|
533
|
+
);
|
|
534
|
+
ExitCode::SUCCESS
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
fn cmd_store_default(verb: &str) -> ExitCode {
|
|
538
|
+
cmd_store(&ok!(tensorfs_core::home::resolve(None)), verb)
|
|
539
|
+
}
|
|
540
|
+
|
|
339
541
|
fn cmd_store(root: &Path, verb: &str) -> ExitCode {
|
|
340
542
|
let s = match verb {
|
|
341
543
|
"init" => ok!(Store::init(root)),
|
|
544
|
+
"ensure" => ok!(Store::ensure(root)),
|
|
342
545
|
_ => ok!(Store::open(root)),
|
|
343
546
|
};
|
|
344
547
|
println!("root: {}", s.root().display());
|
|
@@ -346,6 +549,24 @@ fn cmd_store(root: &Path, verb: &str) -> ExitCode {
|
|
|
346
549
|
ExitCode::SUCCESS
|
|
347
550
|
}
|
|
348
551
|
|
|
552
|
+
fn cmd_store_prepare_readers(explicit: Option<&Path>) -> ExitCode {
|
|
553
|
+
let root = ok!(tensorfs_core::home::resolve(explicit));
|
|
554
|
+
let store = ok!(Store::open(&root));
|
|
555
|
+
ok!(store.prepare_readers());
|
|
556
|
+
println!("root: {}", store.root().display());
|
|
557
|
+
println!("readers: prepared");
|
|
558
|
+
ExitCode::SUCCESS
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
fn cmd_store_complete_cozytensors(explicit: Option<&Path>) -> ExitCode {
|
|
562
|
+
let root = ok!(tensorfs_core::home::resolve(explicit));
|
|
563
|
+
let store = ok!(Store::open(&root));
|
|
564
|
+
for manifest in ok!(store.complete_cozytensors_manifests()) {
|
|
565
|
+
println!("{manifest}");
|
|
566
|
+
}
|
|
567
|
+
ExitCode::SUCCESS
|
|
568
|
+
}
|
|
569
|
+
|
|
349
570
|
fn fault_of(f: &Flags) -> Fault {
|
|
350
571
|
Fault {
|
|
351
572
|
stage: flag(f, "fault").map(|s| s.to_string()),
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
//! TensorFS-owned default Store location.
|
|
2
|
+
//!
|
|
3
|
+
//! Core Store operations remain explicit-root. This resolver exists for user-facing entrypoints
|
|
4
|
+
//! and never consults another product's home or configuration.
|
|
5
|
+
|
|
6
|
+
use std::fs;
|
|
7
|
+
use std::os::unix::fs::MetadataExt;
|
|
8
|
+
use std::path::{Path, PathBuf};
|
|
9
|
+
|
|
10
|
+
use crate::catalog::WriterGuard;
|
|
11
|
+
use crate::err::{refuse, Code, Result};
|
|
12
|
+
use crate::store::{self, Store};
|
|
13
|
+
|
|
14
|
+
pub const ENV: &str = "TENSORFS_HOME";
|
|
15
|
+
|
|
16
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
17
|
+
pub struct MoveReport {
|
|
18
|
+
pub source: PathBuf,
|
|
19
|
+
pub destination: PathBuf,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/// Resolve an optional caller-supplied Store root.
|
|
23
|
+
///
|
|
24
|
+
/// Precedence is explicit argument, `TENSORFS_HOME`, then `$HOME/.tensorfs`.
|
|
25
|
+
pub fn resolve(explicit: Option<&Path>) -> Result<PathBuf> {
|
|
26
|
+
resolve_values(
|
|
27
|
+
explicit,
|
|
28
|
+
std::env::var_os(ENV).map(PathBuf::from).as_deref(),
|
|
29
|
+
std::env::var_os("HOME").map(PathBuf::from).as_deref(),
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
fn resolve_values(
|
|
34
|
+
explicit: Option<&Path>,
|
|
35
|
+
tensorfs_home: Option<&Path>,
|
|
36
|
+
user_home: Option<&Path>,
|
|
37
|
+
) -> Result<PathBuf> {
|
|
38
|
+
if let Some(root) = explicit {
|
|
39
|
+
return nonempty("explicit TensorFS root", root);
|
|
40
|
+
}
|
|
41
|
+
if let Some(root) = tensorfs_home {
|
|
42
|
+
return nonempty(ENV, root);
|
|
43
|
+
}
|
|
44
|
+
match user_home {
|
|
45
|
+
Some(root) if !root.as_os_str().is_empty() => Ok(root.join(".tensorfs")),
|
|
46
|
+
_ => refuse(
|
|
47
|
+
Code::IO_FAILED,
|
|
48
|
+
"TensorFS root is absent: pass one explicitly, set TENSORFS_HOME, or set HOME",
|
|
49
|
+
),
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
fn nonempty(source: &str, root: &Path) -> Result<PathBuf> {
|
|
54
|
+
if root.as_os_str().is_empty() {
|
|
55
|
+
return refuse(Code::IO_FAILED, format!("{source} is empty"));
|
|
56
|
+
}
|
|
57
|
+
Ok(root.to_path_buf())
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/// Move one stopped Store into the resolved TensorFS home without copying or rehashing it.
|
|
61
|
+
///
|
|
62
|
+
/// The destination must not exist and both parents must be on the same filesystem. The Store's
|
|
63
|
+
/// recovery lock refuses already-active readers and writers. This is still an offline operator
|
|
64
|
+
/// command: possible Store openers must be stopped so none can recreate the retired pathname
|
|
65
|
+
/// after the rename. No redirect or reservation is left at the old path.
|
|
66
|
+
pub fn move_store(source: &Path, explicit_destination: Option<&Path>) -> Result<MoveReport> {
|
|
67
|
+
let source_metadata = fs::symlink_metadata(source).map_err(|error| io("stat source", error))?;
|
|
68
|
+
if source_metadata.file_type().is_symlink() || !source_metadata.is_dir() {
|
|
69
|
+
return refuse(
|
|
70
|
+
Code::STORE_ERA,
|
|
71
|
+
format!("{} is not a TensorFS Store directory", source.display()),
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
let source = fs::canonicalize(source).map_err(|error| io("resolve source", error))?;
|
|
75
|
+
Store::open(&source)?;
|
|
76
|
+
|
|
77
|
+
let requested_destination = resolve(explicit_destination)?;
|
|
78
|
+
match fs::symlink_metadata(&requested_destination) {
|
|
79
|
+
Ok(_) => {
|
|
80
|
+
return refuse(
|
|
81
|
+
Code::IO_FAILED,
|
|
82
|
+
format!(
|
|
83
|
+
"destination {} already exists; remove the conflict or choose another root",
|
|
84
|
+
requested_destination.display()
|
|
85
|
+
),
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
|
|
89
|
+
Err(error) => return Err(io("stat destination", error)),
|
|
90
|
+
}
|
|
91
|
+
let destination_name =
|
|
92
|
+
requested_destination
|
|
93
|
+
.file_name()
|
|
94
|
+
.ok_or_else(|| crate::err::Refusal {
|
|
95
|
+
code: Code::IO_FAILED,
|
|
96
|
+
detail: format!(
|
|
97
|
+
"destination {} has no final path component",
|
|
98
|
+
requested_destination.display()
|
|
99
|
+
),
|
|
100
|
+
})?;
|
|
101
|
+
let destination_parent = requested_destination
|
|
102
|
+
.parent()
|
|
103
|
+
.filter(|path| !path.as_os_str().is_empty())
|
|
104
|
+
.unwrap_or_else(|| Path::new("."));
|
|
105
|
+
let destination_parent = fs::canonicalize(destination_parent)
|
|
106
|
+
.map_err(|error| io("resolve destination parent", error))?;
|
|
107
|
+
let destination = destination_parent.join(destination_name);
|
|
108
|
+
if source == destination {
|
|
109
|
+
return refuse(Code::IO_FAILED, "source and destination are the same Store");
|
|
110
|
+
}
|
|
111
|
+
let source_parent = source.parent().ok_or_else(|| crate::err::Refusal {
|
|
112
|
+
code: Code::IO_FAILED,
|
|
113
|
+
detail: format!("source {} has no parent", source.display()),
|
|
114
|
+
})?;
|
|
115
|
+
let source_device = fs::metadata(&source)
|
|
116
|
+
.map_err(|error| io("stat source", error))?
|
|
117
|
+
.dev();
|
|
118
|
+
let destination_device = fs::metadata(&destination_parent)
|
|
119
|
+
.map_err(|error| io("stat destination parent", error))?
|
|
120
|
+
.dev();
|
|
121
|
+
if source_device != destination_device {
|
|
122
|
+
return refuse(
|
|
123
|
+
Code::IO_FAILED,
|
|
124
|
+
format!(
|
|
125
|
+
"source {} and destination {} are on different filesystems; a hardcut requires an atomic rename",
|
|
126
|
+
source.display(),
|
|
127
|
+
destination.display()
|
|
128
|
+
),
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let _exclusive = WriterGuard::lock_rebuild(&source)?;
|
|
133
|
+
fs::rename(&source, &destination).map_err(|error| io("rename Store", error))?;
|
|
134
|
+
store::fsync_dir(&destination_parent)?;
|
|
135
|
+
if source_parent != destination_parent {
|
|
136
|
+
store::fsync_dir(source_parent)?;
|
|
137
|
+
}
|
|
138
|
+
Store::open(&destination)?;
|
|
139
|
+
Ok(MoveReport {
|
|
140
|
+
source,
|
|
141
|
+
destination,
|
|
142
|
+
})
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
fn io(what: &str, error: std::io::Error) -> crate::err::Refusal {
|
|
146
|
+
crate::err::Refusal {
|
|
147
|
+
code: Code::IO_FAILED,
|
|
148
|
+
detail: format!("{what}: {error}"),
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
#[cfg(test)]
|
|
153
|
+
mod tests {
|
|
154
|
+
use super::*;
|
|
155
|
+
|
|
156
|
+
#[test]
|
|
157
|
+
fn precedence_is_explicit_then_tensorfs_home_then_user_home() {
|
|
158
|
+
assert_eq!(
|
|
159
|
+
resolve_values(
|
|
160
|
+
Some(Path::new("/explicit")),
|
|
161
|
+
Some(Path::new("/environment")),
|
|
162
|
+
Some(Path::new("/user")),
|
|
163
|
+
)
|
|
164
|
+
.unwrap(),
|
|
165
|
+
Path::new("/explicit"),
|
|
166
|
+
);
|
|
167
|
+
assert_eq!(
|
|
168
|
+
resolve_values(
|
|
169
|
+
None,
|
|
170
|
+
Some(Path::new("/environment")),
|
|
171
|
+
Some(Path::new("/user"))
|
|
172
|
+
)
|
|
173
|
+
.unwrap(),
|
|
174
|
+
Path::new("/environment"),
|
|
175
|
+
);
|
|
176
|
+
assert_eq!(
|
|
177
|
+
resolve_values(None, None, Some(Path::new("/user"))).unwrap(),
|
|
178
|
+
Path::new("/user/.tensorfs"),
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
#[test]
|
|
183
|
+
fn an_empty_selected_value_refuses_instead_of_falling_through() {
|
|
184
|
+
assert_eq!(
|
|
185
|
+
resolve_values(None, Some(Path::new("")), Some(Path::new("/user")))
|
|
186
|
+
.unwrap_err()
|
|
187
|
+
.code,
|
|
188
|
+
Code::IO_FAILED,
|
|
189
|
+
);
|
|
190
|
+
assert_eq!(
|
|
191
|
+
resolve_values(None, None, None).unwrap_err().code,
|
|
192
|
+
Code::IO_FAILED,
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
@@ -18,6 +18,7 @@ pub mod fill;
|
|
|
18
18
|
pub mod fit;
|
|
19
19
|
pub mod gc;
|
|
20
20
|
pub mod header;
|
|
21
|
+
pub mod home;
|
|
21
22
|
pub mod ids;
|
|
22
23
|
pub mod ingest;
|
|
23
24
|
pub mod jcs;
|
|
@@ -30,6 +31,7 @@ pub mod read;
|
|
|
30
31
|
pub mod receipt;
|
|
31
32
|
pub mod registry;
|
|
32
33
|
pub mod relation;
|
|
34
|
+
pub mod repo_cache;
|
|
33
35
|
pub mod repository;
|
|
34
36
|
pub mod sha256;
|
|
35
37
|
pub mod spec;
|