Mercurial > hg > toybox
comparison toys/posix/cp.c @ 783:7bbb49149bb6
Adapt cp to updated dirtree code.
author | Rob Landley <rob@landley.net> |
---|---|
date | Mon, 07 Jan 2013 21:28:46 -0600 |
parents | 786841fdb1e0 |
children | d8b2f7706f82 |
comparison
equal
deleted
inserted
replaced
782:3d7526f6115b | 783:7bbb49149bb6 |
---|---|
1 /* Copyright 2008 Rob Landley <rob@landley.net> | 1 /* Copyright 2008 Rob Landley <rob@landley.net> |
2 * | 2 * |
3 * See http://opengroup.org/onlinepubs/9699919799/utilities/cp.html | 3 * See http://opengroup.org/onlinepubs/9699919799/utilities/cp.html |
4 * | 4 * |
5 * TODO: "R+ra+d+p+r" sHLPR | 5 * TODO: sHLP |
6 | 6 |
7 USE_CP(NEWTOY(cp, "<2"USE_CP_MORE("rdavsl")"RHLPfip", TOYFLAG_BIN)) | 7 USE_CP(NEWTOY(cp, "<2"USE_CP_MORE("rdavsl")"RHLPfip", TOYFLAG_BIN)) |
8 | 8 |
9 config CP | 9 config CP |
10 bool "cp (broken by dirtree changes)" | 10 bool "cp" |
11 default n | 11 default y |
12 help | 12 help |
13 usage: cp [-fipRHLP] SOURCE... DEST | 13 usage: cp [-fipRHLP] SOURCE... DEST |
14 | 14 |
15 Copy files from SOURCE to DEST. If more than one SOURCE, DEST must | 15 Copy files from SOURCE to DEST. If more than one SOURCE, DEST must |
16 be a directory. | 16 be a directory. |
43 | 43 |
44 // TODO: PLHlsd | 44 // TODO: PLHlsd |
45 | 45 |
46 GLOBALS( | 46 GLOBALS( |
47 char *destname; | 47 char *destname; |
48 int destisdir; | 48 struct stat top; |
49 int keep_symlinks; | |
50 ) | 49 ) |
51 | 50 |
52 // Copy an individual file or directory to target. | 51 // Callback from dirtree_read() for each file/directory under a source dir. |
53 | 52 |
54 void cp_file(char *src, char *dst, struct stat *srcst) | 53 int cp_node(struct dirtree *try) |
55 { | 54 { |
56 int fdout = -1; | 55 int fdout, cfd = try->parent ? try->parent->extra : AT_FDCWD, |
56 tfd = dirtree_parentfd(try); | |
57 char *catch = try->parent ? try->name : TT.destname, *err = "%s"; | |
58 struct stat cst; | |
57 | 59 |
58 // -i flag is specified and dst file exists. | 60 if (!dirtree_notdotdot(try)) return 0; |
59 if ((toys.optflags&FLAG_i) && !access(dst, R_OK) | |
60 && !yesno("cp: overwrite", 1)) | |
61 return; | |
62 | 61 |
63 if (toys.optflags & FLAG_v) printf("'%s' -> '%s'\n", src, dst); | 62 // If returning from COMEAGAIN, jump straight to -p logic at end. |
63 if (S_ISDIR(try->st.st_mode) && try->data == -1) { | |
64 fdout = try->extra; | |
65 err = 0; | |
66 goto dashp; | |
67 } | |
68 | |
69 // Detect recursive copies via repeated top node (cp -R .. .) or | |
70 // identical source/target (fun with hardlinks). | |
71 if ((TT.top.st_dev == try->st.st_dev && TT.top.st_ino == try->st.st_ino | |
72 && (catch = TT.destname)) | |
73 || (!fstatat(cfd, catch, &cst, 0) && cst.st_dev == try->st.st_dev | |
74 && cst.st_ino == try->st.st_ino)) | |
75 { | |
76 char *s = dirtree_path(try, 0); | |
77 error_msg("'%s' is '%s'", catch, s); | |
78 free(s); | |
79 | |
80 return 0; | |
81 } | |
82 | |
83 // Handle -i and -v | |
84 | |
85 if ((toys.optflags & FLAG_i) && !faccessat(cfd, catch, R_OK, 0) | |
86 && !yesno("cp: overwrite", 1)) return 0; | |
87 | |
88 if (toys.optflags & FLAG_v) { | |
89 char *s = dirtree_path(try, 0); | |
90 printf("cp '%s'\n", s); | |
91 free(s); | |
92 } | |
64 | 93 |
65 // Copy directory or file to destination. | 94 // Copy directory or file to destination. |
66 | 95 |
67 if (S_ISDIR(srcst->st_mode)) { | 96 if (S_ISDIR(try->st.st_mode)) { |
68 struct stat st2; | 97 struct stat st2; |
98 | |
99 if (!(toys.optflags & (FLAG_a|FLAG_r|FLAG_R))) { | |
100 err = "Skipped dir '%s'"; | |
101 catch = try->name; | |
69 | 102 |
70 // Always make directory writeable to us, so we can create files in it. | 103 // Always make directory writeable to us, so we can create files in it. |
71 // | 104 // |
72 // Yes, there's a race window between mkdir() and open() so it's | 105 // Yes, there's a race window between mkdir() and open() so it's |
73 // possible that -p can be made to chown a directory other than the one | 106 // possible that -p can be made to chown a directory other than the one |
74 // we created. The closest we can do to closing this is make sure | 107 // we created. The closest we can do to closing this is make sure |
75 // that what we open _is_ a directory rather than something else. | 108 // that what we open _is_ a directory rather than something else. |
76 | 109 |
77 if ((mkdir(dst, srcst->st_mode | 0200) && errno != EEXIST) | 110 } else if ((mkdirat(cfd, catch, try->st.st_mode | 0200) && errno != EEXIST) |
78 || 0>(fdout=open(dst, 0)) || fstat(fdout, &st2) || !S_ISDIR(st2.st_mode)) | 111 || 0>(try->extra = openat(cfd, catch, 0)) || fstat(try->extra, &st2) |
79 { | 112 || !S_ISDIR(st2.st_mode)); |
80 perror_exit("mkdir '%s'", dst); | 113 else return DIRTREE_COMEAGAIN; |
81 } | 114 } else if (S_ISLNK(try->st.st_mode) |
82 } else if (TT.keep_symlinks && S_ISLNK(srcst->st_mode)) { | 115 && (try->parent || (toys.optflags & (FLAG_a|FLAG_d)))) |
83 char *link = xreadlink(src); | 116 { |
84 | 117 int i = readlinkat(tfd, try->name, toybuf, sizeof(toybuf)); |
85 // Note: -p currently has no effect on symlinks. How do you get a | 118 if (i > 0 && i < sizeof(toybuf) && !symlinkat(toybuf, cfd, catch)) err = 0; |
86 // filehandle to them? O_NOFOLLOW causes the open to fail. | |
87 if (!link || symlink(link, dst)) perror_msg("link '%s'", dst); | |
88 free(link); | |
89 return; | |
90 } else if (toys.optflags & FLAG_l) { | 119 } else if (toys.optflags & FLAG_l) { |
91 if (link(src, dst)) perror_msg("link '%s'"); | 120 if (!linkat(tfd, try->name, cfd, catch, 0)) err = 0; |
92 return; | |
93 } else { | 121 } else { |
94 int fdin, i; | 122 int fdin, i; |
95 | 123 |
96 fdin = xopen(src, O_RDONLY); | 124 fdin = openat(tfd, try->name, O_RDONLY); |
97 for (i=2 ; i; i--) { | 125 if (fdin < 0) catch = try->name; |
98 fdout = open(dst, O_RDWR|O_CREAT|O_TRUNC, srcst->st_mode); | 126 else { |
99 if (fdout>=0 || !(toys.optflags & FLAG_f)) break; | 127 for (i=2 ; i; i--) { |
100 unlink(dst); | 128 fdout = openat(cfd, catch, O_RDWR|O_CREAT|O_TRUNC, try->st.st_mode); |
129 if (fdout>=0 || !(toys.optflags & FLAG_f)) break; | |
130 unlinkat(cfd, catch, 0); | |
131 } | |
132 if (fdout >= 0) { | |
133 xsendfile(fdin, fdout); | |
134 err = 0; | |
135 } | |
136 close(fdin); | |
101 } | 137 } |
102 if (fdout<0) perror_exit("%s", dst); | 138 |
103 xsendfile(fdin, fdout); | 139 dashp: |
104 close(fdin); | 140 if (toys.optflags & (FLAG_a|FLAG_p)) { |
141 struct timespec times[2]; | |
142 | |
143 // Inability to set these isn't fatal, some require root access. | |
144 | |
145 fchown(fdout, try->st.st_uid, try->st.st_gid); | |
146 times[0] = try->st.st_atim; | |
147 times[1] = try->st.st_mtim; | |
148 futimens(fdout, times); | |
149 fchmod(fdout, try->st.st_mode); | |
150 } | |
151 | |
152 xclose(fdout); | |
105 } | 153 } |
106 | 154 |
107 // Inability to set these isn't fatal, some require root access. | 155 if (err) perror_msg(err, catch); |
108 // Can't do fchmod() etc here because -p works on mkdir, too. | |
109 | |
110 if (toys.optflags & (FLAG_p|FLAG_a)) { | |
111 int mask = umask(0); | |
112 struct utimbuf ut; | |
113 | |
114 (void) fchown(fdout,srcst->st_uid, srcst->st_gid); | |
115 ut.actime = srcst->st_atime; | |
116 ut.modtime = srcst->st_mtime; | |
117 utime(dst, &ut); | |
118 umask(mask); | |
119 } | |
120 xclose(fdout); | |
121 } | |
122 | |
123 // Callback from dirtree_read() for each file/directory under a source dir. | |
124 | |
125 int cp_node(struct dirtree *node) | |
126 { | |
127 char *path = dirtree_path(node, 0); // TODO: use openat() instead | |
128 char *s = path+strlen(path); | |
129 struct dirtree *n; | |
130 | |
131 // Find appropriate chunk of path for destination. | |
132 | |
133 n = node; | |
134 if (!TT.destisdir) n = n->parent; | |
135 for (;;n = n->parent) { | |
136 while (s!=path) if (*(--s)=='/') break; | |
137 if (!n) break; | |
138 } | |
139 if (s != path) s++; | |
140 | |
141 s = xmsprintf("%s/%s", TT.destname, s); | |
142 cp_file(path, s, &(node->st)); | |
143 free(s); | |
144 free(path); // redo this whole darn function. | |
145 | |
146 return 0; | 156 return 0; |
147 } | 157 } |
148 | 158 |
149 void cp_main(void) | 159 void cp_main(void) |
150 { | 160 { |
151 char *dpath = NULL; | 161 char *destname = toys.optargs[--toys.optc]; |
152 struct stat st, std; | 162 int i, destdir = !stat(destname, &TT.top) && S_ISDIR(TT.top.st_mode); |
153 int i; | |
154 | 163 |
155 // Identify destination | 164 if (toys.optc>1 && !destdir) error_exit("'%s' not directory", destname); |
156 | |
157 if (!stat(TT.destname, &std) && S_ISDIR(std.st_mode)) TT.destisdir++; | |
158 else if (toys.optc>1) error_exit("'%s' not directory", TT.destname); | |
159 | |
160 // TODO: This is too early: we haven't created it yet if we need to | |
161 if (toys.optflags & (FLAG_R|FLAG_r|FLAG_a)) | |
162 dpath = realpath(TT.destname = toys.optargs[--toys.optc], NULL); | |
163 | 165 |
164 // Loop through sources | 166 // Loop through sources |
165 | 167 |
166 for (i=0; i<toys.optc; i++) { | 168 for (i=0; i<toys.optc; i++) { |
167 char *dst, *src = toys.optargs[i]; | 169 struct dirtree *new; |
170 char *src = toys.optargs[i]; | |
168 | 171 |
169 // Skip src==dest (TODO check inodes to catch "cp blah ./blah"). | 172 // Skip nonexistent sources |
170 | 173 if (!(new = dirtree_add_node(0, src, !(toys.optflags & (FLAG_d|FLAG_a))))) |
171 if (!strncmp(src, TT.destname)) continue; | |
172 | |
173 // Skip nonexistent sources. | |
174 | |
175 TT.keep_symlinks = toys.optflags & (FLAG_d|FLAG_a); | |
176 if (TT.keep_symlinks ? lstat(src, &st) : stat(src, &st) | |
177 || (st.st_dev = dst.st_dev && st.st_ino == dst.dst_ino)) | |
178 { | |
179 objection: | |
180 perror_msg("bad '%s'", src); | 174 perror_msg("bad '%s'", src); |
181 toys.exitval = 1; | 175 else { |
182 continue; | 176 if (destdir) TT.destname = xmsprintf("%s/%s", destname, basename(src)); |
177 else TT.destname = destname; | |
178 dirtree_handle_callback(new, cp_node); | |
179 if (destdir) free(TT.destname); | |
183 } | 180 } |
184 | |
185 // Copy directory or file. | |
186 | |
187 if (TT.destisdir) { | |
188 char *s; | |
189 | |
190 // Catch "cp -R .. ." and friends that would go on forever | |
191 if (dpath && (s = realpath(src, NULL)) { | |
192 int i = strlen(s); | |
193 i = (!strncmp(s, dst, i) && (!s[i] || s[i]=='/')); | |
194 free(s); | |
195 | |
196 if (i) goto objection; | |
197 } | |
198 | |
199 // Create destination filename within directory | |
200 dst = strrchr(src, '/'); | |
201 if (dst) dst++; | |
202 else dst=src; | |
203 dst = xmsprintf("%s/%s", TT.destname, dst); | |
204 } else dst = TT.destname; | |
205 | |
206 if (S_ISDIR(st.st_mode)) { | |
207 if (toys.optflags & (FLAG_r|FLAG_R|FLAG_a)) { | |
208 cp_file(src, dst, &st); | |
209 | |
210 TT.keep_symlinks++; | |
211 dirtree_read(src, cp_node); | |
212 } else error_msg("Skipped dir '%s'", src); | |
213 } else cp_file(src, dst, &st); | |
214 if (TT.destisdir) free(dst); | |
215 } | 181 } |
216 | |
217 if (CFG_TOYBOX_FREE) free(dpath); | |
218 return; | |
219 } | 182 } |