(root)/
tar-1.35/
gnu/
unlink.c
       1  /* Work around unlink bugs.
       2  
       3     Copyright (C) 2009-2023 Free Software Foundation, Inc.
       4  
       5     This file is free software: you can redistribute it and/or modify
       6     it under the terms of the GNU Lesser General Public License as
       7     published by the Free Software Foundation; either version 2.1 of the
       8     License, or (at your option) any later version.
       9  
      10     This file is distributed in the hope that it will be useful,
      11     but WITHOUT ANY WARRANTY; without even the implied warranty of
      12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      13     GNU Lesser General Public License for more details.
      14  
      15     You should have received a copy of the GNU Lesser General Public License
      16     along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
      17  
      18  #include <config.h>
      19  
      20  #include <unistd.h>
      21  
      22  #include <errno.h>
      23  #include <stdlib.h>
      24  #include <string.h>
      25  #include <sys/stat.h>
      26  
      27  #include "filename.h"
      28  
      29  #undef unlink
      30  #if defined _WIN32 && !defined __CYGWIN__
      31  # define unlink _unlink
      32  #endif
      33  
      34  /* Remove file NAME.
      35     Return 0 if successful, -1 if not.  */
      36  
      37  int
      38  rpl_unlink (char const *name)
      39  {
      40    /* Work around Solaris 9 bug where unlink("file/") succeeds.  */
      41    size_t len = strlen (name);
      42    int result = 0;
      43    if (len && ISSLASH (name[len - 1]))
      44      {
      45        /* We can't unlink(2) something if it doesn't exist.  If it does
      46           exist, then it resolved to a directory, due to the trailing
      47           slash, and POSIX requires that the unlink attempt to remove
      48           that directory (which would leave the symlink dangling).
      49           Unfortunately, Solaris 9 is one of the platforms where the
      50           root user can unlink directories, and we don't want to
      51           cripple this behavior on real directories, even if it is
      52           seldom needed (at any rate, it's nicer to let coreutils'
      53           unlink(1) give the correct errno for non-root users).  But we
      54           don't know whether name was an actual directory, or a symlink
      55           to a directory; and due to the bug of ignoring trailing
      56           slash, Solaris 9 would end up successfully unlinking the
      57           symlink instead of the directory.  Technically, we could use
      58           realpath to find the canonical directory name to attempt
      59           deletion on.  But that is a lot of work for a corner case; so
      60           we instead just use an lstat on the shortened name, and
      61           reject symlinks with trailing slashes.  The root user of
      62           unlink(1) will just have to live with the rule that they
      63           can't delete a directory via a symlink.  */
      64        struct stat st;
      65        result = lstat (name, &st);
      66        if (result == 0 || errno == EOVERFLOW)
      67          {
      68            /* Trailing NUL will overwrite the trailing slash.  */
      69            char *short_name = malloc (len);
      70            if (!short_name)
      71              return -1;
      72            memcpy (short_name, name, len);
      73            while (len && ISSLASH (short_name[len - 1]))
      74              short_name[--len] = '\0';
      75            if (len && (lstat (short_name, &st) || S_ISLNK (st.st_mode)))
      76              {
      77                free (short_name);
      78                errno = EPERM;
      79                return -1;
      80              }
      81            free (short_name);
      82            result = 0;
      83          }
      84      }
      85    if (!result)
      86      {
      87  #if UNLINK_PARENT_BUG
      88        if (len >= 2 && name[len - 1] == '.' && name[len - 2] == '.'
      89            && (len == 2 || ISSLASH (name[len - 3])))
      90          {
      91            errno = EISDIR; /* could also use EPERM */
      92            return -1;
      93          }
      94  #endif
      95        result = unlink (name);
      96      }
      97    return result;
      98  }