Re: utimes() no good for symbolic links?
Re: utimes() no good for symbolic links?
- Subject: Re: utimes() no good for symbolic links?
- From: Terry Lambert <email@hidden>
- Date: Sat, 16 Dec 2006 21:51:07 -0800
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.
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:
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.
Here is a longer answer that you're really not going to like:
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:
<http://www.opengroup.org/onlinepubs/009695399/functions/lchown.html>
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()).
-- Terry
_______________________________________________
Do not post admin requests to the list. They will be ignored.
Darwin-kernel mailing list (email@hidden)
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden