xref: /inferno-os/appl/cmd/cp.b (revision 37da2899f40661e3e9631e497da8dc59b971cbd0)
1implement Cp;
2
3include "sys.m";
4	sys: Sys;
5
6include "draw.m";
7include "arg.m";
8
9include "readdir.m";
10	readdir: Readdir;
11
12Cp: module
13{
14	init:	fn(nil: ref Draw->Context, args: list of string);
15};
16
17stderr: ref Sys->FD;
18errors := 0;
19gflag := 0;
20uflag := 0;
21xflag := 0;
22
23init(nil: ref Draw->Context, args: list of string)
24{
25	sys = load Sys Sys->PATH;
26	stderr = sys->fildes(2);
27
28	arg := load Arg Arg->PATH;
29	recursive := 0;
30	arg->init(args);
31	arg->setusage("\tcp [-gux] src target\n\tcp [-r] [-gux] src ... directory");
32	while((opt := arg->opt()) != 0)
33		case opt {
34		'r' =>	recursive = 1;
35		'g' => gflag = 1;
36		'u' => uflag = gflag = 1;
37		'x' => xflag = 1;
38		* =>	arg->usage();
39		}
40	args = arg->argv();
41	argc := len args;
42	if(argc < 2)
43		arg->usage();
44	arg = nil;
45
46	dst: string;
47	for(t := args; t != nil; t = tl t)
48		dst = hd t;
49
50	(ok, dir) := sys->stat(dst);
51	todir := (ok != -1 && (dir.mode & Sys->DMDIR));
52	if(argc > 2 && !todir){
53		sys->fprint(stderr, "cp: %s  not a directory\n", dst);
54		raise "fail:error";
55	}
56	if(recursive)
57		cpdir(args, dst);
58	else{
59		for(; tl args != nil; args = tl args){
60			if(todir)
61				cp(hd args, dst, basename(hd args));
62			else
63				cp(hd args, dst, nil);
64		}
65	}
66	if(errors)
67		raise "fail:error";
68}
69
70basename(s: string): string
71{
72	for((nil, ls) := sys->tokenize(s, "/"); ls != nil; ls = tl ls)
73		s = hd ls;
74	return s;
75}
76
77cp(src, dst: string, newname: string)
78{
79	dd: Sys->Dir;
80
81	if(newname != nil)
82		dst += "/" + newname;
83	(ok, ds) := sys->stat(src);
84	if(ok < 0){
85		warning(sys->sprint("%s: %r", src));
86		return;
87	}
88	if(ds.mode & Sys->DMDIR){
89		warning(src + " is a directory");
90		return;
91	}
92	(ok, dd) = sys->stat(dst);
93	if(ok != -1 && samefile(ds, dd)){
94		warning(src + " and " + dst + " are the same file");
95		return;
96	}
97	sfd := sys->open(src, Sys->OREAD);
98	if(sfd == nil){
99		warning(sys->sprint("cannot open %s: %r", src));
100		return;
101	}
102	dfd := sys->create(dst, Sys->OWRITE, ds.mode & 8r777);
103	if(dfd == nil){
104		warning(sys->sprint("cannot create %s: %r", dst));
105		return;
106	}
107	if(copy(sfd, dfd, src, dst)!=0)
108		return;
109	if(wstat(dfd, ds, 0) < 0)
110		warning(sys->sprint("can't wstat %s: %r", src));
111}
112
113copy(sfd, dfd: ref Sys->FD, src, dst: string): int
114{
115	buf := array[Sys->ATOMICIO] of byte;
116	while((r := sys->read(sfd, buf, len buf)) > 0){
117		if(sys->write(dfd, buf, r) != r){
118			warning(sys->sprint("error writing %s: %r", dst));
119			return -1;
120		}
121	}
122	if(r < 0){
123		warning(sys->sprint("error reading %s: %r", src));
124		return -1;
125	}
126	return 0;
127}
128
129cpdir(args: list of string, dst: string)
130{
131	readdir = load Readdir Readdir->PATH;
132	if(readdir == nil){
133		sys->fprint(stderr, "cp: cannot load %s: %r\n", Readdir->PATH);
134		raise "fail:bad module";
135	}
136	cache = array[NCACHE] of list of ref Sys->Dir;
137	dexists := 0;
138	(ok, dd) := sys->stat(dst);
139	# destination file exists
140	if(ok != -1){
141		if((dd.mode & Sys->DMDIR) == 0){
142			warning(dst + ": destination not a directory");
143			return;
144		}
145		dexists = 1;
146	}
147	for(; tl args != nil; args = tl args){
148		ds: Sys->Dir;
149		src := hd args;
150		(ok, ds) = sys->stat(src);
151		if(ok < 0){
152			warning(sys->sprint("can't stat %s: %r", src));
153			continue;
154		}
155		if((ds.mode & Sys->DMDIR) == 0){
156			cp(hd args, dst, basename(hd args));
157		} else if(dexists){
158			if(samefile(ds, dd)){
159				warning("cannot copy " + src + " into itself");
160				continue;
161			}
162			copydir(src, dst + "/" + basename(src), ds);
163		} else
164			copydir(src, dst, ds);
165	}
166}
167
168copydir(src, dst: string, srcd: Sys->Dir)
169{
170	(ok, nil) := sys->stat(dst);
171	if(ok != -1){
172		warning("cannot copy " + src + " onto another directory");
173		return;
174	}
175	tmode := srcd.mode | 8r777;	# Fix for Nt
176	dfd := sys->create(dst, Sys->OREAD, Sys->DMDIR | tmode);
177	if(dfd == nil){
178		warning(sys->sprint("cannot make directory %s: %r", dst));
179		return;
180	}
181	(entries, n) := readdir->init(src, Readdir->COMPACT);
182	for(i := 0; i < n; i++){
183		e := entries[i];
184		path := src + "/" + e.name;
185		if((e.mode & Sys->DMDIR) == 0)
186			cp(path, dst, e.name);
187		else if(seen(e))
188			warning(path + ": directory loop found");
189		else
190			copydir(path, dst + "/" + e.name, *e);
191	}
192	if(wstat(dfd, srcd, 1) < 0)
193		warning(sys->sprint("can't wstat %s: %r", dst));
194}
195
196wstat(dfd: ref Sys->FD, ds: Sys->Dir, mflag: int): int
197{
198	if(!xflag && !gflag && !uflag && !mflag)
199		return 0;
200	d := sys->nulldir;
201	if(xflag)
202		d.mtime = ds.mtime;
203	if(xflag || mflag)
204		d.mode = ds.mode;
205	if(uflag)
206		d.uid = ds.uid;
207	if(gflag)
208		d.gid = ds.gid;
209	return sys->fwstat(dfd, d);
210}
211
212samefile(d1: Sys->Dir, d2: Sys->Dir): int
213{
214	return d1.dtype == d2.dtype && d1.dev == d2.dev &&
215		d1.qid.qtype == d2.qid.qtype && d1.qid.path == d2.qid.path &&
216		d1.qid.vers == d2.qid.vers;
217}
218
219# Avoid loops in tangled namespaces. (from du.b)
220NCACHE: con 64; # must be power of two
221cache: array of list of ref sys->Dir;
222
223seen(dir: ref sys->Dir): int
224{
225	savlist := cache[int dir.qid.path&(NCACHE-1)];
226	for(c := savlist; c!=nil; c = tl c)
227		if(samefile(*dir, *hd c))
228			return 1;
229	cache[int dir.qid.path&(NCACHE-1)] = dir :: savlist;
230	return 0;
231}
232
233warning(e: string)
234{
235	sys->fprint(stderr, "cp: %s\n", e);
236	errors++;
237}
238