site_archiver@lists.apple.com Delivered-To: darwin-kernel@lists.apple.com Here is the short answer: Don't write code that depends on being able to do this. Here is a long answer you are not going to like: Here is a longer answer that you're really not going to like: <http://www.opengroup.org/onlinepubs/009695399/functions/lchown.html> -- Terry _______________________________________________ Do not post admin requests to the list. They will be ignored. Darwin-kernel mailing list (Darwin-kernel@lists.apple.com) Help/Unsubscribe/Update your Subscription: http://lists.apple.com/mailman/options/darwin-kernel/site_archiver%40lists.a... On Dec 15, 2006, at 11:27 PM, Jerry Krinock wrote: It looks to me like utimes() does not work for symbolic links. If the symlink is valid, it returns 0, but sets both atime and mtime to the current time instead of the time I pass in as argument. If the symlink is broken, it returns -1 and also sets atime and mtime to the current time. The documentation man utimes (2) does not mention that there is any problem with symbolic links. Does anyone know, is this a bug in utimes(), a documentation bug, or my misunderstanding? Also, does anyone know of a good function that works for setting atime and mtime of all files, including symlinks? I don't think that FSSetCatalogInfo works for symlinks either, but it's too late to try tonight. And even if it does, it ain't documented. You can try "getattrlist" and "setattrlist". Whether it works will depend on the FS implementation, and there will be no indication in some FSs that it's operating on the link target rather than the link itself. Basically, you can only do it if you know for sure it already works, and you only ever intend to deploy your applciation on a specific FS type mounted by a specific OS type. You need to realize that symbolic links are generally not considered to be first class file system objects (real files, with just a different type, with all their own metadata) or second class file system objects (real files, with a different type, but most metadata from the parent directory, not from the link object), and many filesystems can implement them as immediate files (link data store in the inode) or virtual files (link data stored in the directory entry, with no on disk representation). Historically, HFS did not support symbolic links at all. When symbolic links were added, they were added as second class file systems objects: it was impossible to set the permissions or ownership on them directly, and readability and writeability were controlled by the ownership and flags on the directoy, not the link. When you did an ls -l, the information about the directory, not the link was returned. So the answer for HFS depends on your version of MacOS, and it's been evolving over time - you can't depend on your code acting the same from release to release of the OS, or even from software update to software update. There were several incarnations in UFS (FFS) as well. Initially, links were implemented as second class FS objects. Later, early in the life of FreeBSD, they were reimplemented first as immediate files, and then later as virtual files. In the later incarnation, you only ended up with an inode object if the lnk length was too long to fit into a directory entry block (512b - directory entry bookkeeping information, including a flag as to whether or not the entry was a symlink). Eventually, this was dropped, since the performance advantage was not sufficient to justify the complexity of the implementation. So basically, you should never depend on being able to set symlink ownership or permissions; further, you should expect that if you happen to be using an FS that doesn't support setting link ownership or permissions, that any changes you make with normal FS APIs (like utimes) will result in an operation on the link target (if there is one) or the directory in which the link resides. Typically, if you feel you need to control this type of thing, there's something wrong with your application or your security model (specifically, you are assuming a specific implementation, which might not be true on other system where you might want to one day run your code). To address this, there have been a couple of APIs introduced, initially on Linux. These are lchmod() and lchown(); there is no equivalent for lutimes() or lchflags(), and so on. These interfaces have a number of very serious technical flaws inherent in their design, and this is inherited in the design of software that relies on them: First, only the lchown() operation is specified by the POSIX standard; this is problematic for a number of reasons. The major one is that that means any vendor that implements more than simply the lchown() call is at risk of binary incompatibility if and when anything beyond this becomes standardized. If the (eventual) standard does not match the (premature) implementation, the consequence to the vendor is that they are no longer standards compliant. Because no vendor can afford this, they would have to change to match the standard, and this could introduce a binary compatibility issue - and break software that relied on the old behaviour. Basically, and application that used the old interface and broke would need to offer updates to their irate customers an update, and both the application writer and the vendor would look bad. Second, lchown() standard was rushed through the standardization process and shows it: The most egregious problem with the design of this API is that it specifies that the error in the case of non-support of the API is the error code EOPNOTSUPP; but this is a bogus error code. The error code means "Operation not supported on socket" - in other words, it's a networking specific error code, and symlinks are not sockets. The correct error code from a design perspective would be ENOTSUP, meaning "Operation not supported". Third, because these APIs are filesystem option, it encourages people to write software which depends on them working, because they happen to have a filesystem that both implements symbolic links as a first class FS object, and supports the API. Then when you try to perform the same operation against an FS that *doesn't* support them, your software breaks. Then the application vendor gets a bad review, or they get a phone call from an angry customer who wants the problem fixed. Finally, they are inherently racy and insecure. Specifically, any operation you perform involving symlink-specific APIs will end up being both non-atomic and non-idempotent. For example, say you wanted to change the owner of an object; first, you have to find out whether or not it's a link, so you call lstat() to see if it's a link, then if it is, you call lchown() to change it's mode, otherwise, you call chown(). This is dangerous; if the object was a link, it could be changed to a non-link between your call to lstat() and subsequent call to lchown(); likewise, if it's a non-link, it could be changed to a link between the time you call lstat() and the subsequent call to chown(). The first race is never avoidable. You can avoid the second race by always using lchown(), which will always get you the link rather than the symlink as the target - but what if that is not really what you wanted? The result is either that you chown() a file in a way that you didn't want to chown() it, or you end up with a link that can be redirected to another file at some other point after you expect you are already doing "secure" operations against it. The way you avoid this with files is to open the file, at which point you have a persistent handle on it, and then you use fchown() on it. There's no moral equivalent for an "open" symlink... even if the symlink is stored as a first or second class FS object, there's no guarantee that you will be able to get an open fd referencing it (FreeBSD and Solaris allow this one _some_ FS's by honoring the O_NOFOLLOW flag to open() from user space, but you can't open the file for read or write - you have to just open it with no intent of reading or writing). And on FS's where it's not, you are guaranteed that you *will not* be able to get an fd (it's just like trying to open a directory for read or write, rather than using file creation and deletion operations or opendir() - usually buit on a directory iteration API like getdirentries()). This email sent to site_archiver@lists.apple.com
participants (1)
-
Terry Lambert