680845cb1c
Linux build / Determine Swift version (push) Waiting to run
Linux build / Linux compile check (push) Blocked by required conditions
Build containerization / Verify commit signatures (push) Has been skipped
Build containerization / containerization (push) Successful in 0s
216 lines
9.7 KiB
Swift
216 lines
9.7 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
// Copyright © 2025-2026 Apple Inc. and the Containerization project authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// https://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
import ContainerizationArchive
|
|
import Foundation
|
|
import SystemPackage
|
|
|
|
extension EXT4.EXT4Reader {
|
|
public func export(archive: FilePath) throws {
|
|
let config = ArchiveWriterConfiguration(
|
|
format: .paxRestricted, filter: .none, options: [Options.xattrformat(.schily)])
|
|
let writer = try ArchiveWriter(configuration: config)
|
|
try writer.open(file: archive.url)
|
|
var items = self.tree.root.pointee.children
|
|
let hardlinkedInodes = Set(self.hardlinks.values)
|
|
var hardlinkTargets: [EXT4.InodeNumber: FilePath] = [:]
|
|
|
|
while items.count > 0 {
|
|
let itemPtr = items.removeFirst()
|
|
let item = itemPtr.pointee
|
|
let inode = try self.getInode(number: item.inode)
|
|
let entry = WriteEntry()
|
|
let mode = inode.mode
|
|
let size: UInt64 = (UInt64(inode.sizeHigh) << 32) | UInt64(inode.sizeLow)
|
|
entry.permissions = mode_t(mode)
|
|
guard let path = item.path else {
|
|
continue
|
|
}
|
|
if hardlinkedInodes.contains(item.inode) {
|
|
hardlinkTargets[item.inode] = path
|
|
}
|
|
guard self.hardlinks[path] == nil else {
|
|
continue
|
|
}
|
|
var attributes: [EXT4.ExtendedAttribute] = []
|
|
let buffer: [UInt8] = EXT4.tupleToArray(inode.inlineXattrs)
|
|
if !buffer.allZeros {
|
|
try attributes.append(contentsOf: Self.readInlineExtendedAttributes(from: buffer))
|
|
}
|
|
if inode.xattrBlockLow != 0 {
|
|
let block = inode.xattrBlockLow
|
|
try self.seek(block: block)
|
|
guard let buffer = try self.handle.read(upToCount: Int(self.blockSize)) else {
|
|
throw EXT4.Error.couldNotReadBlock(block)
|
|
}
|
|
try attributes.append(contentsOf: Self.readBlockExtendedAttributes(from: [UInt8](buffer)))
|
|
}
|
|
|
|
var xattrs: [String: Data] = [:]
|
|
for attribute in attributes {
|
|
guard attribute.fullName != "system.data" else {
|
|
continue
|
|
}
|
|
xattrs[attribute.fullName] = Data(attribute.value)
|
|
}
|
|
|
|
let pathStr = path.description
|
|
entry.path = pathStr
|
|
entry.size = Int64(size)
|
|
entry.group = gid_t(inode.gidHigh) << 16 | gid_t(inode.gid)
|
|
entry.owner = uid_t(inode.uidHigh) << 16 | uid_t(inode.uid)
|
|
entry.creationDate = Date(fsTimestamp: UInt64(inode.crtimeExtra) << 32 | UInt64(inode.crtime))
|
|
entry.modificationDate = Date(fsTimestamp: UInt64(inode.mtimeExtra) << 32 | UInt64(inode.mtime))
|
|
entry.contentAccessDate = Date(fsTimestamp: UInt64(inode.atimeExtra) << 32 | UInt64(inode.atime))
|
|
entry.xattrs = xattrs
|
|
|
|
if mode.isDir() {
|
|
entry.fileType = .directory
|
|
for child in item.children {
|
|
items.append(child)
|
|
}
|
|
if pathStr == "" {
|
|
continue
|
|
}
|
|
try writer.writeEntry(entry: entry, data: nil)
|
|
} else if mode.isReg() {
|
|
entry.fileType = .regular
|
|
var data = Data()
|
|
var remaining: UInt64 = size
|
|
if let block = item.blocks {
|
|
for dataBlock in block.start..<block.end {
|
|
try self.seek(block: dataBlock)
|
|
var count: UInt64
|
|
if remaining > self.blockSize {
|
|
count = self.blockSize
|
|
} else {
|
|
count = remaining
|
|
}
|
|
guard let dataBytes = try self.handle.read(upToCount: Int(count)) else {
|
|
throw EXT4.Error.couldNotReadBlock(dataBlock)
|
|
}
|
|
data.append(dataBytes)
|
|
remaining -= UInt64(dataBytes.count)
|
|
}
|
|
}
|
|
if let additionalBlocks = item.additionalBlocks {
|
|
for block in additionalBlocks {
|
|
for dataBlock in block.start..<block.end {
|
|
try self.seek(block: dataBlock)
|
|
var count: UInt64
|
|
if remaining > self.blockSize {
|
|
count = self.blockSize
|
|
} else {
|
|
count = remaining
|
|
}
|
|
guard let dataBytes = try self.handle.read(upToCount: Int(count)) else {
|
|
throw EXT4.Error.couldNotReadBlock(dataBlock)
|
|
}
|
|
data.append(dataBytes)
|
|
remaining -= UInt64(dataBytes.count)
|
|
}
|
|
}
|
|
}
|
|
try writer.writeEntry(entry: entry, data: data)
|
|
} else if mode.isLink() {
|
|
entry.fileType = .symbolicLink
|
|
if size < 60 {
|
|
let linkBytes = EXT4.tupleToArray(inode.block)
|
|
entry.symlinkTarget = String(bytes: linkBytes.prefix(Int(size)), encoding: .utf8) ?? ""
|
|
} else {
|
|
if let block = item.blocks {
|
|
try self.seek(block: block.start)
|
|
guard let linkBytes = try self.handle.read(upToCount: Int(size)) else {
|
|
throw EXT4.Error.couldNotReadBlock(block.start)
|
|
}
|
|
entry.symlinkTarget = String(bytes: linkBytes, encoding: .utf8) ?? ""
|
|
}
|
|
}
|
|
try writer.writeEntry(entry: entry, data: nil)
|
|
} else { // do not process sockets, fifo, character and block devices
|
|
continue
|
|
}
|
|
}
|
|
for (path, number) in self.hardlinks {
|
|
guard let targetPath = hardlinkTargets[number] else {
|
|
continue
|
|
}
|
|
let inode = try self.getInode(number: number)
|
|
let entry = WriteEntry()
|
|
entry.path = path.description
|
|
entry.hardlink = targetPath.description
|
|
entry.permissions = mode_t(inode.mode)
|
|
entry.group = gid_t(inode.gidHigh) << 16 | gid_t(inode.gid)
|
|
entry.owner = uid_t(inode.uidHigh) << 16 | uid_t(inode.uid)
|
|
entry.creationDate = Date(fsTimestamp: UInt64(inode.crtimeExtra) << 32 | UInt64(inode.crtime))
|
|
entry.modificationDate = Date(fsTimestamp: UInt64(inode.mtimeExtra) << 32 | UInt64(inode.mtime))
|
|
entry.contentAccessDate = Date(fsTimestamp: UInt64(inode.atimeExtra) << 32 | UInt64(inode.atime))
|
|
try writer.writeEntry(entry: entry, data: nil)
|
|
}
|
|
try writer.finishEncoding()
|
|
}
|
|
|
|
@available(*, deprecated, renamed: "readInlineExtendedAttributes(from:)")
|
|
public static func readInlineExtenedAttributes(from buffer: [UInt8]) throws -> [EXT4.ExtendedAttribute] {
|
|
try readInlineExtendedAttributes(from: buffer)
|
|
}
|
|
|
|
public static func readInlineExtendedAttributes(from buffer: [UInt8]) throws -> [EXT4.ExtendedAttribute] {
|
|
let header = buffer[0..<4].withUnsafeBytes { $0.loadLittleEndian(as: UInt32.self) }
|
|
if header != EXT4.XAttrHeaderMagic {
|
|
throw EXT4.FileXattrsState.Error.missingXAttrHeader
|
|
}
|
|
return try EXT4.FileXattrsState.read(buffer: buffer, start: 4, offset: 4)
|
|
}
|
|
|
|
@available(*, deprecated, renamed: "readBlockExtendedAttributes(from:)")
|
|
public static func readBlockExtenedAttributes(from buffer: [UInt8]) throws -> [EXT4.ExtendedAttribute] {
|
|
try readBlockExtendedAttributes(from: buffer)
|
|
}
|
|
|
|
public static func readBlockExtendedAttributes(from buffer: [UInt8]) throws -> [EXT4.ExtendedAttribute] {
|
|
let header = buffer[0..<4].withUnsafeBytes { $0.loadLittleEndian(as: UInt32.self) }
|
|
if header != EXT4.XAttrHeaderMagic {
|
|
throw EXT4.FileXattrsState.Error.missingXAttrHeader
|
|
}
|
|
|
|
return try EXT4.FileXattrsState.read(buffer: [UInt8](buffer), start: 32, offset: 0)
|
|
}
|
|
|
|
func seek(block: UInt32) throws {
|
|
try self.handle.seek(toOffset: UInt64(block) * blockSize)
|
|
}
|
|
}
|
|
|
|
extension Date {
|
|
init(fsTimestamp: UInt64) {
|
|
if fsTimestamp == 0 {
|
|
self = Date.distantPast
|
|
return
|
|
}
|
|
|
|
// 32 bits - base: seconds since January 1, 1970, signed (negative for pre-1970 dates)
|
|
// 2 bits - epoch: overflow counter (0-3), how many times the 32-bit seconds field has wrapped
|
|
// 30 bits - nanoseconds (0-999,999,999)
|
|
let base = Int32(truncatingIfNeeded: fsTimestamp)
|
|
let epoch = Int64(fsTimestamp & 0x3_0000_0000)
|
|
let seconds = Int64(base) + epoch
|
|
let nanoseconds = Double(fsTimestamp >> 34) / 1_000_000_000
|
|
|
|
self = Date(timeIntervalSince1970: Double(seconds) + nanoseconds)
|
|
}
|
|
}
|