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 }