Hi, When researching the spurious revert messages reported by Vincent Lefevre [1], I was looking at the code in svn_io__is_finfo_read_only() and svn_io_is_file_executable(). It gets the current UID and GID using the APR function apr_uid_current() and then compares to see if the owner of the file is the current user (then check for user write/execute permissions) or if the group owning the file is the current user's group (then check for group write/execute permissions) or otherwise check for world write/execute permissions.
I think there is a failure mode when a user has write permissions to a file through a secondary group, something like this: [[[ dsg@daniel-23:~/svn_trunk$ groups dsg adm dialout cdrom floppy sudo audio dip video plugdev netdev docker dsg@daniel-23:~/svn_trunk$ ls -l README -rw-rw-r-- 1 root sudo 2341 Sep 22 2022 README dsg@daniel-23:~/svn_trunk$ svn proplist -v README Properties on 'README': svn:eol-style native svn:keywords LastChangedDate dsg@daniel-23:~/svn_trunk$ svn revert README Reverted 'README' dsg@daniel-23:~/svn_trunk$ ls -l README -rw-rw-r-- 1 root sudo 2341 Sep 22 2022 README dsg@daniel-23:~/svn_trunk$ svn revert README Reverted 'README' dsg@daniel-23:~/svn_trunk$ ./subversion/svn/svn revert README dsg@daniel-23:~/svn_trunk$ ls -l README -rw-rw-r-- 1 root sudo 2341 Sep 22 2022 README dsg@daniel-23:~/svn_trunk$ ]]] My user dsg has the primary group dsg and, among others, belong to the sudo group. The README file is owned by root:sudo and has 664 permission. I'm thus able to write to the file. svn revert will report Reverted, since the file README isn't owned by the user DSG or the group DSG and thus svn_io__is_finfo_read_only() will return that the file is readonly. Since the file doesn't have svn:needs-lock it should be RW and the Reverted message comes from Subversion trying to restore the W flag (which fails, thus a second svn revert call will report the same message). I initially thought about rewriting svn_io__is_finfo_read_only() to look also for the user's secondary groups but when asking on dev@apr.a.o if there is an APR way of listing a users secondary groups, Joe Orton suggested [2] to instead check with access(2). I've run the testsuite on Ubuntu 23.10 and OpenBSD 7.4. Initially I had an error on special_tests.py symlink_destination_change() which contains a dangling symlink. It seems the old code was checking for W on the actual symlink, while access() is checking on the target (which doesn't exists and thus ENOENT). To solve this I added the test for ENOENT in svn_io__is_finfo_read_only(). The same test is not needed on svn_io_is_file_executable() since it will not be called for a symlink (see revert_wc_data()). I'm not sure if this is the right way to solve this problem or if a change should be done in revert_wc_data() also for the read_only check. [[[ Index: subversion/libsvn_subr/io.c =================================================================== --- subversion/libsvn_subr/io.c (revision 1915064) +++ subversion/libsvn_subr/io.c (working copy) @@ -2535,7 +2535,14 @@ svn_io__is_finfo_read_only(svn_boolean_t *read_onl apr_uid_t uid; apr_gid_t gid; - *read_only = FALSE; + *read_only = (access(file_info->fname, W_OK) != 0); + /* svn_io__is_finfo_read_only can be called with a dangling + * symlink. access() will check the permission on the missing + * target and return -1 and errno = ENOENT. Check for ENOENT + * and pretend the file is writeable, otherwise we will get + * spurious Reverted messages on the symlink. + */ + if (*read_only && errno == ENOENT) *read_only = FALSE; apr_err = apr_uid_current(&uid, &gid, pool); @@ -2542,16 +2549,6 @@ svn_io__is_finfo_read_only(svn_boolean_t *read_onl if (apr_err) return svn_error_wrap_apr(apr_err, _("Error getting UID of process")); - /* Check write bit for current user. */ - if (apr_uid_compare(uid, file_info->user) == APR_SUCCESS) - *read_only = !(file_info->protection & APR_UWRITE); - - else if (apr_gid_compare(gid, file_info->group) == APR_SUCCESS) - *read_only = !(file_info->protection & APR_GWRITE); - - else - *read_only = !(file_info->protection & APR_WWRITE); - #else /* WIN32 || __OS2__ || !APR_HAS_USER */ *read_only = (file_info->protection & APR_FREADONLY); #endif @@ -2565,27 +2562,7 @@ svn_io__is_finfo_executable(svn_boolean_t *executa apr_pool_t *pool) { #if defined(APR_HAS_USER) && !defined(WIN32) &&!defined(__OS2__) - apr_status_t apr_err; - apr_uid_t uid; - apr_gid_t gid; - - *executable = FALSE; - - apr_err = apr_uid_current(&uid, &gid, pool); - - if (apr_err) - return svn_error_wrap_apr(apr_err, _("Error getting UID of process")); - - /* Check executable bit for current user. */ - if (apr_uid_compare(uid, file_info->user) == APR_SUCCESS) - *executable = (file_info->protection & APR_UEXECUTE); - - else if (apr_gid_compare(gid, file_info->group) == APR_SUCCESS) - *executable = (file_info->protection & APR_GEXECUTE); - - else - *executable = (file_info->protection & APR_WEXECUTE); - + svn_io_is_file_executable(executable, file_info->fname, pool); #else /* WIN32 || __OS2__ || !APR_HAS_USER */ *executable = FALSE; #endif @@ -2599,12 +2576,7 @@ svn_io_is_file_executable(svn_boolean_t *executabl apr_pool_t *pool) { #if defined(APR_HAS_USER) && !defined(WIN32) &&!defined(__OS2__) - apr_finfo_t file_info; - - SVN_ERR(svn_io_stat(&file_info, path, APR_FINFO_PROT | APR_FINFO_OWNER, - pool)); - SVN_ERR(svn_io__is_finfo_executable(executable, &file_info, pool)); - + *executable = (access(path, X_OK) == 0); #else /* WIN32 || __OS2__ || !APR_HAS_USER */ *executable = FALSE; #endif ]]] At the moment I've kept the apr_uid_current() call in svn_io__is_finfo_read_only(). My original plan was to extend svn_io__is_finfo_read_only to report if a file is readonly but owned by someone else (see [3]) but thinking about it again I think we should rather handle this in io_set_perms() - at the moment I'm running out of time but I will come back to this before final commit. Since this isn't my area of expertise, I'd appreciate if someone could review it before I commit. Kind regards, Daniel Sahlberg [1] https://lists.apache.org/thread/p1ky889bxwy8okqly7h1lgckxfpldnxs [2] https://lists.apache.org/thread/3pr76fo7gzbqq397d9xvyd66gq5bwlmw [3] https://lists.apache.org/thread/sfkzqzgwr4wvvwfdcyhqgymhx6lmv24h