aboutsummaryrefslogtreecommitdiffstats
path: root/copy.c
blob: 3df156f6cea04b3eed87eb396ee14a968077b4ad (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#include "git-compat-util.h"
#include "copy.h"
#include "path.h"
#include "gettext.h"
#include "strbuf.h"
#include "abspath.h"

int copy_fd(int ifd, int ofd)
{
	while (1) {
		char buffer[8192];
		ssize_t len = xread(ifd, buffer, sizeof(buffer));
		if (!len)
			break;
		if (len < 0)
			return COPY_READ_ERROR;
		if (write_in_full(ofd, buffer, len) < 0)
			return COPY_WRITE_ERROR;
	}
	return 0;
}

static int copy_times(const char *dst, const char *src)
{
	struct stat st;
	struct utimbuf times;
	if (stat(src, &st) < 0)
		return -1;
	times.actime = st.st_atime;
	times.modtime = st.st_mtime;
	if (utime(dst, &times) < 0)
		return -1;
	return 0;
}

int copy_file(const char *dst, const char *src, int mode)
{
	int fdi, fdo, status;

	mode = (mode & 0111) ? 0777 : 0666;
	if ((fdi = open(src, O_RDONLY)) < 0)
		return fdi;
	if ((fdo = open(dst, O_WRONLY | O_CREAT | O_EXCL, mode)) < 0) {
		close(fdi);
		return fdo;
	}
	status = copy_fd(fdi, fdo);
	switch (status) {
	case COPY_READ_ERROR:
		error_errno("copy-fd: read returned");
		break;
	case COPY_WRITE_ERROR:
		error_errno("copy-fd: write returned");
		break;
	}
	close(fdi);
	if (close(fdo) != 0)
		return error_errno("%s: close error", dst);

	if (!status && adjust_shared_perm(dst))
		return -1;

	return status;
}

int copy_file_with_time(const char *dst, const char *src, int mode)
{
	int status = copy_file(dst, src, mode);
	if (!status)
		return copy_times(dst, src);
	return status;
}

static int do_symlinks_match(const char *path1, const char *path2)
{
	struct strbuf buf1 = STRBUF_INIT, buf2 = STRBUF_INIT;
	int ret = 0;

	if (!strbuf_readlink(&buf1, path1, 0) &&
	    !strbuf_readlink(&buf2, path2, 0))
		ret = !strcmp(buf1.buf, buf2.buf);

	strbuf_release(&buf1);
	strbuf_release(&buf2);
	return ret;
}

int do_files_match(const char *path1, const char *path2)
{
	struct stat st1, st2;
	int fd1 = -1, fd2 = -1, ret = 1;
	char buf1[8192], buf2[8192];

	if ((fd1 = open_nofollow(path1, O_RDONLY)) < 0 ||
	    fstat(fd1, &st1) || !S_ISREG(st1.st_mode)) {
		if (fd1 < 0 && errno == ELOOP)
			/* maybe this is a symbolic link? */
			return do_symlinks_match(path1, path2);
		ret = 0;
	} else if ((fd2 = open_nofollow(path2, O_RDONLY)) < 0 ||
		   fstat(fd2, &st2) || !S_ISREG(st2.st_mode)) {
		ret = 0;
	}

	if (ret)
		/* to match, neither must be executable, or both */
		ret = !(st1.st_mode & 0111) == !(st2.st_mode & 0111);

	if (ret)
		ret = st1.st_size == st2.st_size;

	while (ret) {
		ssize_t len1 = read_in_full(fd1, buf1, sizeof(buf1));
		ssize_t len2 = read_in_full(fd2, buf2, sizeof(buf2));

		if (len1 < 0 || len2 < 0 || len1 != len2)
			ret = 0; /* read error or different file size */
		else if (!len1) /* len2 is also 0; hit EOF on both */
			break; /* ret is still true */
		else
			ret = !memcmp(buf1, buf2, len1);
	}

	if (fd1 >= 0)
		close(fd1);
	if (fd2 >= 0)
		close(fd2);

	return ret;
}