I'm expanding my in-memory-filesystem from this
and I have another problem when I create new text file from bash shell.
the scenario is like this.
$ cat > newfile
...(omitting not important callback logs)...
2019-08-07 04:52:48 +0000 :: createFile called. "/newfile" "Optional([AnyHashable("NSFilePosixPermissions"): 420])" flags 2562
...(omitting not important callback logs)...
111 _\
_
2019-08-07 04:53:47 +0000 :: writeFile called. "Optional("/newfile")" "Optional(fusetester.FileItemInfo)" buffer Optional(0x00000001120eb050) size 4 offset 0
00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
---+-------------------------------------------------+-----------------
00 : 31 31 31 0a : 111.
222 _\
_
2019-08-07 04:54:19 +0000 :: contents called. "/newfile"
finding "newfile" under "/"
00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
---+-------------------------------------------------+-----------------
00 : 31 31 31 0a : 111.
2019-08-07 04:54:19 +0000 :: writeFile called. "Optional("/newfile")" "Optional(fusetester.FileItemInfo)" buffer Optional(0x00000001120eb050) size 8 offset 0
00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
---+-------------------------------------------------+-----------------
00 : 31 31 31 0a 32 32 32 0a : 111.222.
333 _\
_ // there's no "contents" call and buffer is broken
2019-08-07 04:54:35 +0000 :: writeFile called. "Optional("/newfile")" "Optional(fusetester.FileItemInfo)" buffer Optional(0x00000001120eb050) size 12 offset 0
00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
---+-------------------------------------------------+-----------------
00 : 31 31 31 0a 00 00 00 00 33 33 33 0a : 111.....333.
$ cat newfile // after save file by Ctrl+D
2019-08-07 04:55:17 +0000 :: contents called. "/newfile"
finding "newfile" under "/"
00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
---+-------------------------------------------------+-----------------
00 : 31 31 31 0a 00 00 00 00 33 33 33 0a : 111.....333.
when I comment out "contents" callback function,
then it works OK.
(same operation after comment out)
$ cat > newfile
...(omitting not important callback logs)...
2019-08-07 04:57:12 +0000 :: createFile called. "/newfile" "Optional([AnyHashable("NSFilePosixPermissions"): 420])" flags 2562
...(omitting not important callback logs)...
111 _\
_
2019-08-07 04:57:26 +0000 :: writeFile called. "Optional("/newfile")" "Optional(fusetester.FileItemInfo)" buffer Optional(0x00000001061a7050) size 4 offset 0
00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
---+-------------------------------------------------+-----------------
00 : 31 31 31 0a : 111.
222 _\
_
2019-08-07 04:57:40 +0000 :: openFile called. "/newfile" mode 0
finding "newfile" under "/"
2019-08-07 04:57:40 +0000 :: readFile called. "Optional("/newfile")" "Optional(fusetester.FileItemInfo)" size 8 offset 0
00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
---+-------------------------------------------------+-----------------
00 : 31 31 31 0a : 111.
2019-08-07 04:57:40 +0000 :: readFile called. "Optional("/newfile")" "Optional(fusetester.FileItemInfo)" size 4 offset 4
data empty
2019-08-07 04:57:40 +0000 :: writeFile called. "Optional("/newfile")" "Optional(fusetester.FileItemInfo)" buffer Optional(0x00000001061a7050) size 8 offset 0
00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
---+-------------------------------------------------+-----------------
00 : 31 31 31 0a 32 32 32 0a : 111.222.
333 _\
_
2019-08-07 04:57:57 +0000 :: readFile called. "Optional("/newfile")" "Optional(fusetester.FileItemInfo)" size 12 offset 0
00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
---+-------------------------------------------------+-----------------
00 : 31 31 31 0a 32 32 32 0a : 111.222.
2019-08-07 04:57:57 +0000 :: readFile called. "Optional("/newfile")" "Optional(fusetester.FileItemInfo)" size 4 offset 8
data empty
2019-08-07 04:57:57 +0000 :: writeFile called. "Optional("/newfile")" "Optional(fusetester.FileItemInfo)" buffer Optional(0x00000001061a7050) size 12 offset 0
00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
---+-------------------------------------------------+-----------------
00 : 31 31 31 0a 32 32 32 0a 33 33 33 0a : 111.222.333.
environment is same as previous posting.
macos 10.14.4 mojave
xcode 10.2.1
FUSE for macOS 3.10.2
below is complete source code.
import Foundation
let DIRECTORY_ITEM_SIZE = 32
let FILE_SYSTEM_BLOCK_SIZE = 512
var usedSystemFileNumbers: [UInt64] = []
func printData(_ dataIn: Data?) {
guard let data = dataIn else {
print("data nil")
return
}
if data.isEmpty {
print("data empty")
return
}
print(" 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15")
print("---+-------------------------------------------------+-----------------")
let length = data.count
let line = length / 16 + (0 == length % 16 ? 0 : 1)
var dataIndex = data.startIndex
for row in 0..<line {
if dataIndex == data.endIndex {
break
}
print("\(String(format: "%02u", row)) : ", terminator: "")
let initialDataIndex = dataIndex
var cursor = initialDataIndex
for _ in 0...15 {
if cursor == data.endIndex {
print(" ", terminator: "")
continue
}
print("\(String(format: "%02x", data[cursor])) ", terminator: "")
cursor = data.index(after: cursor)
}
print(": ", terminator: "")
cursor = initialDataIndex
for _ in 0...15 {
if cursor == data.endIndex {
break
}
let oneByteString = String(format: "%c", data[cursor])
if let char = oneByteString.unicodeScalars.first {
if CharacterSet.alphanumerics.contains(char) || CharacterSet.whitespaces.contains(char) {
print("\(char)", terminator: "")
} else {
print(".", terminator: "")
}
} else {
print(".", terminator: "")
}
cursor = data.index(after: cursor)
}
print()
dataIndex = cursor
}
}
class FileItemInfo {
var name: String = ""
var type: FileAttributeType = .typeUnknown
var dateModification: Date? = nil
var permission: UInt16? = nil
var ownerAccountId: UInt32? = nil
var groupOwnerAccountId: UInt32? = nil
var systemFileNumber: UInt64
var dateCreation: Date? = nil
var dateBackup: Date? = nil
var dateChange: Date? = nil
var dateAccess: Date? = nil
var flags: UInt32? = nil
var hardLinkTarget: [FileItemInfo] = []
var openCount: UInt64 = 0
var softLinkSource: String? = nil
private var _internalHardLinkSource: FileItemInfo? = nil
var hardLinkSource: FileItemInfo? {
get {
return _internalHardLinkSource
}
set(value) {
if nil != value {
if let index = usedSystemFileNumbers.firstIndex(of: self.systemFileNumber) {
usedSystemFileNumbers.remove(at: index)
}
self.systemFileNumber = 0
_internalHardLinkSource = value
}
}
}
private var _internalSize: UInt64 = 0
var size: UInt64 {
get {
return _internalSize
}
}
private var _internalData: Data? = nil
var data: Data? {
get {
return _internalData
}
set(value) {
if nil != value {
_internalData = value
_internalSize = UInt64(value!.count)
}
}
}
public init(_ name: String, type: FileAttributeType, dateModification: Date? = nil, permission: UInt16? = nil, ownerAccountId: UInt32? = nil, groupOwnerAccountId: UInt32? = nil, dateCreation: Date? = nil, dateBackup: Date? = nil, dateChange: Date? = nil, dateAccess: Date? = nil, flags: UInt32? = nil, data: Data? = nil, hardLinkSource: FileItemInfo? = nil, hardLinkTarget: [FileItemInfo] = []) {
self.name = name
self.type = type
self.dateModification = dateModification
self.permission = permission
self.ownerAccountId = ownerAccountId
self.groupOwnerAccountId = groupOwnerAccountId
self.dateCreation = dateCreation
self.dateBackup = dateBackup
self.dateChange = dateChange
self.dateAccess = dateAccess
self.flags = flags
var gen = SystemRandomNumberGenerator()
var number = gen.next()
while 0 == number || usedSystemFileNumbers.contains(number) {
number = gen.next()
}
usedSystemFileNumbers.append(number)
self.systemFileNumber = number
self.hardLinkSource = hardLinkSource
self.hardLinkTarget = hardLinkTarget
self.data = data
}
}
class FileTree {
var this: FileItemInfo
var children: [FileTree]
public init(_ item: FileItemInfo, children: [FileTree] = []) {
self.this = item
self.children = children
}
}
var root = FileItemInfo("/", type: .typeDirectory, dateModification: Date(), permission: 0o777, ownerAccountId: 501, groupOwnerAccountId: 20, dateCreation: Date(), dateBackup: Date(), dateChange: Date(), dateAccess: Date(), flags: 0)
var root_subdir = FileItemInfo("subdir", type: .typeDirectory, dateModification: Date(), permission: 0o777, ownerAccountId: 501, groupOwnerAccountId: 20, dateCreation: Date(), dateBackup: Date(), dateChange: Date(), dateAccess: Date(), flags: 0)
var root_file = FileItemInfo("file", type: .typeRegular, dateModification: Date(), permission: 0o777, ownerAccountId: 501, groupOwnerAccountId: 20, dateCreation: Date(), dateBackup: Date(), dateChange: Date(), dateAccess: Date(), flags: 0)
//var root_hardLinkToFile = FileItemInfo("hard_link_to_file", type: .typeRegular)
//root_hardLinkToFile.hardLinkSource = root_file
//root_file.hardLinkTarget.append(root_hardLinkToFile)
root_file.data = Data(repeating: 68, count: 2789)
var tree = FileTree(root)
tree.children.append(FileTree(root_subdir))
tree.children.append(FileTree(root_file))
//tree.children.append(FileTree(root_hardLinkToFile))
func getFileTree(byPath: String) -> FileTree? {
var tokens = byPath.components(separatedBy: "/")
if let first = tokens.first {
if "" == first {
tokens = Array(tokens.dropFirst())
}
}
if let last = tokens.last {
if "" == last {
tokens = Array(tokens.dropLast())
}
}
var cursor: FileTree? = tree
var currentDir: String = "/"
if !tokens.isEmpty {
for token in tokens {
print("finding \"\(token)\" under \"\(currentDir)\"")
cursor = cursor?.children.first {$0.this.name == token}
guard let _ = cursor else {
print("failed.")
return nil
}
currentDir += "\(token)/"
}
}
return cursor
}
class FuseDelegator : NSObject {
override func willMount() {
print("\(Date().description) :: willMount called.")
}
override func willUnmount() {
print("\(Date().description) :: willUnmount called.")
}
override func contentsOfDirectory(atPath path: String!) throws -> [Any] {
guard let pathUnwrap = path else {
throw NSError(domain: #function, code: #line, userInfo: nil)
}
print("\n\n------------------------------------------------------------")
print("\(Date().description) :: contentsOfDirectory called. \"\(pathUnwrap)\"")
guard let found = getFileTree(byPath: pathUnwrap) else {
print("error.")
throw NSError(domain: #function, code: #line, userInfo: nil)
}
print("~~~~~~~~~~~ found ~~~~~~~~~~~~~")
let result = found.children.map {$0.this.name}
print("result = \(result)")
return result
}
override func attributesOfItem(atPath path: String!, userData: Any!) throws -> [AnyHashable : Any] {
guard let pathUnwrap = path else {
throw NSError(domain: #function, code: #line, userInfo: nil)
}
print("\(Date().description) :: attributesOfItem called. \"\(pathUnwrap)\" \"\(String(describing: userData))\"")
guard let foundTree = getFileTree(byPath: pathUnwrap) else {
print("error.")
throw NSError(domain: #function, code: #line, userInfo: nil)
}
print("~~~~~~~~~~~ found ~~~~~~~~~~~~~")
var found = foundTree.this
while .typeDirectory != found.type && nil != found.hardLinkSource {
found = found.hardLinkSource!
print("hard link to \"\(found.name)\" filenumber \(found.systemFileNumber)")
}
var result: [AnyHashable : Any] = [FileAttributeKey.type : found.type]
print("type : \(found.type)")
var fileSize: UInt64 = 0
switch found.type {
case .typeDirectory:
fileSize = UInt64(2 + foundTree.children.count) * UInt64(DIRECTORY_ITEM_SIZE)
result.updateValue(fileSize, forKey: FileAttributeKey.size)
print("size : \(fileSize)")
default:
fileSize = found.size
result.updateValue(fileSize, forKey: FileAttributeKey.size)
print("size : \(fileSize)")
}
if let date = found.dateModification {
result.updateValue(date, forKey: FileAttributeKey.modificationDate)
print("mod date : \(date)")
}
switch found.type {
case .typeDirectory:
let count = 2 + foundTree.children.count
result.updateValue(count, forKey: FileAttributeKey.referenceCount)
print("reference count : \(count)")
default:
let count = 1 + found.hardLinkTarget.count
result.updateValue(count, forKey: FileAttributeKey.referenceCount)
print("reference count : \(count)")
}
if let permission = found.permission {
result.updateValue(permission, forKey: FileAttributeKey.posixPermissions)
print("posix permission : \(String(permission, radix: 8, uppercase: false))")
}
if let ownerUid = found.ownerAccountId {
result.updateValue(ownerUid, forKey: FileAttributeKey.ownerAccountID)
print("owner account id \(ownerUid)")
}
if let ownerGid = found.groupOwnerAccountId {
result.updateValue(ownerGid, forKey: FileAttributeKey.groupOwnerAccountID)
print("group owner account id \(ownerGid)")
}
result.updateValue(found.systemFileNumber, forKey: FileAttributeKey.systemFileNumber)
print("system file number \(found.systemFileNumber)")
if let date = found.dateCreation {
result.updateValue(date, forKey: FileAttributeKey.creationDate)
print("create date : \(date)")
}
if let date = found.dateBackup {
result.updateValue(date, forKey: kGMUserFileSystemFileBackupDateKey)
print("backup date : \(date)")
}
if let date = found.dateChange {
result.updateValue(date, forKey: kGMUserFileSystemFileChangeDateKey)
print("change date : \(date)")
}
if let date = found.dateAccess {
result.updateValue(date, forKey: kGMUserFileSystemFileAccessDateKey)
print("access date : \(date)")
}
if let flags = found.flags {
result.updateValue(flags, forKey: kGMUserFileSystemFileFlagsKey)
print("flags : \(String(format: "%o", flags))")
}
var sizeInBlocks = fileSize / UInt64(FILE_SYSTEM_BLOCK_SIZE)
let rest = fileSize % UInt64(FILE_SYSTEM_BLOCK_SIZE)
if 0 < rest {
sizeInBlocks += 1
}
result.updateValue(sizeInBlocks, forKey: kGMUserFileSystemFileSizeInBlocksKey)
print("size in blocks : \(sizeInBlocks)")
return result
}
// override func attributesOfFileSystem(forPath path: String!) throws -> [AnyHashable : Any] {
// print("\(Date().description) :: attributesOfFileSystem called. \"\(path)\"")
// throw NSError(domain: #function, code: #line, userInfo: nil)
// }
override func setAttributes(_ attributes: [AnyHashable : Any]!, ofItemAtPath path: String!, userData: Any!) throws {
guard let pathUnwrap = path else {
throw NSError(domain: #function, code: #line, userInfo: nil)
}
print("\(Date().description) :: setAttributes called. \"\(pathUnwrap)\" \(String(describing: attributes))")
guard let foundTree = getFileTree(byPath: pathUnwrap) else {
print("error.")
throw NSError(domain: #function, code: #line, userInfo: nil)
}
var found = foundTree.this
while .typeDirectory != found.type && nil != found.hardLinkSource {
found = found.hardLinkSource!
print("hard link to \"\(found.name)\" filenumber \(found.systemFileNumber)")
}
if let size = attributes[FileAttributeKey.size] as? UInt64 {
if var data = found.data {
if size <= data.count {
found.data = data.prefix(Int(size))
} else {
data.append(contentsOf: Array(repeating: 0, count: Int(size) - data.count))
found.data = data
}
} else {
found.data = Data(repeating: 0, count: Int(size))
}
}
if let permission = attributes[FileAttributeKey.posixPermissions] as? UInt16 {
found.permission = permission
}
if let date = attributes[FileAttributeKey.modificationDate] as? Date {
found.dateModification = date
}
if let date = attributes[FileAttributeKey.creationDate] as? Date {
found.dateCreation = date
}
if let date = attributes[kGMUserFileSystemFileBackupDateKey] as? Date {
found.dateBackup = date
}
if let date = attributes[kGMUserFileSystemFileChangeDateKey] as? Date {
found.dateChange = date
}
if let date = attributes[kGMUserFileSystemFileAccessDateKey] as? Date {
found.dateAccess = date
}
if let flags = attributes[kGMUserFileSystemFileFlagsKey] as? UInt32 {
found.flags = flags
}
}
override func contents(atPath path: String!) -> Data! {
guard let pathUnwrap = path else {
return nil
}
print("\(Date().description) :: contents called. \"\(pathUnwrap)\"")
guard let foundTree = getFileTree(byPath: pathUnwrap) else {
print("error.")
return nil
}
var found = foundTree.this
while .typeDirectory != found.type && nil != found.hardLinkSource {
found = found.hardLinkSource!
print("hard link to \"\(found.name)\" filenumber \(found.systemFileNumber)")
}
guard let data = found.data else {
return Data(count: 0)
}
printData(data)
return data
}
override func openFile(atPath path: String!, mode: Int32, userData: AutoreleasingUnsafeMutablePointer<AnyObject?>!) throws {
guard let pathUnwrap = path else {
throw NSError(domain: #function, code: #line, userInfo: nil)
}
print("\(Date().description) :: openFile called. \"\(pathUnwrap)\" mode \(mode)")
guard let foundTree = getFileTree(byPath: pathUnwrap) else {
print("error.")
throw NSError(domain: #function, code: #line, userInfo: nil)
}
var found = foundTree.this
while .typeDirectory != found.type && nil != found.hardLinkSource {
found = found.hardLinkSource!
print("hard link to \"\(found.name)\" filenumber \(found.systemFileNumber)")
}
found.openCount += 1
userData.pointee = found
}
override func releaseFile(atPath path: String!, userData: Any!) {
print("\(Date().description) :: releaseFile called. \"\(String(describing: path))\" \"\(String(describing: userData))\"")
guard let item = userData as? FileItemInfo else {
return
}
item.openCount -= 1
}
override func readFile(atPath path: String!, userData: Any!, buffer: UnsafeMutablePointer<Int8>!, size: Int, offset: off_t, error: NSErrorPointer) -> Int32 {
print("\(Date().description) :: readFile called. \"\(String(describing: path))\" \"\(String(describing: userData))\" size \(size) offset \(offset)")
guard let item = userData as? FileItemInfo else {
error?.pointee = NSError(domain: #function, code: #line, userInfo: nil)
return -1
}
guard let data = item.data else {
if 0 == size {
return 0
}
error?.pointee = NSError(domain: #function, code: #line, userInfo: nil)
return -1
}
if data.count < offset {
print("offset over")
error?.pointee = NSError(domain: #function, code: #line, userInfo: nil)
return -1
}
let newData = data.dropFirst(Int(offset))
var newSize = size
if newData.count < newSize {
newSize = newData.count
}
guard let bufferUnwrap = buffer else {
error?.pointee = NSError(domain: #function, code: #line, userInfo: nil)
return -1
}
return bufferUnwrap.withMemoryRebound(to: UInt8.self, capacity: newSize) { body in
newData.copyBytes(to: body, count: newSize)
printData(newData)
return Int32(newSize)
}
}
override func writeFile(atPath path: String!, userData: Any!, buffer: UnsafePointer<Int8>!, size: Int, offset: off_t, error: NSErrorPointer) -> Int32 {
print("\(Date().description) :: writeFile called. \"\(String(describing: path))\" \"\(String(describing: userData))\" buffer \(String(describing: buffer)) size \(size) offset \(offset)")
guard let item = userData as? FileItemInfo else {
error?.pointee = NSError(domain: #function, code: #line, userInfo: nil)
return -1
}
guard let bufferUnwrap = buffer else {
error?.pointee = NSError(domain: #function, code: #line, userInfo: nil)
return -1
}
return bufferUnwrap.withMemoryRebound(to: UInt8.self, capacity: size) { body in
if let oldData = item.data {
var newData = oldData.prefix(Int(offset))
newData.append(body, count: size)
item.data = newData
} else {
if 0 != offset {
error?.pointee = NSError(domain: #function, code: #line, userInfo: nil)
return -1
}
item.data = Data(bytes: body, count: size)
}
printData(item.data)
return Int32(size)
}
}
// override func preallocateFile(atPath path: String!, userData: Any!, options: Int32, offset: off_t, length: off_t) throws {
// print("\(Date().description) :: preallocateFile called. \"\(path)\" \"\(userData)\"")
// throw NSError(domain: #function, code: #line, userInfo: nil)
// }
//
// override func exchangeDataOfItem(atPath path1: String!, withItemAtPath path2: String!) throws {
// print("\(Date().description) :: exchangeDataOfItem called. \"\(path1)\" \"\(path2)\"")
// throw NSError(domain: #function, code: #line, userInfo: nil)
// }
override func createDirectory(atPath path: String!, attributes: [AnyHashable : Any]! = [:]) throws {
guard let pathUnwrap = path else {
throw NSError(domain: #function, code: #line, userInfo: nil)
}
print("\(Date().description) :: createDirectory called. \"\(pathUnwrap)\" \"\(String(describing: attributes))\"")
guard let newNameStart = pathUnwrap.lastIndex(of: "/") else {
throw NSError(domain: #function, code: #line, userInfo: nil)
}
let newName = String(pathUnwrap.suffix(from: pathUnwrap.index(after: newNameStart)))
guard let found = getFileTree(byPath: String(pathUnwrap.prefix(upTo: newNameStart))) else {
print("error.")
throw NSError(domain: #function, code: #line, userInfo: nil)
}
let newSubDir = FileItemInfo(newName, type: .typeDirectory)
if let permission = attributes[FileAttributeKey.posixPermissions] as? UInt16 {
newSubDir.permission = permission
}
found.children.append(FileTree(newSubDir))
}
override func createFile(atPath path: String!, attributes: [AnyHashable : Any]! = [:], flags: Int32, userData: AutoreleasingUnsafeMutablePointer<AnyObject?>!) throws {
guard let pathUnwrap = path else {
throw NSError(domain: #function, code: #line, userInfo: nil)
}
print("\(Date().description) :: createFile called. \"\(pathUnwrap)\" \"\(String(describing: attributes))\" flags \(flags)")
guard let newNameStart = pathUnwrap.lastIndex(of: "/") else {
throw NSError(domain: #function, code: #line, userInfo: nil)
}
let newName = String(pathUnwrap.suffix(from: pathUnwrap.index(after: newNameStart)))
guard let found = getFileTree(byPath: String(pathUnwrap.prefix(upTo: newNameStart))) else {
print("error.")
throw NSError(domain: #function, code: #line, userInfo: nil)
}
let newFile = FileItemInfo(newName, type: .typeRegular)
if let permission = attributes[FileAttributeKey.posixPermissions] as? UInt16 {
newFile.permission = permission
}
found.children.append(FileTree(newFile))
newFile.openCount += 1
userData.pointee = newFile
}
override func moveItem(atPath source: String!, toPath destination: String!) throws {
guard let srcUnwrap = source else {
throw NSError(domain: #function, code: #line, userInfo: nil)
}
guard let dstUnwrap = destination else {
throw NSError(domain: #function, code: #line, userInfo: nil)
}
print("\(Date().description) :: moveItem called. \"\(srcUnwrap)\" \"\(dstUnwrap)\"")
guard let srcNameStart = srcUnwrap.lastIndex(of: "/") else {
throw NSError(domain: #function, code: #line, userInfo: nil)
}
let srcName = String(srcUnwrap.suffix(from: srcUnwrap.index(after: srcNameStart)))
let srcDir = String(srcUnwrap.prefix(upTo: srcNameStart))
guard let dstNameStart = dstUnwrap.lastIndex(of: "/") else {
throw NSError(domain: #function, code: #line, userInfo: nil)
}
let dstName = String(dstUnwrap.suffix(from: dstUnwrap.index(after: dstNameStart)))
let dstDir = String(dstUnwrap.prefix(upTo: dstNameStart))
guard let srcParent = getFileTree(byPath: srcDir) else {
throw NSError(domain: #function, code: #line, userInfo: nil)
}
guard let dstParent = getFileTree(byPath: dstDir) else {
throw NSError(domain: #function, code: #line, userInfo: nil)
}
guard let srcIndex = srcParent.children.firstIndex(where: {$0.this.name == srcName}) else {
throw NSError(domain: #function, code: #line, userInfo: nil)
}
let found = srcParent.children.remove(at: srcIndex)
found.this.name = dstName
dstParent.children.append(found)
}
override func removeDirectory(atPath path: String!) throws {
guard let pathUnwrap = path else {
throw NSError(domain: #function, code: #line, userInfo: nil)
}
print("\(Date().description) :: removeDirectory called. \"\(pathUnwrap)\"")
guard let targetDirNameStart = pathUnwrap.lastIndex(of: "/") else {
throw NSError(domain: #function, code: #line, userInfo: nil)
}
let targetDirName = String(pathUnwrap.suffix(from: pathUnwrap.index(after: targetDirNameStart)))
guard let foundParent = getFileTree(byPath: String(pathUnwrap.prefix(upTo: targetDirNameStart))) else {
print("error.")
throw NSError(domain: #function, code: #line, userInfo: nil)
}
guard let found = foundParent.children.first(where: {$0.this.name == targetDirName}) else {
print("error.")
throw NSError(domain: #function, code: #line, userInfo: nil)
}
if !found.children.isEmpty {
print("not empty")
throw NSError(domain: #function, code: #line, userInfo: nil)
}
guard let index = foundParent.children.firstIndex(where: {$0.this.name == targetDirName}) else {
print("error.")
throw NSError(domain: #function, code: #line, userInfo: nil)
}
foundParent.children.remove(at: index)
}
override func removeItem(atPath path: String!) throws {
guard let pathUnwrap = path else {
throw NSError(domain: #function, code: #line, userInfo: nil)
}
print("\(Date().description) :: removeItem called. \"\(pathUnwrap)\"")
guard let targetDirNameStart = pathUnwrap.lastIndex(of: "/") else {
throw NSError(domain: #function, code: #line, userInfo: nil)
}
let targetDirName = String(pathUnwrap.suffix(from: pathUnwrap.index(after: targetDirNameStart)))
guard let foundParent = getFileTree(byPath: String(pathUnwrap.prefix(upTo: targetDirNameStart))) else {
print("error.")
throw NSError(domain: #function, code: #line, userInfo: nil)
}
guard let foundIndex = foundParent.children.firstIndex(where: {$0.this.name == targetDirName}) else {
print("error.")
throw NSError(domain: #function, code: #line, userInfo: nil)
}
foundParent.children.remove(at: foundIndex)
}
override func linkItem(atPath path: String!, toPath otherPath: String!) throws {
guard let srcUnwrap = path else {
throw NSError(domain: #function, code: #line, userInfo: nil)
}
guard let dstUnwrap = otherPath else {
throw NSError(domain: #function, code: #line, userInfo: nil)
}
print("\(Date().description) :: linkItem called. \"\(srcUnwrap)\" \"\(dstUnwrap)\"")
guard let srcNameStart = srcUnwrap.lastIndex(of: "/") else {
throw NSError(domain: #function, code: #line, userInfo: nil)
}
let srcName = String(srcUnwrap.suffix(from: srcUnwrap.index(after: srcNameStart)))
let srcDir = String(srcUnwrap.prefix(upTo: srcNameStart))
guard let dstNameStart = dstUnwrap.lastIndex(of: "/") else {
throw NSError(domain: #function, code: #line, userInfo: nil)
}
let dstName = String(dstUnwrap.suffix(from: dstUnwrap.index(after: dstNameStart)))
let dstDir = String(dstUnwrap.prefix(upTo: dstNameStart))
guard let srcParent = getFileTree(byPath: srcDir) else {
throw NSError(domain: #function, code: #line, userInfo: nil)
}
guard let dstParent = getFileTree(byPath: dstDir) else {
throw NSError(domain: #function, code: #line, userInfo: nil)
}
guard let srcIndex = srcParent.children.firstIndex(where: {$0.this.name == srcName}) else {
throw NSError(domain: #function, code: #line, userInfo: nil)
}
let found = srcParent.children[srcIndex]
let linked = FileItemInfo(dstName, type: .typeRegular)
linked.hardLinkSource = found.this
found.this.hardLinkTarget.append(linked)
dstParent.children.append(FileTree(linked))
}
override func createSymbolicLink(atPath path: String!, withDestinationPath otherPath: String!) throws {
guard let srcUnwrap = otherPath else {
throw NSError(domain: #function, code: #line, userInfo: nil)
}
guard let dstUnwrap = path else {
throw NSError(domain: #function, code: #line, userInfo: nil)
}
print("\(Date().description) :: createSymbolicLink called. \"\(srcUnwrap)\" \"\(dstUnwrap)\"")
guard let dstNameStart = dstUnwrap.lastIndex(of: "/") else {
throw NSError(domain: #function, code: #line, userInfo: nil)
}
let dstName = String(dstUnwrap.suffix(from: dstUnwrap.index(after: dstNameStart)))
let dstDir = String(dstUnwrap.prefix(upTo: dstNameStart))
guard let dstParent = getFileTree(byPath: dstDir) else {
throw NSError(domain: #function, code: #line, userInfo: nil)
}
let linked = FileItemInfo(dstName, type: .typeSymbolicLink)
linked.softLinkSource = srcUnwrap
dstParent.children.append(FileTree(linked))
}
override func destinationOfSymbolicLink(atPath path: String!) throws -> String {
guard let pathUnwrap = path else {
throw NSError(domain: #function, code: #line, userInfo: nil)
}
print("\(Date().description) :: destinationOfSymbolicLink called. \"\(pathUnwrap)\"")
guard let foundTree = getFileTree(byPath: pathUnwrap) else {
print("error.")
throw NSError(domain: #function, code: #line, userInfo: nil)
}
guard let source = foundTree.this.softLinkSource else {
print("error.")
throw NSError(domain: #function, code: #line, userInfo: nil)
}
return source
}
override func extendedAttributesOfItem(atPath path: Any!) throws -> [Any] {
print("\(Date().description) :: extendedAttributesOfItem called. \"\(path)\"")
return []
}
override func value(ofExtendedAttribute name: String!, ofItemAtPath path: String!, position: off_t) throws -> Data {
print("\(Date().description) :: value called. \"\(path)\" \"\(name)\"")
throw NSError(domain: #function, code: #line, userInfo: nil)
}
override func setExtendedAttribute(_ name: String!, ofItemAtPath path: String!, value: Data!, position: off_t, options: Int32) throws {
print("\(Date().description) :: setExtendedAttribute called. \"\(path)\" \"\(name)\"")
throw NSError(domain: #function, code: #line, userInfo: nil)
}
override func removeExtendedAttribute(_ name: String!, ofItemAtPath path: String!) throws {
print("\(Date().description) :: removeExtendedAttribute called. \"\(path)\" \"\(name)\"")
throw NSError(domain: #function, code: #line, userInfo: nil)
}
}
class Observer {
@objc func didMount(noti: Notification) {
print("============================================================")
print("\(Date().description) !!!! mounted !!!! \(noti) !!!!")
print("============================================================")
}
@objc func mountFailed(noti: Notification) {
print("============================================================")
print("\(Date().description) !!!! mount failed !!!! \(noti) !!!!")
print("============================================================")
}
@objc func unmounted(noti: Notification) {
print("============================================================")
print("\(Date().description) !!!!unmounted !!!! \(noti) !!!!")
print("============================================================")
}
}
let observer = Observer()
NotificationCenter.default.addObserver(observer, selector: #selector(Observer.didMount), name: NSNotification.Name(rawValue: kGMUserFileSystemDidMount), object: nil)
NotificationCenter.default.addObserver(observer, selector: #selector(Observer.mountFailed), name: NSNotification.Name(rawValue: kGMUserFileSystemMountFailed), object: nil)
NotificationCenter.default.addObserver(observer, selector: #selector(Observer.unmounted), name: NSNotification.Name(rawValue: kGMUserFileSystemDidUnmount), object: nil)
let delegator = FuseDelegator()
let test = GMUserFileSystem.init(delegate: delegator, isThreadSafe: false)
test?.mount(atPath: "/Volumes/inMemoryFuse", withOptions: ["nolocalcaches", "noubc"], shouldForeground: true, detachNewThread: false)
defer {
test?.unmount()
}
signal(SIGINT) { s in test?.unmount(); exit(0) }
while true {
sleep(1)
}
Please remove the contents callback. It is only required if you do not implement the open and read callbacks.
The header states:
/*!
* @abstract Returns file contents at the specified path.
* @discussion Returns the full contents at the given path. Implementation of
* this delegate method is recommended only by very simple file systems that are
* not concerned with performance. If contentsAtPath is implemented then you can
* skip open/release/read.
* @param path The path to the file.
* @result The contents of the file or nil if a file does not exist at path.
*/
- (NSData *)contentsAtPath:(NSString *)path GM_AVAILABLE(2_0);
@bfleischer then, If I implement open/release/read, should I not implement contents?
Yes, implementing the contents callback causes the issue above. create will call through to your delegate. Then your write callback is called with 111. The file handle is in open/release/read mode.
Then when you enter 222 <enter> it looks like the file is opened a second time in read-only mode. You have now two open file handles, one in contents mode (read-only) and one in open/release/read mode (read-write). The 222 is written to the open/release/read handle, but the cat you issue thereafter is serviced from the contents mode (read-only) file handle, that never received the 222 write.
The contents mode is only intended for very rudimentary in memory file systems, like the HelloFS example file system.
@bfleischer thank you!