1: /* Fastinit (finit) copyfile() implementation.
2: *
3: * Copyright (c) 2008 Claudio Matsuoka <http://helllabs.org/finit/>
4: *
5: * Permission is hereby granted, free of charge, to any person obtaining a copy
6: * of this software and associated documentation files (the "Software"), to deal
7: * in the Software without restriction, including without limitation the rights
8: * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9: * copies of the Software, and to permit persons to whom the Software is
10: * furnished to do so, subject to the following conditions:
11: *
12: * The above copyright notice and this permission notice shall be included in
13: * all copies or substantial portions of the Software.
14: *
15: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16: * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17: * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18: * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19: * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20: * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21: * THE SOFTWARE.
22: */
23:
24: #include <errno.h>
25: #include <fcntl.h>
26: #include <stdio.h>
27: #include <stdlib.h>
28: #include <sys/stat.h>
29:
30: #include "lite.h"
31:
32:
33: /* Tests if dst is a directory, if so, reallocates dst and appends src filename returning 1 */
34: static int adjust_target(char *src, char **dst)
35: {
36: int isdir = 0;
37:
38: if (fisdir(*dst)) {
39: int slash = 0;
40: char *tmp, *ptr = strrchr(src, '/');
41:
42: if (!ptr)
43: ptr = src;
44: else
45: ptr++;
46:
47: tmp = malloc(strlen(*dst) + strlen(ptr) + 2);
48: if (!tmp) {
49: errno = EISDIR;
50: return 0;
51: }
52:
53: isdir = 1; /* Free dst before exit! */
54: slash = fisslashdir(*dst);
55:
56: sprintf(tmp, "%s%s%s", *dst, slash ? "" : "/", ptr);
57: *dst = tmp;
58: }
59:
60: return isdir;
61: }
62:
63: /* Actual copy loop, used by both copyfile() and fcopyfile()
64: * breaks loop on error and EOF */
65: static ssize_t do_copy(int in, int out, size_t num, char *buffer, size_t len)
66: {
67: ssize_t ret = 0, size = 0;
68:
69: do {
70: size_t count = num > len ? len : num;
71:
72: ret = read(in, buffer, count);
73: if (ret <= 0) {
74: if (ret == -1 && EINTR == errno)
75: continue;
76: break;
77: }
78:
79: if (ret > 0)
80: size += write(out, buffer, ret);
81: num -= count;
82: } while (num > 0);
83:
84: return size;
85: }
86:
87: /**
88: * copyfile - Copy a file to another.
89: * @src: Full path name to source file.
90: * @dst: Full path name to target file.
91: * @len: Number of bytes to copy, zero (0) for entire file.
92: * @sym: Should symlinks be recreated (1) or followed (0)
93: *
94: * This is a C implementation of the command line cp(1) utility. It is one
95: * of the classic missing links in the UNIX C library. This version is from
96: * the finit project, http://helllabs.org/finit/, which is a reimplementation
97: * of fastinit for the Asus EeePC.
98: *
99: * Returns:
100: * The number of bytes copied, zero may be error (check errno!), but it
101: * may also indicate that @src was empty. If @src is a directory @errno
102: * will be set to %EISDIR since copyfile() is not recursive.
103: */
104: ssize_t copyfile(char *src, char *dst, int len, int sym)
105: {
106: char *buffer;
107: int in, out, isdir = 0, saved_errno = 0;
108: size_t num;
109: ssize_t size = 0;
110: struct stat st;
111:
112: errno = 0;
113:
114: buffer = malloc(BUFSIZ);
115: if (!buffer)
116: return 0;
117:
118: if (fisdir(src)) {
119: saved_errno = EISDIR; /* Error: source is a directory */
120: goto exit;
121: }
122:
123: /* Check if target is a directory, then append src filename. */
124: isdir = adjust_target(src, &dst);
125:
126: /* Check if the source file is a symlink ... */
127: if (stat(src, &st)) {
128: size = -1;
129: goto exit;
130: }
131:
132: /* ... only try readlink() if symlink and @sym is set. */
133: if (sym && (st.st_mode & S_IFMT) == S_IFLNK) {
134: size = readlink(src, buffer, BUFSIZ);
135: if (size > 0) {
136: if (size >= (ssize_t)BUFSIZ) {
137: saved_errno = ENOBUFS;
138: size = -1;
139: } else {
140: buffer[size] = 0;
141: size = !symlink(buffer, dst);
142: }
143: }
144:
145: /* Don't fall back to copy, that is a potentially
146: * dangerous race condition, see CWE-367. */
147: goto exit;
148: }
149:
150: /* 0: copy entire file */
151: if (len == 0)
152: num = st.st_size;
153: else
154: num = (size_t)len;
155:
156: in = open(src, O_RDONLY);
157: if (in < 0) {
158: saved_errno = errno;
159: goto exit;
160: }
161:
162: out = open(dst, O_WRONLY | O_CREAT | O_TRUNC, fmode(src));
163: if (out < 0) {
164: close(in);
165: saved_errno = errno;
166: goto exit;
167: }
168:
169: size = do_copy(in, out, num, buffer, BUFSIZ);
170: if (!size && errno)
171: saved_errno = errno;
172:
173: close(out);
174: close(in);
175:
176: exit:
177: free(buffer);
178: if (isdir)
179: free(dst);
180: errno = saved_errno;
181:
182: return size;
183: }
184:
185: /**
186: * movefile - Move a file to another location
187: * @src: Source file.
188: * @dst: Target file, or location.
189: *
190: * This is a C implementation of the command line mv(1) utility.
191: * Usually the rename() API is sufficient, but not when moving across
192: * file system boundaries.
193: *
194: * The @src argument must include the full path to the source file,
195: * whereas the @dst argument may only be a directory, in which case the
196: * same file name from @src is used.
197: *
198: * Returns:
199: * POSIX OK(0), or non-zero with errno set.
200: */
201: int movefile(char *src, char *dst)
202: {
203: int isdir, result = 0;
204:
205: /* Check if target is a directory, then append the src base filename. */
206: isdir = adjust_target(src, &dst);
207:
208: if (rename(src, dst)) {
209: if (errno == EXDEV) {
210: errno = 0;
211: copyfile(src, dst, 0, 1);
212: if (errno)
213: result = 1;
214: else
215: result = remove(src);
216: } else {
217: result = 1;
218: }
219: }
220:
221: if (isdir)
222: free(dst);
223:
224: return result;
225: }
226:
227: /**
228: * fcopyfile - Copy between FILE *fp.
229: * @src: Source FILE.
230: * @dst: Destination FILE.
231: *
232: * Function takes signals into account and will restart the syscalls as
233: * long as error is %EINTR.
234: *
235: * Returns:
236: * POSIX OK(0), or non-zero with errno set on error.
237: */
238: int fcopyfile(FILE *src, FILE *dst)
239: {
240: int ret;
241: char *buffer;
242:
243: if (!src || !dst) {
244: errno = EINVAL;
245: return -1;
246: }
247:
248: buffer = malloc(BUFSIZ);
249: if (!buffer)
250: return -1;
251:
252: ret = do_copy(fileno(src), fileno(dst), BUFSIZ, buffer, BUFSIZ);
253: if (ret > 0)
254: ret = 0;
255: else if (errno)
256: ret = -1;
257:
258: free(buffer);
259:
260: return ret;
261: }
262:
263: #ifdef UNITTEST
264: #include <err.h>
265:
266: int main(void)
267: {
268: int i = 0;
269: char *files[] = {
270: "/etc/passwd", "/tmp/tok", "/tmp/tok",
271: "/etc/passwd", "/tmp/", "/tmp/passwd",
272: "/etc/passwd", "/tmp", "/tmp/passwd",
273: NULL
274: };
275: FILE *src, *dst;
276:
277: printf("=>Start testing fcopyfile()\n");
278: while (files[i]) {
279: printf("fcopyfile(%s, %s)\t", files[i], files[i + 1]);
280: src = fopen(files[i], "r");
281: dst = fopen(files[i + 1], "w");
282: if (fcopyfile(src, dst)) {
283: if (!fisdir(files[i + 1]))
284: err(1, "Failed fcopyfile(%s, %s)", files[i], files[i + 1]);
285: }
286:
287: if (src)
288: fclose(src);
289: if (dst)
290: fclose(dst);
291:
292: if (fexist(files[i + 2]))
293: printf("OK => %s", files[i + 2]);
294: printf("\n");
295:
296: erase(files[i + 2]);
297: i += 3;
298: }
299:
300: printf("\n=>Start testing copyfile()\n");
301: i = 0;
302: while (files[i]) {
303: printf("copyfile(%s, %s)\t", files[i], files[i + 1]);
304: if (!copyfile(files[i], files[i + 1], 0, 0))
305: err(1, "Failed copyfile(%s, %s)", files[i], files[i + 1]);
306:
307: if (fexist(files[i + 2]))
308: printf("OK => %s", files[i + 2]);
309: printf("\n");
310:
311: erase(files[i + 2]);
312: i += 3;
313: }
314:
315: return 0;
316: }
317: #endif /* UNITTEST */
318:
319: /**
320: * Local Variables:
321: * compile-command: "make V=1 -f copyfile.mk"
322: * version-control: t
323: * indent-tabs-mode: t
324: * c-file-style: "linux"
325: * End:
326: */
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>