(root)/
coreutils-9.4/
gnulib-tests/
test-lchown.h
       1  /* Tests of lchown.
       2     Copyright (C) 2009-2023 Free Software Foundation, Inc.
       3  
       4     This program is free software: you can redistribute it and/or modify
       5     it under the terms of the GNU General Public License as published by
       6     the Free Software Foundation, either version 3 of the License, or
       7     (at your option) any later version.
       8  
       9     This program is distributed in the hope that it will be useful,
      10     but WITHOUT ANY WARRANTY; without even the implied warranty of
      11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12     GNU General Public License for more details.
      13  
      14     You should have received a copy of the GNU General Public License
      15     along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
      16  
      17  /* Written by Eric Blake <ebb9@byu.net>, 2009.  */
      18  
      19  #include "nap.h"
      20  
      21  #if !HAVE_GETGID
      22  # define getgid() ((gid_t) -1)
      23  #endif
      24  
      25  #if !HAVE_GETEGID
      26  # define getegid() ((gid_t) -1)
      27  #endif
      28  
      29  #ifndef HAVE_LCHMOD
      30  # define HAVE_LCHMOD 0
      31  #endif
      32  
      33  #ifndef CHOWN_CHANGE_TIME_BUG
      34  # define CHOWN_CHANGE_TIME_BUG 0
      35  #endif
      36  
      37  /* This file is designed to test lchown(n,o,g) and
      38     chownat(AT_FDCWD,n,o,g,AT_SYMLINK_NOFOLLOW).  FUNC is the function
      39     to test.  Assumes that BASE and ASSERT are already defined, and
      40     that appropriate headers are already included.  If PRINT, warn
      41     before skipping symlink tests with status 77.  */
      42  
      43  static int
      44  test_lchown (int (*func) (char const *, uid_t, gid_t), bool print)
      45  {
      46    struct stat st1;
      47    struct stat st2;
      48    gid_t *gids = NULL;
      49    int gids_count;
      50    int result;
      51  
      52    /* Solaris 8 is interesting - if the current process belongs to
      53       multiple groups, the current directory is owned by a group that
      54       the current process belongs to but different than getegid(), and
      55       the current directory does not have the S_ISGID bit, then regular
      56       files created in the directory belong to the directory's group,
      57       but symlinks belong to the current effective group id.  If
      58       S_ISGID is set, then both files and symlinks belong to the
      59       directory's group.  However, it is possible to run the testsuite
      60       from within a directory owned by a group we don't belong to, in
      61       which case all things that we create belong to the current
      62       effective gid.  So, work around the issues by creating a
      63       subdirectory (we are guaranteed that the subdirectory will be
      64       owned by one of our current groups), change ownership of that
      65       directory to the current effective gid (which will thus succeed),
      66       then create all other files within that directory (eliminating
      67       questions on whether inheritance or current id triumphs, since
      68       the two methods resolve to the same gid).  */
      69    ASSERT (mkdir (BASE "dir", 0700) == 0);
      70    ASSERT (stat (BASE "dir", &st1) == 0);
      71  
      72    /* Filter out mingw and file systems which have no concept of groups.  */
      73    result = func (BASE "dir", st1.st_uid, getegid ());
      74    if (result == -1 && (errno == ENOSYS || errno == EPERM))
      75      {
      76        ASSERT (rmdir (BASE "dir") == 0);
      77        if (print)
      78          fputs ("skipping test: no support for ownership\n", stderr);
      79        return 77;
      80      }
      81    ASSERT (result == 0);
      82  
      83    ASSERT (close (creat (BASE "dir/file", 0600)) == 0);
      84    ASSERT (stat (BASE "dir/file", &st1) == 0);
      85    ASSERT (st1.st_uid != (uid_t) -1);
      86    ASSERT (st1.st_gid != (gid_t) -1);
      87    /* On macOS 12, when logged in through ssh, getgid () and getegid () are both
      88       == (gid_t) -1.  */
      89    if (getgid () != (gid_t) -1)
      90      ASSERT (st1.st_gid == getegid ());
      91  
      92    /* Sanity check of error cases.  */
      93    errno = 0;
      94    ASSERT (func ("", -1, -1) == -1);
      95    ASSERT (errno == ENOENT);
      96    errno = 0;
      97    ASSERT (func ("no_such", -1, -1) == -1);
      98    ASSERT (errno == ENOENT);
      99    errno = 0;
     100    ASSERT (func ("no_such/", -1, -1) == -1);
     101    ASSERT (errno == ENOENT);
     102    errno = 0;
     103    ASSERT (func (BASE "dir/file/", -1, -1) == -1);
     104    ASSERT (errno == ENOTDIR);
     105  
     106    /* Check that -1 does not alter ownership.  */
     107    ASSERT (func (BASE "dir/file", -1, st1.st_gid) == 0);
     108    ASSERT (func (BASE "dir/file", st1.st_uid, -1) == 0);
     109    ASSERT (func (BASE "dir/file", (uid_t) -1, (gid_t) -1) == 0);
     110    ASSERT (stat (BASE "dir/file", &st2) == 0);
     111    ASSERT (st1.st_uid == st2.st_uid);
     112    ASSERT (st1.st_gid == st2.st_gid);
     113  
     114    /* Even if the values aren't changing, ctime is required to change
     115       if at least one argument is not -1.  */
     116    nap ();
     117    ASSERT (func (BASE "dir/file", st1.st_uid, st1.st_gid) == 0);
     118    ASSERT (stat (BASE "dir/file", &st2) == 0);
     119    ASSERT (st1.st_ctime < st2.st_ctime
     120            || (st1.st_ctime == st2.st_ctime
     121                && get_stat_ctime_ns (&st1) < get_stat_ctime_ns (&st2)));
     122  
     123    /* Test symlink behavior.  */
     124    if (symlink ("link", BASE "dir/link2"))
     125      {
     126        ASSERT (unlink (BASE "dir/file") == 0);
     127        ASSERT (rmdir (BASE "dir") == 0);
     128        if (print)
     129          fputs ("skipping test: symlinks not supported on this file system\n",
     130                 stderr);
     131        return 77;
     132      }
     133    result = func (BASE "dir/link2", -1, -1);
     134    if (result == -1 && (errno == ENOSYS || errno == EOPNOTSUPP))
     135      {
     136        ASSERT (unlink (BASE "dir/file") == 0);
     137        ASSERT (unlink (BASE "dir/link2") == 0);
     138        ASSERT (rmdir (BASE "dir") == 0);
     139        if (print)
     140          fputs ("skipping test: symlink ownership not supported\n", stderr);
     141        return 77;
     142      }
     143    ASSERT (result == 0);
     144    errno = 0;
     145    ASSERT (func (BASE "dir/link2/", st1.st_uid, st1.st_gid) == -1);
     146    ASSERT (errno == ENOENT);
     147    ASSERT (symlink ("file", BASE "dir/link") == 0);
     148    ASSERT (mkdir (BASE "dir/sub", 0700) == 0);
     149    ASSERT (symlink ("sub", BASE "dir/link3") == 0);
     150  
     151    /* For non-privileged users, lchown can only portably succeed at
     152       changing group ownership of a file we own.  If we belong to at
     153       least two groups, then verifying the correct change is simple.
     154       But if we belong to only one group, then we fall back on the
     155       other observable effect of lchown: the ctime must be updated.  */
     156    gids_count = mgetgroups (NULL, st1.st_gid, &gids);
     157    if (1 < gids_count)
     158      {
     159        ASSERT (gids[1] != st1.st_gid);
     160        if (getgid () != (gid_t) -1)
     161          ASSERT (gids[1] != (gid_t) -1);
     162        ASSERT (lstat (BASE "dir/link", &st2) == 0);
     163        ASSERT (st1.st_uid == st2.st_uid);
     164        ASSERT (st1.st_gid == st2.st_gid);
     165        ASSERT (lstat (BASE "dir/link2", &st2) == 0);
     166        ASSERT (st1.st_uid == st2.st_uid);
     167        ASSERT (st1.st_gid == st2.st_gid);
     168  
     169        errno = 0;
     170        ASSERT (func (BASE "dir/link2/", -1, gids[1]) == -1);
     171        ASSERT (errno == ENOTDIR);
     172        ASSERT (stat (BASE "dir/file", &st2) == 0);
     173        ASSERT (st1.st_uid == st2.st_uid);
     174        ASSERT (st1.st_gid == st2.st_gid);
     175        ASSERT (lstat (BASE "dir/link", &st2) == 0);
     176        ASSERT (st1.st_uid == st2.st_uid);
     177        ASSERT (st1.st_gid == st2.st_gid);
     178        ASSERT (lstat (BASE "dir/link2", &st2) == 0);
     179        ASSERT (st1.st_uid == st2.st_uid);
     180        ASSERT (st1.st_gid == st2.st_gid);
     181  
     182        ASSERT (func (BASE "dir/link2", -1, gids[1]) == 0);
     183        ASSERT (stat (BASE "dir/file", &st2) == 0);
     184        ASSERT (st1.st_uid == st2.st_uid);
     185        ASSERT (st1.st_gid == st2.st_gid);
     186        ASSERT (lstat (BASE "dir/link", &st2) == 0);
     187        ASSERT (st1.st_uid == st2.st_uid);
     188        ASSERT (st1.st_gid == st2.st_gid);
     189        ASSERT (lstat (BASE "dir/link2", &st2) == 0);
     190        ASSERT (st1.st_uid == st2.st_uid);
     191        if (getgid () != (gid_t) -1)
     192          ASSERT (gids[1] == st2.st_gid);
     193  
     194        /* Trailing slash follows through to directory.  */
     195        ASSERT (lstat (BASE "dir/link3", &st2) == 0);
     196        ASSERT (st1.st_uid == st2.st_uid);
     197        ASSERT (st1.st_gid == st2.st_gid);
     198        ASSERT (lstat (BASE "dir/sub", &st2) == 0);
     199        ASSERT (st1.st_uid == st2.st_uid);
     200        ASSERT (st1.st_gid == st2.st_gid);
     201  
     202        ASSERT (func (BASE "dir/link3/", -1, gids[1]) == 0);
     203        ASSERT (lstat (BASE "dir/link3", &st2) == 0);
     204        ASSERT (st1.st_uid == st2.st_uid);
     205        ASSERT (st1.st_gid == st2.st_gid);
     206        ASSERT (lstat (BASE "dir/sub", &st2) == 0);
     207        ASSERT (st1.st_uid == st2.st_uid);
     208        if (getgid () != (gid_t) -1)
     209          ASSERT (gids[1] == st2.st_gid);
     210      }
     211    else if (!CHOWN_CHANGE_TIME_BUG || HAVE_LCHMOD)
     212      {
     213        /* If we don't have lchmod, and lchown fails to change ctime,
     214           then we can't test this part of lchown.  */
     215        struct stat l1;
     216        struct stat l2;
     217        ASSERT (stat (BASE "dir/file", &st1) == 0);
     218        ASSERT (lstat (BASE "dir/link", &l1) == 0);
     219        ASSERT (lstat (BASE "dir/link2", &l2) == 0);
     220  
     221        nap ();
     222        errno = 0;
     223        ASSERT (func (BASE "dir/link2/", -1, st1.st_gid) == -1);
     224        ASSERT (errno == ENOTDIR);
     225        ASSERT (stat (BASE "dir/file", &st2) == 0);
     226        ASSERT (st1.st_ctime == st2.st_ctime);
     227        ASSERT (get_stat_ctime_ns (&st1) == get_stat_ctime_ns (&st2));
     228        ASSERT (lstat (BASE "dir/link", &st2) == 0);
     229        ASSERT (l1.st_ctime == st2.st_ctime);
     230        ASSERT (get_stat_ctime_ns (&l1) == get_stat_ctime_ns (&st2));
     231        ASSERT (lstat (BASE "dir/link2", &st2) == 0);
     232        ASSERT (l2.st_ctime == st2.st_ctime);
     233        ASSERT (get_stat_ctime_ns (&l2) == get_stat_ctime_ns (&st2));
     234  
     235        ASSERT (func (BASE "dir/link2", -1, st1.st_gid) == 0);
     236        ASSERT (stat (BASE "dir/file", &st2) == 0);
     237        ASSERT (st1.st_ctime == st2.st_ctime);
     238        ASSERT (get_stat_ctime_ns (&st1) == get_stat_ctime_ns (&st2));
     239        ASSERT (lstat (BASE "dir/link", &st2) == 0);
     240        ASSERT (l1.st_ctime == st2.st_ctime);
     241        ASSERT (get_stat_ctime_ns (&l1) == get_stat_ctime_ns (&st2));
     242        ASSERT (lstat (BASE "dir/link2", &st2) == 0);
     243        ASSERT (l2.st_ctime < st2.st_ctime
     244                || (l2.st_ctime == st2.st_ctime
     245                    && get_stat_ctime_ns (&l2) < get_stat_ctime_ns (&st2)));
     246  
     247        /* Trailing slash follows through to directory.  */
     248        ASSERT (lstat (BASE "dir/sub", &st1) == 0);
     249        ASSERT (lstat (BASE "dir/link3", &l1) == 0);
     250        nap ();
     251        ASSERT (func (BASE "dir/link3/", -1, st1.st_gid) == 0);
     252        ASSERT (lstat (BASE "dir/link3", &st2) == 0);
     253        ASSERT (l1.st_ctime == st2.st_ctime);
     254        ASSERT (get_stat_ctime_ns (&l1) == get_stat_ctime_ns (&st2));
     255        ASSERT (lstat (BASE "dir/sub", &st2) == 0);
     256        ASSERT (st1.st_ctime < st2.st_ctime
     257                || (st1.st_ctime == st2.st_ctime
     258                    && get_stat_ctime_ns (&st1) < get_stat_ctime_ns (&st2)));
     259      }
     260  
     261    /* Cleanup.  */
     262    free (gids);
     263    ASSERT (unlink (BASE "dir/file") == 0);
     264    ASSERT (unlink (BASE "dir/link") == 0);
     265    ASSERT (unlink (BASE "dir/link2") == 0);
     266    ASSERT (unlink (BASE "dir/link3") == 0);
     267    ASSERT (rmdir (BASE "dir/sub") == 0);
     268    ASSERT (rmdir (BASE "dir") == 0);
     269    return 0;
     270  }