From fcbe6d76da4d8fd222ea35ffa15a52136dc18216 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Wed, 1 Aug 2018 20:29:03 +0300 Subject: [PATCH] [swcomp] Implement create option for tar command --- Sources/swcomp/Containers/TarCommand.swift | 109 +++++++++++++++++++-- 1 file changed, 99 insertions(+), 10 deletions(-) diff --git a/Sources/swcomp/Containers/TarCommand.swift b/Sources/swcomp/Containers/TarCommand.swift index b683cab3..b2d11c61 100644 --- a/Sources/swcomp/Containers/TarCommand.swift +++ b/Sources/swcomp/Containers/TarCommand.swift @@ -18,28 +18,35 @@ class TarCommand: Command { let info = Flag("-i", "--info", description: "Print list of entries in container and their attributes") let extract = Key("-e", "--extract", description: "Extract container into specified directory") - let format = Flag("-f", "--format", description: "Prints \"format\" of the container") + let format = Flag("-f", "--format", description: "Print \"format\" of the container") + let create = Key("-c", "--create", + description: "Create a new container containing specified file/directory (recursively)") let verbose = Flag("--verbose", description: "Print the list of extracted files and directories.") var optionGroups: [OptionGroup] { let compressions = OptionGroup(options: [gz, bz2, xz], restriction: .atMostOne) - let actions = OptionGroup(options: [info, extract, format], restriction: .exactlyOne) + let actions = OptionGroup(options: [info, extract, format, create], restriction: .exactlyOne) return [compressions, actions] } let archive = Parameter() func execute() throws { - var fileData = try Data(contentsOf: URL(fileURLWithPath: self.archive.value), - options: .mappedIfSafe) + var fileData: Data + if self.create.value == nil { + fileData = try Data(contentsOf: URL(fileURLWithPath: self.archive.value), + options: .mappedIfSafe) - if gz.value { - fileData = try GzipArchive.unarchive(archive: fileData) - } else if bz2.value { - fileData = try BZip2.decompress(data: fileData) - } else if xz.value { - fileData = try XZArchive.unarchive(archive: fileData) + if gz.value { + fileData = try GzipArchive.unarchive(archive: fileData) + } else if bz2.value { + fileData = try BZip2.decompress(data: fileData) + } else if xz.value { + fileData = try XZArchive.unarchive(archive: fileData) + } + } else { + fileData = Data() } if info.value { @@ -65,8 +72,90 @@ class TarCommand: Command { case .pax: print("TAR format: PAX") } + } else if let inputPath = self.create.value { + let fileManager = FileManager.default + + guard !fileManager.fileExists(atPath: self.archive.value) else { + print("ERROR: Output path already exists.") + exit(1) + } + if gz.value || bz2.value || xz.value { + print("Warning: compression options are unsupported and ignored when creating new container.") + } + + guard fileManager.fileExists(atPath: inputPath) else { + print("ERROR: Specified path doesn't exist.") + exit(1) + } + let entries = try self.createEntries(inputPath) + let containerData = try TarContainer.create(from: entries) + try containerData.write(to: URL(fileURLWithPath: self.archive.value)) } } + private func createEntries(_ inputPath: String) throws -> [TarEntry] { + let inputURL = URL(fileURLWithPath: inputPath) + let fileManager = FileManager.default + + let fileAttributes = try fileManager.attributesOfItem(atPath: inputPath) + + let name = inputURL.relativePath + + let entryType: ContainerEntryType + if let typeFromAttributes = fileAttributes[.type] as? FileAttributeType { + switch typeFromAttributes { + case .typeBlockSpecial: + entryType = .blockSpecial + case .typeCharacterSpecial: + entryType = .characterSpecial + case .typeDirectory: + entryType = .directory + case .typeRegular: + entryType = .regular + case .typeSocket: + entryType = .socket + case .typeSymbolicLink: + entryType = .symbolicLink + case .typeUnknown: + entryType = .unknown + default: + entryType = .unknown + } + } else { + entryType = .unknown + } + + var info = TarEntryInfo(name: name, type: entryType) + info.creationTime = fileAttributes[.creationDate] as? Date + info.groupID = (fileAttributes[.groupOwnerAccountID] as? NSNumber)?.intValue + info.ownerGroupName = fileAttributes[.groupOwnerAccountName] as? String + info.modificationTime = fileAttributes[.modificationDate] as? Date + info.ownerID = (fileAttributes[.ownerAccountID] as? NSNumber)?.intValue + info.ownerUserName = fileAttributes[.ownerAccountName] as? String + if let posixPermissions = (fileAttributes[.posixPermissions] as? NSNumber)?.intValue { + info.permissions = Permissions(rawValue: UInt32(truncatingIfNeeded: posixPermissions)) + } + + var entryData = Data() + if entryType == .symbolicLink { + info.linkName = try fileManager.destinationOfSymbolicLink(atPath: inputPath) + } else if entryType != .directory { + entryData = try Data(contentsOf: URL(fileURLWithPath: inputPath)) + } + + let entry = TarEntry(info: info, data: entryData) + + var entries = [TarEntry]() + entries.append(entry) + + if entryType == .directory { + for subPath in try fileManager.contentsOfDirectory(atPath: inputPath) { + entries.append(contentsOf: try self.createEntries(inputURL.appendingPathComponent(subPath).relativePath)) + } + } + + return entries + } + }