Skip to main content

treeship_core/bundle/
mod.rs

1use std::path::Path;
2
3use crate::{
4    attestation::{sign, ArtifactId, Envelope, SignError, Signer, Verifier, VerifyError},
5    statements::{payload_type, ArtifactRef, BundleStatement},
6    storage::{Record, StorageError, Store},
7};
8
9/// Error from bundle operations.
10#[derive(Debug)]
11pub enum BundleError {
12    Storage(StorageError),
13    Sign(SignError),
14    Io(std::io::Error),
15    Json(serde_json::Error),
16    ArtifactNotFound(String),
17    InvalidBundle(String),
18    /// A signature on an imported envelope did not verify against the
19    /// configured trust root. Carries the offending envelope's index in
20    /// the export (0 = bundle envelope, 1..=N = artifact envelopes) and
21    /// the underlying verification error.
22    UnverifiedEnvelope {
23        index: usize,
24        source: VerifyError,
25    },
26    /// `import` was called with an empty trust root. Without trusted keys
27    /// there is nothing to verify signatures against, so import would
28    /// degenerate to "trust whatever the file says" — refused loudly.
29    NoTrustRoot,
30}
31
32impl std::fmt::Display for BundleError {
33    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34        match self {
35            Self::Storage(e) => write!(f, "bundle storage: {e}"),
36            Self::Sign(e) => write!(f, "bundle sign: {e}"),
37            Self::Io(e) => write!(f, "bundle io: {e}"),
38            Self::Json(e) => write!(f, "bundle json: {e}"),
39            Self::ArtifactNotFound(id) => write!(f, "artifact not found: {id}"),
40            Self::InvalidBundle(msg) => write!(f, "invalid bundle: {msg}"),
41            Self::UnverifiedEnvelope { index, source } => write!(
42                f,
43                "envelope {index} failed signature verification: {source}",
44            ),
45            Self::NoTrustRoot => write!(
46                f,
47                "bundle import requires a configured trust root: \
48                 generate or import a signer key (treeship init / treeship keys add) \
49                 before importing a .treeship bundle",
50            ),
51        }
52    }
53}
54
55impl std::error::Error for BundleError {}
56impl From<StorageError> for BundleError {
57    fn from(e: StorageError) -> Self {
58        Self::Storage(e)
59    }
60}
61impl From<SignError> for BundleError {
62    fn from(e: SignError) -> Self {
63        Self::Sign(e)
64    }
65}
66impl From<std::io::Error> for BundleError {
67    fn from(e: std::io::Error) -> Self {
68        Self::Io(e)
69    }
70}
71impl From<serde_json::Error> for BundleError {
72    fn from(e: serde_json::Error) -> Self {
73        Self::Json(e)
74    }
75}
76
77/// The result of creating a bundle.
78#[derive(Debug)]
79pub struct CreateResult {
80    pub artifact_id: ArtifactId,
81    pub digest: String,
82    pub record: Record,
83    pub statement: BundleStatement,
84}
85
86/// A .treeship export file: the bundle envelope plus all referenced artifact envelopes.
87#[derive(Debug, serde::Serialize, serde::Deserialize)]
88pub struct ExportFile {
89    /// Format version for forward compatibility.
90    pub version: String,
91
92    /// The signed bundle envelope.
93    pub bundle: Envelope,
94
95    /// All artifact envelopes referenced by the bundle, in chain order.
96    pub artifacts: Vec<Envelope>,
97}
98
99const EXPORT_VERSION: &str = "treeship-export/v1";
100
101/// Create a bundle from a list of artifact IDs.
102///
103/// Reads each artifact from storage, builds a `BundleStatement` referencing
104/// them, signs it, and stores the bundle as a regular artifact.
105pub fn create(
106    artifact_ids: &[&str],
107    tag: Option<&str>,
108    description: Option<&str>,
109    storage: &Store,
110    signer: &dyn Signer,
111) -> Result<CreateResult, BundleError> {
112    if artifact_ids.is_empty() {
113        return Err(BundleError::InvalidBundle(
114            "no artifact IDs provided".into(),
115        ));
116    }
117
118    // Read each artifact and build the reference list.
119    let mut refs = Vec::with_capacity(artifact_ids.len());
120    let mut records = Vec::with_capacity(artifact_ids.len());
121
122    for &id in artifact_ids {
123        let rec = storage
124            .read(id)
125            .map_err(|_| BundleError::ArtifactNotFound(id.to_string()))?;
126        refs.push(ArtifactRef {
127            id: rec.artifact_id.clone(),
128            digest: rec.digest.clone(),
129            type_: rec.payload_type.clone(),
130        });
131        records.push(rec);
132    }
133
134    let stmt = BundleStatement {
135        type_: crate::statements::TYPE_BUNDLE.into(),
136        timestamp: crate::statements::unix_to_rfc3339(now_secs()),
137        tag: tag.map(|s| s.to_string()),
138        description: description.map(|s| s.to_string()),
139        artifacts: refs,
140        policy_ref: None,
141        meta: None,
142    };
143
144    let pt = payload_type("bundle");
145    let result = sign(&pt, &stmt, signer)?;
146
147    let record = Record {
148        artifact_id: result.artifact_id.clone(),
149        digest: result.digest.clone(),
150        payload_type: pt,
151        key_id: signer.key_id().to_string(),
152        signed_at: stmt.timestamp.clone(),
153        parent_id: None,
154        envelope: result.envelope,
155        hub_url: None,
156        // Just signed locally; nothing external has witnessed it yet.
157        anchors: Vec::new(),
158    };
159
160    storage.write(&record)?;
161
162    Ok(CreateResult {
163        artifact_id: result.artifact_id,
164        digest: result.digest,
165        record,
166        statement: stmt,
167    })
168}
169
170/// Export a bundle to a .treeship file.
171///
172/// The export file contains the bundle envelope and all referenced artifact
173/// envelopes. This is the portable format for sharing proof chains.
174pub fn export(bundle_id: &str, out_path: &Path, storage: &Store) -> Result<(), BundleError> {
175    let bundle_rec = storage.read(bundle_id)?;
176
177    // Verify this is actually a bundle.
178    let expected_pt = payload_type("bundle");
179    if bundle_rec.payload_type != expected_pt {
180        return Err(BundleError::InvalidBundle(format!(
181            "artifact {} is {}, not a bundle",
182            bundle_id, bundle_rec.payload_type
183        )));
184    }
185
186    // Decode the bundle statement to get artifact references.
187    let stmt: BundleStatement = bundle_rec
188        .envelope
189        .unmarshal_statement()
190        .map_err(|e| BundleError::InvalidBundle(format!("cannot decode bundle: {e}")))?;
191
192    // Collect all referenced artifact envelopes.
193    let mut artifact_envelopes = Vec::with_capacity(stmt.artifacts.len());
194    for art_ref in &stmt.artifacts {
195        let rec = storage
196            .read(&art_ref.id)
197            .map_err(|_| BundleError::ArtifactNotFound(art_ref.id.clone()))?;
198        artifact_envelopes.push(rec.envelope);
199    }
200
201    let export = ExportFile {
202        version: EXPORT_VERSION.into(),
203        bundle: bundle_rec.envelope,
204        artifacts: artifact_envelopes,
205    };
206
207    let json = serde_json::to_vec_pretty(&export)?;
208    std::fs::write(out_path, &json)?;
209
210    Ok(())
211}
212
213/// Import a .treeship file into local storage.
214///
215/// Reads the export file, verifies every envelope's signatures against the
216/// provided `verifier`, re-derives content-addressed IDs, and stores everything
217/// locally. Returns the bundle's artifact ID.
218///
219/// P0 #5 (audit): import previously called `record_from_envelope` with no
220/// signature check, which made imported bundles forged-record vectors. The
221/// `verifier` argument carries the caller's trust root — typically built from
222/// the local keystore's public keys. If verification fails for any envelope
223/// the entire import is rejected; partial writes are avoided by verifying all
224/// envelopes before writing any record.
225pub fn import(
226    path: &Path,
227    storage: &Store,
228    verifier: &Verifier,
229) -> Result<ArtifactId, BundleError> {
230    let bytes = std::fs::read(path)?;
231    let export: ExportFile = serde_json::from_slice(&bytes)?;
232
233    if export.version != EXPORT_VERSION {
234        return Err(BundleError::InvalidBundle(format!(
235            "unsupported export version: {} (expected {})",
236            export.version, EXPORT_VERSION
237        )));
238    }
239
240    // Verify every envelope before writing any record. `verify_any` (not
241    // `verify`) is the right primitive here: an envelope may carry multiple
242    // signatures from a rotation/co-sign setup and the local trust root only
243    // needs one to match. Index 0 = bundle envelope, 1..=N = artifact
244    // envelopes (matches the order shown in error messages and CLI output).
245    let bundle_vr = verifier
246        .verify_any(&export.bundle)
247        .map_err(|source| BundleError::UnverifiedEnvelope { index: 0, source })?;
248    let mut artifact_verified_keys: Vec<Option<String>> =
249        Vec::with_capacity(export.artifacts.len());
250    for (i, env) in export.artifacts.iter().enumerate() {
251        let vr = verifier
252            .verify_any(env)
253            .map_err(|source| BundleError::UnverifiedEnvelope {
254                index: i + 1,
255                source,
256            })?;
257        artifact_verified_keys.push(vr.verified_key_ids.first().cloned());
258    }
259
260    // All signatures check out: now write, attributing each record to the key
261    // that actually verified (AUD-13), not to signatures.first().
262    for (env, vk) in export.artifacts.iter().zip(artifact_verified_keys.iter()) {
263        let record = record_from_envelope(env, vk.as_deref())?;
264        storage.write(&record)?;
265    }
266
267    let bundle_record = record_from_envelope(
268        &export.bundle,
269        bundle_vr.verified_key_ids.first().map(|s| s.as_str()),
270    )?;
271    let bundle_id = bundle_record.artifact_id.clone();
272    storage.write(&bundle_record)?;
273
274    Ok(bundle_id)
275}
276
277/// Reconstruct a Record from a DSSE envelope by re-deriving the artifact ID.
278///
279/// `verified_key_id` is the key id whose signature ACTUALLY verified for this
280/// envelope (from the caller's `verify_any` result). It is used verbatim for
281/// the stored `key_id` so a decoy signature prepended ahead of the real one
282/// cannot misattribute the artifact's signer in local metadata (AUD-13). When
283/// None (no verification context), we fall back to the first signature's keyid.
284fn record_from_envelope(
285    envelope: &Envelope,
286    verified_key_id: Option<&str>,
287) -> Result<Record, BundleError> {
288    use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
289
290    let payload_bytes = URL_SAFE_NO_PAD
291        .decode(&envelope.payload)
292        .map_err(|e| BundleError::InvalidBundle(format!("bad payload base64: {e}")))?;
293
294    let pae_bytes = crate::attestation::pae(&envelope.payload_type, &payload_bytes);
295    let artifact_id = crate::attestation::artifact_id_from_pae(&pae_bytes);
296    let digest = crate::attestation::digest_from_pae(&pae_bytes);
297
298    // Extract timestamp from the payload if possible.
299    let signed_at = serde_json::from_slice::<serde_json::Value>(&payload_bytes)
300        .ok()
301        .and_then(|v| {
302            v.get("timestamp")
303                .and_then(|t| t.as_str().map(|s| s.to_string()))
304        })
305        .unwrap_or_default();
306
307    // Extract parent_id from the payload if present.
308    let parent_id = serde_json::from_slice::<serde_json::Value>(&payload_bytes)
309        .ok()
310        .and_then(|v| {
311            v.get("parentId")
312                .and_then(|t| t.as_str().map(|s| s.to_string()))
313        });
314
315    // AUD-13: attribute the record to the key that actually verified, not
316    // signatures.first() (which an attacker can prepend a decoy keyid to).
317    let key_id = verified_key_id
318        .map(|s| s.to_string())
319        .or_else(|| envelope.signatures.first().map(|s| s.keyid.clone()))
320        .unwrap_or_default();
321
322    Ok(Record {
323        artifact_id,
324        digest,
325        payload_type: envelope.payload_type.clone(),
326        key_id,
327        signed_at,
328        parent_id,
329        envelope: envelope.clone(),
330        hub_url: None,
331        // Imported from a bundle. Any anchors the exporter held are their
332        // evidence, not ours -- we record what we witness.
333        anchors: Vec::new(),
334    })
335}
336
337fn now_secs() -> u64 {
338    use std::time::{SystemTime, UNIX_EPOCH};
339    SystemTime::now()
340        .duration_since(UNIX_EPOCH)
341        .unwrap_or_default()
342        .as_secs()
343}
344
345#[cfg(test)]
346mod tests {
347    use super::*;
348    use crate::attestation::Ed25519Signer;
349    use crate::statements::{ActionStatement, ApprovalStatement};
350
351    fn tmp_store() -> (Store, std::path::PathBuf) {
352        let mut p = std::env::temp_dir();
353        p.push(format!("treeship-bundle-test-{}", {
354            use rand::RngCore;
355            let mut b = [0u8; 4];
356            rand::thread_rng().fill_bytes(&mut b);
357            b.iter().fold(String::new(), |mut s, byte| {
358                s.push_str(&format!("{:02x}", byte));
359                s
360            })
361        }));
362        let store = Store::open(&p).unwrap();
363        (store, p)
364    }
365
366    fn rm(p: std::path::PathBuf) {
367        let _ = std::fs::remove_dir_all(p);
368    }
369
370    fn sign_and_store(
371        store: &Store,
372        signer: &dyn Signer,
373        pt: &str,
374        stmt: &impl serde::Serialize,
375    ) -> String {
376        let result = sign(pt, stmt, signer).unwrap();
377        store
378            .write(&Record {
379                artifact_id: result.artifact_id.clone(),
380                digest: result.digest.clone(),
381                payload_type: pt.to_string(),
382                key_id: signer.key_id().to_string(),
383                signed_at: String::new(),
384                parent_id: None,
385                envelope: result.envelope,
386                hub_url: None,
387                anchors: Vec::new(),
388            })
389            .unwrap();
390        result.artifact_id
391    }
392
393    #[test]
394    fn create_bundle() {
395        let (store, dir) = tmp_store();
396        let signer = Ed25519Signer::generate("key_test").unwrap();
397
398        let a1 = sign_and_store(
399            &store,
400            &signer,
401            &payload_type("action"),
402            &ActionStatement::new("agent://a", "tool.call"),
403        );
404        let a2 = sign_and_store(
405            &store,
406            &signer,
407            &payload_type("approval"),
408            &ApprovalStatement::new("human://b", "nonce_1"),
409        );
410
411        let result = create(&[&a1, &a2], Some("test-bundle"), None, &store, &signer).unwrap();
412
413        assert!(result.artifact_id.starts_with("art_"));
414        assert_eq!(result.statement.artifacts.len(), 2);
415        assert_eq!(result.statement.tag.as_deref(), Some("test-bundle"));
416
417        // Bundle is stored
418        assert!(store.exists(&result.artifact_id));
419        rm(dir);
420    }
421
422    #[test]
423    fn create_empty_fails() {
424        let (store, dir) = tmp_store();
425        let signer = Ed25519Signer::generate("key_test").unwrap();
426        let err = create(&[], None, None, &store, &signer).unwrap_err();
427        assert!(err.to_string().contains("no artifact IDs"));
428        rm(dir);
429    }
430
431    #[test]
432    fn create_missing_artifact_fails() {
433        let (store, dir) = tmp_store();
434        let signer = Ed25519Signer::generate("key_test").unwrap();
435        let err = create(
436            &["art_doesnotexist1234567890123456"],
437            None,
438            None,
439            &store,
440            &signer,
441        )
442        .unwrap_err();
443        assert!(err.to_string().contains("not found"));
444        rm(dir);
445    }
446
447    #[test]
448    fn export_and_import_roundtrip() {
449        let (store, dir) = tmp_store();
450        let signer = Ed25519Signer::generate("key_test").unwrap();
451        let verifier = crate::attestation::Verifier::from_signer(&signer);
452
453        let a1 = sign_and_store(
454            &store,
455            &signer,
456            &payload_type("action"),
457            &ActionStatement::new("agent://a", "tool.call"),
458        );
459        let a2 = sign_and_store(
460            &store,
461            &signer,
462            &payload_type("action"),
463            &ActionStatement::new("agent://b", "web.fetch"),
464        );
465
466        let bundle = create(&[&a1, &a2], Some("roundtrip"), None, &store, &signer).unwrap();
467
468        // Export
469        let export_path = dir.join("test.treeship");
470        export(&bundle.artifact_id, &export_path, &store).unwrap();
471        assert!(export_path.exists());
472
473        // Read and check the export file structure
474        let bytes = std::fs::read(&export_path).unwrap();
475        let ef: ExportFile = serde_json::from_slice(&bytes).unwrap();
476        assert_eq!(ef.version, EXPORT_VERSION);
477        assert_eq!(ef.artifacts.len(), 2);
478
479        // Import into a fresh store
480        let (store2, dir2) = tmp_store();
481        let imported_id = import(&export_path, &store2, &verifier).unwrap();
482        assert_eq!(imported_id, bundle.artifact_id);
483
484        // All artifacts are now in the new store
485        assert!(store2.exists(&a1));
486        assert!(store2.exists(&a2));
487        assert!(store2.exists(&bundle.artifact_id));
488
489        rm(dir);
490        rm(dir2);
491    }
492
493    #[test]
494    fn export_non_bundle_fails() {
495        let (store, dir) = tmp_store();
496        let signer = Ed25519Signer::generate("key_test").unwrap();
497        let a1 = sign_and_store(
498            &store,
499            &signer,
500            &payload_type("action"),
501            &ActionStatement::new("agent://a", "tool.call"),
502        );
503
504        let export_path = dir.join("bad.treeship");
505        let err = export(&a1, &export_path, &store).unwrap_err();
506        assert!(err.to_string().contains("not a bundle"));
507        rm(dir);
508    }
509
510    #[test]
511    fn import_bad_version_fails() {
512        let (store, dir) = tmp_store();
513        let signer = Ed25519Signer::generate("key_test").unwrap();
514        let verifier = crate::attestation::Verifier::from_signer(&signer);
515        let bad = ExportFile {
516            version: "bad/v99".into(),
517            bundle: Envelope {
518                payload: String::new(),
519                payload_type: String::new(),
520                signatures: vec![],
521            },
522            artifacts: vec![],
523        };
524        let path = dir.join("bad.treeship");
525        std::fs::write(&path, serde_json::to_vec(&bad).unwrap()).unwrap();
526
527        let err = import(&path, &store, &verifier).unwrap_err();
528        assert!(err.to_string().contains("unsupported export version"));
529        rm(dir);
530    }
531
532    #[test]
533    fn import_rejects_envelope_with_invalid_signature() {
534        // P0 #5: before this fix, `import` called `record_from_envelope`
535        // straight from the file with no verification — an attacker could
536        // hand-craft a `.treeship` whose envelopes pointed at any payload
537        // and the import would happily land it in storage. Now every
538        // envelope must verify against the caller's trust root.
539        use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
540
541        let (store, dir) = tmp_store();
542        let signer = Ed25519Signer::generate("key_test").unwrap();
543        let verifier = crate::attestation::Verifier::from_signer(&signer);
544
545        // Build a normal bundle so we have a valid export to start from.
546        let a1 = sign_and_store(
547            &store,
548            &signer,
549            &payload_type("action"),
550            &ActionStatement::new("agent://a", "tool.call"),
551        );
552        let bundle = create(&[&a1], Some("tampered"), None, &store, &signer).unwrap();
553        let export_path = dir.join("tampered.treeship");
554        export(&bundle.artifact_id, &export_path, &store).unwrap();
555
556        // Tamper the *first* artifact envelope's signature bytes. The keyid
557        // remains correct (still our trusted key) but the cipher-bytes no
558        // longer verify against the PAE.
559        let raw = std::fs::read(&export_path).unwrap();
560        let mut ef: ExportFile = serde_json::from_slice(&raw).unwrap();
561        // Replace the 64-byte signature with all zeros — well-formed length,
562        // mathematically invalid.
563        ef.artifacts[0].signatures[0].sig = URL_SAFE_NO_PAD.encode([0u8; 64]);
564        std::fs::write(&export_path, serde_json::to_vec(&ef).unwrap()).unwrap();
565
566        // Import into a fresh store. The tamper must be detected and the
567        // import must fail; the fresh store must remain empty of the
568        // artifact and bundle.
569        let (store2, dir2) = tmp_store();
570        let err = import(&export_path, &store2, &verifier).unwrap_err();
571        assert!(
572            matches!(err, BundleError::UnverifiedEnvelope { index: 1, .. }),
573            "expected UnverifiedEnvelope{{index:1, ..}}, got: {err}"
574        );
575        // Verify nothing was written: signatures are checked before any
576        // record is persisted, so the destination store stays clean.
577        assert!(!store2.exists(&a1));
578        assert!(!store2.exists(&bundle.artifact_id));
579
580        rm(dir);
581        rm(dir2);
582    }
583
584    #[test]
585    fn import_attributes_record_to_verified_key_not_decoy() {
586        // AUD-13: a decoy signature prepended ahead of the real one must not
587        // misattribute the stored record's signer. verify_any skips the decoy
588        // and passes on the real sig; the stored key_id must be the key that
589        // actually verified, not signatures.first().
590        use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
591
592        let (store, dir) = tmp_store();
593        let signer = Ed25519Signer::generate("key_real").unwrap();
594        let verifier = crate::attestation::Verifier::from_signer(&signer);
595
596        let a1 = sign_and_store(
597            &store,
598            &signer,
599            &payload_type("action"),
600            &ActionStatement::new("agent://a", "tool.call"),
601        );
602        let bundle = create(&[&a1], Some("b"), None, &store, &signer).unwrap();
603        let export_path = dir.join("b.treeship");
604        export(&bundle.artifact_id, &export_path, &store).unwrap();
605
606        // Prepend a decoy signature (attacker keyid, garbage bytes) ahead of
607        // the real one on the first artifact envelope.
608        let raw = std::fs::read(&export_path).unwrap();
609        let mut ef: ExportFile = serde_json::from_slice(&raw).unwrap();
610        let real_sig = ef.artifacts[0].signatures[0].clone();
611        let decoy = crate::attestation::Signature {
612            keyid: "key_ceo".into(),
613            sig: URL_SAFE_NO_PAD.encode([0u8; 64]),
614        };
615        ef.artifacts[0].signatures = vec![decoy, real_sig];
616        std::fs::write(&export_path, serde_json::to_vec(&ef).unwrap()).unwrap();
617
618        // Import into a fresh store: the real sig still verifies via verify_any.
619        let (store2, dir2) = tmp_store();
620        import(&export_path, &store2, &verifier).unwrap();
621
622        let rec = store2.read(&a1).unwrap();
623        assert_eq!(
624            rec.key_id, "key_real",
625            "record must be attributed to the VERIFIED key, not the prepended decoy"
626        );
627
628        rm(dir);
629        rm(dir2);
630    }
631
632    #[test]
633    fn import_rejects_unsigned_envelope() {
634        // Companion to the P0 #4 fix: a bundle file whose envelopes carry
635        // zero signatures must not import. The `Verifier` returns
636        // `NoValidSignature` and `import` propagates it as
637        // `UnverifiedEnvelope`.
638        let (store, dir) = tmp_store();
639        let signer = Ed25519Signer::generate("key_test").unwrap();
640        let verifier = crate::attestation::Verifier::from_signer(&signer);
641
642        let a1 = sign_and_store(
643            &store,
644            &signer,
645            &payload_type("action"),
646            &ActionStatement::new("agent://a", "tool.call"),
647        );
648        let bundle = create(&[&a1], Some("unsigned"), None, &store, &signer).unwrap();
649        let export_path = dir.join("unsigned.treeship");
650        export(&bundle.artifact_id, &export_path, &store).unwrap();
651
652        // Strip every signature off every envelope in the export.
653        let raw = std::fs::read(&export_path).unwrap();
654        let mut ef: ExportFile = serde_json::from_slice(&raw).unwrap();
655        ef.bundle.signatures.clear();
656        for env in &mut ef.artifacts {
657            env.signatures.clear();
658        }
659        std::fs::write(&export_path, serde_json::to_vec(&ef).unwrap()).unwrap();
660
661        let (store2, dir2) = tmp_store();
662        let err = import(&export_path, &store2, &verifier).unwrap_err();
663        assert!(
664            matches!(err, BundleError::UnverifiedEnvelope { index: 0, .. }),
665            "expected UnverifiedEnvelope{{index:0, ..}} (bundle envelope first), got: {err}"
666        );
667
668        rm(dir);
669        rm(dir2);
670    }
671}