Osxfuse: broken buffer of writeFile callback function

Created on 7 Aug 2019  路  4Comments  路  Source: osxfuse/osxfuse

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)
}
question

All 4 comments

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!

Was this page helpful?
0 / 5 - 0 ratings