coreutils 9.1 version
This commit is contained in:
3198
src/copy.c
Normal file
3198
src/copy.c
Normal file
File diff suppressed because it is too large
Load Diff
316
src/copy.h
Normal file
316
src/copy.h
Normal file
@@ -0,0 +1,316 @@
|
||||
/* core functions for copying files and directories
|
||||
Copyright (C) 1989-2022 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 3 of the License, 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, see <https://www.gnu.org/licenses/>. */
|
||||
|
||||
/* Extracted from cp.c and librarified by Jim Meyering. */
|
||||
|
||||
#ifndef COPY_H
|
||||
# define COPY_H
|
||||
|
||||
# include <stdbool.h>
|
||||
# include "hash.h"
|
||||
|
||||
struct selabel_handle;
|
||||
|
||||
/* Control creation of sparse files (files with holes). */
|
||||
enum Sparse_type
|
||||
{
|
||||
SPARSE_UNUSED,
|
||||
|
||||
/* Never create holes in DEST. */
|
||||
SPARSE_NEVER,
|
||||
|
||||
/* This is the default. Use a crude (and sometimes inaccurate)
|
||||
heuristic to determine if SOURCE has holes. If so, try to create
|
||||
holes in DEST. */
|
||||
SPARSE_AUTO,
|
||||
|
||||
/* For every sufficiently long sequence of bytes in SOURCE, try to
|
||||
create a corresponding hole in DEST. There is a performance penalty
|
||||
here because CP has to search for holes in SRC. But if the holes are
|
||||
big enough, that penalty can be offset by the decrease in the amount
|
||||
of data written to the file system. */
|
||||
SPARSE_ALWAYS
|
||||
};
|
||||
|
||||
/* Control creation of COW files. */
|
||||
enum Reflink_type
|
||||
{
|
||||
/* Do a standard copy. */
|
||||
REFLINK_NEVER,
|
||||
|
||||
/* Try a COW copy and fall back to a standard copy; this is the default. */
|
||||
REFLINK_AUTO,
|
||||
|
||||
/* Require a COW copy and fail if not available. */
|
||||
REFLINK_ALWAYS
|
||||
};
|
||||
|
||||
/* This type is used to help mv (via copy.c) distinguish these cases. */
|
||||
enum Interactive
|
||||
{
|
||||
I_ALWAYS_YES = 1,
|
||||
I_ALWAYS_NO,
|
||||
I_ASK_USER,
|
||||
I_UNSPECIFIED
|
||||
};
|
||||
|
||||
/* How to handle symbolic links. */
|
||||
enum Dereference_symlink
|
||||
{
|
||||
DEREF_UNDEFINED = 1,
|
||||
|
||||
/* Copy the symbolic link itself. -P */
|
||||
DEREF_NEVER,
|
||||
|
||||
/* If the symbolic is a command line argument, then copy
|
||||
its referent. Otherwise, copy the symbolic link itself. -H */
|
||||
DEREF_COMMAND_LINE_ARGUMENTS,
|
||||
|
||||
/* Copy the referent of the symbolic link. -L */
|
||||
DEREF_ALWAYS
|
||||
};
|
||||
|
||||
# define VALID_SPARSE_MODE(Mode) \
|
||||
((Mode) == SPARSE_NEVER \
|
||||
|| (Mode) == SPARSE_AUTO \
|
||||
|| (Mode) == SPARSE_ALWAYS)
|
||||
|
||||
# define VALID_REFLINK_MODE(Mode) \
|
||||
((Mode) == REFLINK_NEVER \
|
||||
|| (Mode) == REFLINK_AUTO \
|
||||
|| (Mode) == REFLINK_ALWAYS)
|
||||
|
||||
/* These options control how files are copied by at least the
|
||||
following programs: mv (when rename doesn't work), cp, install.
|
||||
So, if you add a new member, be sure to initialize it in
|
||||
mv.c, cp.c, and install.c. */
|
||||
struct cp_options
|
||||
{
|
||||
enum backup_type backup_type;
|
||||
|
||||
/* How to handle symlinks in the source. */
|
||||
enum Dereference_symlink dereference;
|
||||
|
||||
/* This value is used to determine whether to prompt before removing
|
||||
each existing destination file. It works differently depending on
|
||||
whether move_mode is set. See code/comments in copy.c. */
|
||||
enum Interactive interactive;
|
||||
|
||||
/* Control creation of sparse files. */
|
||||
enum Sparse_type sparse_mode;
|
||||
|
||||
/* Set the mode of the destination file to exactly this value
|
||||
if SET_MODE is nonzero. */
|
||||
mode_t mode;
|
||||
|
||||
/* If true, copy all files except (directories and, if not dereferencing
|
||||
them, symbolic links,) as if they were regular files. */
|
||||
bool copy_as_regular;
|
||||
|
||||
/* If true, remove each existing destination nondirectory before
|
||||
trying to open it. */
|
||||
bool unlink_dest_before_opening;
|
||||
|
||||
/* If true, first try to open each existing destination nondirectory,
|
||||
then, if the open fails, unlink and try again.
|
||||
This option must be set for 'cp -f', in case the destination file
|
||||
exists when the open is attempted. It is irrelevant to 'mv' since
|
||||
any destination is sure to be removed before the open. */
|
||||
bool unlink_dest_after_failed_open;
|
||||
|
||||
/* If true, create hard links instead of copying files.
|
||||
Create destination directories as usual. */
|
||||
bool hard_link;
|
||||
|
||||
/* If true, rather than copying, first attempt to use rename.
|
||||
If that fails, then resort to copying. */
|
||||
bool move_mode;
|
||||
|
||||
/* If true, install(1) is the caller. */
|
||||
bool install_mode;
|
||||
|
||||
/* Whether this process has appropriate privileges to chown a file
|
||||
whose owner is not the effective user ID. */
|
||||
bool chown_privileges;
|
||||
|
||||
/* Whether this process has appropriate privileges to do the
|
||||
following operations on a file even when it is owned by some
|
||||
other user: set the file's atime, mtime, mode, or ACL; remove or
|
||||
rename an entry in the file even though it is a sticky directory,
|
||||
or to mount on the file. */
|
||||
bool owner_privileges;
|
||||
|
||||
/* If true, when copying recursively, skip any subdirectories that are
|
||||
on different file systems from the one we started on. */
|
||||
bool one_file_system;
|
||||
|
||||
/* If true, attempt to give the copies the original files' permissions,
|
||||
owner, group, and timestamps. */
|
||||
bool preserve_ownership;
|
||||
bool preserve_mode;
|
||||
bool preserve_timestamps;
|
||||
bool explicit_no_preserve_mode;
|
||||
|
||||
/* If non-null, attempt to set specified security context */
|
||||
struct selabel_handle *set_security_context;
|
||||
|
||||
/* Enabled for mv, and for cp by the --preserve=links option.
|
||||
If true, attempt to preserve in the destination files any
|
||||
logical hard links between the source files. If used with cp's
|
||||
--no-dereference option, and copying two hard-linked files,
|
||||
the two corresponding destination files will also be hard linked.
|
||||
|
||||
If used with cp's --dereference (-L) option, then, as that option implies,
|
||||
hard links are *not* preserved. However, when copying a file F and
|
||||
a symlink S to F, the resulting S and F in the destination directory
|
||||
will be hard links to the same file (a copy of F). */
|
||||
bool preserve_links;
|
||||
|
||||
/* Optionally don't copy the data, either with CoW reflink files or
|
||||
explicitly with the --attributes-only option. */
|
||||
bool data_copy_required;
|
||||
|
||||
/* If true and any of the above (for preserve) file attributes cannot
|
||||
be applied to a destination file, treat it as a failure and return
|
||||
nonzero immediately. E.g. for cp -p this must be true, for mv it
|
||||
must be false. */
|
||||
bool require_preserve;
|
||||
|
||||
/* If true, attempt to preserve the SELinux security context, too.
|
||||
Set this only if the kernel is SELinux enabled. */
|
||||
bool preserve_security_context;
|
||||
|
||||
/* Useful only when preserve_context is true.
|
||||
If true, a failed attempt to preserve file's security context
|
||||
propagates failure "out" to the caller, along with full diagnostics.
|
||||
If false, a failure to preserve file's security context does not
|
||||
change the invoking application's exit status, but may output diagnostics.
|
||||
For example, with 'cp --preserve=context' this flag is "true",
|
||||
while with 'cp --preserve=all' or 'cp -a', it is "false". */
|
||||
bool require_preserve_context;
|
||||
|
||||
/* If true, attempt to preserve extended attributes using libattr.
|
||||
Ignored if coreutils are compiled without xattr support. */
|
||||
bool preserve_xattr;
|
||||
|
||||
/* Useful only when preserve_xattr is true.
|
||||
If true, a failed attempt to preserve file's extended attributes
|
||||
propagates failure "out" to the caller, along with full diagnostics.
|
||||
If false, a failure to preserve file's extended attributes does not
|
||||
change the invoking application's exit status, but may output diagnostics.
|
||||
For example, with 'cp --preserve=xattr' this flag is "true",
|
||||
while with 'cp --preserve=all' or 'cp -a', it is "false". */
|
||||
bool require_preserve_xattr;
|
||||
|
||||
/* This allows us to output warnings in cases 2 and 4 below,
|
||||
while being quiet for case 1 (when reduce_diagnostics is true).
|
||||
1. cp -a try to copy xattrs with no errors
|
||||
2. cp --preserve=all copy xattrs with all but ENOTSUP warnings
|
||||
3. cp --preserve=xattr,context copy xattrs with all errors
|
||||
4. mv copy xattrs with all but ENOTSUP warnings
|
||||
*/
|
||||
bool reduce_diagnostics;
|
||||
|
||||
/* If true, copy directories recursively and copy special files
|
||||
as themselves rather than copying their contents. */
|
||||
bool recursive;
|
||||
|
||||
/* If true, set file mode to value of MODE. Otherwise,
|
||||
set it based on current umask modified by UMASK_KILL. */
|
||||
bool set_mode;
|
||||
|
||||
/* If true, create symbolic links instead of copying files.
|
||||
Create destination directories as usual. */
|
||||
bool symbolic_link;
|
||||
|
||||
/* If true, do not copy a nondirectory that has an existing destination
|
||||
with the same or newer modification time. */
|
||||
bool update;
|
||||
|
||||
/* If true, display the names of the files before copying them. */
|
||||
bool verbose;
|
||||
|
||||
/* If true, stdin is a tty. */
|
||||
bool stdin_tty;
|
||||
|
||||
/* If true, open a dangling destination symlink when not in move_mode.
|
||||
Otherwise, copy_reg gives a diagnostic (it refuses to write through
|
||||
such a symlink) and returns false. */
|
||||
bool open_dangling_dest_symlink;
|
||||
|
||||
/* If true, this is the last filed to be copied. mv uses this to
|
||||
avoid some unnecessary work. */
|
||||
bool last_file;
|
||||
|
||||
/* Zero if the source has already been renamed to the destination; a
|
||||
positive errno number if this failed with the given errno; -1 if
|
||||
no attempt has been made to rename. Always -1, except for mv. */
|
||||
int rename_errno;
|
||||
|
||||
/* Control creation of COW files. */
|
||||
enum Reflink_type reflink_mode;
|
||||
|
||||
/* This is a set of destination name/inode/dev triples. Each such triple
|
||||
represents a file we have created corresponding to a source file name
|
||||
that was specified on the command line. Use it to avoid clobbering
|
||||
source files in commands like this:
|
||||
rm -rf a b c; mkdir a b c; touch a/f b/f; mv a/f b/f c
|
||||
For now, it protects only regular files when copying (i.e., not renaming).
|
||||
When renaming, it protects all non-directories.
|
||||
Use dest_info_init to initialize it, or set it to NULL to disable
|
||||
this feature. */
|
||||
Hash_table *dest_info;
|
||||
|
||||
/* FIXME */
|
||||
Hash_table *src_info;
|
||||
};
|
||||
|
||||
/* Arrange to make rename calls go through the wrapper function
|
||||
on systems with a rename function that fails for a source file name
|
||||
specified with a trailing slash. */
|
||||
# if RENAME_TRAILING_SLASH_BUG
|
||||
int rpl_rename (char const *, char const *);
|
||||
# undef rename
|
||||
# define rename rpl_rename
|
||||
# endif
|
||||
|
||||
bool copy (char const *src_name, char const *dst_name,
|
||||
int dst_dirfd, char const *dst_relname,
|
||||
int nonexistent_dst, const struct cp_options *options,
|
||||
bool *copy_into_self, bool *rename_succeeded)
|
||||
_GL_ATTRIBUTE_NONNULL ((1, 2, 4, 6, 7));
|
||||
|
||||
extern bool set_process_security_ctx (char const *src_name,
|
||||
char const *dst_name,
|
||||
mode_t mode, bool new_dst,
|
||||
const struct cp_options *x)
|
||||
_GL_ATTRIBUTE_NONNULL ();
|
||||
|
||||
extern bool set_file_security_ctx (char const *dst_name,
|
||||
bool recurse, const struct cp_options *x)
|
||||
_GL_ATTRIBUTE_NONNULL ();
|
||||
|
||||
void dest_info_init (struct cp_options *) _GL_ATTRIBUTE_NONNULL ();
|
||||
void dest_info_free (struct cp_options *) _GL_ATTRIBUTE_NONNULL ();
|
||||
void src_info_init (struct cp_options *) _GL_ATTRIBUTE_NONNULL ();
|
||||
void src_info_free (struct cp_options *) _GL_ATTRIBUTE_NONNULL ();
|
||||
|
||||
void cp_options_default (struct cp_options *) _GL_ATTRIBUTE_NONNULL ();
|
||||
bool chown_failure_ok (struct cp_options const *)
|
||||
_GL_ATTRIBUTE_NONNULL () _GL_ATTRIBUTE_PURE;
|
||||
mode_t cached_umask (void);
|
||||
|
||||
#endif
|
||||
155
src/cp-hash.c
Normal file
155
src/cp-hash.c
Normal file
@@ -0,0 +1,155 @@
|
||||
/* cp-hash.c -- file copying (hash search routines)
|
||||
Copyright (C) 1989-2022 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 3 of the License, 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, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Written by Torbjorn Granlund, Sweden (tege@sics.se).
|
||||
Rewritten to use lib/hash.c by Jim Meyering. */
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include "system.h"
|
||||
|
||||
#include "hash.h"
|
||||
#include "cp-hash.h"
|
||||
|
||||
/* Use ST_DEV and ST_INO as the key, FILENAME as the value.
|
||||
These are used e.g., in copy.c to associate the destination name with
|
||||
the source device/inode pair so that if we encounter a matching dev/ino
|
||||
pair in the source tree we can arrange to create a hard link between
|
||||
the corresponding names in the destination tree. */
|
||||
struct Src_to_dest
|
||||
{
|
||||
ino_t st_ino;
|
||||
dev_t st_dev;
|
||||
/* Destination file name (of non-directory or pre-existing directory)
|
||||
corresponding to the dev/ino of a copied file, or the destination file
|
||||
name corresponding to a dev/ino pair for a newly-created directory. */
|
||||
char *name;
|
||||
};
|
||||
|
||||
/* This table maps source dev/ino to destination file name.
|
||||
We use it to preserve hard links when copying. */
|
||||
static Hash_table *src_to_dest;
|
||||
|
||||
/* Initial size of the above hash table. */
|
||||
#define INITIAL_TABLE_SIZE 103
|
||||
|
||||
static size_t
|
||||
src_to_dest_hash (void const *x, size_t table_size)
|
||||
{
|
||||
struct Src_to_dest const *p = x;
|
||||
|
||||
/* Ignoring the device number here should be fine. */
|
||||
/* The cast to uintmax_t prevents negative remainders
|
||||
if st_ino is negative. */
|
||||
return (uintmax_t) p->st_ino % table_size;
|
||||
}
|
||||
|
||||
/* Compare two Src_to_dest entries.
|
||||
Return true if their keys are judged 'equal'. */
|
||||
static bool
|
||||
src_to_dest_compare (void const *x, void const *y)
|
||||
{
|
||||
struct Src_to_dest const *a = x;
|
||||
struct Src_to_dest const *b = y;
|
||||
return SAME_INODE (*a, *b) ? true : false;
|
||||
}
|
||||
|
||||
static void
|
||||
src_to_dest_free (void *x)
|
||||
{
|
||||
struct Src_to_dest *a = x;
|
||||
free (a->name);
|
||||
free (x);
|
||||
}
|
||||
|
||||
/* Remove the entry matching INO/DEV from the table
|
||||
that maps source ino/dev to destination file name. */
|
||||
extern void
|
||||
forget_created (ino_t ino, dev_t dev)
|
||||
{
|
||||
struct Src_to_dest probe;
|
||||
struct Src_to_dest *ent;
|
||||
|
||||
probe.st_ino = ino;
|
||||
probe.st_dev = dev;
|
||||
probe.name = NULL;
|
||||
|
||||
ent = hash_remove (src_to_dest, &probe);
|
||||
if (ent)
|
||||
src_to_dest_free (ent);
|
||||
}
|
||||
|
||||
/* If INO/DEV correspond to an already-copied source file, return the
|
||||
name of the corresponding destination file. Otherwise, return NULL. */
|
||||
|
||||
extern char *
|
||||
src_to_dest_lookup (ino_t ino, dev_t dev)
|
||||
{
|
||||
struct Src_to_dest ent;
|
||||
struct Src_to_dest const *e;
|
||||
ent.st_ino = ino;
|
||||
ent.st_dev = dev;
|
||||
e = hash_lookup (src_to_dest, &ent);
|
||||
return e ? e->name : NULL;
|
||||
}
|
||||
|
||||
/* Add file NAME, copied from inode number INO and device number DEV,
|
||||
to the list of files we have copied.
|
||||
Return NULL if inserted, otherwise non-NULL. */
|
||||
|
||||
extern char *
|
||||
remember_copied (char const *name, ino_t ino, dev_t dev)
|
||||
{
|
||||
struct Src_to_dest *ent;
|
||||
struct Src_to_dest *ent_from_table;
|
||||
|
||||
ent = xmalloc (sizeof *ent);
|
||||
ent->name = xstrdup (name);
|
||||
ent->st_ino = ino;
|
||||
ent->st_dev = dev;
|
||||
|
||||
ent_from_table = hash_insert (src_to_dest, ent);
|
||||
if (ent_from_table == NULL)
|
||||
{
|
||||
/* Insertion failed due to lack of memory. */
|
||||
xalloc_die ();
|
||||
}
|
||||
|
||||
/* Determine whether there was already an entry in the table
|
||||
with a matching key. If so, free ENT (it wasn't inserted) and
|
||||
return the 'name' from the table entry. */
|
||||
if (ent_from_table != ent)
|
||||
{
|
||||
src_to_dest_free (ent);
|
||||
return (char *) ent_from_table->name;
|
||||
}
|
||||
|
||||
/* New key; insertion succeeded. */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Initialize the hash table. */
|
||||
extern void
|
||||
hash_init (void)
|
||||
{
|
||||
src_to_dest = hash_initialize (INITIAL_TABLE_SIZE, NULL,
|
||||
src_to_dest_hash,
|
||||
src_to_dest_compare,
|
||||
src_to_dest_free);
|
||||
if (src_to_dest == NULL)
|
||||
xalloc_die ();
|
||||
}
|
||||
5
src/cp-hash.h
Normal file
5
src/cp-hash.h
Normal file
@@ -0,0 +1,5 @@
|
||||
void hash_init (void);
|
||||
void forget_created (ino_t ino, dev_t dev);
|
||||
char *remember_copied (char const *node, ino_t ino, dev_t dev)
|
||||
_GL_ATTRIBUTE_NONNULL ();
|
||||
char *src_to_dest_lookup (ino_t ino, dev_t dev);
|
||||
31
src/die.h
Normal file
31
src/die.h
Normal file
@@ -0,0 +1,31 @@
|
||||
/* Report an error and exit.
|
||||
Copyright 2016-2022 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 3, 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., 51 Franklin Street - Fifth Floor, Boston, MA
|
||||
02110-1301, USA. */
|
||||
|
||||
#ifndef DIE_H
|
||||
# define DIE_H
|
||||
|
||||
# include <error.h>
|
||||
# include <stdbool.h>
|
||||
# include <verify.h>
|
||||
|
||||
/* Like 'error (STATUS, ...)', except STATUS must be a nonzero constant.
|
||||
This may pacify the compiler or help it generate better code. */
|
||||
# define die(status, ...) \
|
||||
verify_expr (status, (error (status, __VA_ARGS__), assume (false)))
|
||||
|
||||
#endif /* DIE_H */
|
||||
184
src/force-link.c
Normal file
184
src/force-link.c
Normal file
@@ -0,0 +1,184 @@
|
||||
/* Implement ln -f "atomically"
|
||||
|
||||
Copyright 2017-2022 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 3 of the License, 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, see <https://www.gnu.org/licenses/>. */
|
||||
|
||||
/* Written by Paul Eggert. */
|
||||
|
||||
/* A naive "ln -f A B" unlinks B and then links A to B. This module
|
||||
instead links A to a randomly-named temporary T in B's directory,
|
||||
and then renames T to B. This approach has a window with a
|
||||
randomly-named temporary, which is safer for many applications than
|
||||
a window where B does not exist. */
|
||||
|
||||
#include <config.h>
|
||||
#include "system.h"
|
||||
|
||||
#include "force-link.h"
|
||||
|
||||
#include <tempname.h>
|
||||
|
||||
/* A basename pattern suitable for a temporary file. It should work
|
||||
even on file systems like FAT that support only short names.
|
||||
"Cu" is short for "Coreutils" or for "Changeable unstable",
|
||||
take your pick.... */
|
||||
|
||||
static char const simple_pattern[] = "CuXXXXXX";
|
||||
enum { x_suffix_len = sizeof "XXXXXX" - 1 };
|
||||
|
||||
/* A size for smallish buffers containing file names. Longer file
|
||||
names can use malloc. */
|
||||
|
||||
enum { smallsize = 256 };
|
||||
|
||||
/* Return a template for a file in the same directory as DSTNAME.
|
||||
Use BUF if the template fits, otherwise use malloc and return NULL
|
||||
(setting errno) if unsuccessful. */
|
||||
|
||||
static char *
|
||||
samedir_template (char const *dstname, char buf[smallsize])
|
||||
{
|
||||
ptrdiff_t dstdirlen = last_component (dstname) - dstname;
|
||||
size_t dsttmpsize = dstdirlen + sizeof simple_pattern;
|
||||
char *dsttmp;
|
||||
if (dsttmpsize <= smallsize)
|
||||
dsttmp = buf;
|
||||
else
|
||||
{
|
||||
dsttmp = malloc (dsttmpsize);
|
||||
if (!dsttmp)
|
||||
return dsttmp;
|
||||
}
|
||||
strcpy (mempcpy (dsttmp, dstname, dstdirlen), simple_pattern);
|
||||
return dsttmp;
|
||||
}
|
||||
|
||||
|
||||
/* Auxiliaries for force_linkat. */
|
||||
|
||||
struct link_arg
|
||||
{
|
||||
int srcdir;
|
||||
char const *srcname;
|
||||
int dstdir;
|
||||
int flags;
|
||||
};
|
||||
|
||||
static int
|
||||
try_link (char *dest, void *arg)
|
||||
{
|
||||
struct link_arg *a = arg;
|
||||
return linkat (a->srcdir, a->srcname, a->dstdir, dest, a->flags);
|
||||
}
|
||||
|
||||
/* Hard-link directory SRCDIR's file SRCNAME to directory DSTDIR's
|
||||
file DSTNAME, using linkat-style FLAGS to control the linking.
|
||||
If FORCE and DSTNAME already exists, replace it atomically.
|
||||
If LINKAT_ERRNO is 0, the hard link is already done; if positive,
|
||||
the hard link was tried and failed with errno == LINKAT_ERRNO. Return
|
||||
-1 if successful and DSTNAME already existed,
|
||||
0 if successful and DSTNAME did not already exist, and
|
||||
a positive errno value on failure. */
|
||||
extern int
|
||||
force_linkat (int srcdir, char const *srcname,
|
||||
int dstdir, char const *dstname, int flags, bool force,
|
||||
int linkat_errno)
|
||||
{
|
||||
if (linkat_errno < 0)
|
||||
linkat_errno = (linkat (srcdir, srcname, dstdir, dstname, flags) == 0
|
||||
? 0 : errno);
|
||||
if (!force || linkat_errno != EEXIST)
|
||||
return linkat_errno;
|
||||
|
||||
char buf[smallsize];
|
||||
char *dsttmp = samedir_template (dstname, buf);
|
||||
if (! dsttmp)
|
||||
return errno;
|
||||
struct link_arg arg = { srcdir, srcname, dstdir, flags };
|
||||
int err;
|
||||
|
||||
if (try_tempname_len (dsttmp, 0, &arg, try_link, x_suffix_len) != 0)
|
||||
err = errno;
|
||||
else
|
||||
{
|
||||
err = renameat (dstdir, dsttmp, dstdir, dstname) == 0 ? -1 : errno;
|
||||
/* Unlink DSTTMP even if renameat succeeded, in case DSTTMP
|
||||
and DSTNAME were already the same hard link and renameat
|
||||
was a no-op. */
|
||||
unlinkat (dstdir, dsttmp, 0);
|
||||
}
|
||||
|
||||
if (dsttmp != buf)
|
||||
free (dsttmp);
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
/* Auxiliaries for force_symlinkat. */
|
||||
|
||||
struct symlink_arg
|
||||
{
|
||||
char const *srcname;
|
||||
int dstdir;
|
||||
};
|
||||
|
||||
static int
|
||||
try_symlink (char *dest, void *arg)
|
||||
{
|
||||
struct symlink_arg *a = arg;
|
||||
return symlinkat (a->srcname, a->dstdir, dest);
|
||||
}
|
||||
|
||||
/* Create a symlink containing SRCNAME in directory DSTDIR's file DSTNAME.
|
||||
If FORCE and DSTNAME already exists, replace it atomically.
|
||||
If SYMLINKAT_ERRNO is 0, the symlink is already done; if positive,
|
||||
the symlink was tried and failed with errno == SYMLINKAT_ERRNO. Return
|
||||
-1 if successful and DSTNAME already existed,
|
||||
0 if successful and DSTNAME did not already exist, and
|
||||
a positive errno value on failure. */
|
||||
extern int
|
||||
force_symlinkat (char const *srcname, int dstdir, char const *dstname,
|
||||
bool force, int symlinkat_errno)
|
||||
{
|
||||
if (symlinkat_errno < 0)
|
||||
symlinkat_errno = symlinkat (srcname, dstdir, dstname) == 0 ? 0 : errno;
|
||||
if (!force || symlinkat_errno != EEXIST)
|
||||
return symlinkat_errno;
|
||||
|
||||
char buf[smallsize];
|
||||
char *dsttmp = samedir_template (dstname, buf);
|
||||
if (!dsttmp)
|
||||
return errno;
|
||||
struct symlink_arg arg = { srcname, dstdir };
|
||||
int err;
|
||||
|
||||
if (try_tempname_len (dsttmp, 0, &arg, try_symlink, x_suffix_len) != 0)
|
||||
err = errno;
|
||||
else if (renameat (dstdir, dsttmp, dstdir, dstname) != 0)
|
||||
{
|
||||
err = errno;
|
||||
unlinkat (dstdir, dsttmp, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Don't worry about renameat being a no-op, since DSTTMP is
|
||||
newly created. */
|
||||
err = -1;
|
||||
}
|
||||
|
||||
if (dsttmp != buf)
|
||||
free (dsttmp);
|
||||
return err;
|
||||
}
|
||||
4
src/force-link.h
Normal file
4
src/force-link.h
Normal file
@@ -0,0 +1,4 @@
|
||||
extern int force_linkat (int, char const *, int, char const *, int, bool, int)
|
||||
_GL_ATTRIBUTE_NONNULL ();
|
||||
extern int force_symlinkat (char const *, int, char const *, bool, int)
|
||||
_GL_ATTRIBUTE_NONNULL ();
|
||||
82
src/ioblksize.h
Normal file
82
src/ioblksize.h
Normal file
@@ -0,0 +1,82 @@
|
||||
/* I/O block size definitions for coreutils
|
||||
Copyright (C) 1989-2022 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 3 of the License, 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, see <https://www.gnu.org/licenses/>. */
|
||||
|
||||
/* Include this file _after_ system headers if possible. */
|
||||
|
||||
/* sys/stat.h and minmax.h will already have been included by system.h. */
|
||||
#include "idx.h"
|
||||
#include "stat-size.h"
|
||||
|
||||
|
||||
/* As of May 2014, 128KiB is determined to be the minimium
|
||||
blksize to best minimize system call overhead.
|
||||
This can be tested with this script:
|
||||
|
||||
for i in $(seq 0 10); do
|
||||
bs=$((1024*2**$i))
|
||||
printf "%7s=" $bs
|
||||
timeout --foreground -sINT 2 \
|
||||
dd bs=$bs if=/dev/zero of=/dev/null 2>&1 \
|
||||
| sed -n 's/.* \([0-9.]* [GM]B\/s\)/\1/p'
|
||||
done
|
||||
|
||||
With the results shown for these systems:
|
||||
system #1: 1.7GHz pentium-m with 400MHz DDR2 RAM, arch=i686
|
||||
system #2: 2.1GHz i3-2310M with 1333MHz DDR3 RAM, arch=x86_64
|
||||
system #3: 3.2GHz i7-970 with 1333MHz DDR3, arch=x86_64
|
||||
system #4: 2.20GHz Xeon E5-2660 with 1333MHz DDR3, arch=x86_64
|
||||
system #5: 2.30GHz i7-3615QM with 1600MHz DDR3, arch=x86_64
|
||||
system #6: 1.30GHz i5-4250U with 1-channel 1600MHz DDR3, arch=x86_64
|
||||
system #7: 3.55GHz IBM,8231-E2B with 1066MHz DDR3, POWER7 revision 2.1
|
||||
|
||||
per-system transfer rate (GB/s)
|
||||
blksize #1 #2 #3 #4 #5 #6 #7
|
||||
------------------------------------------------------------------------
|
||||
1024 .73 1.7 2.6 .64 1.0 2.5 1.3
|
||||
2048 1.3 3.0 4.4 1.2 2.0 4.4 2.5
|
||||
4096 2.4 5.1 6.5 2.3 3.7 7.4 4.8
|
||||
8192 3.5 7.3 8.5 4.0 6.0 10.4 9.2
|
||||
16384 3.9 9.4 10.1 6.3 8.3 13.3 16.8
|
||||
32768 5.2 9.9 11.1 8.1 10.7 13.2 28.0
|
||||
65536 5.3 11.2 12.0 10.6 12.8 16.1 41.4
|
||||
131072 5.5 11.8 12.3 12.1 14.0 16.7 54.8
|
||||
262144 5.7 11.6 12.5 12.3 14.7 16.4 40.0
|
||||
524288 5.7 11.4 12.5 12.1 14.7 15.5 34.5
|
||||
1048576 5.8 11.4 12.6 12.2 14.9 15.7 36.5
|
||||
|
||||
|
||||
Note that this is to minimize system call overhead.
|
||||
Other values may be appropriate to minimize file system
|
||||
overhead. For example on my current GNU/Linux system
|
||||
the readahead setting is 128KiB which was read using:
|
||||
|
||||
file="."
|
||||
device=$(df --output=source --local "$file" | tail -n1)
|
||||
echo $(( $(blockdev --getra $device) * 512 ))
|
||||
|
||||
However there isn't a portable way to get the above.
|
||||
In the future we could use the above method if available
|
||||
and default to io_blksize() if not.
|
||||
*/
|
||||
enum { IO_BUFSIZE = 128 * 1024 };
|
||||
static inline idx_t
|
||||
io_blksize (struct stat sb)
|
||||
{
|
||||
/* Don’t go above the largest power of two that fits in idx_t and size_t,
|
||||
as that is asking for trouble. */
|
||||
return MIN (MIN (IDX_MAX, SIZE_MAX) / 2 + 1,
|
||||
MAX (IO_BUFSIZE, ST_BLKSIZE (sb)));
|
||||
}
|
||||
556
src/mv.c
Normal file
556
src/mv.c
Normal file
@@ -0,0 +1,556 @@
|
||||
/* mv -- move or rename files
|
||||
Copyright (C) 1986-2022 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 3 of the License, 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, see <https://www.gnu.org/licenses/>. */
|
||||
|
||||
/* Written by Mike Parker, David MacKenzie, and Jim Meyering */
|
||||
|
||||
#include <config.h>
|
||||
#include <stdio.h>
|
||||
#include <getopt.h>
|
||||
#include <sys/types.h>
|
||||
#include <assert.h>
|
||||
#include <selinux/label.h>
|
||||
|
||||
#include "system.h"
|
||||
#include "backupfile.h"
|
||||
#include "copy.h"
|
||||
#include "cp-hash.h"
|
||||
#include "die.h"
|
||||
#include "error.h"
|
||||
#include "filenamecat.h"
|
||||
#include "remove.h"
|
||||
#include "renameatu.h"
|
||||
#include "root-dev-ino.h"
|
||||
#include "targetdir.h"
|
||||
#include "priv-set.h"
|
||||
|
||||
/* The official name of this program (e.g., no 'g' prefix). */
|
||||
#define PROGRAM_NAME "rm_"
|
||||
|
||||
#define BUFFER_SIZE 270
|
||||
|
||||
#define AUTHORS \
|
||||
proper_name ("Mike Parker"), \
|
||||
proper_name ("David MacKenzie"), \
|
||||
proper_name ("Jim Meyering"), \
|
||||
proper_name ("AIXIAO@AIXIAO")
|
||||
|
||||
/* For long options that have no equivalent short option, use a
|
||||
non-character as a pseudo short option, starting with CHAR_MAX + 1. */
|
||||
enum {
|
||||
STRIP_TRAILING_SLASHES_OPTION = CHAR_MAX + 1
|
||||
};
|
||||
|
||||
static struct option const long_options[] = {
|
||||
{ "backup", optional_argument, NULL, 'b' },
|
||||
{ "context", no_argument, NULL, 'Z' },
|
||||
{ "force", no_argument, NULL, 'f' },
|
||||
{ "interactive", no_argument, NULL, 'i' },
|
||||
{ "no-clobber", no_argument, NULL, 'n' },
|
||||
{ "no-target-directory", no_argument, NULL, 'T' },
|
||||
{ "strip-trailing-slashes", no_argument, NULL, STRIP_TRAILING_SLASHES_OPTION },
|
||||
{ "suffix", required_argument, NULL, 'S' },
|
||||
{ "target-directory", required_argument, NULL, 't' },
|
||||
{ "update", no_argument, NULL, 'u' },
|
||||
{ "verbose", no_argument, NULL, 'v' },
|
||||
{ GETOPT_HELP_OPTION_DECL },
|
||||
{ GETOPT_VERSION_OPTION_DECL },
|
||||
{ NULL, 0, NULL, 0 }
|
||||
};
|
||||
|
||||
static void rm_option_init(struct rm_options *x)
|
||||
{
|
||||
x->ignore_missing_files = false;
|
||||
x->remove_empty_directories = true;
|
||||
x->recursive = true;
|
||||
x->one_file_system = false;
|
||||
|
||||
/* Should we prompt for removal, too? No. Prompting for the 'move'
|
||||
part is enough. It implies removal. */
|
||||
x->interactive = RMI_NEVER;
|
||||
x->stdin_tty = false;
|
||||
|
||||
x->verbose = false;
|
||||
|
||||
/* Since this program may well have to process additional command
|
||||
line arguments after any call to 'rm', that function must preserve
|
||||
the initial working directory, in case one of those is a
|
||||
'.'-relative name. */
|
||||
x->require_restore_cwd = true;
|
||||
|
||||
{
|
||||
static struct dev_ino dev_ino_buf;
|
||||
x->root_dev_ino = get_root_dev_ino(&dev_ino_buf);
|
||||
if (x->root_dev_ino == NULL)
|
||||
die(EXIT_FAILURE, errno, _("failed to get attributes of %s"), quoteaf("/"));
|
||||
}
|
||||
|
||||
x->preserve_all_root = false;
|
||||
}
|
||||
|
||||
static void cp_option_init(struct cp_options *x)
|
||||
{
|
||||
bool selinux_enabled = (0 < is_selinux_enabled());
|
||||
|
||||
cp_options_default(x);
|
||||
x->copy_as_regular = false; /* FIXME: maybe make this an option */
|
||||
x->reflink_mode = REFLINK_AUTO;
|
||||
x->dereference = DEREF_NEVER;
|
||||
x->unlink_dest_before_opening = false;
|
||||
x->unlink_dest_after_failed_open = false;
|
||||
x->hard_link = false;
|
||||
x->interactive = I_UNSPECIFIED;
|
||||
x->move_mode = true;
|
||||
x->install_mode = false;
|
||||
x->one_file_system = false;
|
||||
x->preserve_ownership = true;
|
||||
x->preserve_links = true;
|
||||
x->preserve_mode = true;
|
||||
x->preserve_timestamps = true;
|
||||
x->explicit_no_preserve_mode = false;
|
||||
x->preserve_security_context = selinux_enabled;
|
||||
x->set_security_context = NULL;
|
||||
x->reduce_diagnostics = false;
|
||||
x->data_copy_required = true;
|
||||
x->require_preserve = false; /* FIXME: maybe make this an option */
|
||||
x->require_preserve_context = false;
|
||||
x->preserve_xattr = true;
|
||||
x->require_preserve_xattr = false;
|
||||
x->recursive = true;
|
||||
x->sparse_mode = SPARSE_AUTO; /* FIXME: maybe make this an option */
|
||||
x->symbolic_link = false;
|
||||
x->set_mode = false;
|
||||
x->mode = 0;
|
||||
x->stdin_tty = isatty(STDIN_FILENO);
|
||||
|
||||
x->open_dangling_dest_symlink = false;
|
||||
x->update = false;
|
||||
x->verbose = false;
|
||||
x->dest_info = NULL;
|
||||
x->src_info = NULL;
|
||||
}
|
||||
|
||||
/* Move SOURCE onto DEST aka DEST_DIRFD+DEST_RELNAME.
|
||||
Handle cross-file-system moves.
|
||||
If SOURCE is a directory, DEST must not exist.
|
||||
Return true if successful. */
|
||||
|
||||
static bool do_move(char const *source, char const *dest, int dest_dirfd, char const *dest_relname, const struct cp_options *x)
|
||||
{
|
||||
bool copy_into_self;
|
||||
bool rename_succeeded;
|
||||
bool ok = copy(source, dest, dest_dirfd, dest_relname, 0, x,
|
||||
©_into_self, &rename_succeeded);
|
||||
|
||||
if (ok) {
|
||||
char const *dir_to_remove;
|
||||
if (copy_into_self) {
|
||||
/* In general, when copy returns with copy_into_self set, SOURCE is
|
||||
the same as, or a parent of DEST. In this case we know it's a
|
||||
parent. It doesn't make sense to move a directory into itself, and
|
||||
besides in some situations doing so would give highly nonintuitive
|
||||
results. Run this 'mkdir b; touch a c; mv * b' in an empty
|
||||
directory. Here's the result of running echo $(find b -print):
|
||||
b b/a b/b b/b/a b/c. Notice that only file 'a' was copied
|
||||
into b/b. Handle this by giving a diagnostic, removing the
|
||||
copied-into-self directory, DEST ('b/b' in the example),
|
||||
and failing. */
|
||||
|
||||
dir_to_remove = NULL;
|
||||
ok = false;
|
||||
} else if (rename_succeeded) {
|
||||
/* No need to remove anything. SOURCE was successfully
|
||||
renamed to DEST. Or the user declined to rename a file. */
|
||||
dir_to_remove = NULL;
|
||||
} else {
|
||||
/* This may mean SOURCE and DEST referred to different devices.
|
||||
It may also conceivably mean that even though they referred
|
||||
to the same device, rename wasn't implemented for that device.
|
||||
|
||||
E.g., (from Joel N. Weber),
|
||||
[...] there might someday be cases where you can't rename
|
||||
but you can copy where the device name is the same, especially
|
||||
on Hurd. Consider an ftpfs with a primitive ftp server that
|
||||
supports uploading, downloading and deleting, but not renaming.
|
||||
|
||||
Also, note that comparing device numbers is not a reliable
|
||||
check for 'can-rename'. Some systems can be set up so that
|
||||
files from many different physical devices all have the same
|
||||
st_dev field. This is a feature of some NFS mounting
|
||||
configurations.
|
||||
|
||||
We reach this point if SOURCE has been successfully copied
|
||||
to DEST. Now we have to remove SOURCE.
|
||||
|
||||
This function used to resort to copying only when rename
|
||||
failed and set errno to EXDEV. */
|
||||
|
||||
dir_to_remove = source;
|
||||
}
|
||||
|
||||
if (dir_to_remove != NULL) {
|
||||
struct rm_options rm_options;
|
||||
enum RM_status status;
|
||||
char const *dir[2];
|
||||
|
||||
rm_option_init(&rm_options);
|
||||
rm_options.verbose = x->verbose;
|
||||
dir[0] = dir_to_remove;
|
||||
dir[1] = NULL;
|
||||
|
||||
status = rm((void *)dir, &rm_options);
|
||||
assert(VALID_STATUS(status));
|
||||
if (status == RM_ERROR)
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
void usage(int status)
|
||||
{
|
||||
if (status != EXIT_SUCCESS)
|
||||
emit_try_help();
|
||||
else {
|
||||
printf(_("\
|
||||
Usage: %s [OPTION]... [-T] SOURCE DEST\n\
|
||||
or: %s [OPTION]... SOURCE... DIRECTORY\n\
|
||||
or: %s [OPTION]... -t DIRECTORY SOURCE...\n\
|
||||
"), program_name, program_name, program_name);
|
||||
fputs(_("\
|
||||
Rename SOURCE to DEST, or move SOURCE(s) to DIRECTORY.\n\
|
||||
"), stdout);
|
||||
|
||||
emit_mandatory_arg_note();
|
||||
|
||||
fputs(_("\
|
||||
--backup[=CONTROL] make a backup of each existing destination file\
|
||||
\n\
|
||||
-b like --backup but does not accept an argument\n\
|
||||
-f, --force do not prompt before overwriting\n\
|
||||
-i, --interactive prompt before overwrite\n\
|
||||
-n, --no-clobber do not overwrite an existing file\n\
|
||||
If you specify more than one of -i, -f, -n, only the final one takes effect.\n\
|
||||
"), stdout);
|
||||
fputs(_("\
|
||||
--strip-trailing-slashes remove any trailing slashes from each SOURCE\n\
|
||||
argument\n\
|
||||
-S, --suffix=SUFFIX override the usual backup suffix\n\
|
||||
"), stdout);
|
||||
fputs(_("\
|
||||
-t, --target-directory=DIRECTORY move all SOURCE arguments into DIRECTORY\n\
|
||||
-T, --no-target-directory treat DEST as a normal file\n\
|
||||
-u, --update move only when the SOURCE file is newer\n\
|
||||
than the destination file or when the\n\
|
||||
destination file is missing\n\
|
||||
-v, --verbose explain what is being done\n\
|
||||
-Z, --context set SELinux security context of destination\n\
|
||||
file to default type\n\
|
||||
"), stdout);
|
||||
fputs(HELP_OPTION_DESCRIPTION, stdout);
|
||||
fputs(VERSION_OPTION_DESCRIPTION, stdout);
|
||||
|
||||
printf("\n\033[31mInstead of rm command to prevent mistaken deletion of files\n\
|
||||
Author: AIXIAO@AIXIAO.ME\033[0m\n");
|
||||
printf("\n\033[31m代替rm命令,以防止错误删除文件\n\
|
||||
作者: AIXIAO@AIXIAO.ME\033[0m\n");
|
||||
|
||||
emit_backup_suffix_note();
|
||||
emit_ancillary_info(PROGRAM_NAME);
|
||||
}
|
||||
exit(status);
|
||||
}
|
||||
|
||||
// 在这里删除字符串里的'\n'
|
||||
static void delchar(char *str, char c)
|
||||
{
|
||||
int i, j;
|
||||
|
||||
for (i = j = 0; str[i] != '\0'; i++) {
|
||||
if (str[i] != c) //判断是否有和待删除字符一样的字符
|
||||
{
|
||||
str[j++] = str[i];
|
||||
}
|
||||
}
|
||||
str[j] = '\0'; //字符串结束
|
||||
}
|
||||
|
||||
// 读取配置文件
|
||||
static char *read_config(char *config_file, char *buffer)
|
||||
{
|
||||
char temp_buffer[270];
|
||||
FILE *fd;
|
||||
|
||||
fd = fopen(config_file, "r"); //打开文件
|
||||
if (fd == NULL) {
|
||||
return strcpy(buffer, "/tmp/");
|
||||
}
|
||||
memset(temp_buffer, 0, BUFFER_SIZE);
|
||||
size_t ret = fread(temp_buffer, sizeof(temp_buffer) / sizeof((temp_buffer)[0]), sizeof(*temp_buffer), fd); //读取配置
|
||||
/*
|
||||
if (ret != sizeof(*temp_buffer)) {
|
||||
fprintf(stderr, "fread() failed: %zu\n", ret);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
*/
|
||||
delchar(temp_buffer, 10); // 删除'\n'
|
||||
fclose(fd);
|
||||
return strcpy(buffer, temp_buffer);
|
||||
}
|
||||
|
||||
// 处理参数
|
||||
int process_argv(int argc, char *argv[], char **argvs)
|
||||
{
|
||||
char delete_path[BUFFER_SIZE];
|
||||
char mkdir_s[BUFFER_SIZE];
|
||||
|
||||
memset(delete_path, 0, BUFFER_SIZE);
|
||||
read_config("/etc/rm_.conf", delete_path); // 如果有配置文件存在, 读取配置文件, 如果读取配置文件失败默认使用"/tmp/"目录来当作垃圾桶目录
|
||||
|
||||
strcpy(mkdir_s, "mkdir -p ");
|
||||
strcat(mkdir_s, delete_path);
|
||||
|
||||
if (!access(delete_path, 0)) {
|
||||
;//printf("%s EXISITS!\n", delete_path);
|
||||
} else {
|
||||
printf("%s DOESN'T EXISIT!\n", delete_path);
|
||||
if (-1 == system("mount -o remount,rw /")) { // 调用mount命令改变根目录读写权限
|
||||
perror("system mount");
|
||||
}
|
||||
if (-1 == system(mkdir_s)) { // 调用mkdir命令创建不存在的垃圾桶目录
|
||||
perror("system mkdir");
|
||||
} else {
|
||||
printf("%s Directory Created Successfully!\n", delete_path);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < argc; i++) {
|
||||
argvs[i] = argv[i];
|
||||
}
|
||||
|
||||
char *s = malloc(BUFFER_SIZE);
|
||||
strcpy(s, delete_path);
|
||||
argvs[argc] = s;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int c;
|
||||
bool ok;
|
||||
bool make_backups = false;
|
||||
char const *backup_suffix = NULL;
|
||||
char *version_control_string = NULL;
|
||||
struct cp_options x;
|
||||
bool remove_trailing_slashes = false;
|
||||
char const *target_directory = NULL;
|
||||
bool no_target_directory = false;
|
||||
int n_files;
|
||||
char **file;
|
||||
bool selinux_enabled = (0 < is_selinux_enabled());
|
||||
|
||||
/* 处理 argv */
|
||||
char **head_argvs;
|
||||
char *argvs[20] = { malloc(sizeof(char *)) };
|
||||
process_argv(argc, argv, &(argvs[0]));
|
||||
head_argvs = &(argvs[0]); // head_argvs指向argvs[0]
|
||||
argc = argc + 1; // 改变argc数
|
||||
|
||||
/* 打印新的参数 */
|
||||
/*
|
||||
for (int i = 0; i < argc; i++) {
|
||||
printf("%s\n", argvs[i]);
|
||||
}
|
||||
*/
|
||||
|
||||
initialize_main(&argc, &argvs);
|
||||
set_program_name(argvs[0]);
|
||||
setlocale(LC_ALL, "");
|
||||
bindtextdomain(PACKAGE, LOCALEDIR);
|
||||
textdomain(PACKAGE);
|
||||
|
||||
atexit(close_stdin);
|
||||
|
||||
cp_option_init(&x);
|
||||
|
||||
/* Try to disable the ability to unlink a directory. */
|
||||
priv_set_remove_linkdir();
|
||||
|
||||
while ((c = getopt_long(argc, argvs, "bfint:uvS:TZ", long_options, NULL))
|
||||
!= -1) {
|
||||
switch (c) {
|
||||
case 'b':
|
||||
make_backups = true;
|
||||
if (optarg)
|
||||
version_control_string = optarg;
|
||||
break;
|
||||
case 'f':
|
||||
x.interactive = I_ALWAYS_YES;
|
||||
break;
|
||||
case 'i':
|
||||
x.interactive = I_ASK_USER;
|
||||
break;
|
||||
case 'n':
|
||||
x.interactive = I_ALWAYS_NO;
|
||||
break;
|
||||
case STRIP_TRAILING_SLASHES_OPTION:
|
||||
remove_trailing_slashes = true;
|
||||
break;
|
||||
case 't':
|
||||
if (target_directory)
|
||||
die(EXIT_FAILURE, 0, _("multiple target directories specified"));
|
||||
target_directory = optarg;
|
||||
break;
|
||||
case 'T':
|
||||
no_target_directory = true;
|
||||
break;
|
||||
case 'u':
|
||||
x.update = true;
|
||||
break;
|
||||
case 'v':
|
||||
x.verbose = true;
|
||||
break;
|
||||
case 'S':
|
||||
make_backups = true;
|
||||
backup_suffix = optarg;
|
||||
break;
|
||||
case 'Z':
|
||||
/* As a performance enhancement, don't even bother trying
|
||||
to "restorecon" when not on an selinux-enabled kernel. */
|
||||
if (selinux_enabled) {
|
||||
x.preserve_security_context = false;
|
||||
x.set_security_context = selabel_open(SELABEL_CTX_FILE, NULL, 0);
|
||||
if (!x.set_security_context)
|
||||
error(0, errno, _("warning: ignoring --context"));
|
||||
}
|
||||
break;
|
||||
case_GETOPT_HELP_CHAR;
|
||||
case_GETOPT_VERSION_CHAR(PROGRAM_NAME, AUTHORS);
|
||||
default:
|
||||
usage(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
x.interactive = I_ALWAYS_YES; // 默认使用-f参数
|
||||
|
||||
n_files = argc - optind;
|
||||
file = head_argvs + optind;
|
||||
|
||||
if (n_files <= !target_directory) {
|
||||
if (n_files <= 0)
|
||||
error(0, 0, _("missing file operand"));
|
||||
else
|
||||
error(0, 0, _("missing destination file operand after %s"), quoteaf(file[0]));
|
||||
usage(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
struct stat sb;
|
||||
sb.st_mode = 0;
|
||||
int target_dirfd = AT_FDCWD;
|
||||
if (no_target_directory) {
|
||||
if (target_directory)
|
||||
die(EXIT_FAILURE, 0, _("cannot combine --target-directory (-t) " "and --no-target-directory (-T)"));
|
||||
if (2 < n_files) {
|
||||
error(0, 0, _("extra operand %s"), quoteaf(file[2]));
|
||||
usage(EXIT_FAILURE);
|
||||
}
|
||||
} else if (target_directory) {
|
||||
target_dirfd = target_directory_operand(target_directory, &sb);
|
||||
if (!target_dirfd_valid(target_dirfd))
|
||||
die(EXIT_FAILURE, errno, _("target directory %s"), quoteaf(target_directory));
|
||||
} else {
|
||||
char const *lastfile = file[n_files - 1];
|
||||
if (n_files == 2)
|
||||
x.rename_errno = (renameatu(AT_FDCWD, file[0], AT_FDCWD, lastfile, RENAME_NOREPLACE)
|
||||
? errno : 0);
|
||||
if (x.rename_errno != 0) {
|
||||
int fd = target_directory_operand(lastfile, &sb);
|
||||
if (target_dirfd_valid(fd)) {
|
||||
x.rename_errno = -1;
|
||||
target_dirfd = fd;
|
||||
target_directory = lastfile;
|
||||
n_files--;
|
||||
} else {
|
||||
/* The last operand LASTFILE cannot be opened as a directory.
|
||||
If there are more than two operands, report an error.
|
||||
|
||||
Also, report an error if LASTFILE is known to be a directory
|
||||
even though it could not be opened, which can happen if
|
||||
opening failed with EACCES on a platform lacking O_PATH.
|
||||
In this case use stat to test whether LASTFILE is a
|
||||
directory, in case opening a non-directory with (O_SEARCH
|
||||
| O_DIRECTORY) failed with EACCES not ENOTDIR. */
|
||||
int err = errno;
|
||||
if (2 < n_files || (O_PATHSEARCH == O_SEARCH && err == EACCES && (sb.st_mode != 0 || stat(lastfile, &sb) == 0)
|
||||
&& S_ISDIR(sb.st_mode)))
|
||||
die(EXIT_FAILURE, err, _("target %s"), quoteaf(lastfile));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Handle the ambiguity in the semantics of mv induced by the
|
||||
varying semantics of the rename function. POSIX-compatible
|
||||
systems (e.g., GNU/Linux) have a rename function that honors a
|
||||
trailing slash in the source, while others (Solaris 9, FreeBSD
|
||||
7.2) have a rename function that ignores it. */
|
||||
if (remove_trailing_slashes)
|
||||
for (int i = 0; i < n_files; i++)
|
||||
strip_trailing_slashes(file[i]);
|
||||
|
||||
if (x.interactive == I_ALWAYS_NO)
|
||||
x.update = false;
|
||||
|
||||
if (make_backups && x.interactive == I_ALWAYS_NO) {
|
||||
error(0, 0, _("options --backup and --no-clobber are mutually exclusive"));
|
||||
usage(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
x.backup_type = (make_backups ? xget_version(_("backup type"), version_control_string)
|
||||
: no_backups);
|
||||
set_simple_backup_suffix(backup_suffix);
|
||||
|
||||
hash_init();
|
||||
|
||||
if (target_directory) {
|
||||
/* Initialize the hash table only if we'll need it.
|
||||
The problem it is used to detect can arise only if there are
|
||||
two or more files to move. */
|
||||
if (2 <= n_files)
|
||||
dest_info_init(&x);
|
||||
|
||||
ok = true;
|
||||
for (int i = 0; i < n_files; ++i) {
|
||||
x.last_file = i + 1 == n_files;
|
||||
char const *source = file[i];
|
||||
char const *source_basename = last_component(source);
|
||||
char *dest_relname;
|
||||
char *dest = file_name_concat(target_directory, source_basename,
|
||||
&dest_relname);
|
||||
strip_trailing_slashes(dest_relname);
|
||||
ok &= do_move(source, dest, target_dirfd, dest_relname, &x);
|
||||
free(dest);
|
||||
}
|
||||
} else {
|
||||
x.last_file = true;
|
||||
ok = do_move(file[0], file[1], AT_FDCWD, file[1], &x);
|
||||
}
|
||||
|
||||
if (argvs[argc] != NULL)
|
||||
free(argvs[argc]);
|
||||
|
||||
main_exit(ok ? EXIT_SUCCESS : EXIT_FAILURE);
|
||||
}
|
||||
623
src/remove.c
Normal file
623
src/remove.c
Normal file
@@ -0,0 +1,623 @@
|
||||
/* remove.c -- core functions for removing files and directories
|
||||
Copyright (C) 1988-2022 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 3 of the License, 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, see <https://www.gnu.org/licenses/>. */
|
||||
|
||||
/* Extracted from rm.c, librarified, then rewritten twice by Jim Meyering. */
|
||||
|
||||
#include <config.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "system.h"
|
||||
#include "error.h"
|
||||
#include "file-type.h"
|
||||
#include "filenamecat.h"
|
||||
#include "ignore-value.h"
|
||||
#include "remove.h"
|
||||
#include "root-dev-ino.h"
|
||||
#include "write-any-file.h"
|
||||
#include "xfts.h"
|
||||
#include "yesno.h"
|
||||
|
||||
enum Ternary
|
||||
{
|
||||
T_UNKNOWN = 2,
|
||||
T_NO,
|
||||
T_YES
|
||||
};
|
||||
typedef enum Ternary Ternary;
|
||||
|
||||
/* The prompt function may be called twice for 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
|
||||
};
|
||||
|
||||
/* D_TYPE(D) is the type of directory entry D if known, DT_UNKNOWN
|
||||
otherwise. */
|
||||
#if ! HAVE_STRUCT_DIRENT_D_TYPE
|
||||
/* Any int values will do here, so long as they're distinct.
|
||||
Undef any existing macros out of the way. */
|
||||
# undef DT_UNKNOWN
|
||||
# undef DT_DIR
|
||||
# undef DT_LNK
|
||||
# define DT_UNKNOWN 0
|
||||
# define DT_DIR 1
|
||||
# define DT_LNK 2
|
||||
#endif
|
||||
|
||||
/* Like fstatat, but cache the result. If ST->st_size is -1, the
|
||||
status has not been gotten yet. If less than -1, fstatat failed
|
||||
with errno == ST->st_ino. Otherwise, the status has already
|
||||
been gotten, so return 0. */
|
||||
static int
|
||||
cache_fstatat (int fd, char const *file, struct stat *st, int flag)
|
||||
{
|
||||
if (st->st_size == -1 && fstatat (fd, file, st, flag) != 0)
|
||||
{
|
||||
st->st_size = -2;
|
||||
st->st_ino = errno;
|
||||
}
|
||||
if (0 <= st->st_size)
|
||||
return 0;
|
||||
errno = (int) st->st_ino;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Initialize a fstatat cache *ST. Return ST for convenience. */
|
||||
static inline struct stat *
|
||||
cache_stat_init (struct stat *st)
|
||||
{
|
||||
st->st_size = -1;
|
||||
return st;
|
||||
}
|
||||
|
||||
/* Return 1 if FILE is an unwritable non-symlink,
|
||||
0 if it is writable or some other type of file,
|
||||
-1 and set errno if there is some problem in determining the answer.
|
||||
Set *BUF to the file status. */
|
||||
static int
|
||||
write_protected_non_symlink (int fd_cwd,
|
||||
char const *file,
|
||||
struct stat *buf)
|
||||
{
|
||||
if (can_write_any_file ())
|
||||
return 0;
|
||||
if (cache_fstatat (fd_cwd, file, buf, AT_SYMLINK_NOFOLLOW) != 0)
|
||||
return -1;
|
||||
if (S_ISLNK (buf->st_mode))
|
||||
return 0;
|
||||
/* Here, we know FILE is not a symbolic link. */
|
||||
|
||||
/* In order to be reentrant -- i.e., to avoid changing the working
|
||||
directory, and at the same time to be able to deal with alternate
|
||||
access control mechanisms (ACLs, xattr-style attributes) and
|
||||
arbitrarily deep trees -- we need a function like eaccessat, i.e.,
|
||||
like Solaris' eaccess, but fd-relative, in the spirit of openat. */
|
||||
|
||||
/* In the absence of a native eaccessat function, here are some of
|
||||
the implementation choices [#4 and #5 were suggested by Paul Eggert]:
|
||||
1) call openat with O_WRONLY|O_NOCTTY
|
||||
Disadvantage: may create the file and doesn't work for directory,
|
||||
may mistakenly report 'unwritable' for EROFS or ACLs even though
|
||||
perm bits say the file is writable.
|
||||
|
||||
2) fake eaccessat (save_cwd, fchdir, call euidaccess, restore_cwd)
|
||||
Disadvantage: changes working directory (not reentrant) and can't
|
||||
work if save_cwd fails.
|
||||
|
||||
3) if (euidaccess (full_name, W_OK) == 0)
|
||||
Disadvantage: doesn't work if full_name is too long.
|
||||
Inefficient for very deep trees (O(Depth^2)).
|
||||
|
||||
4) If the full pathname is sufficiently short (say, less than
|
||||
PATH_MAX or 8192 bytes, whichever is shorter):
|
||||
use method (3) (i.e., euidaccess (full_name, W_OK));
|
||||
Otherwise: vfork, fchdir in the child, run euidaccess in the
|
||||
child, then the child exits with a status that tells the parent
|
||||
whether euidaccess succeeded.
|
||||
|
||||
This avoids the O(N**2) algorithm of method (3), and it also avoids
|
||||
the failure-due-to-too-long-file-names of method (3), but it's fast
|
||||
in the normal shallow case. It also avoids the lack-of-reentrancy
|
||||
and the save_cwd problems.
|
||||
Disadvantage; it uses a process slot for very-long file names,
|
||||
and would be very slow for hierarchies with many such files.
|
||||
|
||||
5) If the full file name is sufficiently short (say, less than
|
||||
PATH_MAX or 8192 bytes, whichever is shorter):
|
||||
use method (3) (i.e., euidaccess (full_name, W_OK));
|
||||
Otherwise: look just at the file bits. Perhaps issue a warning
|
||||
the first time this occurs.
|
||||
|
||||
This is like (4), except for the "Otherwise" case where it isn't as
|
||||
"perfect" as (4) but is considerably faster. It conforms to current
|
||||
POSIX, and is uniformly better than what Solaris and FreeBSD do (they
|
||||
mess up with long file names). */
|
||||
|
||||
{
|
||||
if (faccessat (fd_cwd, file, W_OK, AT_EACCESS) == 0)
|
||||
return 0;
|
||||
|
||||
return errno == EACCES ? 1 : -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Prompt whether to remove FILENAME (ent->, if required via a combination of
|
||||
the options specified by X and/or file attributes. If the file may
|
||||
be removed, return RM_OK. If the user declines to remove the file,
|
||||
return RM_USER_DECLINED. If not ignoring missing files and we
|
||||
cannot lstat FILENAME, then return RM_ERROR.
|
||||
|
||||
IS_DIR is true if ENT designates a directory, false otherwise.
|
||||
|
||||
Depending on MODE, ask whether to 'descend into' or to 'remove' the
|
||||
directory FILENAME. MODE is ignored when FILENAME is not a directory.
|
||||
Set *IS_EMPTY_P to T_YES if FILENAME is an empty directory, and it is
|
||||
appropriate to try to remove it with rmdir (e.g. recursive mode).
|
||||
Don't even try to set *IS_EMPTY_P when MODE == PA_REMOVE_DIR. */
|
||||
static enum RM_status
|
||||
prompt (FTS const *fts, FTSENT const *ent, bool is_dir,
|
||||
struct rm_options const *x, enum Prompt_action mode,
|
||||
Ternary *is_empty_p)
|
||||
{
|
||||
int fd_cwd = fts->fts_cwd_fd;
|
||||
char const *full_name = ent->fts_path;
|
||||
char const *filename = ent->fts_accpath;
|
||||
if (is_empty_p)
|
||||
*is_empty_p = T_UNKNOWN;
|
||||
|
||||
struct stat st;
|
||||
struct stat *sbuf = &st;
|
||||
cache_stat_init (sbuf);
|
||||
|
||||
int dirent_type = is_dir ? DT_DIR : DT_UNKNOWN;
|
||||
int write_protected = 0;
|
||||
|
||||
bool is_empty = false;
|
||||
if (is_empty_p)
|
||||
{
|
||||
is_empty = is_empty_dir (fd_cwd, filename);
|
||||
*is_empty_p = is_empty ? T_YES : T_NO;
|
||||
}
|
||||
|
||||
/* When nonzero, this indicates that we failed to remove a child entry,
|
||||
either because the user declined an interactive prompt, or due to
|
||||
some other failure, like permissions. */
|
||||
if (ent->fts_number)
|
||||
return RM_USER_DECLINED;
|
||||
|
||||
if (x->interactive == RMI_NEVER)
|
||||
return RM_OK;
|
||||
|
||||
int wp_errno = 0;
|
||||
if (!x->ignore_missing_files
|
||||
&& ((x->interactive == RMI_ALWAYS) || x->stdin_tty)
|
||||
&& dirent_type != DT_LNK)
|
||||
{
|
||||
write_protected = write_protected_non_symlink (fd_cwd, filename, sbuf);
|
||||
wp_errno = errno;
|
||||
}
|
||||
|
||||
if (write_protected || x->interactive == RMI_ALWAYS)
|
||||
{
|
||||
if (0 <= write_protected && dirent_type == DT_UNKNOWN)
|
||||
{
|
||||
if (cache_fstatat (fd_cwd, filename, sbuf, AT_SYMLINK_NOFOLLOW) == 0)
|
||||
{
|
||||
if (S_ISLNK (sbuf->st_mode))
|
||||
dirent_type = DT_LNK;
|
||||
else if (S_ISDIR (sbuf->st_mode))
|
||||
dirent_type = DT_DIR;
|
||||
/* Otherwise it doesn't matter, so leave it DT_UNKNOWN. */
|
||||
}
|
||||
else
|
||||
{
|
||||
/* This happens, e.g., with 'rm '''. */
|
||||
write_protected = -1;
|
||||
wp_errno = errno;
|
||||
}
|
||||
}
|
||||
|
||||
if (0 <= write_protected)
|
||||
switch (dirent_type)
|
||||
{
|
||||
case DT_LNK:
|
||||
/* Using permissions doesn't make sense for symlinks. */
|
||||
if (x->interactive != RMI_ALWAYS)
|
||||
return RM_OK;
|
||||
break;
|
||||
|
||||
case DT_DIR:
|
||||
/* Unless we're either deleting directories or deleting
|
||||
recursively, we want to raise an EISDIR error rather than
|
||||
prompting the user */
|
||||
if ( ! (x->recursive || (x->remove_empty_directories && is_empty)))
|
||||
{
|
||||
write_protected = -1;
|
||||
wp_errno = EISDIR;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
char const *quoted_name = quoteaf (full_name);
|
||||
|
||||
if (write_protected < 0)
|
||||
{
|
||||
error (0, wp_errno, _("cannot remove %s"), quoted_name);
|
||||
return RM_ERROR;
|
||||
}
|
||||
|
||||
/* Issue the prompt. */
|
||||
if (dirent_type == DT_DIR
|
||||
&& mode == PA_DESCEND_INTO_DIR
|
||||
&& !is_empty)
|
||||
fprintf (stderr,
|
||||
(write_protected
|
||||
? _("%s: descend into write-protected directory %s? ")
|
||||
: _("%s: descend into directory %s? ")),
|
||||
program_name, quoted_name);
|
||||
else
|
||||
{
|
||||
if (cache_fstatat (fd_cwd, filename, sbuf, AT_SYMLINK_NOFOLLOW) != 0)
|
||||
{
|
||||
error (0, errno, _("cannot remove %s"), quoted_name);
|
||||
return RM_ERROR;
|
||||
}
|
||||
|
||||
fprintf (stderr,
|
||||
(write_protected
|
||||
/* TRANSLATORS: In the next two strings the second %s is
|
||||
replaced by the type of the file. To avoid grammatical
|
||||
problems, it may be more convenient to translate these
|
||||
strings instead as: "%1$s: %3$s is write-protected and
|
||||
is of type '%2$s' -- remove it? ". */
|
||||
? _("%s: remove write-protected %s %s? ")
|
||||
: _("%s: remove %s %s? ")),
|
||||
program_name, file_type (sbuf), quoted_name);
|
||||
}
|
||||
|
||||
if (!yesno ())
|
||||
return RM_USER_DECLINED;
|
||||
}
|
||||
return RM_OK;
|
||||
}
|
||||
|
||||
/* When a function like unlink, rmdir, or fstatat fails with an errno
|
||||
value of ERRNUM, return true if the specified file system object
|
||||
is guaranteed not to exist; otherwise, return false. */
|
||||
static inline bool
|
||||
nonexistent_file_errno (int errnum)
|
||||
{
|
||||
/* Do not include ELOOP here, since the specified file may indeed
|
||||
exist, but be (in)accessible only via too long a symlink chain.
|
||||
Likewise for ENAMETOOLONG, since rm -f ./././.../foo may fail
|
||||
if the "..." part expands to a long enough sequence of "./"s,
|
||||
even though ./foo does indeed exist.
|
||||
|
||||
Another case to consider is when a particular name is invalid for
|
||||
a given file system. In 2011, smbfs returns EINVAL, but the next
|
||||
revision of POSIX will require EILSEQ for that situation:
|
||||
http://austingroupbugs.net/view.php?id=293
|
||||
*/
|
||||
|
||||
switch (errnum)
|
||||
{
|
||||
case EILSEQ:
|
||||
case EINVAL:
|
||||
case ENOENT:
|
||||
case ENOTDIR:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Encapsulate the test for whether the errno value, ERRNUM, is ignorable. */
|
||||
static inline bool
|
||||
ignorable_missing (struct rm_options const *x, int errnum)
|
||||
{
|
||||
return x->ignore_missing_files && nonexistent_file_errno (errnum);
|
||||
}
|
||||
|
||||
/* Tell fts not to traverse into the hierarchy at ENT. */
|
||||
static void
|
||||
fts_skip_tree (FTS *fts, FTSENT *ent)
|
||||
{
|
||||
fts_set (fts, ent, FTS_SKIP);
|
||||
/* Ensure that we do not process ENT a second time. */
|
||||
ignore_value (fts_read (fts));
|
||||
}
|
||||
|
||||
/* Upon unlink failure, or when the user declines to remove ENT, mark
|
||||
each of its ancestor directories, so that we know not to prompt for
|
||||
its removal. */
|
||||
static void
|
||||
mark_ancestor_dirs (FTSENT *ent)
|
||||
{
|
||||
FTSENT *p;
|
||||
for (p = ent->fts_parent; FTS_ROOTLEVEL <= p->fts_level; p = p->fts_parent)
|
||||
{
|
||||
if (p->fts_number)
|
||||
break;
|
||||
p->fts_number = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Remove the file system object specified by ENT. IS_DIR specifies
|
||||
whether it is expected to be a directory or non-directory.
|
||||
Return RM_OK upon success, else RM_ERROR. */
|
||||
static enum RM_status
|
||||
excise (FTS *fts, FTSENT *ent, struct rm_options const *x, bool is_dir)
|
||||
{
|
||||
int flag = is_dir ? AT_REMOVEDIR : 0;
|
||||
if (unlinkat (fts->fts_cwd_fd, ent->fts_accpath, flag) == 0)
|
||||
{
|
||||
if (x->verbose)
|
||||
{
|
||||
printf ((is_dir
|
||||
? _("removed directory %s\n")
|
||||
: _("removed %s\n")), quoteaf (ent->fts_path));
|
||||
}
|
||||
return RM_OK;
|
||||
}
|
||||
|
||||
/* The unlinkat from kernels like linux-2.6.32 reports EROFS even for
|
||||
nonexistent files. When the file is indeed missing, map that to ENOENT,
|
||||
so that rm -f ignores it, as required. Even without -f, this is useful
|
||||
because it makes rm print the more precise diagnostic. */
|
||||
if (errno == EROFS)
|
||||
{
|
||||
struct stat st;
|
||||
if ( ! (fstatat (fts->fts_cwd_fd, ent->fts_accpath, &st,
|
||||
AT_SYMLINK_NOFOLLOW)
|
||||
&& errno == ENOENT))
|
||||
errno = EROFS;
|
||||
}
|
||||
|
||||
if (ignorable_missing (x, errno))
|
||||
return RM_OK;
|
||||
|
||||
/* When failing to rmdir an unreadable directory, we see errno values
|
||||
like EISDIR or ENOTDIR (or, on Solaris 10, EEXIST), but they would be
|
||||
meaningless in a diagnostic. When that happens and the errno value
|
||||
from the failed open is EPERM or EACCES, use the earlier, more
|
||||
descriptive errno value. */
|
||||
if (ent->fts_info == FTS_DNR
|
||||
&& (errno == ENOTEMPTY || errno == EISDIR || errno == ENOTDIR
|
||||
|| errno == EEXIST)
|
||||
&& (ent->fts_errno == EPERM || ent->fts_errno == EACCES))
|
||||
errno = ent->fts_errno;
|
||||
error (0, errno, _("cannot remove %s"), quoteaf (ent->fts_path));
|
||||
mark_ancestor_dirs (ent);
|
||||
return RM_ERROR;
|
||||
}
|
||||
|
||||
/* This function is called once for every file system object that fts
|
||||
encounters. fts performs a depth-first traversal.
|
||||
A directory is usually processed twice, first with fts_info == FTS_D,
|
||||
and later, after all of its entries have been processed, with FTS_DP.
|
||||
Return RM_ERROR upon error, RM_USER_DECLINED for a negative response
|
||||
to an interactive prompt, and otherwise, RM_OK. */
|
||||
static enum RM_status
|
||||
rm_fts (FTS *fts, FTSENT *ent, struct rm_options const *x)
|
||||
{
|
||||
switch (ent->fts_info)
|
||||
{
|
||||
case FTS_D: /* preorder directory */
|
||||
if (! x->recursive
|
||||
&& !(x->remove_empty_directories
|
||||
&& is_empty_dir (fts->fts_cwd_fd, ent->fts_accpath)))
|
||||
{
|
||||
/* This is the first (pre-order) encounter with a directory
|
||||
that we cannot delete.
|
||||
Not recursive, and it's not an empty directory (if we're removing
|
||||
them) so arrange to skip contents. */
|
||||
int err = x->remove_empty_directories ? ENOTEMPTY : EISDIR;
|
||||
error (0, err, _("cannot remove %s"), quoteaf (ent->fts_path));
|
||||
mark_ancestor_dirs (ent);
|
||||
fts_skip_tree (fts, ent);
|
||||
return RM_ERROR;
|
||||
}
|
||||
|
||||
/* Perform checks that can apply only for command-line arguments. */
|
||||
if (ent->fts_level == FTS_ROOTLEVEL)
|
||||
{
|
||||
/* POSIX says:
|
||||
If the basename of a command line argument is "." or "..",
|
||||
diagnose it and do nothing more with that argument. */
|
||||
if (dot_or_dotdot (last_component (ent->fts_accpath)))
|
||||
{
|
||||
error (0, 0,
|
||||
_("refusing to remove %s or %s directory: skipping %s"),
|
||||
quoteaf_n (0, "."), quoteaf_n (1, ".."),
|
||||
quoteaf_n (2, ent->fts_path));
|
||||
fts_skip_tree (fts, ent);
|
||||
return RM_ERROR;
|
||||
}
|
||||
|
||||
/* POSIX also says:
|
||||
If a command line argument resolves to "/" (and --preserve-root
|
||||
is in effect -- default) diagnose and skip it. */
|
||||
if (ROOT_DEV_INO_CHECK (x->root_dev_ino, ent->fts_statp))
|
||||
{
|
||||
ROOT_DEV_INO_WARN (ent->fts_path);
|
||||
fts_skip_tree (fts, ent);
|
||||
return RM_ERROR;
|
||||
}
|
||||
|
||||
/* If a command line argument is a mount point and
|
||||
--preserve-root=all is in effect, diagnose and skip it.
|
||||
This doesn't handle "/", but that's handled above. */
|
||||
if (x->preserve_all_root)
|
||||
{
|
||||
bool failed = false;
|
||||
char *parent = file_name_concat (ent->fts_accpath, "..", NULL);
|
||||
struct stat statbuf;
|
||||
|
||||
if (!parent || lstat (parent, &statbuf))
|
||||
{
|
||||
error (0, 0,
|
||||
_("failed to stat %s: skipping %s"),
|
||||
quoteaf_n (0, parent),
|
||||
quoteaf_n (1, ent->fts_accpath));
|
||||
failed = true;
|
||||
}
|
||||
|
||||
free (parent);
|
||||
|
||||
if (failed || fts->fts_dev != statbuf.st_dev)
|
||||
{
|
||||
if (! failed)
|
||||
{
|
||||
error (0, 0,
|
||||
_("skipping %s, since it's on a different device"),
|
||||
quoteaf (ent->fts_path));
|
||||
error (0, 0, _("and --preserve-root=all is in effect"));
|
||||
}
|
||||
fts_skip_tree (fts, ent);
|
||||
return RM_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
Ternary is_empty_directory;
|
||||
enum RM_status s = prompt (fts, ent, true /*is_dir*/, x,
|
||||
PA_DESCEND_INTO_DIR, &is_empty_directory);
|
||||
|
||||
if (s == RM_OK && is_empty_directory == T_YES)
|
||||
{
|
||||
/* When we know (from prompt when in interactive mode)
|
||||
that this is an empty directory, don't prompt twice. */
|
||||
s = excise (fts, ent, x, true);
|
||||
if (s == RM_OK)
|
||||
fts_skip_tree (fts, ent);
|
||||
}
|
||||
|
||||
if (s != RM_OK)
|
||||
{
|
||||
mark_ancestor_dirs (ent);
|
||||
fts_skip_tree (fts, ent);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
case FTS_F: /* regular file */
|
||||
case FTS_NS: /* stat(2) failed */
|
||||
case FTS_SL: /* symbolic link */
|
||||
case FTS_SLNONE: /* symbolic link without target */
|
||||
case FTS_DP: /* postorder directory */
|
||||
case FTS_DNR: /* unreadable directory */
|
||||
case FTS_NSOK: /* e.g., dangling symlink */
|
||||
case FTS_DEFAULT: /* none of the above */
|
||||
{
|
||||
/* With --one-file-system, do not attempt to remove a mount point.
|
||||
fts' FTS_XDEV ensures that we don't process any entries under
|
||||
the mount point. */
|
||||
if (ent->fts_info == FTS_DP
|
||||
&& x->one_file_system
|
||||
&& FTS_ROOTLEVEL < ent->fts_level
|
||||
&& ent->fts_statp->st_dev != fts->fts_dev)
|
||||
{
|
||||
mark_ancestor_dirs (ent);
|
||||
error (0, 0, _("skipping %s, since it's on a different device"),
|
||||
quoteaf (ent->fts_path));
|
||||
return RM_ERROR;
|
||||
}
|
||||
|
||||
bool is_dir = ent->fts_info == FTS_DP || ent->fts_info == FTS_DNR;
|
||||
enum RM_status s = prompt (fts, ent, is_dir, x, PA_REMOVE_DIR, NULL);
|
||||
if (s != RM_OK)
|
||||
return s;
|
||||
return excise (fts, ent, x, is_dir);
|
||||
}
|
||||
|
||||
case FTS_DC: /* directory that causes cycles */
|
||||
emit_cycle_warning (ent->fts_path);
|
||||
fts_skip_tree (fts, ent);
|
||||
return RM_ERROR;
|
||||
|
||||
case FTS_ERR:
|
||||
/* Various failures, from opendir to ENOMEM, to failure to "return"
|
||||
to preceding directory, can provoke this. */
|
||||
error (0, ent->fts_errno, _("traversal failed: %s"),
|
||||
quotef (ent->fts_path));
|
||||
fts_skip_tree (fts, ent);
|
||||
return RM_ERROR;
|
||||
|
||||
default:
|
||||
error (0, 0, _("unexpected failure: fts_info=%d: %s\n"
|
||||
"please report to %s"),
|
||||
ent->fts_info,
|
||||
quotef (ent->fts_path),
|
||||
PACKAGE_BUGREPORT);
|
||||
abort ();
|
||||
}
|
||||
}
|
||||
|
||||
/* Remove FILEs, honoring options specified via X.
|
||||
Return RM_OK if successful. */
|
||||
enum RM_status
|
||||
rm (char *const *file, struct rm_options const *x)
|
||||
{
|
||||
enum RM_status rm_status = RM_OK;
|
||||
|
||||
if (*file)
|
||||
{
|
||||
int bit_flags = (FTS_CWDFD
|
||||
| FTS_NOSTAT
|
||||
| FTS_PHYSICAL);
|
||||
|
||||
if (x->one_file_system)
|
||||
bit_flags |= FTS_XDEV;
|
||||
|
||||
FTS *fts = xfts_open (file, bit_flags, NULL);
|
||||
|
||||
while (true)
|
||||
{
|
||||
FTSENT *ent;
|
||||
|
||||
ent = fts_read (fts);
|
||||
if (ent == NULL)
|
||||
{
|
||||
if (errno != 0)
|
||||
{
|
||||
error (0, errno, _("fts_read failed"));
|
||||
rm_status = RM_ERROR;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
enum RM_status s = rm_fts (fts, ent, x);
|
||||
|
||||
assert (VALID_STATUS (s));
|
||||
UPDATE_STATUS (rm_status, s);
|
||||
}
|
||||
|
||||
if (fts_close (fts) != 0)
|
||||
{
|
||||
error (0, errno, _("fts_close failed"));
|
||||
rm_status = RM_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
return rm_status;
|
||||
}
|
||||
101
src/remove.h
Normal file
101
src/remove.h
Normal file
@@ -0,0 +1,101 @@
|
||||
/* Remove directory entries.
|
||||
|
||||
Copyright (C) 1998-2022 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 3 of the License, 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, see <https://www.gnu.org/licenses/>. */
|
||||
|
||||
#ifndef REMOVE_H
|
||||
# define REMOVE_H
|
||||
|
||||
# include "dev-ino.h"
|
||||
|
||||
enum rm_interactive
|
||||
{
|
||||
/* Start with any number larger than 1, so that any legacy tests
|
||||
against values of 0 or 1 will fail. */
|
||||
RMI_ALWAYS = 3,
|
||||
RMI_SOMETIMES,
|
||||
RMI_NEVER
|
||||
};
|
||||
|
||||
struct rm_options
|
||||
{
|
||||
/* If true, ignore nonexistent files. */
|
||||
bool ignore_missing_files;
|
||||
|
||||
/* If true, query the user about whether to remove each file. */
|
||||
enum rm_interactive interactive;
|
||||
|
||||
/* FIXME: remove */
|
||||
/* If true, do not traverse into (or remove) any directory that is
|
||||
on a file system (i.e., that has a different device number) other
|
||||
than that of the corresponding command line argument. Note that
|
||||
even without this option, rm will fail in the end, due to its
|
||||
probable inability to remove the mount point. But there, the
|
||||
diagnostic comes too late -- after removing all contents. */
|
||||
bool one_file_system;
|
||||
|
||||
/* If true, recursively remove directories. */
|
||||
bool recursive;
|
||||
|
||||
/* If true, remove empty directories. */
|
||||
bool remove_empty_directories;
|
||||
|
||||
/* Pointer to the device and inode numbers of '/', when --recursive
|
||||
and preserving '/'. Otherwise NULL. */
|
||||
struct dev_ino *root_dev_ino;
|
||||
|
||||
/* If true, do not traverse into (or remove) any directory that is
|
||||
the root of a file system. I.e., a separate device. */
|
||||
bool preserve_all_root;
|
||||
|
||||
/* If nonzero, stdin is a tty. */
|
||||
bool stdin_tty;
|
||||
|
||||
/* If true, display the name of each file removed. */
|
||||
bool verbose;
|
||||
|
||||
/* If true, treat the failure by the rm function to restore the
|
||||
current working directory as a fatal error. I.e., if this field
|
||||
is true and the rm function cannot restore cwd, it must exit with
|
||||
a nonzero status. Some applications require that the rm function
|
||||
restore cwd (e.g., mv) and some others do not (e.g., rm,
|
||||
in many cases). */
|
||||
bool require_restore_cwd;
|
||||
};
|
||||
|
||||
enum RM_status
|
||||
{
|
||||
/* These must be listed in order of increasing seriousness. */
|
||||
RM_OK = 2,
|
||||
RM_USER_DECLINED,
|
||||
RM_ERROR,
|
||||
RM_NONEMPTY_DIR
|
||||
};
|
||||
|
||||
# define VALID_STATUS(S) \
|
||||
((S) == RM_OK || (S) == RM_USER_DECLINED || (S) == RM_ERROR)
|
||||
|
||||
# define UPDATE_STATUS(S, New_value) \
|
||||
do \
|
||||
{ \
|
||||
if ((New_value) == RM_ERROR \
|
||||
|| ((New_value) == RM_USER_DECLINED && (S) == RM_OK)) \
|
||||
(S) = (New_value); \
|
||||
} \
|
||||
while (0)
|
||||
|
||||
extern enum RM_status rm (char *const *file, struct rm_options const *x);
|
||||
|
||||
#endif
|
||||
327
src/selinux.c
Normal file
327
src/selinux.c
Normal file
@@ -0,0 +1,327 @@
|
||||
/* selinux - core functions for maintaining SELinux labeling
|
||||
Copyright (C) 2012-2022 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 3 of the License, 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, see <https://www.gnu.org/licenses/>. */
|
||||
|
||||
/* Written by Daniel Walsh <dwalsh@redhat.com> */
|
||||
|
||||
#include <config.h>
|
||||
#include <selinux/label.h>
|
||||
#include <selinux/context.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "system.h"
|
||||
#include "canonicalize.h"
|
||||
#include "xfts.h"
|
||||
#include "selinux.h"
|
||||
|
||||
#if HAVE_SELINUX_LABEL_H
|
||||
|
||||
# if ! HAVE_MODE_TO_SECURITY_CLASS
|
||||
/*
|
||||
This function has been added to libselinux-2.1.12-5, but is here
|
||||
for support with older versions of SELinux
|
||||
|
||||
Translates a mode into an Internal SELinux security_class definition.
|
||||
Returns 0 on failure, with errno set to EINVAL.
|
||||
*/
|
||||
static security_class_t
|
||||
mode_to_security_class (mode_t m)
|
||||
{
|
||||
|
||||
if (S_ISREG (m))
|
||||
return string_to_security_class ("file");
|
||||
if (S_ISDIR (m))
|
||||
return string_to_security_class ("dir");
|
||||
if (S_ISCHR (m))
|
||||
return string_to_security_class ("chr_file");
|
||||
if (S_ISBLK (m))
|
||||
return string_to_security_class ("blk_file");
|
||||
if (S_ISFIFO (m))
|
||||
return string_to_security_class ("fifo_file");
|
||||
if (S_ISLNK (m))
|
||||
return string_to_security_class ("lnk_file");
|
||||
if (S_ISSOCK (m))
|
||||
return string_to_security_class ("sock_file");
|
||||
|
||||
errno = EINVAL;
|
||||
return 0;
|
||||
}
|
||||
# endif
|
||||
|
||||
/*
|
||||
This function takes a PATH and a MODE and then asks SELinux what the label
|
||||
of the path object would be if the current process label created it.
|
||||
It then returns the label.
|
||||
|
||||
Returns -1 on failure. errno will be set appropriately.
|
||||
*/
|
||||
|
||||
static int
|
||||
computecon (char const *path, mode_t mode, char **con)
|
||||
{
|
||||
char *scon = NULL;
|
||||
char *tcon = NULL;
|
||||
security_class_t tclass;
|
||||
int rc = -1;
|
||||
|
||||
char *dir = dir_name (path);
|
||||
if (!dir)
|
||||
goto quit;
|
||||
if (getcon (&scon) < 0)
|
||||
goto quit;
|
||||
if (getfilecon (dir, &tcon) < 0)
|
||||
goto quit;
|
||||
tclass = mode_to_security_class (mode);
|
||||
if (!tclass)
|
||||
goto quit;
|
||||
rc = security_compute_create (scon, tcon, tclass, con);
|
||||
|
||||
quit:;
|
||||
int err = errno;
|
||||
free (dir);
|
||||
freecon (scon);
|
||||
freecon (tcon);
|
||||
errno = err;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
This function takes a handle, path and mode, it calls computecon to get the
|
||||
label of the path object if the current process created it, then it calls
|
||||
selabel_lookup to get the default type for the object. It substitutes the
|
||||
default type into label. It tells the SELinux Kernel to label all new file
|
||||
system objects created by the current process with this label.
|
||||
|
||||
Returns -1 on failure. errno will be set appropriately.
|
||||
*/
|
||||
int
|
||||
defaultcon (struct selabel_handle *selabel_handle,
|
||||
char const *path, mode_t mode)
|
||||
{
|
||||
int rc = -1;
|
||||
char *scon = NULL;
|
||||
char *tcon = NULL;
|
||||
context_t scontext = 0, tcontext = 0;
|
||||
char const *contype;
|
||||
char *constr;
|
||||
char *newpath = NULL;
|
||||
|
||||
if (! IS_ABSOLUTE_FILE_NAME (path))
|
||||
{
|
||||
/* Generate absolute name as required by subsequent selabel_lookup. */
|
||||
newpath = canonicalize_filename_mode (path, CAN_MISSING);
|
||||
if (! newpath)
|
||||
goto quit;
|
||||
path = newpath;
|
||||
}
|
||||
|
||||
if (selabel_lookup (selabel_handle, &scon, path, mode) < 0)
|
||||
{
|
||||
/* "No such file or directory" is a confusing error,
|
||||
when processing files, when in fact it was the
|
||||
associated default context that was not found.
|
||||
Therefore map the error to something more appropriate
|
||||
to the context in which we're using selabel_lookup(). */
|
||||
if (errno == ENOENT)
|
||||
errno = ENODATA;
|
||||
goto quit;
|
||||
}
|
||||
if (computecon (path, mode, &tcon) < 0)
|
||||
goto quit;
|
||||
if (!(scontext = context_new (scon)))
|
||||
goto quit;
|
||||
if (!(tcontext = context_new (tcon)))
|
||||
goto quit;
|
||||
|
||||
if (!(contype = context_type_get (scontext)))
|
||||
goto quit;
|
||||
if (context_type_set (tcontext, contype))
|
||||
goto quit;
|
||||
if (!(constr = context_str (tcontext)))
|
||||
goto quit;
|
||||
|
||||
rc = setfscreatecon (constr);
|
||||
|
||||
quit:;
|
||||
int err = errno;
|
||||
context_free (scontext);
|
||||
context_free (tcontext);
|
||||
freecon (scon);
|
||||
freecon (tcon);
|
||||
free (newpath);
|
||||
errno = err;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
If SELABEL_HANDLE is null, set PATH's label to the default to the
|
||||
local process. Otherwise use selabel_lookup to determine the
|
||||
default label, extract the type field and then modify the file
|
||||
system object. Note only the type field is updated, thus preserving MLS
|
||||
levels and user identity etc. of the PATH.
|
||||
|
||||
Returns -1 on failure. errno will be set appropriately.
|
||||
*/
|
||||
static int
|
||||
restorecon_private (struct selabel_handle *selabel_handle, char const *path)
|
||||
{
|
||||
int rc = -1;
|
||||
struct stat sb;
|
||||
char *scon = NULL;
|
||||
char *tcon = NULL;
|
||||
context_t scontext = 0, tcontext = 0;
|
||||
char const *contype;
|
||||
char *constr;
|
||||
int fd;
|
||||
|
||||
if (!selabel_handle)
|
||||
{
|
||||
if (getfscreatecon (&tcon) < 0)
|
||||
return rc;
|
||||
if (!tcon)
|
||||
{
|
||||
errno = ENODATA;
|
||||
return rc;
|
||||
}
|
||||
rc = lsetfilecon (path, tcon);
|
||||
int err = errno;
|
||||
freecon (tcon);
|
||||
errno = err;
|
||||
return rc;
|
||||
}
|
||||
|
||||
fd = open (path, O_RDONLY | O_NOFOLLOW);
|
||||
if (fd == -1 && (errno != ELOOP))
|
||||
goto quit;
|
||||
|
||||
if (fd != -1)
|
||||
{
|
||||
if (fstat (fd, &sb) < 0)
|
||||
goto quit;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (lstat (path, &sb) < 0)
|
||||
goto quit;
|
||||
}
|
||||
|
||||
if (selabel_lookup (selabel_handle, &scon, path, sb.st_mode) < 0)
|
||||
{
|
||||
/* "No such file or directory" is a confusing error,
|
||||
when processing files, when in fact it was the
|
||||
associated default context that was not found.
|
||||
Therefore map the error to something more appropriate
|
||||
to the context in which we're using selabel_lookup. */
|
||||
if (errno == ENOENT)
|
||||
errno = ENODATA;
|
||||
goto quit;
|
||||
}
|
||||
if (!(scontext = context_new (scon)))
|
||||
goto quit;
|
||||
|
||||
if (fd != -1)
|
||||
{
|
||||
if (fgetfilecon (fd, &tcon) < 0)
|
||||
goto quit;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (lgetfilecon (path, &tcon) < 0)
|
||||
goto quit;
|
||||
}
|
||||
|
||||
if (!(tcontext = context_new (tcon)))
|
||||
goto quit;
|
||||
|
||||
if (!(contype = context_type_get (scontext)))
|
||||
goto quit;
|
||||
if (context_type_set (tcontext, contype))
|
||||
goto quit;
|
||||
if (!(constr = context_str (tcontext)))
|
||||
goto quit;
|
||||
|
||||
if (fd != -1)
|
||||
rc = fsetfilecon (fd, constr);
|
||||
else
|
||||
rc = lsetfilecon (path, constr);
|
||||
|
||||
quit:;
|
||||
int err = errno;
|
||||
if (fd != -1)
|
||||
close (fd);
|
||||
context_free (scontext);
|
||||
context_free (tcontext);
|
||||
freecon (scon);
|
||||
freecon (tcon);
|
||||
errno = err;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
This function takes three parameters:
|
||||
|
||||
SELABEL_HANDLE for selabel_lookup, or null to preserve.
|
||||
|
||||
PATH of an existing file system object.
|
||||
|
||||
A RECURSE boolean which if the file system object is a directory, will
|
||||
call restorecon_private on every file system object in the directory.
|
||||
|
||||
Return false on failure. errno will be set appropriately.
|
||||
*/
|
||||
bool
|
||||
restorecon (struct selabel_handle *selabel_handle,
|
||||
char const *path, bool recurse)
|
||||
{
|
||||
char *newpath = NULL;
|
||||
|
||||
if (! IS_ABSOLUTE_FILE_NAME (path))
|
||||
{
|
||||
/* Generate absolute name as required by subsequent selabel_lookup.
|
||||
When RECURSE, this also generates absolute names in the
|
||||
fts entries, which may be quicker to process in any case. */
|
||||
newpath = canonicalize_filename_mode (path, CAN_MISSING);
|
||||
if (! newpath)
|
||||
return false;
|
||||
path = newpath;
|
||||
}
|
||||
|
||||
if (! recurse)
|
||||
{
|
||||
bool ok = restorecon_private (selabel_handle, path) != -1;
|
||||
int err = errno;
|
||||
free (newpath);
|
||||
errno = err;
|
||||
return ok;
|
||||
}
|
||||
|
||||
char const *ftspath[2] = { path, NULL };
|
||||
FTS *fts = xfts_open ((char *const *) ftspath, FTS_PHYSICAL, NULL);
|
||||
|
||||
int err = 0;
|
||||
for (FTSENT *ent; (ent = fts_read (fts)); )
|
||||
if (restorecon_private (selabel_handle, fts->fts_path) < 0)
|
||||
err = errno;
|
||||
|
||||
if (errno != 0)
|
||||
err = errno;
|
||||
|
||||
if (fts_close (fts) != 0)
|
||||
err = errno;
|
||||
|
||||
free (newpath);
|
||||
return !err;
|
||||
}
|
||||
#endif
|
||||
55
src/selinux.h
Normal file
55
src/selinux.h
Normal file
@@ -0,0 +1,55 @@
|
||||
/* selinux - core functions for maintaining SELinux labeling
|
||||
Copyright (C) 2012-2022 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 3 of the License, 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, see <https://www.gnu.org/licenses/>. */
|
||||
|
||||
/* Written by Daniel Walsh <dwalsh@redhat.com> */
|
||||
|
||||
#ifndef COREUTILS_SELINUX_H
|
||||
# define COREUTILS_SELINUX_H
|
||||
|
||||
struct selabel_handle;
|
||||
|
||||
/* Return true if ERR corresponds to an unsupported request,
|
||||
or if there is no context or it's inaccessible. */
|
||||
static inline bool
|
||||
ignorable_ctx_err (int err)
|
||||
{
|
||||
return err == ENOTSUP || err == ENODATA;
|
||||
}
|
||||
|
||||
# if HAVE_SELINUX_LABEL_H
|
||||
|
||||
extern bool
|
||||
restorecon (struct selabel_handle *selabel_handle,
|
||||
char const *path, bool recurse);
|
||||
extern int
|
||||
defaultcon (struct selabel_handle *selabel_handle,
|
||||
char const *path, mode_t mode);
|
||||
|
||||
# else
|
||||
|
||||
static inline bool
|
||||
restorecon (struct selabel_handle *selabel_handle,
|
||||
char const *path, bool recurse)
|
||||
{ errno = ENOTSUP; return false; }
|
||||
|
||||
static inline int
|
||||
defaultcon (struct selabel_handle *selabel_handle,
|
||||
char const *path, mode_t mode)
|
||||
{ errno = ENOTSUP; return -1; }
|
||||
|
||||
# endif
|
||||
|
||||
#endif
|
||||
778
src/system.h
Normal file
778
src/system.h
Normal file
@@ -0,0 +1,778 @@
|
||||
/* system-dependent definitions for coreutils
|
||||
Copyright (C) 1989-2022 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 3 of the License, 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, see <https://www.gnu.org/licenses/>. */
|
||||
|
||||
/* Include this file _after_ system headers if possible. */
|
||||
|
||||
#include <attribute.h>
|
||||
|
||||
#include <alloca.h>
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
/* Commonly used file permission combination. */
|
||||
#define MODE_RW_UGO (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)
|
||||
|
||||
#if HAVE_SYS_PARAM_H
|
||||
# include <sys/param.h>
|
||||
#endif
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
#include "pathmax.h"
|
||||
#ifndef PATH_MAX
|
||||
# define PATH_MAX 8192
|
||||
#endif
|
||||
|
||||
#include "configmake.h"
|
||||
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
|
||||
/* Since major is a function on SVR4, we can't use 'ifndef major'. */
|
||||
#if MAJOR_IN_MKDEV
|
||||
# include <sys/mkdev.h>
|
||||
# define HAVE_MAJOR
|
||||
#endif
|
||||
#if MAJOR_IN_SYSMACROS
|
||||
# include <sys/sysmacros.h>
|
||||
# define HAVE_MAJOR
|
||||
#endif
|
||||
#ifdef major /* Might be defined in sys/types.h. */
|
||||
# define HAVE_MAJOR
|
||||
#endif
|
||||
|
||||
#ifndef HAVE_MAJOR
|
||||
# define major(dev) (((dev) >> 8) & 0xff)
|
||||
# define minor(dev) ((dev) & 0xff)
|
||||
# define makedev(maj, min) (((maj) << 8) | (min))
|
||||
#endif
|
||||
#undef HAVE_MAJOR
|
||||
|
||||
#if ! defined makedev && defined mkdev
|
||||
# define makedev(maj, min) mkdev (maj, min)
|
||||
#endif
|
||||
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
/* Some systems don't define this; POSIX mentions it but says it is
|
||||
obsolete. gnulib defines it, but only on native Windows systems,
|
||||
and there only because MSVC 10 does. */
|
||||
#ifndef ENODATA
|
||||
# define ENODATA (-1)
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include "version.h"
|
||||
|
||||
/* Exit statuses for programs like 'env' that exec other programs. */
|
||||
enum
|
||||
{
|
||||
EXIT_TIMEDOUT = 124, /* Time expired before child completed. */
|
||||
EXIT_CANCELED = 125, /* Internal error prior to exec attempt. */
|
||||
EXIT_CANNOT_INVOKE = 126, /* Program located, but not usable. */
|
||||
EXIT_ENOENT = 127 /* Could not find program to exec. */
|
||||
};
|
||||
|
||||
#include "exitfail.h"
|
||||
|
||||
/* Set exit_failure to STATUS if that's not the default already. */
|
||||
static inline void
|
||||
initialize_exit_failure (int status)
|
||||
{
|
||||
if (status != EXIT_FAILURE)
|
||||
exit_failure = status;
|
||||
}
|
||||
|
||||
#include <fcntl.h>
|
||||
#ifdef O_PATH
|
||||
enum { O_PATHSEARCH = O_PATH };
|
||||
#else
|
||||
enum { O_PATHSEARCH = O_SEARCH };
|
||||
#endif
|
||||
|
||||
#include <dirent.h>
|
||||
#ifndef _D_EXACT_NAMLEN
|
||||
# define _D_EXACT_NAMLEN(dp) strlen ((dp)->d_name)
|
||||
#endif
|
||||
|
||||
enum
|
||||
{
|
||||
NOT_AN_INODE_NUMBER = 0
|
||||
};
|
||||
|
||||
#ifdef D_INO_IN_DIRENT
|
||||
# define D_INO(dp) (dp)->d_ino
|
||||
#else
|
||||
/* Some systems don't have inodes, so fake them to avoid lots of ifdefs. */
|
||||
# define D_INO(dp) NOT_AN_INODE_NUMBER
|
||||
#endif
|
||||
|
||||
/* include here for SIZE_MAX. */
|
||||
#include <inttypes.h>
|
||||
|
||||
/* Redirection and wildcarding when done by the utility itself.
|
||||
Generally a noop, but used in particular for OS/2. */
|
||||
#ifndef initialize_main
|
||||
# ifndef __OS2__
|
||||
# define initialize_main(ac, av)
|
||||
# else
|
||||
# define initialize_main(ac, av) \
|
||||
do { _wildcard (ac, av); _response (ac, av); } while (0)
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#include "stat-macros.h"
|
||||
|
||||
#include "timespec.h"
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
/* ISDIGIT differs from isdigit, as follows:
|
||||
- Its arg may be any int or unsigned int; it need not be an unsigned char
|
||||
or EOF.
|
||||
- It's typically faster.
|
||||
POSIX says that only '0' through '9' are digits. Prefer ISDIGIT to
|
||||
isdigit unless it's important to use the locale's definition
|
||||
of 'digit' even when the host does not conform to POSIX. */
|
||||
#define ISDIGIT(c) ((unsigned int) (c) - '0' <= 9)
|
||||
|
||||
/* Convert a possibly-signed character to an unsigned character. This is
|
||||
a bit safer than casting to unsigned char, since it catches some type
|
||||
errors that the cast doesn't. */
|
||||
static inline unsigned char to_uchar (char ch) { return ch; }
|
||||
|
||||
/* '\n' is considered a field separator with --zero-terminated. */
|
||||
static inline bool
|
||||
field_sep (unsigned char ch)
|
||||
{
|
||||
return isblank (ch) || ch == '\n';
|
||||
}
|
||||
|
||||
#include <locale.h>
|
||||
|
||||
/* Take care of NLS matters. */
|
||||
|
||||
#include "gettext.h"
|
||||
#if ! ENABLE_NLS
|
||||
# undef textdomain
|
||||
# define textdomain(Domainname) /* empty */
|
||||
# undef bindtextdomain
|
||||
# define bindtextdomain(Domainname, Dirname) /* empty */
|
||||
#endif
|
||||
|
||||
#define _(msgid) gettext (msgid)
|
||||
#define N_(msgid) msgid
|
||||
|
||||
/* Return a value that pluralizes the same way that N does, in all
|
||||
languages we know of. */
|
||||
static inline unsigned long int
|
||||
select_plural (uintmax_t n)
|
||||
{
|
||||
/* Reduce by a power of ten, but keep it away from zero. The
|
||||
gettext manual says 1000000 should be safe. */
|
||||
enum { PLURAL_REDUCER = 1000000 };
|
||||
return (n <= ULONG_MAX ? n : n % PLURAL_REDUCER + PLURAL_REDUCER);
|
||||
}
|
||||
|
||||
#define STREQ(a, b) (strcmp (a, b) == 0)
|
||||
#define STREQ_LEN(a, b, n) (strncmp (a, b, n) == 0)
|
||||
#define STRPREFIX(a, b) (strncmp (a, b, strlen (b)) == 0)
|
||||
|
||||
/* Just like strncmp, but the second argument must be a literal string
|
||||
and you don't specify the length; that comes from the literal. */
|
||||
#define STRNCMP_LIT(s, lit) strncmp (s, "" lit "", sizeof (lit) - 1)
|
||||
|
||||
#if !HAVE_DECL_GETLOGIN
|
||||
char *getlogin (void);
|
||||
#endif
|
||||
|
||||
#if !HAVE_DECL_TTYNAME
|
||||
char *ttyname (int);
|
||||
#endif
|
||||
|
||||
#if !HAVE_DECL_GETEUID
|
||||
uid_t geteuid (void);
|
||||
#endif
|
||||
|
||||
#if !HAVE_DECL_GETPWUID
|
||||
struct passwd *getpwuid (uid_t);
|
||||
#endif
|
||||
|
||||
#if !HAVE_DECL_GETGRGID
|
||||
struct group *getgrgid (gid_t);
|
||||
#endif
|
||||
|
||||
/* Interix has replacements for getgr{gid,nam,ent}, that don't
|
||||
query the domain controller for group members when not required.
|
||||
This speeds up the calls tremendously (<1 ms vs. >3 s). */
|
||||
/* To protect any system that could provide _nomembers functions
|
||||
other than interix, check for HAVE_SETGROUPS, as interix is
|
||||
one of the very few (the only?) platform that lacks it */
|
||||
#if ! HAVE_SETGROUPS
|
||||
# if HAVE_GETGRGID_NOMEMBERS
|
||||
# define getgrgid(gid) getgrgid_nomembers(gid)
|
||||
# endif
|
||||
# if HAVE_GETGRNAM_NOMEMBERS
|
||||
# define getgrnam(nam) getgrnam_nomembers(nam)
|
||||
# endif
|
||||
# if HAVE_GETGRENT_NOMEMBERS
|
||||
# define getgrent() getgrent_nomembers()
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if !HAVE_DECL_GETUID
|
||||
uid_t getuid (void);
|
||||
#endif
|
||||
|
||||
#include "xalloc.h"
|
||||
#include "verify.h"
|
||||
|
||||
/* This is simply a shorthand for the common case in which
|
||||
the third argument to x2nrealloc would be 'sizeof *(P)'.
|
||||
Ensure that sizeof *(P) is *not* 1. In that case, it'd be
|
||||
better to use X2REALLOC, although not strictly necessary. */
|
||||
#define X2NREALLOC(P, PN) verify_expr (sizeof *(P) != 1, \
|
||||
x2nrealloc (P, PN, sizeof *(P)))
|
||||
|
||||
/* Using x2realloc (when appropriate) usually makes your code more
|
||||
readable than using x2nrealloc, but it also makes it so your
|
||||
code will malfunction if sizeof *(P) ever becomes 2 or greater.
|
||||
So use this macro instead of using x2realloc directly. */
|
||||
#define X2REALLOC(P, PN) verify_expr (sizeof *(P) == 1, \
|
||||
x2realloc (P, PN))
|
||||
|
||||
#include "unlocked-io.h"
|
||||
#include "same-inode.h"
|
||||
|
||||
#include "dirname.h"
|
||||
#include "openat.h"
|
||||
|
||||
static inline bool
|
||||
dot_or_dotdot (char const *file_name)
|
||||
{
|
||||
if (file_name[0] == '.')
|
||||
{
|
||||
char sep = file_name[(file_name[1] == '.') + 1];
|
||||
return (! sep || ISSLASH (sep));
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
/* A wrapper for readdir so that callers don't see entries for '.' or '..'. */
|
||||
static inline struct dirent const *
|
||||
readdir_ignoring_dot_and_dotdot (DIR *dirp)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
struct dirent const *dp = readdir (dirp);
|
||||
if (dp == NULL || ! dot_or_dotdot (dp->d_name))
|
||||
return dp;
|
||||
}
|
||||
}
|
||||
|
||||
/* Return true if DIR is determined to be an empty directory.
|
||||
Return false with ERRNO==0 if DIR is a non empty directory.
|
||||
Return false if not able to determine if directory empty. */
|
||||
static inline bool
|
||||
is_empty_dir (int fd_cwd, char const *dir)
|
||||
{
|
||||
DIR *dirp;
|
||||
struct dirent const *dp;
|
||||
int saved_errno;
|
||||
int fd = openat (fd_cwd, dir,
|
||||
(O_RDONLY | O_DIRECTORY
|
||||
| O_NOCTTY | O_NOFOLLOW | O_NONBLOCK));
|
||||
|
||||
if (fd < 0)
|
||||
return false;
|
||||
|
||||
dirp = fdopendir (fd);
|
||||
if (dirp == NULL)
|
||||
{
|
||||
close (fd);
|
||||
return false;
|
||||
}
|
||||
|
||||
errno = 0;
|
||||
dp = readdir_ignoring_dot_and_dotdot (dirp);
|
||||
saved_errno = errno;
|
||||
closedir (dirp);
|
||||
errno = saved_errno;
|
||||
if (dp != NULL)
|
||||
return false;
|
||||
return saved_errno == 0 ? true : false;
|
||||
}
|
||||
|
||||
/* Factor out some of the common --help and --version processing code. */
|
||||
|
||||
/* These enum values cannot possibly conflict with the option values
|
||||
ordinarily used by commands, including CHAR_MAX + 1, etc. Avoid
|
||||
CHAR_MIN - 1, as it may equal -1, the getopt end-of-options value. */
|
||||
enum
|
||||
{
|
||||
GETOPT_HELP_CHAR = (CHAR_MIN - 2),
|
||||
GETOPT_VERSION_CHAR = (CHAR_MIN - 3)
|
||||
};
|
||||
|
||||
#define GETOPT_HELP_OPTION_DECL \
|
||||
"help", no_argument, NULL, GETOPT_HELP_CHAR
|
||||
#define GETOPT_VERSION_OPTION_DECL \
|
||||
"version", no_argument, NULL, GETOPT_VERSION_CHAR
|
||||
#define GETOPT_SELINUX_CONTEXT_OPTION_DECL \
|
||||
"context", optional_argument, NULL, 'Z'
|
||||
|
||||
#define case_GETOPT_HELP_CHAR \
|
||||
case GETOPT_HELP_CHAR: \
|
||||
usage (EXIT_SUCCESS); \
|
||||
break;
|
||||
|
||||
/* Program_name must be a literal string.
|
||||
Usually it is just PROGRAM_NAME. */
|
||||
#define USAGE_BUILTIN_WARNING \
|
||||
_("\n" \
|
||||
"NOTE: your shell may have its own version of %s, which usually supersedes\n" \
|
||||
"the version described here. Please refer to your shell's documentation\n" \
|
||||
"for details about the options it supports.\n")
|
||||
|
||||
#define HELP_OPTION_DESCRIPTION \
|
||||
_(" --help display this help and exit\n")
|
||||
#define VERSION_OPTION_DESCRIPTION \
|
||||
_(" --version output version information and exit\n")
|
||||
|
||||
#include "closein.h"
|
||||
#include "closeout.h"
|
||||
|
||||
#define emit_bug_reporting_address unused__emit_bug_reporting_address
|
||||
#include "version-etc.h"
|
||||
#undef emit_bug_reporting_address
|
||||
|
||||
#include "propername.h"
|
||||
/* Define away proper_name (leaving proper_name_utf8, which affects far
|
||||
fewer programs), since it's not worth the cost of adding ~17KB to
|
||||
the x86_64 text size of every single program. This avoids a 40%
|
||||
(almost ~2MB) increase in the file system space utilization for the set
|
||||
of the 100 binaries. */
|
||||
#define proper_name(x) (x)
|
||||
|
||||
#include "progname.h"
|
||||
|
||||
#define case_GETOPT_VERSION_CHAR(Program_name, Authors) \
|
||||
case GETOPT_VERSION_CHAR: \
|
||||
version_etc (stdout, Program_name, PACKAGE_NAME, Version, Authors, \
|
||||
(char *) NULL); \
|
||||
exit (EXIT_SUCCESS); \
|
||||
break;
|
||||
|
||||
#include "minmax.h"
|
||||
#include "intprops.h"
|
||||
|
||||
#ifndef SSIZE_MAX
|
||||
# define SSIZE_MAX TYPE_MAXIMUM (ssize_t)
|
||||
#endif
|
||||
|
||||
#ifndef OFF_T_MIN
|
||||
# define OFF_T_MIN TYPE_MINIMUM (off_t)
|
||||
#endif
|
||||
|
||||
#ifndef OFF_T_MAX
|
||||
# define OFF_T_MAX TYPE_MAXIMUM (off_t)
|
||||
#endif
|
||||
|
||||
#ifndef UID_T_MAX
|
||||
# define UID_T_MAX TYPE_MAXIMUM (uid_t)
|
||||
#endif
|
||||
|
||||
#ifndef GID_T_MAX
|
||||
# define GID_T_MAX TYPE_MAXIMUM (gid_t)
|
||||
#endif
|
||||
|
||||
#ifndef PID_T_MAX
|
||||
# define PID_T_MAX TYPE_MAXIMUM (pid_t)
|
||||
#endif
|
||||
|
||||
/* Use this to suppress gcc warnings. */
|
||||
#ifdef lint
|
||||
# define IF_LINT(Code) Code
|
||||
#else
|
||||
# define IF_LINT(Code) /* empty */
|
||||
#endif
|
||||
|
||||
/* main_exit should be called only from the main function. It is
|
||||
equivalent to 'exit'. When checking for lint it calls 'exit', to
|
||||
pacify gcc -fsanitize=lint which would otherwise have false alarms
|
||||
for pointers in the main function's activation record. Otherwise
|
||||
it simply returns from 'main'; this used to be what gcc's static
|
||||
checking preferred and may yet be again. */
|
||||
#ifdef lint
|
||||
# define main_exit(status) exit (status)
|
||||
#else
|
||||
# define main_exit(status) return status
|
||||
#endif
|
||||
|
||||
#ifdef __GNUC__
|
||||
# define LIKELY(cond) __builtin_expect ((cond), 1)
|
||||
# define UNLIKELY(cond) __builtin_expect ((cond), 0)
|
||||
#else
|
||||
# define LIKELY(cond) (cond)
|
||||
# define UNLIKELY(cond) (cond)
|
||||
#endif
|
||||
|
||||
|
||||
#if defined strdupa
|
||||
# define ASSIGN_STRDUPA(DEST, S) \
|
||||
do { DEST = strdupa (S); } while (0)
|
||||
#else
|
||||
# define ASSIGN_STRDUPA(DEST, S) \
|
||||
do \
|
||||
{ \
|
||||
char const *s_ = (S); \
|
||||
size_t len_ = strlen (s_) + 1; \
|
||||
char *tmp_dest_ = alloca (len_); \
|
||||
DEST = memcpy (tmp_dest_, s_, len_); \
|
||||
} \
|
||||
while (0)
|
||||
#endif
|
||||
|
||||
#if ! HAVE_SYNC
|
||||
# define sync() /* empty */
|
||||
#endif
|
||||
|
||||
/* Compute the greatest common divisor of U and V using Euclid's
|
||||
algorithm. U and V must be nonzero. */
|
||||
|
||||
ATTRIBUTE_CONST
|
||||
static inline size_t
|
||||
gcd (size_t u, size_t v)
|
||||
{
|
||||
do
|
||||
{
|
||||
size_t t = u % v;
|
||||
u = v;
|
||||
v = t;
|
||||
}
|
||||
while (v);
|
||||
|
||||
return u;
|
||||
}
|
||||
|
||||
/* Compute the least common multiple of U and V. U and V must be
|
||||
nonzero. There is no overflow checking, so callers should not
|
||||
specify outlandish sizes. */
|
||||
|
||||
ATTRIBUTE_CONST
|
||||
static inline size_t
|
||||
lcm (size_t u, size_t v)
|
||||
{
|
||||
return u * (v / gcd (u, v));
|
||||
}
|
||||
|
||||
/* Return PTR, aligned upward to the next multiple of ALIGNMENT.
|
||||
ALIGNMENT must be nonzero. The caller must arrange for ((char *)
|
||||
PTR) through ((char *) PTR + ALIGNMENT - 1) to be addressable
|
||||
locations. */
|
||||
|
||||
static inline void *
|
||||
ptr_align (void const *ptr, size_t alignment)
|
||||
{
|
||||
char const *p0 = ptr;
|
||||
char const *p1 = p0 + alignment - 1;
|
||||
return (void *) (p1 - (size_t) p1 % alignment);
|
||||
}
|
||||
|
||||
/* Return whether the buffer consists entirely of NULs.
|
||||
Based on memeqzero in CCAN by Rusty Russell under CC0 (Public domain). */
|
||||
|
||||
ATTRIBUTE_PURE
|
||||
static inline bool
|
||||
is_nul (void const *buf, size_t length)
|
||||
{
|
||||
const unsigned char *p = buf;
|
||||
/* Using possibly unaligned access for the first 16 bytes
|
||||
saves about 30-40 cycles, though it is strictly undefined behavior
|
||||
and so would need __attribute__ ((__no_sanitize_undefined__))
|
||||
to avoid -fsanitize=undefined warnings.
|
||||
Considering coreutils is mainly concerned with relatively
|
||||
large buffers, we'll just use the defined behavior. */
|
||||
#if 0 && (_STRING_ARCH_unaligned || _STRING_INLINE_unaligned)
|
||||
unsigned long word;
|
||||
#else
|
||||
unsigned char word;
|
||||
#endif
|
||||
|
||||
if (! length)
|
||||
return true;
|
||||
|
||||
/* Check len bytes not aligned on a word. */
|
||||
while (UNLIKELY (length & (sizeof word - 1)))
|
||||
{
|
||||
if (*p)
|
||||
return false;
|
||||
p++;
|
||||
length--;
|
||||
if (! length)
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Check up to 16 bytes a word at a time. */
|
||||
for (;;)
|
||||
{
|
||||
memcpy (&word, p, sizeof word);
|
||||
if (word)
|
||||
return false;
|
||||
p += sizeof word;
|
||||
length -= sizeof word;
|
||||
if (! length)
|
||||
return true;
|
||||
if (UNLIKELY (length & 15) == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
/* Now we know first 16 bytes are NUL, memcmp with self. */
|
||||
return memcmp (buf, p, length) == 0;
|
||||
}
|
||||
|
||||
/* If 10*Accum + Digit_val is larger than the maximum value for Type,
|
||||
then don't update Accum and return false to indicate it would
|
||||
overflow. Otherwise, set Accum to that new value and return true.
|
||||
Verify at compile-time that Type is Accum's type, and that Type is
|
||||
unsigned. Accum must be an object, so that we can take its
|
||||
address. Accum and Digit_val may be evaluated multiple times.
|
||||
|
||||
The "Added check" below is not strictly required, but it causes GCC
|
||||
to return a nonzero exit status instead of merely a warning
|
||||
diagnostic, and that is more useful. */
|
||||
|
||||
#define DECIMAL_DIGIT_ACCUMULATE(Accum, Digit_val, Type) \
|
||||
( \
|
||||
(void) (&(Accum) == (Type *) NULL), /* The type matches. */ \
|
||||
verify_expr (! TYPE_SIGNED (Type), /* The type is unsigned. */ \
|
||||
(((Type) -1 / 10 < (Accum) \
|
||||
|| (Type) ((Accum) * 10 + (Digit_val)) < (Accum)) \
|
||||
? false \
|
||||
: (((Accum) = (Accum) * 10 + (Digit_val)), true))) \
|
||||
)
|
||||
|
||||
static inline void
|
||||
emit_stdin_note (void)
|
||||
{
|
||||
fputs (_("\n\
|
||||
With no FILE, or when FILE is -, read standard input.\n\
|
||||
"), stdout);
|
||||
}
|
||||
static inline void
|
||||
emit_mandatory_arg_note (void)
|
||||
{
|
||||
fputs (_("\n\
|
||||
Mandatory arguments to long options are mandatory for short options too.\n\
|
||||
"), stdout);
|
||||
}
|
||||
|
||||
static inline void
|
||||
emit_size_note (void)
|
||||
{
|
||||
fputs (_("\n\
|
||||
The SIZE argument is an integer and optional unit (example: 10K is 10*1024).\n\
|
||||
Units are K,M,G,T,P,E,Z,Y (powers of 1024) or KB,MB,... (powers of 1000).\n\
|
||||
Binary prefixes can be used, too: KiB=K, MiB=M, and so on.\n\
|
||||
"), stdout);
|
||||
}
|
||||
|
||||
static inline void
|
||||
emit_blocksize_note (char const *program)
|
||||
{
|
||||
printf (_("\n\
|
||||
Display values are in units of the first available SIZE from --block-size,\n\
|
||||
and the %s_BLOCK_SIZE, BLOCK_SIZE and BLOCKSIZE environment variables.\n\
|
||||
Otherwise, units default to 1024 bytes (or 512 if POSIXLY_CORRECT is set).\n\
|
||||
"), program);
|
||||
}
|
||||
|
||||
static inline void
|
||||
emit_backup_suffix_note (void)
|
||||
{
|
||||
fputs (_("\
|
||||
\n\
|
||||
The backup suffix is '~', unless set with --suffix or SIMPLE_BACKUP_SUFFIX.\n\
|
||||
The version control method may be selected via the --backup option or through\n\
|
||||
the VERSION_CONTROL environment variable. Here are the values:\n\
|
||||
\n\
|
||||
"), stdout);
|
||||
fputs (_("\
|
||||
none, off never make backups (even if --backup is given)\n\
|
||||
numbered, t make numbered backups\n\
|
||||
existing, nil numbered if numbered backups exist, simple otherwise\n\
|
||||
simple, never always make simple backups\n\
|
||||
"), stdout);
|
||||
}
|
||||
|
||||
static inline void
|
||||
emit_ancillary_info (char const *program)
|
||||
{
|
||||
struct infomap { char const *program; char const *node; } const infomap[] = {
|
||||
{ "[", "test invocation" },
|
||||
{ "coreutils", "Multi-call invocation" },
|
||||
{ "sha224sum", "sha2 utilities" },
|
||||
{ "sha256sum", "sha2 utilities" },
|
||||
{ "sha384sum", "sha2 utilities" },
|
||||
{ "sha512sum", "sha2 utilities" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
char const *node = program;
|
||||
struct infomap const *map_prog = infomap;
|
||||
|
||||
while (map_prog->program && ! STREQ (program, map_prog->program))
|
||||
map_prog++;
|
||||
|
||||
if (map_prog->node)
|
||||
node = map_prog->node;
|
||||
|
||||
printf (_("\n%s online help: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
|
||||
|
||||
/* Don't output this redundant message for English locales.
|
||||
Note we still output for 'C' so that it gets included in the man page. */
|
||||
char const *lc_messages = setlocale (LC_MESSAGES, NULL);
|
||||
if (lc_messages && STRNCMP_LIT (lc_messages, "en_"))
|
||||
{
|
||||
/* TRANSLATORS: Replace LANG_CODE in this URL with your language code
|
||||
<https://translationproject.org/team/LANG_CODE.html> to form one of
|
||||
the URLs at https://translationproject.org/team/. Otherwise, replace
|
||||
the entire URL with your translation team's email address. */
|
||||
fputs (_("Report any translation bugs to "
|
||||
"<https://translationproject.org/team/>\n"), stdout);
|
||||
}
|
||||
/* .htaccess on the coreutils web site maps programs to the appropriate page,
|
||||
however we explicitly handle "[" -> "test" here as the "[" is not
|
||||
recognized as part of a URL by default in terminals. */
|
||||
char const *url_program = STREQ (program, "[") ? "test" : program;
|
||||
printf (_("Full documentation <%s%s>\n"),
|
||||
PACKAGE_URL, url_program);
|
||||
printf (_("or available locally via: info '(coreutils) %s%s'\n"),
|
||||
node, node == program ? " invocation" : "");
|
||||
}
|
||||
|
||||
/* Use a macro rather than an inline function, as this references
|
||||
the global program_name, which causes dynamic linking issues
|
||||
in libstdbuf.so on some systems where unused functions
|
||||
are not removed by the linker. */
|
||||
#define emit_try_help() \
|
||||
do \
|
||||
{ \
|
||||
fprintf (stderr, _("Try '%s --help' for more information.\n"), \
|
||||
program_name); \
|
||||
} \
|
||||
while (0)
|
||||
|
||||
#include "inttostr.h"
|
||||
|
||||
static inline char *
|
||||
timetostr (time_t t, char *buf)
|
||||
{
|
||||
return (TYPE_SIGNED (time_t)
|
||||
? imaxtostr (t, buf)
|
||||
: umaxtostr (t, buf));
|
||||
}
|
||||
|
||||
static inline char *
|
||||
bad_cast (char const *s)
|
||||
{
|
||||
return (char *) s;
|
||||
}
|
||||
|
||||
/* Return a boolean indicating whether SB->st_size is defined. */
|
||||
static inline bool
|
||||
usable_st_size (struct stat const *sb)
|
||||
{
|
||||
return (S_ISREG (sb->st_mode) || S_ISLNK (sb->st_mode)
|
||||
|| S_TYPEISSHM (sb) || S_TYPEISTMO (sb));
|
||||
}
|
||||
|
||||
_Noreturn void usage (int status);
|
||||
|
||||
/* Like error(0, 0, ...), but without an implicit newline.
|
||||
Also a noop unless the global DEV_DEBUG is set. */
|
||||
#define devmsg(...) \
|
||||
do \
|
||||
{ \
|
||||
if (dev_debug) \
|
||||
fprintf (stderr, __VA_ARGS__); \
|
||||
} \
|
||||
while (0)
|
||||
|
||||
#define emit_cycle_warning(file_name) \
|
||||
do \
|
||||
{ \
|
||||
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"), \
|
||||
quotef (file_name)); \
|
||||
} \
|
||||
while (0)
|
||||
|
||||
/* Like stpncpy, but do ensure that the result is NUL-terminated,
|
||||
and do not NUL-pad out to LEN. I.e., when strnlen (src, len) == len,
|
||||
this function writes a NUL byte into dest[len]. Thus, the length
|
||||
of the destination buffer must be at least LEN + 1.
|
||||
The DEST and SRC buffers must not overlap. */
|
||||
static inline char *
|
||||
stzncpy (char *restrict dest, char const *restrict src, size_t len)
|
||||
{
|
||||
char const *src_end = src + len;
|
||||
while (src < src_end && *src)
|
||||
*dest++ = *src++;
|
||||
*dest = 0;
|
||||
return dest;
|
||||
}
|
||||
|
||||
#ifndef ARRAY_CARDINALITY
|
||||
# define ARRAY_CARDINALITY(Array) (sizeof (Array) / sizeof *(Array))
|
||||
#endif
|
||||
|
||||
/* Return true if ERR is ENOTSUP or EOPNOTSUPP, otherwise false.
|
||||
This wrapper function avoids the redundant 'or'd comparison on
|
||||
systems like Linux for which they have the same value. It also
|
||||
avoids the gcc warning to that effect. */
|
||||
static inline bool
|
||||
is_ENOTSUP (int err)
|
||||
{
|
||||
return err == EOPNOTSUPP || (ENOTSUP != EOPNOTSUPP && err == ENOTSUP);
|
||||
}
|
||||
|
||||
|
||||
/* How coreutils quotes filenames, to minimize use of outer quotes,
|
||||
but also provide better support for copy and paste when used. */
|
||||
#include "quotearg.h"
|
||||
|
||||
/* Use these to shell quote only when necessary,
|
||||
when the quoted item is already delimited with colons. */
|
||||
#define quotef(arg) \
|
||||
quotearg_n_style_colon (0, shell_escape_quoting_style, arg)
|
||||
#define quotef_n(n, arg) \
|
||||
quotearg_n_style_colon (n, shell_escape_quoting_style, arg)
|
||||
|
||||
/* Use these when there are spaces around the file name,
|
||||
in the error message. */
|
||||
#define quoteaf(arg) \
|
||||
quotearg_style (shell_escape_always_quoting_style, arg)
|
||||
#define quoteaf_n(n, arg) \
|
||||
quotearg_n_style (n, shell_escape_always_quoting_style, arg)
|
||||
2
src/version.c
Normal file
2
src/version.c
Normal file
@@ -0,0 +1,2 @@
|
||||
#include <config.h>
|
||||
char const *Version = "9.1";
|
||||
1
src/version.h
Normal file
1
src/version.h
Normal file
@@ -0,0 +1 @@
|
||||
extern char const *Version;
|
||||
Reference in New Issue
Block a user