This patch adds support to KIO to preserve xattr of files and directories on copy or move.
Working for copy/move including overwrite and rename cases.
Not working for "write into" when coping folders have name conflict. I'm against adding to this user case as the name of the action don't imply overwrite.
Why? ERANGE means we need to come up with new value for valuelen, so we set it to zero and start over. On the new iteration it gets passed into fgetxattr/extattr_get_fd to find out sufficient value.
isnt this ignoring acl_from_mode part ? I mean not sure but i think we need to check if it is nullptr or not before assigning it otherwise we will ignore the acl_from_mode part.
You seem to be right. I blamed the code down to svn->git import and this bug was present all the time.
To properly fix this I'd start with a test for KIO::chmod job that changes ACL permissions, but I can't test this on FreeBSD as we don't have acl_from_mode.
This is already done by cmake/FindACL.cmake, why not add the other one (extattr.h) to that file as well?
The CMakeLists.txt in this directory calls find_package(ACL) so it'll be used.
Even though this isn't technically about ACLs, I'm guessing it all links *because* FindACL.cmake links to libattr, right?
And then this might also mean that the condition in FindACL.cmake needs to be updated, it currently *requires* HAVE_SYS_XATTR_H instead of allowing both variants.
But then I guess this means kpropertiesdialog.cpp needs to be ported to the sys/extattr.h API.
Has this been tested on a system with sys/extattr.h? I'm wondering if what will happen is: FindACL didn't find sys/attr.h so it doesn't link to libattr, and then the new code here fails to link. Or am I missing something?
This whole method should be in #if HAVE_SYS_XATTR_H || HAVE_SYS_EXTATTR_H.
Otherwise, on systems with neither defined, a lot of compiler warnings will happen, like "keyLen isn't initialized" on line 591.
You can leave the method in the .h without #if (those defines aren't known there).
Here you can either #if the method itself (declared and not defined and not used, is OK, although some might say, a bit unclean)
or #if the whole method *contents*, but then in the #else you need Q_UNUSED(src_fd); Q_UNUSED(dest_fd); to avoid compiler warnings about unused parameters.
Ah, I see, OK. No extra lib needed makes it simple.
The FindACL.cmake stuff is a bit of a mess now, with the need for extended attributes outside the ACL related code.
Maybe this could be split up into "find extended attribute stuff" and "find ACL stuff", the latter relying on the former.
But this merge request has been pending for long enough, let's do buildsystem refactoring as part of it.
Let's leave this part as is for now.
If you feel like it, I suggest followup commits to 1) enable the ACL stuff on systems with extattr, see the little bit of code in kpropertiesdialog.cpp, and 2) separate the cmake stuff for ACLs from the cmake stuff for extended attributes.
Phabricator didn't actually close the revision after I landed it because @bruns forgot to change his status to accepted. You can close this now, @arrowd. Great work!
I'm talking about the implementation of fileSystemSupportsACL in kpropertiesdialog.cpp, which uses getxattr.
But now that I take another look at it, I see that it's already implemented on FreeBSD, without the use of extattr, it uses statfs instead.
So I think I was wrong, this part is fine.
I guess the only thing that's a bit weird is that kio_file's attr code (unrelated to ACLs) relies on FindACL linking to the right lib (on Linux). It would be cleaner if the attr stuff was separate. But no big deal I guess.
There are other parts of code that are guarded with HAVE_POSIX_ACLs, and these aren't enabled ATM for FreeBSD. So, the change is needed and I'm willing to implement it.
I plan to move set(HAVE_POSIX_ACL ...) part into FindACL.cmake and use this module everywhere. Does that sound OK?
I was wondering why copying files in Krusader or Dolphin from a vfat-formatted memory card stopped working for me after update. After copying the first file, the transfer stopped progressing and the process ended up consuming 100% CPU forever. Later I discovered that the cause of the breakage was this patch: If fgetxattr() fails with ENOTSUP, the code loops indefinitely calling fgetxattr().
The following hotfix made copying of files in Krusader work again for me:
However, the code might need bigger changes to cover all the cases. I do not fully understand the (IMO over-complicated) loops around flistxattr() and fgetxattr(). Note that the fact that one of them uses while (true) { ... } whereas the other one uses do { ... } while (true) does not improve code readability either.
I was wondering why copying files in Krusader or Dolphin from a vfat-formatted memory card stopped working for me after update. After copying the first file, the transfer stopped progressing and the process ended up consuming 100% CPU forever. Later I discovered that the cause of the breakage was this patch: If fgetxattr() fails with ENOTSUP, the code loops indefinitely calling fgetxattr().
The following hotfix made copying of files in Krusader work again for me:
...
However, the code might need bigger changes to cover all the cases. I do not fully understand the (IMO over-complicated) loops around flistxattr() and fgetxattr(). Note that the fact that one of them uses while (true) { ... } whereas the other one uses do { ... } while (true) does not improve code readability either.
I think, the correct fix is something like
diff --git a/src/ioslaves/file/file_unix.cpp b/src/ioslaves/file/file_unix.cppindex 74767123..4bc87c57 100644--- a/src/ioslaves/file/file_unix.cpp+++ b/src/ioslaves/file/file_unix.cpp@@ -614,8 +614,8 @@ bool FileProtocol::copyXattrs(const int src_fd, const int dest_fd)
valuelen = 0;
continue;
}
- // happens when attr value is an empty string- if (valuelen == 0) {+ // valuelen=0 happens when attr value is an empty string+ if (valuelen == 0 || valuelen == -1) {
break;
}
} while (true);
Even after applying the proposed patch, the code still looks problematic to me. I would prefer to have it explained first. When fgetxattr(..., 0) returns -1/ERANGE, what is the point of calling fgetxattr(..., 0) again? It is still going to busy-loop indefinitely in this case, doesn't it? How many times do we actually need to call fgetxattr() on a single file descriptor? Twice? Then unbounded loop is not the best construction to begin with.
Even after applying the proposed patch, the code still looks problematic to me. I would prefer to have it explained first. When fgetxattr(..., 0) returns -1/ERANGE, what is the point of calling fgetxattr(..., 0) again? It is still going to busy-loop indefinitely in this case, doesn't it? How many times do we actually need to call fgetxattr() on a single file descriptor? Twice? Then unbounded loop is not the best construction to begin with.
Ever heard of a TOCTOU race?
Quoting from man 2 getxattr:
If size is specified as zero, these calls return the current size of the named extended attribute (and leave value unchanged). This can be used to determine the size of the buffer that should be supplied in a subsequent call. (But, bear in mind that there is a possibility that the attribute value may change between the two calls, so that it is still necessary to check the return status from the second call.)
The man page says that one has to check return value of the second call. It does not say that the function needs to be called in a loop indefinitely until it succeeds.
The man page says that one has to check return value of the second call. It does not say that the function needs to be called in a loop indefinitely until it succeeds.
And if the second call then returns ERANGE you have to start from the beginning.
I do not think we have to. Please have a look at how attr_copy_fd() from <attr/libattr.h> is implemented: http://git.savannah.nongnu.org/cgit/attr.git/tree/libattr/attr_copy_fd.c?id=a4187bec#n111 This code is quite mature and it is used by cp(1) on GNU/Linux for instance. I do not think that KDE users will appreciate some blindly coded fancy features on top of that when the basic functionality gets totally broken as a result of these experiments. Please keep the code simple and reliable.
I do not think we have to. Please have a look at how attr_copy_fd() from <attr/libattr.h> is implemented: http://git.savannah.nongnu.org/cgit/attr.git/tree/libattr/attr_copy_fd.c?id=a4187bec#n111 This code is quite mature and it is used by cp(1) on GNU/Linux for instance. I do not think that KDE users will appreciate some blindly coded fancy features on top of that when the basic functionality gets totally broken as a result of these experiments. Please keep the code simple and reliable.
Its not blindly coded. Please keep your arrogant comments to yourself.
And obviously, there is something strange going on on your system - flistxattr returns a list of keys, but fgetxattr then returns ENOTSUP?
@bruns I find your attitude unnecessarily hostile. If you think that the code is perfect as it is, feel free to patch it case by case until it eventually works for everybody. That is your choice.
Anyway, strace of cp --preserve=xattr on the same device looks like this:
$ LC_ALL=C strace -e '%file,/.*xattr' cp --preserve=xattr /mnt/mmc/file .execve("/bin/cp", ["cp", "--preserve=xattr", "/mnt/mmc/file", "."], 0x7ffdf8529c88 /* 81 vars */) = 0access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3openat(AT_FDCWD, "/lib64/libacl.so.1", O_RDONLY|O_CLOEXEC) = 3openat(AT_FDCWD, "/lib64/libattr.so.1", O_RDONLY|O_CLOEXEC) = 3openat(AT_FDCWD, "/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3stat(".", {st_mode=S_IFDIR|S_ISVTX|0777, st_size=1522, ...}) = 0newfstatat(AT_FDCWD, "/mnt/mmc/file", {st_mode=S_IFREG|0755, st_size=0, ...}, 0) = 0newfstatat(AT_FDCWD, "./file", 0x7ffea3f59f50, 0) = -1 ENOENT (No such file or directory)openat(AT_FDCWD, "/mnt/mmc/file", O_RDONLY) = 3openat(AT_FDCWD, "./file", O_WRONLY|O_CREAT|O_EXCL, 0755) = 4flistxattr(3, NULL, 0) = 17flistxattr(3, "security.selinux\0", 17) = 17openat(AT_FDCWD, "/etc/xattr.conf", O_RDONLY) = 5fgetxattr(3, "security.selinux", NULL, 0) = -1 EOPNOTSUPP (Operation not supported)cp: getting attribute 'security.selinux' of 'security.selinux': Operation not supported+++ exited with 1 +++
@bruns I find your attitude unnecessarily hostile. If you think that the code is perfect as it is, feel free to patch it case by case until it eventually works for everybody. That is your choice.
I never said its perfect. There is exactly one case which was missed (and only in this one location, for both listxattr and setxattr a return value of -1 is handled correctly).
I do not think that KDE users will appreciate some blindly coded fancy features on top of that when the basic functionality gets totally broken as a result of these experiments.
Such well chosen words ...
Btw, there are plenty of more cases where kio deliberately deviates from the behavior of low level system tools.
I am fine with kio implementing things differently as long as the basic functionality keeps working. I used to use Krusader to get images out of my camera and it simply stopped working for me with no obvious indication of what went wrong. This code tries to implement some advanced error handling, yet it is written in a way that it passed a review with a fundamental programming mistake in the basic error handling.
The fix proposed in https://phabricator.kde.org/D17816#677448 is incomplete, as I understand it. If we break the loop with valuelen == -1, the value will be passed as size_t to the size argument of fsetxattr(), which may lead to reading value.constData() out of bounds.
What is the status of this? How do we move forwards? I know this has gone on a long time and we're all getting tired, but I think we can push this past the finish line without too much trouble, hopefully. :)
What is the status of this? How do we move forwards? I know this has gone on a long time and we're all getting tired, but I think we can push this past the finish line without too much trouble, hopefully. :)
This has landed long time ago. Unfortunately, in combination with SELINUX getfattr returns an error not mentioned in any man page, which causes an inifinite loop in the current code.