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


数据运维技术 » Linux cp命令源码解析 (linux cp命令源码)