coreutils 9.1 version

This commit is contained in:
2022-07-28 14:16:50 +08:00
parent 8d7bbe5417
commit d03e779a72
880 changed files with 39483 additions and 121331 deletions

3198
src/copy.c Normal file

File diff suppressed because it is too large Load Diff

316
src/copy.h Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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)
{
/* Dont 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
View 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,
&copy_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
View 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
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,2 @@
#include <config.h>
char const *Version = "9.1";

1
src/version.h Normal file
View File

@@ -0,0 +1 @@
extern char const *Version;