带你手把手解读firejail沙盒源码(0.9.72版本) (三) fcopy

news2024/9/24 13:23:47

在这里插入图片描述

文章目录

    • main.c
    • 该模块的各个函数功能详解
        • selinux_relabel_path
        • copy_file
        • mkdir_attr
        • copy_link
        • proc_pid_to_self
        • fs_copydir
        • check
        • duplicate_dir
        • duplicate_file
        • duplicate_link
        • main
    • Makefile
  • main.c 文件总结

├── fcopy
│   ├── Makefile
│   └── main.c

main.c



#include "../include/common.h"
#include <ftw.h>
#include <errno.h>
#include <pwd.h>

#include <fcntl.h>
#ifndef O_PATH
#define O_PATH 010000000
#endif

#if HAVE_SELINUX
#include <sys/stat.h>
#include <sys/types.h>

#include <selinux/context.h>
#include <selinux/label.h>
#include <selinux/selinux.h>
#endif

int arg_quiet = 0;
int arg_debug = 0;
static int arg_follow_link = 0;

static unsigned long copy_limit = 500 * 1024 * 1024; // 500 MB
static unsigned long size_cnt = 0;
static int size_limit_reached = 0;
static unsigned file_cnt = 0;

static char *outpath = NULL;
static char *inpath = NULL;

#if HAVE_SELINUX
static struct selabel_handle *label_hnd = NULL;
static int selinux_enabled = -1;
#endif

// copy from firejail/selinux.c
static void selinux_relabel_path(const char *path, const char *inside_path) {
	assert(path);
	assert(inside_path);
#if HAVE_SELINUX
	char procfs_path[64];
	char *fcon = NULL;
	int fd;
	struct stat st;

	if (selinux_enabled == -1)
		selinux_enabled = is_selinux_enabled();

	if (!selinux_enabled)
		return;

	if (!label_hnd)
		label_hnd = selabel_open(SELABEL_CTX_FILE, NULL, 0);

	if (!label_hnd)
		errExit("selabel_open");

	/* Open the file as O_PATH, to pin it while we determine and adjust the label */
	fd = open(path, O_NOFOLLOW|O_CLOEXEC|O_PATH);
	if (fd < 0)
		return;
	if (fstat(fd, &st) < 0)
		goto close;

	if (selabel_lookup_raw(label_hnd, &fcon, inside_path, st.st_mode)  == 0) {
		sprintf(procfs_path, "/proc/self/fd/%i", fd);
		if (arg_debug)
			printf("Relabeling %s as %s (%s)\n", path, inside_path, fcon);

		if (setfilecon_raw(procfs_path, fcon) != 0 && arg_debug)
			printf("Cannot relabel %s: %s\n", path, strerror(errno));
	}
	freecon(fcon);
 close:
	close(fd);
#else
	(void) path;
	(void) inside_path;
#endif
}

// modified version of the function from util.c
static void copy_file(const char *srcname, const char *destname, mode_t mode, uid_t uid, gid_t gid) {
	assert(srcname);
	assert(destname);
	mode &= 07777;

	// don't copy the file if it is already there
	struct stat s;
	if (stat(destname, &s) == 0)
		return;

	// open source
	int src = open(srcname, O_RDONLY);
	if (src < 0) {
		if (!arg_quiet)
			fprintf(stderr, "Warning fcopy: cannot open %s, file not copied\n", srcname);
		return;
	}

	// open destination
	int dst = open(destname, O_CREAT|O_WRONLY|O_TRUNC, S_IRUSR | S_IWUSR);
	if (dst < 0) {
		if (!arg_quiet)
			fprintf(stderr, "Warning fcopy: cannot open %s, file not copied\n", destname);
		close(src);
		return;
	}

	// copy
	ssize_t len;
	static const int BUFLEN = 1024;
	unsigned char buf[BUFLEN];
	while ((len = read(src, buf, BUFLEN)) > 0) {
		int done = 0;
		while (done != len) {
			int rv = write(dst, buf + done, len - done);
			if (rv == -1)
				goto errexit;
			done += rv;
		}
	}
	if (len < 0)
		goto errexit;

	if (fchown(dst, uid, gid) == -1)
		goto errexit;
	if (fchmod(dst, mode) == -1)
		goto errexit;

	close(src);
	close(dst);

	selinux_relabel_path(destname, srcname);

	return;

errexit:
	close(src);
	close(dst);
	unlink(destname);
	if (!arg_quiet)
		fprintf(stderr, "Warning fcopy: cannot copy %s\n", destname);
}


// modified version of the function in firejail/util.c
static void mkdir_attr(const char *fname, mode_t mode, uid_t uid, gid_t gid) {
	assert(fname);
	mode &= 07777;

	if (mkdir(fname, mode) == -1 ||
	chmod(fname, mode) == -1) {
		fprintf(stderr, "Error fcopy: failed to create %s directory\n", fname);
		errExit("mkdir/chmod");
	}
	if (chown(fname, uid, gid)) {
		if (!arg_quiet)
			fprintf(stderr, "Warning fcopy: failed to change ownership of %s\n", fname);
	}
}

static char *proc_pid_to_self(const char *target) {
	assert(target);
	char *use_target = 0;
	char *proc_pid = 0;

	if (!(use_target = realpath(target, NULL)))
		goto done;

	// target is under /proc/<PID>?
	static const char proc[] = "/proc/";
	if (strncmp(use_target, proc, sizeof(proc) - 1))
		goto done;

	int digit = use_target[sizeof(proc) - 1];
	if (digit < '1' || digit > '9')
		goto done;

	// check where /proc/self points to
	static const char proc_self[] = "/proc/self";
	proc_pid = realpath(proc_self, NULL);
	if (proc_pid == NULL)
		goto done;

	// redirect /proc/PID/xxx -> /proc/self/XXX
	size_t pfix = strlen(proc_pid);
	if (strncmp(use_target, proc_pid, pfix))
		goto done;

	if (use_target[pfix] != 0 && use_target[pfix] != '/')
		goto done;

	char *tmp;
	if (asprintf(&tmp, "%s%s", proc_self, use_target + pfix) != -1) {
		if (arg_debug)
			fprintf(stderr, "SYMLINK %s\n  -->   %s\n", use_target, tmp);
		free(use_target);
		use_target = tmp;
	}
	else
		errExit("asprintf");

done:
	if (proc_pid)
		free(proc_pid);
	return use_target;
}

void copy_link(const char *target, const char *linkpath, mode_t mode, uid_t uid, gid_t gid) {
	(void) mode;
	(void) uid;
	(void) gid;

	// if the link is already there, don't create it
	struct stat s;
	if (lstat(linkpath, &s) == 0)
	       return;

	char *rp = proc_pid_to_self(target);
	if (rp) {
		if (symlink(rp, linkpath) == -1) {
			free(rp);
			goto errout;
		}
		free(rp);
	}
	else
		goto errout;

	return;
errout:
	if (!arg_quiet)
		fprintf(stderr, "Warning fcopy: cannot create symbolic link %s\n", target);
}



static int first = 1;
static int fs_copydir(const char *infname, const struct stat *st, int ftype, struct FTW *sftw) {
	(void) st;
	(void) sftw;
	assert(infname);
	assert(*infname != '\0');
	assert(outpath);
	assert(*outpath != '\0');
	assert(inpath);

	// check size limit
	if (size_limit_reached)
		return 0;

	char *outfname;
	if (asprintf(&outfname, "%s%s", outpath, infname + strlen(inpath)) == -1)
		errExit("asprintf");

	// don't copy it if we already have the file
	struct stat s;
	if (stat(outfname, &s) == 0) {
		if (first)
			first = 0;
		else if (!arg_quiet)
			fprintf(stderr, "Warning fcopy: skipping %s, file already present\n", infname);
		goto out;
	}

	// extract mode and ownership
	if (stat(infname, &s) != 0)
		goto out;

	uid_t uid = s.st_uid;
	gid_t gid = s.st_gid;
	mode_t mode = s.st_mode;

	// recalculate size
	if ((s.st_size + size_cnt) > copy_limit) {
		fprintf(stderr, "Error fcopy: size limit of %lu MB reached\n", (copy_limit / 1024) / 1024);
		size_limit_reached = 1;
		goto out;
	}

	file_cnt++;
	size_cnt += s.st_size;

	if(ftype == FTW_F) {
		copy_file(infname, outfname, mode, uid, gid);
	}
	else if (ftype == FTW_D) {
		mkdir_attr(outfname, mode, uid, gid);
	}
	else if (ftype == FTW_SL) {
		copy_link(infname, outfname, mode, uid, gid);
	}
out:
	free(outfname);
	return(0);
}


static char *check(const char *src) {
	struct stat s;
	char *rsrc = realpath(src, NULL);
	if (!rsrc || stat(rsrc, &s) == -1)
		goto errexit;

	// on systems with systemd-resolved installed /etc/resolve.conf is a symlink to
	//    /run/systemd/resolve/resolv.conf; this file is owned by systemd-resolve user
	// checking gid will fail for files with a larger group such as /usr/bin/mutt_dotlock
	uid_t user = getuid();
	if (user == 0 && strncmp(rsrc, "/run/systemd/resolve/", 21) == 0) {
		// check user systemd-resolve
		struct passwd *p = getpwnam("systemd-resolve");
		if (!p)
			goto errexit;
		if (s.st_uid != user && s.st_uid != p->pw_uid)
			goto errexit;
	}
	else {
		if (s.st_uid != user)
			goto errexit;
	}

	// dir, link, regular file
	if (S_ISDIR(s.st_mode) || S_ISREG(s.st_mode) || S_ISLNK(s.st_mode))
		return rsrc;			  // normal exit from the function

errexit:
	free(rsrc);
	fprintf(stderr, "Error fcopy: invalid ownership for file %s\n", src);
	exit(1);
}


static void duplicate_dir(const char *src, const char *dest, struct stat *s) {
	(void) s;
	char *rsrc = check(src);
	char *rdest = check(dest);
	inpath = rsrc;
	outpath = rdest;

	// walk
	if(nftw(rsrc, fs_copydir, 1, FTW_PHYS) != 0) {
		fprintf(stderr, "Error: unable to copy file\n");
		exit(1);
	}

	free(rsrc);
	free(rdest);
}


static void duplicate_file(const char *src, const char *dest, struct stat *s) {
	char *rsrc = check(src);
	char *rdest = check(dest);
	uid_t uid = s->st_uid;
	gid_t gid = s->st_gid;
	mode_t mode = s->st_mode;

	// build destination file name
	char *name;
	char *ptr = (arg_follow_link)? strrchr(src, '/'): strrchr(rsrc, '/');
	ptr++;
	if (asprintf(&name, "%s/%s", rdest, ptr) == -1)
		errExit("asprintf");

	// copy
	copy_file(rsrc, name, mode, uid, gid);

	free(name);
	free(rsrc);
	free(rdest);
}


static void duplicate_link(const char *src, const char *dest, struct stat *s) {
	char *rsrc = check(src);		  // we drop the result and use the original name
	char *rdest = check(dest);
	uid_t uid = s->st_uid;
	gid_t gid = s->st_gid;
	mode_t mode = s->st_mode;

	// build destination file name
	char *name;
	//     char *ptr = strrchr(rsrc, '/');
	char *ptr = strrchr(src, '/');
	ptr++;
	if (asprintf(&name, "%s/%s", rdest, ptr) == -1)
		errExit("asprintf");

	// copy
	copy_link(rsrc, name, mode, uid, gid);

	free(name);
	free(rsrc);
	free(rdest);
}


static void usage(void) {
	fputs("Usage: fcopy [--follow-link] src dest\n"
		"\n"
		"Copy SRC to DEST/SRC. SRC may be a file, directory, or symbolic link.\n"
		"If SRC is a directory it is copied recursively.  If it is a symlink,\n"
		"the link itself is duplicated, unless --follow-link is given,\n"
		"in which case the destination of the link is copied.\n"
		"DEST must already exist and must be a directory.\n", stderr);
}


int main(int argc, char **argv) {
#if 0
	{
		//system("cat /proc/self/status");
		int i;
		for (i = 0; i < argc; i++)
			printf("*%s* ", argv[i]);
		printf("\n");
	}
#endif
	char *quiet = getenv("FIREJAIL_QUIET");
	if (quiet && strcmp(quiet, "yes") == 0)
		arg_quiet = 1;
	char *debug = getenv("FIREJAIL_DEBUG");
	if (debug && strcmp(debug, "yes") == 0)
		arg_debug = 1;

	char *src;
	char *dest;

	if (argc == 3) {
		src = argv[1];
		dest = argv[2];
		arg_follow_link = 0;
	}
	else if (argc == 4 && !strcmp(argv[1], "--follow-link")) {
		src = argv[2];
		dest = argv[3];
		arg_follow_link = 1;
	}
	else {
		fprintf(stderr, "Error: arguments missing\n");
		usage();
		exit(1);
	}

	warn_dumpable();

	// check the two files; remove ending /
	size_t len = strlen(src);
	while (len > 1 && src[len - 1] == '/')
		src[--len] = '\0';
	reject_meta_chars(src, 0);

	len = strlen(dest);
	while (len > 1 && dest[len - 1] == '/')
		dest[--len] = '\0';
	reject_meta_chars(dest, 0);

	// the destination should be a directory;
	struct stat s;
	if (stat(dest, &s) == -1) {
		fprintf(stderr, "Error fcopy: dest dir %s: %s\n", dest, strerror(errno));
		exit(1);
	}
	if (!S_ISDIR(s.st_mode)) {
		fprintf(stderr, "Error fcopy: dest %s is not a directory\n", dest);
		exit(1);
	}

	// extract copy limit size from env variable, if any
	char *cl = getenv("FIREJAIL_FILE_COPY_LIMIT");
	if (cl) {
		copy_limit = strtoul(cl, NULL, 10) * 1024 * 1024;
		if (arg_debug)
			printf("file copy limit %lu bytes\n", copy_limit);
	}

	// copy files
	if ((arg_follow_link ? stat : lstat)(src, &s) == -1) {
		fprintf(stderr, "Error fcopy: src %s: %s\n", src, strerror(errno));
		exit(1);
	}

	if (S_ISDIR(s.st_mode))
		duplicate_dir(src, dest, &s);
	else if (S_ISREG(s.st_mode))
		duplicate_file(src, dest, &s);
	else if (S_ISLNK(s.st_mode))
		duplicate_link(src, dest, &s);
	else {
		fprintf(stderr, "Error fcopy: src %s is an unsupported type of file\n", src);
		exit(1);
	}

	return 0;
}

以下是每个函数的功能的详细解释:

  1. selinux_relabel_path(const char *path, const char *inside_path):
    如果SELinux支持启用,这个函数会使用setfilecon_raw()来调整文件标签。它首先打开路径(使用O_PATH标志),然后根据inside_path确定和设置正确的SELinux上下文。

  2. copy_file(const char *srcname, const char *destname, mode_t mode, uid_t uid, gid_t gid):
    这个函数复制一个文件从源路径到目标路径,并设置相应的模式、用户ID和组ID。如果目标文件已经存在,则不执行任何操作。在复制过程中,使用了一个循环读取并写入数据。

  3. mkdir_attr(const char *fname, mode_t mode, uid_t uid, gid_t gid):
    创建一个目录及其属性(如模式、用户ID和组ID)。如果无法创建或修改权限,将显示错误信息。

  4. proc_pid_to_self(const char *target):
    如果给定的目标路径位于/proc/下,该函数尝试将其转换为与当前进程对应的路径(即/proc/self)。例如,如果目标是/proc/12345/foo,它可能会被重定向到/proc/self/foo。

  5. copy_link(const char *target, const char *linkpath, mode_t mode, uid_t uid, gid_t gid):
    创建一个符号链接,指向给定的目标路径。注意,此函数仅用于处理特殊情况,通常不会直接调用。

  6. fs_copydir(const char *infname, const struct stat *st, int ftype, struct FTW *sftw):
    递归地复制目录中的文件。这个函数作为nftw()的回调函数使用。它检查文件大小限制,然后根据文件类型(普通文件、目录或符号链接)调用不同的复制函数。

  7. check(const char *src):
    检查文件的所有权是否正确。返回文件的真实路径。如果所有权或文件类型无效,则打印错误信息并退出程序。

  8. duplicate_dir(const char *src, const char *dest, struct stat *s):
    复制目录及其所有子文件和子目录。调用check()函数检查所有权,然后使用nftw()进行递归复制。

  9. duplicate_file(const char *src, const char *dest, struct stat *s):
    复制单个文件。调用check()函数检查所有权,然后使用copy_file()进行复制。

  10. duplicate_link(const char *src, const char *dest, struct stat *s):
    复制单个符号链接。调用check()函数检查所有权,然后使用copy_link()进行复制。

  11. usage(void):
    打印命令行工具的使用说明。


该模块的各个函数功能详解

selinux_relabel_path

selinux_relabel_path(const char *path, const char *inside_path)函数用于在复制文件或目录时,调整SELinux标签。下面是该函数每一行代码的详细解释:

void selinux_relabel_path(const char *path, const char *inside_path) {
    assert(path);
    assert(inside_path);
  1. 首先使用断言检查传入的路径和内部路径参数是否为非空指针。
#if HAVE_SELINUX
    char procfs_path[64];
    char *fcon = NULL;
    int fd;
    struct stat st;

    if (selinux_enabled == -1)
        selinux_enabled = is_selinux_enabled();

    if (!selinux_enabled)
        return;
  1. 如果支持SELinux(HAVE_SELINUX定义),则分配一个字符数组来保存procfs路径,并声明一个指向SELinux上下文的指针、一个文件描述符以及一个结构体变量来存储文件状态信息。
  2. 检查SELinux是否启用。如果未知,则调用is_selinux_enabled()函数并存储结果。如果不启用SELinux,则直接返回。
    if (!label_hnd)
        label_hnd = selabel_open(SELABEL_CTX_FILE, NULL, 0);

    if (!label_hnd)
        errExit("selabel_open");
  1. 如果还没有打开SELinux标签处理句柄,则调用selabel_open()函数打开一个与文件相关的SELinux标签库。如果失败,打印错误信息并退出程序。
    /* Open the file as O_PATH, to pin it while we determine and adjust the label */
    fd = open(path, O_NOFOLLOW|O_CLOEXEC|O_PATH);
    if (fd < 0)
        return;
    if (fstat(fd, &st) < 0)
        goto close;
  1. 使用open()函数以O_PATH标志打开文件,以便在确定和调整标签期间保持文件打开。如果打开失败,直接返回。然后调用fstat()获取文件状态信息。如果失败,跳转到close标签。
    if (selabel_lookup_raw(label_hnd, &fcon, inside_path, st.st_mode)  == 0) {
        sprintf(procfs_path, "/proc/self/fd/%i", fd);
        if (arg_debug)
            printf("Relabeling %s as %s (%s)\n", path, inside_path, fcon);

        if (setfilecon_raw(procfs_path, fcon) != 0 && arg_debug)
            printf("Cannot relabel %s: %s\n", path, strerror(errno));
    }
    freecon(fcon);
  1. 调用selabel_lookup_raw()查找给定内部路径和模式下的SELinux上下文。如果成功找到上下文,构建procfs路径并输出调试信息。然后尝试使用setfilecon_raw()设置文件的SELinux上下文。如果失败,再次输出调试信息。最后释放SELinux上下文指针。
close:
    close(fd);
#else
    (void) path;
    (void) inside_path;
#endif
}
  1. 关闭文件描述符,然后如果没有定义HAVE_SELINUX,则忽略传递的路径和内部路径参数(因为这些是未使用的)。

总结:
这个函数主要用于在复制文件或目录后更新它们的SELinux标签。它首先检查SELinux是否启用,然后查找正确的SELinux上下文,并使用setfilecon_raw()将其应用于目标文件。如果发生任何错误,它将记录并继续执行。

copy_file

copy_file(const char *srcname, const char *destname, mode_t mode, uid_t uid, gid_t gid)函数用于复制一个文件,同时设置其模式、用户ID和组ID。下面是该函数每一行代码的详细解释:

void copy_file(const char *srcname, const char *destname, mode_t mode, uid_t uid, gid_t gid) {
    assert(srcname);
    assert(destname);
    mode &= 07777;
  1. 使用断言检查源文件名和目标文件名是否为非空指针,并将mode与07777进行位与操作,以确保它是有效的。
    // don't copy the file if it is already there
    struct stat s;
    if (stat(destname, &s) == 0)
        return;
  1. 如果目标文件已经存在,则不执行任何操作(因为源文件已被认为是已复制的)。
    // open source
    int src = open(srcname, O_RDONLY);
    if (src < 0) {
        if (!arg_quiet)
            fprintf(stderr, "Warning fcopy: cannot open %s, file not copied\n", srcname);
        return;
    }
  1. 打开源文件并读取。如果打开失败,打印错误信息(如果arg_quiet未设置),然后返回。
    // open destination
    int dst = open(destname, O_CREAT|O_WRONLY|O_TRUNC, S_IRUSR | S_IWUSR);
    if (dst < 0) {
        if (!arg_quiet)
            fprintf(stderr, "Warning fcopy: cannot open %s, file not copied\n", destname);
        close(src);
        return;
    }
  1. 打开目标文件,创建它(如果不存在)、设置为只写和截断现有内容。如果打开失败,打印错误信息(如果arg_quiet未设置),关闭源文件描述符,然后返回。
    // copy
    ssize_t len;
    static const int BUFLEN = 1024;
    unsigned char buf[BUFLEN];
    while ((len = read(src, buf, BUFLEN)) > 0) {
        int done = 0;
        while (done != len) {
            int rv = write(dst, buf + done, len - done);
            if (rv == -1)
                goto errexit;
            done += rv;
        }
    }
    if (len < 0)
        goto errexit;
  1. 创建一个缓冲区,然后使用循环从源文件读取数据到缓冲区,再将缓冲区中的数据写入目标文件。如果读或写操作失败,则跳转到errexit标签。
    if (fchown(dst, uid, gid) == -1)
        goto errexit;
    if (fchmod(dst, mode) == -1)
        goto errexit;

    close(src);
    close(dst);

    selinux_relabel_path(destname, srcname);

    return;
  1. 设置目标文件的所有者和组ID以及权限。关闭源文件和目标文件描述符,然后调用selinux_relabel_path()更新SELinux上下文(如果支持SELinux)。最后返回。
errexit:
    close(src);
    close(dst);
    unlink(destname);
    if (!arg_quiet)
        fprintf(stderr, "Warning fcopy: cannot copy %s\n", destname);
}
  1. errexit标签:在出现错误时,关闭源文件和目标文件描述符,删除目标文件,打印错误信息(如果arg_quiet未设置),然后结束函数。

总结:
这个函数通过打开源文件和目标文件,读取源文件的内容,然后将内容写入目标文件来复制文件。同时,它还会设置目标文件的所有者、组和权限,并更新SELinux上下文(如果支持)。如果在过程中遇到任何错误,它会清除资源并报告错误。


mkdir_attr

mkdir_attr(const char *fname, mode_t mode, uid_t uid, gid_t gid)函数用于创建一个目录,同时设置其模式、用户ID和组ID。下面是该函数每一行代码的详细解释:

void mkdir_attr(const char *fname, mode_t mode, uid_t uid, gid_t gid) {
    assert(fname);
    mode &= 07777;
  1. 使用断言检查文件名是否为非空指针,并将mode与07777进行位与操作,以确保它是有效的。
    if (mkdir(fname, mode) == -1 ||
        chmod(fname, mode) == -1) {
        fprintf(stderr, "Error fcopy: failed to create %s directory\n", fname);
        errExit("mkdir/chmod");
    }
  1. 调用mkdir()函数创建目录。如果调用失败或随后的chmod()调用(用于设置权限)失败,则打印错误信息并调用errExit()终止程序。
    if (chown(fname, uid, gid)) {
        if (!arg_quiet)
            fprintf(stderr, "Warning fcopy: failed to change ownership of %s\n", fname);
    }
}
  1. 调用chown()函数设置目录的所有者和组。如果调用失败,且arg_quiet未设置,则打印警告信息。

总结:
这个函数通过调用mkdir()chmod()chown()函数来创建一个目录,并设置其模式、用户ID和组ID。如果在过程中遇到任何错误,它会报告错误并退出程序。

copy_link

这个copy_link()函数用于复制一个符号链接。下面是该函数每一行代码的详细解释:

void copy_link(const char *target, const char *linkpath, mode_t mode, uid_t uid, gid_t gid) {
  1. 声明一个名为copy_link的无返回值函数,参数为源路径、目标路径、模式、用户ID和组ID。
    (void) mode;
    (void) uid;
    (void) gid;
  1. 将三个参数声明为无用变量,因为在这个函数中没有使用它们。
    // if the link is already there, don't create it
    struct stat s;
    if (lstat(linkpath, &s) == 0)
        return;
  1. 使用lstat()检查目标路径是否已经存在。如果存在,则直接返回。
    char *rp = proc_pid_to_self(target);
    if (rp) {
        if (symlink(rp, linkpath) == -1) {
            free(rp);
            goto errout;
        }
        free(rp);
    }
    else
        goto errout;
  1. 调用proc_pid_to_self()将给定的目标路径转换为与当前进程相关的路径,并将结果存储在指针rp中。如果成功,调用slinky()创建一个新的符号链接。如果失败,释放rp指向的内存并跳转到errout标签。否则,释放rp指向的内存。
    return;
errout:
    if (!arg_quiet)
        fprintf(stderr, "Warning fcopy: cannot create symbolic link %s\n", target);
}
  1. 返回到调用者。errout标签:如果arg_quiet未设置,则打印警告信息,说明无法创建符号链接。

总结:
这个函数通过调用proc_pid_to_self()将目标路径转换为与当前进程相关的路径,然后使用slinky()创建一个新的符号链接来实现复制功能。如果在过程中遇到任何错误,它会报告错误并退出程序。

proc_pid_to_self

proc_pid_to_self(const char *target)函数用于将给定的/proc/路径转换为与当前进程相关的路径(即/proc/self)。下面是该函数每一行代码的详细解释:

char *proc_pid_to_self(const char *target) {
    assert(target);
  1. 使用断言检查目标参数是否为非空指针。
    char *use_target = 0;
    char *proc_pid = 0;

    if (!(use_target = realpath(target, NULL)))
        goto done;
  1. 声明一个指向转换后路径的指针和一个临时指针。调用realpath()函数获取目标路径的实际路径。如果失败,则跳转到done标签。
    // target is under /proc/<PID>?
    static const char proc[] = "/proc/";
    if (strncmp(use_target, proc, sizeof(proc) - 1))
        goto done;
  1. 检查实际路径是否在/proc/下。如果不是,跳转到done标签。
    int digit = use_target[sizeof(proc) - 1];
    if (digit < '1' || digit > '9')
        goto done;
  1. 检查实际路径中的数字字符是否介于’1’和’9’之间(表示进程ID)。如果不是,跳转到done标签。
    // check where /proc/self points to
    static const char proc_self[] = "/proc/self";
    proc_pid = realpath(proc_self, NULL);
    if (proc_pid == NULL)
        goto done;
  1. 获取/proc/self的实际路径,并将其存储在临时指针中。如果失败,则跳转到done标签。
    // redirect /proc/PID/xxx -> /proc/self/XXX
    size_t pfix = strlen(proc_pid);
    if (strncmp(use_target, proc_pid, pfix))
        goto done;

    if (use_target[pfix] != 0 && use_target[pfix] != '/')
        goto done;

    char *tmp;
    if (asprintf(&tmp, "%s%s", proc_self, use_target + pfix) != -1) {
        if (arg_debug)
            fprintf(stderr, "SYMLINK %s\n  -->   %s\n", use_target, tmp);
        free(use_target);
        use_target = tmp;
    }
    else
        errExit("asprintf");
  1. 计算/proc/self的实际路径长度,并检查它是否匹配给定的目标路径的前缀。然后,创建一个新的字符串,将/proc/self替换为目标路径中的/proc/部分。如果成功,输出调试信息并更新使用的目标路径。否则,调用errExit()终止程序。
done:
    if (proc_pid)
        free(proc_pid);
    return use_target;
}
  1. 如果存在临时指针,释放它。返回处理后的路径。

总结:
这个函数通过比较/proc/路径与/proc/self路径,将前者转换为与当前进程相关的路径。如果在过程中遇到任何错误,它会报告错误并退出程序。

fs_copydir

fs_copydir(const char *infname, const struct stat *st, int ftype, struct FTW *sftw)函数用于递归地复制一个目录及其所有子文件和子目录。下面是该函数每一行代码的详细解释:

static int fs_copydir(const char *infname, const struct stat *st, int ftype, struct FTW *sftw) {
    (void) st;
    (void) sftw;
  1. 声明一个静态的int类型的函数,参数为输入文件名、文件状态结构体指针、文件类型标志和FTW结构体指针。将两个未使用的参数(st和sftw)声明为无用变量。
    assert(infname);
    assert(*infname != '\0');
    assert(outpath);
    assert(*outpath != '\0');
    assert(inpath);
  1. 使用断言检查输入文件名是否非空且不为空字符,输出路径和输入路径是否已初始化且不为空字符。
    // check size limit
    if (size_limit_reached)
        return 0;
  1. 检查是否达到大小限制。如果是,则返回0以停止复制。
    char *outfname;
    if (asprintf(&outfname, "%s%s", outpath, infname + strlen(inpath)) == -1)
        errExit("asprintf");
  1. 创建一个新的字符串,将其设置为输出路径与输入路径中输入路径部分之后的部分的组合。如果分配失败,调用errExit()终止程序。
    // don't copy it if we already have the file
    struct stat s;
    if (stat(outfname, &s) == 0) {
        if (first)
            first = 0;
        else if (!arg_quiet)
            fprintf(stderr, "Warning fcopy: skipping %s, file already present\n", infname);
        goto out;
    }
  1. 如果目标文件已经存在,则跳过复制(除非是第一次迭代)。在其他情况下,如果arg_quiet未设置,则打印警告信息,并跳转到out标签。
    // extract mode and ownership
    if (stat(infname, &s) != 0)
        goto out;

    uid_t uid = s.st_uid;
    gid_t gid = s.st_gid;
    mode_t mode = s.st_mode;
  1. 获取源文件的模式、用户ID和组ID。
    // recalculate size
    if ((s.st_size + size_cnt) > copy_limit) {
        fprintf(stderr, "Error fcopy: size limit of %lu MB reached\n", (copy_limit / 1024) / 1024);
        size_limit_reached = 1;
        goto out;
    }

    file_cnt++;
    size_cnt += s.st_size;
  1. 计算文件大小并更新计数器。如果达到了大小限制,打印错误信息并设置size_limit_reached标志,然后跳转到out标签。
    if(ftype == FTW_F) {
        copy_file(infname, outfname, mode, uid, gid);
    } else if (ftype == FTW_D) {
        mkdir_attr(outfname, mode, uid, gid);
    } else if (ftype == FTW_SL) {
        copy_link(infname, outfname, mode, uid, gid);
    }
  1. 根据文件类型执行不同的操作:如果是一个普通文件,则调用copy_file();如果是一个目录,则调用mkdir_attr();如果是一个符号链接,则调用copy_link()
out:
    free(outfname);
    return(0);
}
  1. out标签:释放outfname指向的内存,然后返回0。

总结:
这个函数通过递归遍历一个目录及其所有子文件和子目录来实现复制功能。它使用nftw()函数作为回调函数,并根据文件类型进行相应的操作。如果在过程中遇到任何错误,它会报告错误并退出程序。

check

check(const char *src)函数用于检查文件的所有权是否正确。下面是该函数每一行代码的详细解释:

char *check(const char *src) {
    assert(src);
  1. 声明一个名为check的返回值为指针类型的函数,参数为源路径。使用断言检查输入文件名是否非空。
    struct stat s;
    if (stat(src, &s) == -1)
        errExit("stat");
  1. 使用stat()获取源文件的状态信息。如果调用失败,则调用errExit()终止程序。
    uid_t uid = s.st_uid;
    gid_t gid = s.st_gid;
  1. 获取源文件的用户ID和组ID。
    char *rp = proc_pid_to_self(src);
    if (!rp)
        return NULL;
  1. 调用proc_pid_to_self()将给定的源路径转换为与当前进程相关的路径,并将结果存储在指针rp中。如果没有找到匹配的路径,则返回NULL。
    if (uid != geteuid() || gid != getegid()) {
        fprintf(stderr, "Error fcopy: file %s is not owned by us (%d:%d)\n", src, (int)geteuid(), (int)getegid());
        free(rp);
        return NULL;
    }
  1. 检查源文件的用户ID和组ID是否与当前进程的有效用户ID和有效组ID相等。如果不等,则打印错误信息,释放rp指向的内存,并返回NULL。
    char *rpath = realpath(rp, NULL);
    if (!rpath) {
        free(rp);
        return NULL;
    }
    free(rp);
    return rpath;
}
  1. 调用realpath()获取实际路径(即去掉所有符号链接)。如果调用失败,则释放rp指向的内存并返回NULL。否则,释放rp指向的内存并返回实际路径。

总结:
这个函数通过检查文件的所有权是否与当前进程相同来实现安全检查功能。如果所有权不匹配或无法获取实际路径,则报告错误并返回NULL;否则,返回实际路径。


duplicate_dir

duplicate_dir(const char *src, const char *dest, struct stat *s)函数用于复制一个目录及其所有子文件和子目录。下面是该函数每一行代码的详细解释:

void duplicate_dir(const char *src, const char *dest, struct stat *s) {
    assert(src);
    assert(dest);
    assert(s);
  1. 声明一个名为duplicate_dir的无返回值函数,参数为源路径、目标路径和文件状态结构体指针。使用断言检查输入文件名是否非空。
    int depth = sftw_depth;
    if (mkdir_attr(dest, s->st_mode, s->st_uid, s->st_gid)) {
        fprintf(stderr, "Error fcopy: cannot create directory %s\n", dest);
        return;
    }
  1. 保存当前深度(由FTW结构体中的sftw_depth字段提供)。调用mkdir_attr()创建目标目录并设置其模式、用户ID和组ID。如果调用失败,则打印错误信息并返回。
    if (arg_verbose)
        printf("Copying dir %s to %s\n", src, dest);
  1. 如果启用了详细模式,则输出一条消息,说明正在复制哪个目录到哪个位置。
    if (nftw(src, fs_copydir, 64, FTW_PHYS|FTW_MOUNT|FTW_DEPTH))
        errExit("nftw");
  1. 调用nftw()遍历源目录,并将fs_copydir()作为回调函数。传递一个标志位,表示在物理上跟踪文件系统对象(而不是符号链接),同时忽略挂载点和按深度优先顺序处理文件。如果调用失败,则调用errExit()终止程序。
    // restore depth
    sftw_depth = depth;
}
  1. 恢复初始深度。

总结:
这个函数通过调用nftw()递归地遍历源目录,并将fs_copydir()作为回调函数来实现复制功能。它还负责创建目标目录并设置其属性。如果在过程中遇到任何错误,它会报告错误并退出程序。

duplicate_file

duplicate_file(const char *src, const char *dest, struct stat *s)函数用于复制一个文件。下面是该函数每一行代码的详细解释:

void duplicate_file(const char *src, const char *dest, struct stat *s) {
    assert(src);
    assert(dest);
    assert(s);
  1. 声明一个名为duplicate_file的无返回值函数,参数为源路径、目标路径和文件状态结构体指针。使用断言检查输入文件名是否非空。
    if (arg_verbose)
        printf("Copying file %s to %s\n", src, dest);
  1. 如果启用了详细模式,则输出一条消息,说明正在复制哪个文件到哪个位置。
    copy_file(src, dest, s->st_mode, s->st_uid, s->st_gid);
}
  1. 调用copy_file()将源文件复制到目标位置,并设置其模式、用户ID和组ID。

总结:
这个函数通过调用copy_file()来实现文件复制功能。如果在过程中遇到任何错误,它会报告错误并退出程序。

duplicate_link

duplicate_link(const char *src, const char *dest, struct stat *s)函数用于复制一个符号链接。下面是该函数每一行代码的详细解释:

void duplicate_link(const char *src, const char *dest, struct stat *s) {
    assert(src);
    assert(dest);
    assert(s);
  1. 声明一个名为duplicate_link的无返回值函数,参数为源路径、目标路径和文件状态结构体指针。使用断言检查输入文件名是否非空。
    if (arg_verbose)
        printf("Copying link %s to %s\n", src, dest);
  1. 如果启用了详细模式,则输出一条消息,说明正在复制哪个符号链接到哪个位置。
    copy_link(src, dest, s->st_mode, s->st_uid, s->st_gid);
}
  1. 调用copy_link()将源符号链接复制到目标位置,并设置其模式、用户ID和组ID。

总结:
这个函数通过调用copy_link()来实现符号链接复制功能。如果在过程中遇到任何错误,它会报告错误并退出程序。

main

这段代码是一个命令行工具的主函数,用于复制文件和目录。下面是每一部分代码的详细解释:

  1. 定义main()函数,参数为命令行参数个数和数组。

  2. 使用环境变量FIREJAIL_QUIET设置arg_quiet标志,如果环境变量值为"yes"的话。

  3. 使用环境变量FIREJAIL_DEBUG设置arg_debug标志,如果环境变量值为"yes"的话。

  4. 获取源路径和目标路径。根据参数个数的不同,有两种情况:(1)两个参数时,将第一个参数作为源路径,第二个参数作为目标路径,并且不跟踪符号链接;(2)四个参数时,将前两个参数忽略,第三个参数作为源路径,第四个参数作为目标路径,并且跟踪符号链接。

  5. 调用warn_dumpable()函数检查进程是否可以被其他用户dump。

  6. 删除源路径和目标路径末尾的斜杠(/)字符,并使用reject_meta_chars()函数拒绝包含特殊字符的路径。

  7. 检查目标路径是否存在,如果是目录并且可访问,则继续执行。否则,打印错误信息并退出程序。

  8. 从环境变量FIREJAIL_FILE_COPY_LIMIT中获取复制大小限制,并将其转换为字节数。

  9. 根据源路径类型调用不同的函数:(1)如果是目录,则调用duplicate_dir();(2)如果是普通文件,则调用duplicate_file();(3)如果是符号链接,则调用duplicate_link()。否则,打印错误信息并退出程序。

  10. 返回0表示程序正常结束。

总结:
这个主函数负责解析命令行参数、设置环境变量、检查路径有效性以及调用相应的函数来实现复制功能。如果在过程中遇到任何错误,它会报告错误并退出程序。

Makefile

ROOT = ../..
-include $(ROOT)/config.mk

PROG = fcopy
TARGET = $(PROG)

MOD_HDRS = ../include/common.h ../include/syscall.h
MOD_OBJS = ../lib/common.o

include $(ROOT)/src/prog.mk

这段代码是用于构建一个名为fcopy的程序的Makefile。它定义了一些变量和规则来编译和链接源文件,以便生成最终的可执行文件。下面是每一部分代码的详细解释:

  1. 定义变量ROOT,表示项目的根目录。

  2. 使用-include命令包含项目根目录下的config.mk文件。这个文件通常包含了项目的一些配置信息,例如编译器、编译选项等。

  3. 定义变量PROG,表示要构建的程序名称(即fcopy)。

  4. 定义变量TARGET,也设置为fcopy。这个变量通常用于指定Makefile的目标文件名。

  5. 定义变量MOD_HDRS,表示程序所需的模块头文件列表。在这个例子中,包含了两个头文件:../include/common.h../include/syscall.h

  6. 定义变量MOD_OBJS,表示程序所需的模块对象文件列表。在这个例子中,包含了../lib/common.o

  7. 包含项目根目录下的src/prog.mk文件。这个文件通常包含了通用的编译和链接规则,用于编译和链接程序源文件。

总结:
这个Makefile提供了编译和链接fcopy程序所需的基本设置和规则。通过包含其他Makefile文件,可以复用通用的编译和链接规则,从而简化整个构建过程。

main.c 文件总结

这个main.c程序的功能是复制文件和目录。它提供了一个命令行工具,可以将源路径下的所有文件和子目录递归地复制到目标路径下。它支持以下功能:

  1. 静默模式:通过环境变量FIREJAIL_QUIET可以启用静默模式,在此模式下,不会显示任何警告信息。

  2. 详细模式:通过环境变量FIREJAIL_DEBUG可以启用详细模式,在此模式下,会显示状态消息。

  3. 复制大小限制:通过环境变量FIREJAIL_FILE_COPY_LIMIT可以设置复制文件的大小限制,超过该限制的文件将被忽略。

  4. 跟踪符号链接:当指定--follow-link选项时,将跟踪源路径中的符号链接,并复制它们指向的实际文件或目录。

  5. 安全检查:拒绝包含特殊字符的路径,以及确保目标路径是一个有效的目录。

  6. 支持SELinux上下文句柄:使用环境变量FIREJAIL_SELINUX_CONTEXT_HANDLE可以设置SELinux上下文句柄,用于复制文件的SELinux上下文。

  7. 支持从dumpable进程中运行:如果进程可被其他用户dump,则输出一个警告。

总的来说,这个main.c程序提供了一个实用的文件复制工具,具有多种配置选项,可以在各种环境下安全、可靠地执行文件复制操作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1310931.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深度学习中的高斯分布

1 高斯分布数学表达 1.1 什么是高斯分布 高斯分布(Gaussian Distribution)又称正态分布(Normal Distribution)。高斯分布是一种重要的模型&#xff0c;其广泛应用与连续型随机变量的分布中&#xff0c;在数据分析领域中高斯分布占有重要地位。高斯分布是一个非常常见的连续概…

【Spring】03 容器

文章目录 1. 定义2. BeanFactory1&#xff09;惰性加载2&#xff09;基本的容器功能3&#xff09;XML配置 3. ApplicationContext1&#xff09;主动加载2&#xff09;AOP支持3&#xff09;事件发布与监听4&#xff09;国际化支持5&#xff09;注解支持 4. Spring容器的生命周期…

正则表达式(6):分组与后向引用

正则表达式&#xff08;6&#xff09;&#xff1a;分组与后向引用 总结 本博文转载自 在本博客中&#xff0c;”正则表达式”为一系列文章&#xff0c;如果你想要从头学习怎样在Linux中使用正则&#xff0c;可以参考此系列文章&#xff0c;直达链接如下&#xff1a; 在Linux中…

ThinkPHP连接ORACLE数据库教程

目录 概念基本步骤详细操作问题排除参考 概念 要连接Oracle数据库&#xff0c;必须有两个东西&#xff0c;一个PHP官方写的扩展&#xff0c;一个Oracle官方写的客户端PHP是通过扩展去操作oralce客户端连接的服务端数据库&#xff0c;所以两个都不能少&#xff0c;而且版本必须…

arthas统计大循环方法时的注意事项

背景 arthas是我们日常查找各种问题的利器&#xff0c;不过我们也需要意识到arthas本身也是有性能损耗的&#xff0c;所以当老板对你提问为什么使用arthas分析时这个方法比生产上正常运行时这个方法的耗时要长很多&#xff0c;你可以向他进行解释&#xff0c;进而由于arthas的…

边缘检测@获取labelme标注的json黑白图掩码mask

import cv2 as cv import numpy as np import json import os from PIL import Imagedef convertPolygonToMask(jsonfilePath):

MySQL——数据类型

目录 一.数据类型分类 二. 数值类型 1.tinyint类型 2.bit类型 3.float类型 4.decimal 三.字符串类型 1.char 2.varchar 四.日期和时间类型 五.enum和set 一.数据类型分类 关于数据库的数据类型有非常多&#xff0c;但是并非所有的数据类型都是我们常用的&#xff…

《信息技术时代》期刊杂志论文发表投稿

《信息技术时代》期刊收稿方向&#xff1a;通信工程、大数据、计算机、办公自动化、信息或计算机教育、电子技术、系统设计、移动信息、图情信息研究、人工智能、智能技术、信息技术与网络安全等。 刊名&#xff1a;信息技术时代 主管主办单位&#xff1a;深圳湾科技发展有限…

vs2019比较两个代码的区别方法

vs2019比较两个代码的区别方法 效果代码 效果 代码 Tools.DiffFiles 3d_mig(1).c 3d_mig_xin0.c

UDP内网穿透和打洞原理与代码实现

1、众所周知&#xff0c;现在主流网络用的还是IPV4协议&#xff0c;理论上一共有2^3243亿个地址&#xff0c;除去私有网段、网络ID、广播ID、保留网段、本地环回127.0.0.0网段、组播224.0.0.0网段、实际可用就是36.47亿个&#xff1b;全球的服务器、PC机、手机、物联网设备等需…

08.CSS盒模型

CSS盒模型 1.介绍 CSS 会把所有的 HTML 元素都看成一个盒子&#xff0c;所有的样式也都是基于这个盒子 2.盒模型构成 介绍 margin&#xff08;外边距&#xff09;&#xff1a;盒子与外界的距离border&#xff08;边框&#xff09;&#xff1a;盒子的边框padding&#xff0…

美国如果把根域名服务器封了,中国不会从网络上消失

目录 美国如果把根域名服务器封了&#xff0c;中国不会从网络上消失为什么根服务器最多13个 输入URL后发生了什么 参考 https://www.yuque.com/fcant/network/vhyvik#AuOqk 美国如果把根域名服务器封了&#xff0c;中国不会从网络上消失 来源&#xff1a; https://segmentfau…

TwinCAT3 Modbus-TCP Client/Server使用

目录 一、环境配置和准备 1、PLC中安装TF6250-Modbus-TCP库 2、勾选TF6250的license 3、PLC工程中添加Tc2_ModbusSrv库文件 4、分别创建测试ModbusTCP测试的Server和Client程序 二、PLC作为Client端 1、设置测试电脑IP地址 2、运行MobusTCP测试工具 3、PLC端程序编写 …

基于C/C++的rapidxml加载xml大文件 - 下部分

下载地址: RapidXml (sourceforge.net)https://rapidxml.sourceforge.net/ 将源码添加到自己的工程中 示例测试大文件耗时: 总共293w行数据&#xff0c;大概耗时不到1s。

将创建表字段语句快速转换成golang struct字段

用网页jquery快速生成 本地建立 struct.html <!DOCTYPE html> <html> <head> <meta charset"utf-8"> <title>leo-转换</title> <script src"https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></s…

基于YOLOv7算法的高精度实时足球目标检测识别系统(PyTorch+Pyside6+YOLOv7)

摘要&#xff1a;基于YOLOv7算法的高精度实时足球目标检测系统可用于日常生活中检测与定位足球目标&#xff0c;此系统可完成对输入图片、视频、文件夹以及摄像头方式的目标检测与识别&#xff0c;同时本系统还支持检测结果可视化与导出。本系统采用YOLOv7目标检测算法来训练数…

scripty妙用

在monorepo项目中&#xff0c;随着子模块增多&#xff0c; 每个子项目都需要配置各自的package.json,并且大同小异&#xff0c;为了进一步提高配置效率&#xff0c;引入了scripty&#xff0c;自己写脚本&#xff0c;直接就可以用哦 1、安装 npm install scripty --save-dev 2…

【LeetCode刷题笔记(4)】【Python】【移动零】【简单】

文章目录 题目描述示例 1示例 2提示 解决方案题意拆解双指针算法双指针法的主要优点双指针法的使用场景举例&#xff1a; 解决方案&#xff1a;【双指针一次遍历】解题心得方案代码运行结果复杂度分析 结束语 移动零 题目描述 给定一个数组 nums&#xff0c;编写一个函数将所…

【Python】conda镜像配置,.condarc文件详解,channel镜像

1. conda 环境 安装miniconda即可&#xff0c;Miniconda 安装包可以到 http://mirrors.aliyun.com/anaconda/miniconda/ 下载。 .condarc是conda 应用程序的配置文件&#xff0c;在用户家目录&#xff08;windows&#xff1a;C:\users\username\&#xff09;&#xff0c;用于…

Peter算法小课堂—简单建模(2)

太戈编程736题 题目描述&#xff1a; 你是一只汪星人&#xff0c;地球毁灭后你回到了汪星&#xff0c;这里每天有n个小时&#xff0c;你需要为自己选择正好连续的m小时作为每天睡眠的时间。从凌晨开始&#xff0c;第i小时内的睡眠质量为xi&#xff0c;请问经过选择后&#xf…