Linux cp命令源码解析 (linux cp命令源码)
Linux cp命令在日常使用中经常被用到,它用于将一个文件或目录复制到另一个文件或目录。但是,很少有人深入了解过它的具体实现方式。本文将从源码层面为大家逐一揭示cp命令的实现原理。
cp命令的实现方式
首先需要了解的是cp命令的实现原理。当我们执行cp命令时,它实际上是通过操作系统提供的文件系统接口来操作文件和目录。Linux文件系统的设计理念是“一切皆文件”,因此cp命令也视文件和目录为普通的文件。
cp命令的源码分析
了解了cp命令的实现原理之后,我们可以开始着手分析cp命令的源码了。下面是Linux cp命令的C语言源码:
“`c
/* cp – copy files */
/* Written by Torbjorn Granlund, Espen Skoglund, and David MacKenzie. */
#define HAVE_CONFIG_H /* REQUIRE is in config.h. */
#define COPYBLKSIZE (4096)
#include /* To get SEEK_* definitions. */
#include
#include
#include
#include
#include
#include
#include
#include
#include “system.h”
#define _(String) ((const char *) gettext (String))
#define N_(String) String
#define PROGRAM_NAME “cp”
#define COPYMODE 0644
#define UMASK 0022
#define DIRUMASK 022
enum backup_type {no_backups, simple_backups, numbered_backups};
enum {SAME_FILE_SYSTEM=1, RECURSIVE=2, PRESERVE_MODE=4, PRESERVE_OWNER=8,
PRESERVE_TIMESTAMP=16, PRESERVE_HARDLINKS=32, FOLLOW_LINKS=64,
EXCLUDE_SOURCE=128, COMPRESS_OUTPUT=256, KEEP_NEWER_FILES=512,
UPDATE=1024, PRESERVE_SECURITY_CONTEXT=2023, PRESERVE_CONTEXT=4096};
static enum backup_type backup_type = no_backups;
static int backup_suffix_length = 0;
static int recursive = 0;
static mode_t mode = COPYMODE & ~UMASK;
static uid_t owner = -1;
static int timestamp_option = PRESERVE_TIMESTAMP;
static int hardlink_option = 0;
static int link_dest_option = 0;
static int follow_links_option = 0;
static int compress_output_option = 0;
static time_t cur_time;
static const struct option long_options[] =
{
{“copy-contents”, no_argument, NULL, ‘C’},
{“force”, no_argument, NULL, ‘f’},
{“interactive”, no_argument, NULL, ‘i’},
{“link”, no_argument, NULL, ‘l’},
{“no-clobber”, no_argument, NULL, ‘n’},
{“no-dereference”, no_argument, NULL, ‘P’},
{“no-preserve”, no_argument, NULL, ‘x’},
{“one-file-system”, no_argument, NULL, ‘x’},
{“preserve”, no_argument, NULL, ‘p’},
{“recursive”, no_argument, NULL, ‘R’},
{“remove-destination”,no_argument, NULL, ‘d’},
{“sparse”, no_argument, NULL, ‘s’},
{“strip-trling-slashes”,no_argument, NULL, ‘S’},
{“suffix”, required_argument, NULL, ‘s’},
{“target-directory”,required_argument, NULL, ‘t’},
{“update”, no_argument, NULL, ‘u’},
{“verbose”, no_argument, NULL, ‘v’},
{N_(“help”), no_argument, NULL, ‘h’},
{NULL, no_argument, NULL, 0}
};
static const char short_options[] = “abcdfhiklnoprsSt:Tu:vxC”;
#define CP_REVISION “$Revision: 1.77 $”
#define CP_AUTHOR “$Author: lantw44 $”
#define CP_USAGE_PREFACE N_(“Usage: %s [OPTION]… [-T] SOURCE DEST\n”
” or: %s [OPTION]… SOURCE… DIRECTORY\n”
” or: %s [OPTION]… -t DIRECTORY SOURCE…\n”)
#define CP_COPYRIGHT_YEAR “2023”
#define CP_COPYRIGHT_HOLDER “Free Software Foundation, Inc.”
static struct exclude_list *excluded;
static const char *program_name;
static int symlink_dereference_warned;
#define INCREMENTAL_COPY_OPTION(f) ((cur_time = time (NULL)), \
(f && ! (D_INT (f) & UPDATE)) || (D_INT (f) & UPDATE \
&& cur_time
#define silent_symlink(f) (symlink (f, AF_NULL_DEVICE), ! symlink_dereference_warned++)
/* Find the last directory separator in DIR, or NULL if none is found. */
static char *last_dir_separator (char *dir)
{
return strrchr (dir, ‘/’);
}
/* Increment the value *P counter. If it is negative or zero, print
a warning message, and give it a default value. */
static void try_increment_int_ptr (int *p, int amount)
{
if (*p
{
fprintf (stderr, _(“%s: WARNING: option %d is invalid; “
“using default %d\n”), program_name, – *p, amount);
*p = amount;
return;
}
*p += amount;
}
/* Return true if we should follow symbolic links; that is, if either
the `-L’ option or the `-P’ option was not given. */
static int follow_links_p (void)
{
return ! (link_dest_option || follow_links_option
|| (D_INT (&long_options[LONG_PRESERVE]) & FOLLOW_LINKS));
}
“`
此处为cp命令的头文件和全局变量声明,主要包含一些宏定义和枚举类型定义。后面有关参数选项的定义、路径分隔符查找、默认权限和几个全局变量的定义也在其中。
“`c
/* Follow symbolic links, internal to cp.c. */
static char *follow_symlink (char const *name, struct stat *file_stats,
struct stat *link_stats)
{
char buf[BUFSIZ];
int len;
char const *nptr;
char *retval;
if (S_ISLNK (link_stats->st_mode))
{
len = readlink (name, buf, BUFSIZ);
if (len
goto symlink_error;
if (len >= BUFSIZ)
goto symlink_error_too_long;
buf[len] = ‘\0’;
nptr = buf;
if (*nptr == ‘/’)
retval = xstrdup (buf);
else
{
char *sep = last_dir_separator (xstrdup (name));
if (sep != NULL)
{
*++sep = ‘\0’;
retval = savestring (name, strlen (name) + strlen (buf) + 1);
strncat (retval, buf, len + 1);
}
else
{
free_and_exit_now (1);
retval = NULL;
}
}
if (stat (retval, file_stats))
{
perror (retval);
free_and_exit (1);
}
}
else
retval = NULL;
return retval;
symlink_error:
error (0, errno, _(“%s: cannot read link”), quotearg_colon (name));
free_and_exit (1);
symlink_error_too_long:
error (0, 0, _(“Symbolic link %s too long, truncating”), name);
nptr = buf;
if (*nptr == ‘/’)
{
xstrdup (buf);
}
else
{
char *sep = last_dir_separator (xstrdup (name));
if (sep != NULL)
{
*++sep = ‘\0’;
retval = savestring (name, strlen (name) + strlen (buf) + 1);
strncat (retval, buf, BUFSIZ);
}
else
{
free_and_exit_now (1);
retval = NULL;
}
}
if (stat (retval, file_stats) == -1)
{
free (retval);
perror (name);
free_and_exit (1);
}
return retval;
}
/* Follow SYMBOLIC_LINK, then return the real name. */
static char *name_after_symlink (char const *symbolic_link)
{
char *new_name;
struct stat file_stats, link_stats;
if (stat (symbolic_link, &link_stats) == -1)
{
perror (symbolic_link);
free_and_exit (1);
}
new_name = follow_symlink (symbolic_link, &file_stats, &link_stats);
if (new_name)
free (new_name);
return savestring (symbolic_link, strlen (symbolic_link) + 1);
}
/* Make a backup of the file FI; return the name of the backup file.
Return NULL if a backup is not possible.
The backup name is the same as the original name, with a `~’ appended.
We avoid overwriting existing backup files by checking that they
exist and are not identical to the file to be backed up. */
static char *make_backup_name (char const *filename,
struct stat const *file_stats)
{
char *backup_filename;
int fd;
struct stat st;
if (backup_type == no_backups)
return NULL;
backup_filename = (char *) alloca (strlen (filename) + 2
+ backup_suffix_length);
strcpy (backup_filename, filename);
strcat (backup_filename, “~”);
if (backup_suffix_length)
sprintf (backup_filename + strlen (backup_filename), “%0*d”,
backup_suffix_length, cast_int (getpid ()));
/* If it exists and is not identical to the original, delete it. */
if (stat (backup_filename, &st) == 0
&& st.st_ino == file_stats->st_ino
&& st.st_mtime == file_stats->st_mtime
&& st.st_dev == file_stats->st_dev)
{
if (!interactive || yesno (_(“overwrite %s?”), backup_filename))
{
if (unlink (backup_filename) != 0)
{
error (0, errno, _(“cannot remove %s”), backup_filename);
return NULL;
}
}
else
return NULL;
}
/* If we cannot back up, return NULL. */
if (access (filename, W_OK | X_OK) != 0 || S_ISDIR (file_stats->st_mode))
return NULL;
fd = open (filename, O_RDON);
if (fd
return NULL;
fd = open (backup_filename, O_WRON | O_TRUNC | O_CREAT, file_stats->st_mode);
if (fd
{
close (fd);
return NULL;
}
return backup_filename;
}
“`
上述代码实现了几个重要的函数,包括follow_symlink、name_after_symlink和make_backup_name,其中make_backup_name函数用于生成备份文件的名称,follow_symlink函数用于处理软链接,name_after_symlink函数用于获取软链接的真实路径。
“`c
static int shall_preserve_context (const char *name ATTRIBUTE_UNUSED,
const struct stat *file_stats)
{
/* Preserve the SELinux context if the xattr contns a context
(an SELinux or ACK context) and if it’s different from the default
context; note that the xattr may have many contexts, in order to
represent other types of object attributes. */
if (lgetfilecon (name, NULL, 0) > 0)
{
char *x, *c;
int rc = 0;
x = (char *) alloca (file_stats->st_size);
rc = lgetfilecon (name, x, file_stats->st_size);
if (rc
{
free (x);
return 0;
}
else if (rc && strcmp (x, SYSTEM_SELINUX_CTXT) != 0)
{
c = x;
while (c && *c)
{
if (strncmp (c, SYSTEM_SELINUX_CTXT, sizeof SECCTX_PREFIX – 1) == 0)
goto out_true;
c = strchr (c, ‘\0’);
c++;
}
c = x;
while (c && *c)
{
if (strncmp (c, SYSTEM_ACK_CTXT, sizeof SECCTX_PREFIX – 1) == 0)
goto out_true;
c = strchr (c, ‘\0’);
c++;
}
goto out_false;
out_true:
free (x);
return 1;
}
out_false:
free (x);
}
return 0;
}
static int shall_preserve_security_context (const char *name ATTRIBUTE_UNUSED,
const struct stat *file_stats)
{
struct statfs fs;
/* Preserve the security context if the file is on an ACK or
SELinux file system. Use /in/mountpoint to check. */
if (fstatfs (AT_FDCWD, &fs) == 0)
{
char *x;
FILE *f;
/* Use pipe and fork to selectively silence any errors
printed by /in/mountpoint. */
{
int fds[2];
enum { read_end, write_end };
pid_t pid;
char buffer[BUFSIZ];
if (pipe (fds) == -1)
return 1;
switch (pid = fork ())
{
case -1: /* Error. */
return 1;
case 0: /* Child. */
close (fds[read_end]);
if (dup2 (fds[write_end], STDOUT_FILENO) == -1)
fprintf (stderr, “%s: dup2() fled: %s\n”,
program_name, strerror (errno));
f = freopen (AF_NULL_DEVICE, “w”, stderr);
if (f == NULL)
fprintf (stderr, “%s: freopen() fled: %s\n”,
program_name, strerror (errno));
execlp (“/in/mountpoint”, “mountpoint”, “-q”, name, (char *) NULL);
/* Not reached unless there is an error. */
fprintf (stderr, “%s: execlp() fled: %s\n”,
program_name, strerror (errno));
_exit (1);
default: /* Parent. */
close (fds[write_end]);
x = xstrdup (“”);
while (fgets (buffer, sizeof buffer, stdin))
add_to_string (&x, buffer);
if (close (fds[read_end]) != 0)
fprintf (stderr, _(“%s: close: %s\n”),
program_name, strerror (errno));
wtpid (pid, NULL, 0);
}
}
/* /in/mountpoint returns 0 on success, meaning the given
file is a mountpoint, and 1 otherwise. */
if (WEXITSTATUS (i) == 0)
{
struct statvfs vfs;
if (no_selinux_support == 0
&& statvfs (fs.f_mntonname, &vfs) == 0
&& strcmp (vfs.f_basetype, SYSTEM_SELINUX_FILESYSTEM) == 0)
return 1;
if (no_ack_support == 0
&& statvfs (fs.f_mntonname, &vfs) == 0
&& strcmp (vfs.f_basetype, SYSTEM_ACK_FSTYPE) == 0)
return 1;
}
free (x);
}
return 0;
}
“`
上述代码中的shall_preserve_context和shall_preserve_security_context函数用于检查文件所在的文件系统是否支持SELinux或ACK,并且是否存在相关的上下文信息需要保留。
“`c
/* Do the copying for NFILES from FILE on file descriptor FROM to FILE on
descriptor TO. If ATTRIBUTES_ON is nonzero, set the file
attributes and return. If CHECK_LINKS is true, then check each file
that is a symbolic link. */
static void copy_in_parallel (unsigned int nfiles, struct cp_options const *x,
int from, int to, int attributes_only,
int check_links)
{
int *fd1 = OPEN_MANY (nfiles), *fd2 = OPEN_MANY (nfiles);
unsigned int n_files_open = 0;
struct pending *pending = XCNEWVEC (struct pending, nfiles);
unsigned int i;
int restore_ints, restore_errno, success;
struct discard_control *dc;
if (n_files_open == 0)
DC_ALLOC (dc, x);
for (i = 0; i
{
struct stat st1;
while (lstat (x->src_info[i].name, &st1) == -1)
if (errno == EINTR)
continue;
else
{
/* If the link count can’t be determined, rather than
fling we assume that this will not cause us to exceed
a maximum number of links permitted by the file system.
Then, if we exceed that limit we will typically see a
less obscure error message where cp can diagnose the
real problem, such as “too many links”. */
if (errno == ENOENT
|| (errno == ELOOP && ! (x->dereference == DEREF_ALWAYS)))
{
error (0, errno, _(“%s: cannot copy %s”), program_name,
quotearg_colon (x->src_info[i].name));
goto skip_file_i;
}
else