#[test] fn semantic_corpus_preserves_supported_changesets_and_read_only_lower_use() { let fixture = Fixture::new(); let first = layer(&lower_entries()); let second = layer(&upper_entries()); let request = fixture.request( 1, &[ (compress(&first, OCI_GZIP), OCI_GZIP), (compress(&second, OCI_ZSTD), OCI_ZSTD), ], ); let artifact = fixture.store.prepare(&request).unwrap(); let root = fixture.artifact_root(&artifact); assert!(!root.join("gone").exists()); assert!(!root.join("opaque/old").exists()); assert_eq!(std::fs::read(root.join("opaque/new")).unwrap(), b"new"); assert_eq!( std::fs::read(root.join("directory-to-file")).unwrap(), b"replacement" ); assert_eq!( std::fs::read(root.join("file-to-directory/new")).unwrap(), b"new" ); assert_eq!( std::fs::read_link(root.join("symbolic")).unwrap(), Path::new("target") ); assert_eq!( std::fs::metadata(root.join("hard")).unwrap().ino(), std::fs::metadata(root.join("target")).unwrap().ino() ); assert_eq!(std::fs::read(root.join("forward")).unwrap(), b"forward"); assert_eq!( std::fs::metadata(root.join("later")) .unwrap() .permissions() .mode() & 0o7777, 0o4755 ); std::fs::remove_dir_all(&request.layout_path).unwrap(); let mut lower = File::open(root.join("directory-to-file")).unwrap(); let mut bytes = Vec::new(); lower.read_to_end(&mut bytes).unwrap(); assert_eq!(bytes, b"replacement"); assert!( fixture .root .join("stages") .read_dir() .unwrap() .next() .is_none() ); } #[test] fn request_manifest_identity_must_match_verified_layout_manifest() { let fixture = Fixture::new(); let bytes = layer(&[Entry { path: "payload", kind: EntryType::Regular, data: b"content", target: None, mode: 0o644, }]); let mut request = fixture.request(3, &[(bytes, OCI_TAR)]); request.selected_manifest_digest = [0xff; 32]; assert!(matches!( fixture.store.prepare(&request), Err(PreparedLowerError::ManifestSelection { .. }) )); let snapshot = fixture.catalog.snapshot().unwrap(); assert!(snapshot.prepared_artifacts.is_empty()); assert!(snapshot.prepare_operations.is_empty()); } #[test] fn layer_entry_matching_internal_spool_name_is_preserved() { let fixture = Fixture::new(); let bytes = layer(&[Entry { path: ".layer-0.tar", kind: EntryType::Regular, data: b"image-owned", target: None, mode: 0o640, }]); let request = fixture.request(4, &[(bytes, OCI_TAR)]); let artifact = fixture.store.prepare(&request).unwrap(); assert_eq!( std::fs::read(fixture.artifact_root(&artifact).join(".layer-0.tar")).unwrap(), b"image-owned" ); } #[test] fn root_directory_layer_entry_is_applied() { let fixture = Fixture::new(); let bytes = layer(&[ Entry { path: ".", kind: EntryType::Directory, data: b"", target: None, mode: 0o755, }, Entry { path: "payload", kind: EntryType::Regular, data: b"content", target: None, mode: 0o644, }, ]); let request = fixture.request(5, &[(bytes, OCI_TAR)]); let artifact = fixture.store.prepare(&request).unwrap(); let root = fixture.artifact_root(&artifact); assert_eq!(std::fs::read(root.join("payload")).unwrap(), b"content"); assert_eq!( root.metadata().unwrap().permissions().mode() & 0o7777, 0o755 ); } #[test] fn container_root_owner_maps_to_invoking_host_ids() { let fixture = Fixture::new(); let bytes = layer_with_owner( &[Entry { path: "payload", kind: EntryType::Regular, data: b"content", target: None, mode: 0o640, }], 0, 0, ); let mut request = fixture.request(6, &[(bytes, OCI_TAR)]); request.id_map = IdMap { uid: vec![IdMapRange { container_id: 0, namespace_id: rustix::process::geteuid().as_raw(), size: 1, }], gid: vec![IdMapRange { container_id: 0, namespace_id: rustix::process::getegid().as_raw(), size: 1, }], }; let artifact = fixture.store.prepare(&request).unwrap(); let metadata = fixture .artifact_root(&artifact) .join("payload") .metadata() .unwrap(); assert_eq!(metadata.uid(), rustix::process::geteuid().as_raw()); assert_eq!(metadata.gid(), rustix::process::getegid().as_raw()); } #[test] fn unmapped_nonzero_owners_refuse_before_publication() { for (operation, uid, gid) in [(7, 1, 0), (8, 0, 1)] { let fixture = Fixture::new(); let bytes = layer_with_owner( &[Entry { path: "payload", kind: EntryType::Regular, data: b"content", target: None, mode: 0o640, }], uid, gid, ); let mut request = fixture.request(operation, &[(bytes, OCI_TAR)]); request.id_map = IdMap { uid: vec![IdMapRange { container_id: 0, namespace_id: rustix::process::geteuid().as_raw(), size: 1, }], gid: vec![IdMapRange { container_id: 0, namespace_id: rustix::process::getegid().as_raw(), size: 1, }], }; assert!(matches!( fixture.store.prepare(&request), Err(PreparedLowerError::Materialization { .. }) )); assert!( fixture .catalog .snapshot() .unwrap() .prepared_artifacts .is_empty() ); } } #[test] fn root_opaque_whiteout_removes_only_lower_layer_entries() { let fixture = Fixture::new(); let lower = layer(&[Entry { path: "old", kind: EntryType::Regular, data: b"old", target: None, mode: 0o644, }]); let upper = layer(&[ Entry { path: "new", kind: EntryType::Regular, data: b"new", target: None, mode: 0o644, }, Entry { path: ".wh..wh..opq", kind: EntryType::Regular, data: b"", target: None, mode: 0o000, }, ]); let request = fixture.request(2, &[(lower, OCI_TAR), (upper, OCI_TAR)]); let artifact = fixture.store.prepare(&request).unwrap(); let root = fixture.artifact_root(&artifact); assert!(!root.join("old").exists()); assert_eq!(std::fs::read(root.join("new")).unwrap(), b"new"); } #[test] fn hostile_and_corrupt_layers_refuse_without_publication() { let cases = [ layer(&[Entry { path: "pipe", kind: EntryType::Fifo, data: b"", target: None, mode: 0o644, }]), layer(&[Entry { path: "escape", kind: EntryType::Link, data: b"", target: Some("../outside"), mode: 0o644, }]), xattr_layer(), ]; for (index, bytes) in cases.into_iter().enumerate() { let fixture = Fixture::new(); let request = fixture.request(u8::try_from(index).unwrap() + 10, &[(bytes, OCI_TAR)]); assert!(matches!( fixture.store.prepare(&request), Err(PreparedLowerError::Materialization { .. }) )); let snapshot = fixture.catalog.snapshot().unwrap(); assert!(snapshot.prepared_artifacts.is_empty()); assert!(snapshot.prepare_operations.is_empty()); assert!( fixture .root .join("stages") .read_dir() .unwrap() .next() .is_none() ); } let fixture = Fixture::new(); let bytes = layer(&[Entry { path: "file", kind: EntryType::Regular, data: b"content", target: None, mode: 0o644, }]); let request = fixture.request(20, &[(bytes, OCI_TAR)]); let blob = request .layout_path .join("blobs/sha256") .read_dir() .unwrap() .map(|entry| entry.unwrap().path()) .find(|path| std::fs::metadata(path).unwrap().len() > 1024) .unwrap(); OpenOptionsExt::append_byte(&blob); assert!(matches!( fixture.store.prepare(&request), Err(PreparedLowerError::LayerValidation { .. }) )); assert!( fixture .catalog .snapshot() .unwrap() .prepared_artifacts .is_empty() ); } struct OpenOptionsExt; impl OpenOptionsExt { fn append_byte(path: &Path) { let mut file = std::fs::OpenOptions::new().append(true).open(path).unwrap(); file.write_all(b"x").unwrap(); } }