📄 remove.c
字号:
/* remove.c -- core functions for removing files and directories Copyright (C) 88, 90, 91, 1994-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *//* Extracted from rm.c and librarified by Jim Meyering. */#ifdef _AIX #pragma alloca#endif#include <config.h>#include <stdio.h>#include <sys/types.h>#include <assert.h>#include "save-cwd.h"#include "system.h"#include "dirname.h"#include "error.h"#include "file-type.h"#include "hash.h"#include "hash-pjw.h"#include "obstack.h"#include "quote.h"#include "remove.h"/* Avoid shadowing warnings because these are functions declared in dirname.h as well as locals used below. */#define dir_name rm_dir_name#define dir_len rm_dir_len#define obstack_chunk_alloc malloc#define obstack_chunk_free free#ifndef PARAMS# if defined (__GNUC__) || __STDC__# define PARAMS(args) args# else# define PARAMS(args) ()# endif#endif/* FIXME: if possible, use autoconf... */#ifdef __GLIBC__# define ROOT_CAN_UNLINK_DIRS 0#else# define ROOT_CAN_UNLINK_DIRS 1#endifenum Ternary { T_UNKNOWN = 2, T_NO, T_YES };typedef enum Ternary Ternary;/* The prompt function may be called twice a given directory. The first time, we ask whether to descend into it, and the second time, we ask whether to remove it. */enum Prompt_action { PA_DESCEND_INTO_DIR = 2, PA_REMOVE_DIR };/* On systems with an lstat function that accepts the empty string, arrange to make lstat calls go through the wrapper function. */#if HAVE_LSTAT_EMPTY_STRING_BUGint rpl_lstat PARAMS((const char *, struct stat *));# define lstat(Name, Stat_buf) rpl_lstat(Name, Stat_buf)#endif#ifdef D_INO_IN_DIRENT# define D_INO(dp) ((dp)->d_ino)# define ENABLE_CYCLE_CHECK#else/* Some systems don't have inodes, so fake them to avoid lots of ifdefs. */# define D_INO(dp) 1#endif#if !defined S_ISLNK# define S_ISLNK(Mode) 0#endif/* Initial capacity of per-directory hash table of entries that have been processed but not been deleted. */#define HT_UNREMOVABLE_INITIAL_CAPACITY 13/* An entry in the active directory stack. Each entry corresponds to an `active' directory. */struct AD_ent{ /* For a given active directory, this is the set of names of entries in that directory that could/should not be removed. For example, `.' and `..', as well as files/dirs for which unlink/rmdir failed e.g., due to access restrictions. */ Hash_table *unremovable; /* Record the status for a given active directory; we need to know whether an entry was not removed, either because of an error or because the user declined. */ enum RM_status status; union { /* The directory's dev/ino. Used to ensure that `chdir some-subdir', then `chdir ..' takes us back to the same directory from which we started). (valid for all but the bottommost entry on the stack. */ struct dev_ino a; /* Enough information to restore the initial working directory. (valid only for the bottommost entry on the stack) */ struct saved_cwd saved_cwd; } u;};int euidaccess ();int yesno ();extern char *program_name;/* The name of the directory (starting with and relative to a command line argument) being processed. When a subdirectory is entered, a new component is appended (pushed). When RM chdir's out of a directory, the top component is removed (popped). This is used to form a full file name when necessary. */static struct obstack dir_stack;/* Stack of lengths of directory names (including trailing slash) appended to dir_stack. We have to have a separate stack of lengths (rather than just popping back to previous slash) because the first element pushed onto the dir stack may contain slashes. */static struct obstack len_stack;/* Stack of active directory entries. The first `active' directory is the initial working directory. Additional active dirs are pushed onto the stack as rm `chdir's into each nonempty directory it must remove. When rm has finished removing the hierarchy under a directory, it pops the active dir stack. */static struct obstack Active_dir;static voidhash_freer (void *x){ free (x);}static boolhash_compare_strings (void const *x, void const *y){ return STREQ (x, y) ? true : false;}static inline voidpush_dir (const char *dir_name){ size_t len; len = strlen (dir_name); /* Append the string onto the stack. */ obstack_grow (&dir_stack, dir_name, len); /* Append a trailing slash. */ obstack_1grow (&dir_stack, '/'); /* Add one for the slash. */ ++len; /* Push the length (including slash) onto its stack. */ obstack_grow (&len_stack, &len, sizeof (len));}/* Return the entry name of the directory on the top of the stack in malloc'd storage. */static inline char *top_dir (void){ int n_lengths = obstack_object_size (&len_stack) / sizeof (size_t); size_t *length = (size_t *) obstack_base (&len_stack); size_t top_len = length[n_lengths - 1]; char const *p = obstack_next_free (&dir_stack) - top_len; char *q = xmalloc (top_len); memcpy (q, p, top_len - 1); q[top_len - 1] = 0; return q;}static inline voidpop_dir (void){ int n_lengths = obstack_object_size (&len_stack) / sizeof (size_t); size_t *length = (size_t *) obstack_base (&len_stack); size_t top_len; assert (n_lengths > 0); top_len = length[n_lengths - 1]; assert (top_len >= 2); /* Pop off the specified length of pathname. */ assert (obstack_object_size (&dir_stack) >= top_len); obstack_blank (&dir_stack, -top_len); /* Pop the length stack, too. */ assert (obstack_object_size (&len_stack) >= sizeof (size_t)); obstack_blank (&len_stack, (int) -(sizeof (size_t)));}/* Copy the SRC_LEN bytes of data beginning at SRC into the DST_LEN-byte buffer, DST, so that the last source byte is at the end of the destination buffer. If SRC_LEN is longer than DST_LEN, then set *TRUNCATED to non-zero. Set *RESULT to point to the beginning of (the portion of) the source data in DST. Return the number of bytes remaining in the destination buffer. */static size_tright_justify (char *dst, size_t dst_len, const char *src, size_t src_len, char **result, int *truncated){ const char *sp; char *dp; if (src_len <= dst_len) { sp = src; dp = dst + (dst_len - src_len); *truncated = 0; } else { sp = src + (src_len - dst_len); dp = dst; src_len = dst_len; *truncated = 1; } *result = memcpy (dp, sp, src_len); return dst_len - src_len;}/* Using the global directory name obstack, create the full path to FILENAME. Return it in sometimes-realloc'd space that should not be freed by the caller. Realloc as necessary. If realloc fails, use a static buffer and put as long a suffix in that buffer as possible. */static char *full_filename (const char *filename){ static char *buf = NULL; static size_t n_allocated = 0; int dir_len = obstack_object_size (&dir_stack); char *dir_name = (char *) obstack_base (&dir_stack); size_t n_bytes_needed; size_t filename_len; filename_len = strlen (filename); n_bytes_needed = dir_len + filename_len + 1; if (n_bytes_needed > n_allocated) { /* This code requires that realloc accept NULL as the first arg. This function must not use xrealloc. Otherwise, an out-of-memory error involving a file name to be expanded here wouldn't ever be issued. Use realloc and fall back on using a static buffer if memory allocation fails. */ buf = realloc (buf, n_bytes_needed); n_allocated = n_bytes_needed; if (buf == NULL) {#define SBUF_SIZE 512#define ELLIPSES_PREFIX "[...]" static char static_buf[SBUF_SIZE]; int truncated; size_t len; char *p; len = right_justify (static_buf, SBUF_SIZE, filename, filename_len + 1, &p, &truncated); right_justify (static_buf, len, dir_name, dir_len, &p, &truncated); if (truncated) { memcpy (static_buf, ELLIPSES_PREFIX, sizeof (ELLIPSES_PREFIX) - 1); } return p; } } /* Copy directory part, including trailing slash, and then append the filename part, including a trailing zero byte. */ memcpy (mempcpy (buf, dir_name, dir_len), filename, filename_len + 1); assert (strlen (buf) + 1 == n_bytes_needed); return buf;}static size_tAD_stack_height (void){ return obstack_object_size (&Active_dir) / sizeof (struct AD_ent);}static struct AD_ent *AD_stack_top (void){ return (struct AD_ent *) ((char *) obstack_next_free (&Active_dir) - sizeof (struct AD_ent));}static voidAD_stack_pop (void){ /* operate on Active_dir. pop and free top entry */ struct AD_ent *top = AD_stack_top (); if (top->unremovable) hash_free (top->unremovable); obstack_blank (&Active_dir, -sizeof (struct AD_ent)); pop_dir ();}/* chdir `up' one level. Whenever using chdir '..', verify that the post-chdir dev/ino numbers for `.' match the saved ones. Return the name (in malloc'd storage) of the directory (usually now empty) from which we're coming. */static char *AD_pop_and_chdir (void){ /* Get the name of the current directory from the top of the stack. */ char *dir = top_dir (); enum RM_status old_status = AD_stack_top()->status; struct stat sb; struct AD_ent *top; AD_stack_pop (); /* Propagate any failure to parent. */ UPDATE_STATUS (AD_stack_top()->status, old_status); assert (AD_stack_height ()); top = AD_stack_top (); if (1 < AD_stack_height ()) { /* We can give a better diagnostic here, since the target is relative. */ if (chdir ("..")) { error (EXIT_FAILURE, errno, _("cannot chdir from %s to .."), quote (full_filename ("."))); } } else { if (restore_cwd (&top->u.saved_cwd, NULL, NULL)) exit (EXIT_FAILURE); } if (lstat (".", &sb)) error (EXIT_FAILURE, errno, _("cannot lstat `.' in %s"), quote (full_filename ("."))); if (1 < AD_stack_height ()) { /* Ensure that post-chdir dev/ino match the stored ones. */ if ( ! SAME_INODE (sb, top->u.a)) error (EXIT_FAILURE, 0, _("%s changed dev/ino"), quote (full_filename ("."))); } return dir;}/* Initialize *HT if it is NULL. Insert FILENAME into HT. */static voidAD_mark_helper (Hash_table **ht, char const *filename){ if (*ht == NULL) *ht = hash_initialize (HT_UNREMOVABLE_INITIAL_CAPACITY, NULL, hash_pjw, hash_compare_strings, hash_freer); if (*ht == NULL) xalloc_die (); if (! hash_insert (*ht, filename)) xalloc_die ();}/* Mark FILENAME (in current directory) as unremovable. */static voidAD_mark_as_unremovable (char const *filename){ AD_mark_helper (&AD_stack_top()->unremovable, xstrdup (filename));}/* Mark the current directory as unremovable. I.e., mark the entry in the parent directory corresponding to `.'. This happens e.g., when an opendir fails and the only name the caller has conveniently at hand is `.'. */static voidAD_mark_current_as_unremovable (void){ struct AD_ent *top = AD_stack_top (); const char *curr = top_dir (); assert (1 < AD_stack_height ()); --top; AD_mark_helper (&top->unremovable, curr);}/* Push the initial cwd info onto the stack. This will always be the bottommost entry on the stack. */static voidAD_push_initial (struct saved_cwd const *cwd){ struct AD_ent *top; /* Extend the stack. */ obstack_blank (&Active_dir, sizeof (struct AD_ent)); /* Fill in the new values. */ top = AD_stack_top (); top->u.saved_cwd = *cwd; top->status = RM_OK; top->unremovable = NULL;}/* Push info about the current working directory (".") onto the active directory stack. DIR is the ./-relative name through which we've just `chdir'd to this directory. DIR_SB_FROM_PARENT is the result of calling lstat on DIR from the parent of DIR. */static voidAD_push (char const *dir, struct stat const *dir_sb_from_parent){ struct stat sb; struct AD_ent *top; push_dir (dir); if (lstat (".", &sb)) error (EXIT_FAILURE, errno, _("cannot lstat `.' in %s"), quote (full_filename ("."))); if ( ! SAME_INODE (sb, *dir_sb_from_parent)) error (EXIT_FAILURE, errno, _("%s changed dev/ino"), quote (full_filename ("."))); /* Extend the stack. */ obstack_blank (&Active_dir, sizeof (struct AD_ent)); /* Fill in the new values. */ top = AD_stack_top (); top->u.a.st_dev = sb.st_dev; top->u.a.st_ino = sb.st_ino; top->status = RM_OK; top->unremovable = NULL;}static intAD_is_removable (char const *file){ struct AD_ent *top = AD_stack_top (); return ! (top->unremovable && hash_lookup (top->unremovable, file));}static inline boolis_power_of_two (unsigned int i){ return (i & (i - 1)) == 0;}static voidcycle_check (struct stat const *sb){#ifdef ENABLE_CYCLE_CHECK /* If there is a directory cycle, detect it (lazily) and die. */ static struct dev_ino dir_cycle_detect_dev_ino; static unsigned int chdir_counter; /* If the current directory ever happens to be the same as the one we last recorded for the cycle detection, then it's obviously part of a cycle. */ if (chdir_counter && SAME_INODE (*sb, dir_cycle_detect_dev_ino)) { error (0, 0, _("\WARNING: Circular directory structure.\n\This almost certainly means that you have a corrupted file system.\n\NOTIFY YOUR SYSTEM MANAGER.\n\The following directory is part of the cycle:\n %s\n"), quote (full_filename ("."))); exit (EXIT_FAILURE); } /* If the number of `descending' chdir calls is a power of two, record the dev/ino of the current directory. */ if (is_power_of_two (++chdir_counter)) { dir_cycle_detect_dev_ino.st_dev = sb->st_dev; dir_cycle_detect_dev_ino.st_ino = sb->st_ino; }#endif}static boolis_empty_dir (char const *dir){ DIR *dirp = opendir (dir); if (dirp == NULL) return false; while (1) { struct dirent *dp = readdir (dirp); const char *f;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -