(root)/
tar-1.35/
src/
unlink.c
       1  /* Unlink files.
       2  
       3     Copyright 2009-2023 Free Software Foundation, Inc.
       4  
       5     This file is part of GNU tar.
       6  
       7     GNU tar is free software; you can redistribute it and/or modify
       8     it under the terms of the GNU General Public License as published by
       9     the Free Software Foundation; either version 3 of the License, or
      10     (at your option) any later version.
      11  
      12     GNU tar is distributed in the hope that it will be useful,
      13     but WITHOUT ANY WARRANTY; without even the implied warranty of
      14     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      15     GNU General Public License for more details.
      16  
      17     You should have received a copy of the GNU General Public License
      18     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
      19  
      20  #include <system.h>
      21  #include "common.h"
      22  #include <quotearg.h>
      23  
      24  struct deferred_unlink
      25    {
      26      struct deferred_unlink *next;   /* Next unlink in the queue */
      27      int dir_idx;                    /* Directory index in wd */
      28      char *file_name;                /* Name of the file to unlink, relative
      29  				       to dir_idx */
      30      bool is_dir;                    /* True if file_name is a directory */
      31      off_t records_written;          /* Number of records written when this
      32  				       entry got added to the queue */
      33    };
      34  
      35  #define IS_CWD(p) \
      36    ((p)->is_dir \
      37     && ((p)->file_name[0] == 0 || strcmp ((p)->file_name, ".") == 0))
      38  
      39  /* The unlink queue */
      40  static struct deferred_unlink *dunlink_head, *dunlink_tail;
      41  
      42  /* Number of entries in the queue */
      43  static size_t dunlink_count;
      44  
      45  /* List of entries available for allocation */
      46  static struct deferred_unlink *dunlink_avail;
      47  
      48  /* Delay (number of records written) between adding entry to the
      49     list and its actual removal. */
      50  static size_t deferred_unlink_delay = 0;
      51  
      52  static struct deferred_unlink *
      53  dunlink_alloc (void)
      54  {
      55    struct deferred_unlink *p;
      56    if (dunlink_avail)
      57      {
      58        p = dunlink_avail;
      59        dunlink_avail = p->next;
      60        p->next  = NULL;
      61      }
      62    else
      63      p = xmalloc (sizeof (*p));
      64    return p;
      65  }
      66  
      67  static void
      68  dunlink_insert (struct deferred_unlink *anchor, struct deferred_unlink *p)
      69  {
      70    if (anchor)
      71      {
      72        p->next = anchor->next;
      73        anchor->next = p;
      74      }
      75    else 
      76      {
      77        p->next = dunlink_head;
      78        dunlink_head = p;
      79      }
      80    if (!p->next)
      81      dunlink_tail = p;
      82    dunlink_count++;
      83  }
      84  
      85  static void
      86  dunlink_reclaim (struct deferred_unlink *p)
      87  {
      88    free (p->file_name);
      89    p->next = dunlink_avail;
      90    dunlink_avail = p;
      91  }
      92  
      93  static void
      94  flush_deferred_unlinks (bool force)
      95  {
      96    struct deferred_unlink *p, *prev = NULL;
      97    int saved_chdir = chdir_current;
      98    
      99    for (p = dunlink_head; p; )
     100      {
     101        struct deferred_unlink *next = p->next;
     102  
     103        if (force
     104  	  || records_written > p->records_written + deferred_unlink_delay)
     105  	{
     106  	  chdir_do (p->dir_idx);
     107  	  if (p->is_dir)
     108  	    {
     109  	      const char *fname;
     110  
     111  	      if (p->dir_idx && IS_CWD (p))
     112  		{
     113  		  prev = p;
     114  		  p = next;
     115  		  continue;
     116  		}
     117  	      else
     118  		fname = p->file_name;
     119  
     120  	      if (unlinkat (chdir_fd, fname, AT_REMOVEDIR) != 0)
     121  		{
     122  		  switch (errno)
     123  		    {
     124  		    case ENOENT:
     125  		      /* nothing to worry about */
     126  		      break;
     127  		    case EEXIST:
     128  		      /* OpenSolaris >=10 sets EEXIST instead of ENOTEMPTY
     129  			 if trying to remove a non-empty directory */
     130  #if defined ENOTEMPTY && ENOTEMPTY != EEXIST
     131  		    case ENOTEMPTY:
     132  #endif
     133  		      /* Keep the record in list, in the hope we'll
     134  			 be able to remove it later */
     135  		      prev = p;
     136  		      p = next;
     137  		      continue;
     138  
     139  		    default:
     140  		      rmdir_error (fname);
     141  		    }
     142  		}
     143  	    }
     144  	  else
     145  	    {
     146  	      if (unlinkat (chdir_fd, p->file_name, 0) != 0 && errno != ENOENT)
     147  		unlink_error (p->file_name);
     148  	    }
     149  	  dunlink_reclaim (p);
     150  	  dunlink_count--;
     151  	  p = next;
     152  	  if (prev)
     153  	    prev->next = p;
     154  	  else
     155  	    dunlink_head = p;
     156  	}
     157        else
     158  	{
     159  	  prev = p;
     160  	  p = next;
     161  	}
     162      }
     163    if (!dunlink_head)
     164      dunlink_tail = NULL;
     165    else if (force)
     166      {
     167        for (p = dunlink_head; p; )
     168  	{
     169  	  struct deferred_unlink *next = p->next;
     170  	  const char *fname;
     171  
     172  	  chdir_do (p->dir_idx);
     173  	  if (p->dir_idx && IS_CWD (p))
     174  	    {
     175  	      fname = tar_dirname ();
     176  	      chdir_do (p->dir_idx - 1);
     177  	    }
     178  	  else
     179  	    fname = p->file_name;
     180  
     181  	  if (unlinkat (chdir_fd, fname, AT_REMOVEDIR) != 0)
     182  	    {
     183  	      if (errno != ENOENT)
     184  		rmdir_error (fname);
     185  	    }
     186  	  dunlink_reclaim (p);
     187  	  dunlink_count--;
     188  	  p = next;
     189  	}
     190        dunlink_head = dunlink_tail = NULL;
     191      }	  
     192  	    
     193    chdir_do (saved_chdir);
     194  }
     195  
     196  void
     197  finish_deferred_unlinks (void)
     198  {
     199    flush_deferred_unlinks (true);
     200    
     201    while (dunlink_avail)
     202      {
     203        struct deferred_unlink *next = dunlink_avail->next;
     204        free (dunlink_avail);
     205        dunlink_avail = next;
     206      }
     207  }
     208  
     209  void
     210  queue_deferred_unlink (const char *name, bool is_dir)
     211  {
     212    struct deferred_unlink *p;
     213  
     214    if (dunlink_head
     215        && records_written > dunlink_head->records_written + deferred_unlink_delay)
     216      flush_deferred_unlinks (false);
     217  
     218    p = dunlink_alloc ();
     219    p->next = NULL;
     220    p->dir_idx = chdir_current;
     221    p->file_name = xstrdup (name);
     222    normalize_filename_x (p->file_name);
     223    p->is_dir = is_dir;
     224    p->records_written = records_written;
     225  
     226    if (IS_CWD (p))
     227      {
     228        struct deferred_unlink *q, *prev;
     229        for (q = dunlink_head, prev = NULL; q; prev = q, q = q->next)
     230  	if (IS_CWD (q) && q->dir_idx < p->dir_idx)
     231  	  break;
     232        if (q)
     233  	dunlink_insert (prev, p);
     234        else
     235  	dunlink_insert (dunlink_tail, p);
     236      }
     237    else
     238      dunlink_insert (dunlink_tail, p);
     239  }