(root)/
coreutils-9.4/
gnulib-tests/
test-chown.h
       1  /* Tests of chown.
       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  /* This file is designed to test chown(n,o,g) and
      30     chownat(AT_FDCWD,n,o,g,0).  FUNC is the function to test.  Assumes
      31     that BASE and ASSERT are already defined, and that appropriate
      32     headers are already included.  If PRINT, warn before skipping
      33     symlink tests with status 77.  */
      34  
      35  static int
      36  test_chown (int (*func) (char const *, uid_t, gid_t), bool print)
      37  {
      38    struct stat st1;
      39    struct stat st2;
      40    gid_t *gids = NULL;
      41    int gids_count;
      42    int result;
      43  
      44    /* Solaris 8 is interesting - if the current process belongs to
      45       multiple groups, the current directory is owned by a group that
      46       the current process belongs to but different than getegid(), and
      47       the current directory does not have the S_ISGID bit, then regular
      48       files created in the directory belong to the directory's group,
      49       but symlinks belong to the current effective group id.  If
      50       S_ISGID is set, then both files and symlinks belong to the
      51       directory's group.  However, it is possible to run the testsuite
      52       from within a directory owned by a group we don't belong to, in
      53       which case all things that we create belong to the current
      54       effective gid.  So, work around the issues by creating a
      55       subdirectory (we are guaranteed that the subdirectory will be
      56       owned by one of our current groups), change ownership of that
      57       directory to the current effective gid (which will thus succeed),
      58       then create all other files within that directory (eliminating
      59       questions on whether inheritance or current id triumphs, since
      60       the two methods resolve to the same gid).  */
      61    ASSERT (mkdir (BASE "dir", 0700) == 0);
      62    ASSERT (stat (BASE "dir", &st1) == 0);
      63  
      64    /* Filter out mingw and file systems which have no concept of groups.  */
      65    result = func (BASE "dir", st1.st_uid, getegid ());
      66    if (result == -1 && (errno == ENOSYS || errno == EPERM))
      67      {
      68        ASSERT (rmdir (BASE "dir") == 0);
      69        if (print)
      70          fputs ("skipping test: no support for ownership\n", stderr);
      71        return 77;
      72      }
      73    ASSERT (result == 0);
      74  
      75    ASSERT (close (creat (BASE "dir/file", 0600)) == 0);
      76    ASSERT (stat (BASE "dir/file", &st1) == 0);
      77    ASSERT (st1.st_uid != (uid_t) -1);
      78    ASSERT (st1.st_gid != (gid_t) -1);
      79    /* On macOS 12, when logged in through ssh, getgid () and getegid () are both
      80       == (gid_t) -1.  */
      81    if (getgid () != (gid_t) -1)
      82      ASSERT (st1.st_gid == getegid ());
      83  
      84    /* Sanity check of error cases.  */
      85    errno = 0;
      86    ASSERT (func ("", -1, -1) == -1);
      87    ASSERT (errno == ENOENT);
      88    errno = 0;
      89    ASSERT (func ("no_such", -1, -1) == -1);
      90    ASSERT (errno == ENOENT);
      91    errno = 0;
      92    ASSERT (func ("no_such/", -1, -1) == -1);
      93    ASSERT (errno == ENOENT);
      94    errno = 0;
      95    ASSERT (func (BASE "dir/file/", -1, -1) == -1);
      96    ASSERT (errno == ENOTDIR);
      97  
      98    /* Check that -1 does not alter ownership.  */
      99    ASSERT (func (BASE "dir/file", -1, st1.st_gid) == 0);
     100    ASSERT (func (BASE "dir/file", st1.st_uid, -1) == 0);
     101    ASSERT (func (BASE "dir/file", (uid_t) -1, (gid_t) -1) == 0);
     102    ASSERT (stat (BASE "dir/file", &st2) == 0);
     103    ASSERT (st1.st_uid == st2.st_uid);
     104    ASSERT (st1.st_gid == st2.st_gid);
     105  
     106    /* Even if the values aren't changing, ctime is required to change
     107       if at least one argument is not -1.  */
     108    nap ();
     109    ASSERT (func (BASE "dir/file", st1.st_uid, st1.st_gid) == 0);
     110    ASSERT (stat (BASE "dir/file", &st2) == 0);
     111    ASSERT (st1.st_ctime < st2.st_ctime
     112            || (st1.st_ctime == st2.st_ctime
     113                && get_stat_ctime_ns (&st1) < get_stat_ctime_ns (&st2)));
     114  
     115    /* Test symlink behavior.  */
     116    if (symlink ("link", BASE "dir/link2"))
     117      {
     118        ASSERT (unlink (BASE "dir/file") == 0);
     119        ASSERT (rmdir (BASE "dir") == 0);
     120        if (print)
     121          fputs ("skipping test: symlinks not supported on this file system\n",
     122                 stderr);
     123        return 77;
     124      }
     125    errno = 0;
     126    ASSERT (func (BASE "dir/link2", -1, -1) == -1);
     127    ASSERT (errno == ENOENT);
     128    errno = 0;
     129    ASSERT (func (BASE "dir/link2/", st1.st_uid, st1.st_gid) == -1);
     130    ASSERT (errno == ENOENT);
     131    ASSERT (symlink ("file", BASE "dir/link") == 0);
     132  
     133    /* For non-privileged users, chown can only portably succeed at
     134       changing group ownership of a file we own.  If we belong to at
     135       least two groups, then verifying the correct change is simple.
     136       But if we belong to only one group, then we fall back on the
     137       other observable effect of chown: the ctime must be updated.  */
     138    gids_count = mgetgroups (NULL, st1.st_gid, &gids);
     139    if (1 < gids_count)
     140      {
     141        ASSERT (gids[1] != st1.st_gid);
     142        if (getgid () != (gid_t) -1)
     143          ASSERT (gids[1] != (gid_t) -1);
     144        ASSERT (lstat (BASE "dir/link", &st2) == 0);
     145        ASSERT (st1.st_uid == st2.st_uid);
     146        ASSERT (st1.st_gid == st2.st_gid);
     147        ASSERT (lstat (BASE "dir/link2", &st2) == 0);
     148        ASSERT (st1.st_uid == st2.st_uid);
     149        ASSERT (st1.st_gid == st2.st_gid);
     150  
     151        errno = 0;
     152        ASSERT (func (BASE "dir/link2/", -1, gids[1]) == -1);
     153        ASSERT (errno == ENOTDIR);
     154        ASSERT (stat (BASE "dir/file", &st2) == 0);
     155        ASSERT (st1.st_uid == st2.st_uid);
     156        ASSERT (st1.st_gid == st2.st_gid);
     157        ASSERT (lstat (BASE "dir/link", &st2) == 0);
     158        ASSERT (st1.st_uid == st2.st_uid);
     159        ASSERT (st1.st_gid == st2.st_gid);
     160        ASSERT (lstat (BASE "dir/link2", &st2) == 0);
     161        ASSERT (st1.st_uid == st2.st_uid);
     162        ASSERT (st1.st_gid == st2.st_gid);
     163  
     164        ASSERT (func (BASE "dir/link2", -1, gids[1]) == 0);
     165        ASSERT (stat (BASE "dir/file", &st2) == 0);
     166        ASSERT (st1.st_uid == st2.st_uid);
     167        if (getgid () != (gid_t) -1)
     168          ASSERT (gids[1] == st2.st_gid);
     169        ASSERT (lstat (BASE "dir/link", &st2) == 0);
     170        ASSERT (st1.st_uid == st2.st_uid);
     171        ASSERT (st1.st_gid == st2.st_gid);
     172        ASSERT (lstat (BASE "dir/link2", &st2) == 0);
     173        ASSERT (st1.st_uid == st2.st_uid);
     174        ASSERT (st1.st_gid == st2.st_gid);
     175      }
     176    else
     177      {
     178        struct stat l1;
     179        struct stat l2;
     180        ASSERT (stat (BASE "dir/file", &st1) == 0);
     181        ASSERT (lstat (BASE "dir/link", &l1) == 0);
     182        ASSERT (lstat (BASE "dir/link2", &l2) == 0);
     183  
     184        nap ();
     185        errno = 0;
     186        ASSERT (func (BASE "dir/link2/", -1, st1.st_gid) == -1);
     187        ASSERT (errno == ENOTDIR);
     188        ASSERT (stat (BASE "dir/file", &st2) == 0);
     189        ASSERT (st1.st_ctime == st2.st_ctime);
     190        ASSERT (get_stat_ctime_ns (&st1) == get_stat_ctime_ns (&st2));
     191        ASSERT (lstat (BASE "dir/link", &st2) == 0);
     192        ASSERT (l1.st_ctime == st2.st_ctime);
     193        ASSERT (get_stat_ctime_ns (&l1) == get_stat_ctime_ns (&st2));
     194        ASSERT (lstat (BASE "dir/link2", &st2) == 0);
     195        ASSERT (l2.st_ctime == st2.st_ctime);
     196        ASSERT (get_stat_ctime_ns (&l2) == get_stat_ctime_ns (&st2));
     197  
     198        ASSERT (func (BASE "dir/link2", -1, st1.st_gid) == 0);
     199        ASSERT (stat (BASE "dir/file", &st2) == 0);
     200        ASSERT (st1.st_ctime < st2.st_ctime
     201                || (st1.st_ctime == st2.st_ctime
     202                    && get_stat_ctime_ns (&st1) < get_stat_ctime_ns (&st2)));
     203        ASSERT (lstat (BASE "dir/link", &st2) == 0);
     204        ASSERT (l1.st_ctime == st2.st_ctime);
     205        ASSERT (get_stat_ctime_ns (&l1) == get_stat_ctime_ns (&st2));
     206        ASSERT (lstat (BASE "dir/link2", &st2) == 0);
     207        ASSERT (l2.st_ctime == st2.st_ctime);
     208        ASSERT (get_stat_ctime_ns (&l2) == get_stat_ctime_ns (&st2));
     209      }
     210  
     211    /* Cleanup.  */
     212    free (gids);
     213    ASSERT (unlink (BASE "dir/file") == 0);
     214    ASSERT (unlink (BASE "dir/link") == 0);
     215    ASSERT (unlink (BASE "dir/link2") == 0);
     216    ASSERT (rmdir (BASE "dir") == 0);
     217    return 0;
     218  }