I've added two new methods to FilePath that make it easier to delete files and empty directories. The remove() method deletes a file at a given path, and the removedirectory() method deletes an empty directory. Both methods support a retryOnInterrupt parameter that defaults to true, which automatically retries the operation if it gets interrupted. They throw errors that you can catch and handle properly. These methods are available starting with System 1.7.0 and work on unix. Before this change, developers had to use lower-level system calls or work with FileDescriptor to delete files, so these new methods make file and directory deletion much simpler and more consistent with other FilePath operations. The implementation uses the standard POSIX functions unlink() for files and rmdir() for directories, and it follows the same patterns as other FilePath methods. windows…The changes are available in the feature/filepath-remove-operations branch. Thanks you very much.
Thanks for the contribution, Akshat.
The unlink and rmdir functions definitely deserve coverage in swift-system for the POSIX platforms. For Windows, it seems that the behaviour is different enough that we should have wrappers for the native API instead, not the POSIX-emulation layer. The various platforms aren’t required to have parity, and thus it’s acceptable to not yet have a solution for Windows.
For the next package release (1.7.0), swift-system will require Swift 6.0, which supports typed throws. We would like to transition the package to using typed throws and to throw(Errno) when wrapping C API that use errno as their error reporting mechanism. The addition (in progress) of the Stat type wrapping fstat(), fstat(), stat() and fstatat() uses typed throws.
As a result, I think the public API should be:
extension FilePath {
public func remove(retryOnInterrupt: Bool = true) throws(Errno) {
...
}
public func removeDirectory(retryOnInterrupt: Bool = true) throws(Errno) {
...
}
}
Note that the extensions to UnsafePointer should not exist or, at most, be internal implementation details.
Thanks for contributing to swift-system!
The unlink wrapper is quite natural, since we have FileDescriptor.open(), which wraps the correct way to create files (with the appropriate options.)
The rmdir wrapper feels strange, since we have not exposed directory-related functions. Should we also expose mkdir, opendir, readdir (and others) to enable operations on hierarchies?
We can keep them as it is? Or should i go with that typed throws? and make make the UnsafePointer extensions internal?
We should definitely make them throws(Errno). Can we avoid the UnsafePointer extension entirely? Right now we have only one UnsafePointer extension and it is for a commonly-used utility function for Windows syscalls.
I tried writing a test case that unlinks a file, and right away I found an issue with naming the file removal function “remove()”:
Since FilePath has a Collection-like interface to its components, we already have a bunch of other removeXXX functions. It would be much clearer to name the removeFile().
What do you think?
How about delete()? That is still a commonly used verb for this action and it avoids any overlap with the collection methods.
delete() sounds more definite than this operation. The operation is unlink, which detaches the given path from the underlying file. Deletion only occurs if the underlying file has no incoming links.
unlink is a very UNIX-specific terminology.
Could these APIs be added to a new type similar to Foundation.FileManager?
To me, the conflict with the collection API names indicates that these methods maybe should not belong to FilePath. As far as I can tell, FilePath’s API is currently only concerned with representing a file path, not interfacing with the file system.
I prefer FilePath over Foundation’s URL in many cases because it is so focused on this single purpose and clearly communicates that I am dealing with a file path, not a file itself. In my code, I frequently want to represent file paths that do not correspond to actual files on the machine on which the program is running.
URL has all kinds of attributes and communicates with the file system to check for directory status, making it more complicate to reason about what a value of type URL represents and how involved the file system is in any given URL method/property.
@akshatsinha0 With the additions you are pitching, is there a natural way to also add coverage for the rmdirat() and unlinkat() functions? The ability to remove a directory seems to call for the ability to create directories (mkdir, mkdirat), at a minimum.
I sympathize with Florian’s concern with mixing the semantic operations that currently exist on FilePath instances with operations that perform operations in the filesystem. There isn’t a natural place to host filesystem operations right now; should one be created?
There is swift-nio's somewhat new "NIOFileSystem" which is a modern implementation for working with a fs, but that requires pulling in swift-nio.
“NIOFileSystem” also does a lot more than what we would want to do in swift-system, since it segregates FS-related syscalls on their own thread pool.
+1 I would expect NIOFileSystem to sit on top of swift-system.
As a matter of fact NIOFileSystem does already sit on top of swift-system. swift-system is one of the few external dependencies that swift-nio (and the NIOFileSystem target) take.
I was just making sure everyone is aware of NIOFileSystem, I don't know enough to say whether it should/can be used or not.
I do prefer to not have multiple of the same functionality in the ecosystem, so thought there might be a chance that NIOFileSystem can help avoid this extra implementation if possible, plus avoid having to create another "file-system" type, given that it does really sound a bit weird to see that at least the original plan was to introduce these functions on top of the file-path type.
It does sound like NIOFileSystem is not a fit here, though. I only wonder if the external library (?) that is going to benefit from these new swift-system capabilities can just go for NIOFileSystem directly, but the post doesn't mention for what purpose these new functions are being introduced so I can't tell.
These at variants family are safer(with the public api layer), actually avoid toctou races and can be flexible also. Yah we can create that too.So createDirectory() / mkdirat() for symmetry. Also system_mkdir() is already used internally in FilePathTempPosix.swift
