xref: /inferno-os/appl/cmd/install/install.b (revision 37da2899f40661e3e9631e497da8dc59b971cbd0)
1implement Install;
2
3#
4# Determine which packages need installing and calls install/inst
5# to actually install each one
6#
7
8# usage: install/install -d -F -g -s -u -i installdir -p platform -r root -P package
9
10include "sys.m";
11	sys: Sys;
12include "draw.m";
13include "bufio.m";
14	bufio: Bufio;
15	Iobuf: import bufio;
16include "string.m";
17	str: String;
18include "arg.m";
19	arg: Arg;
20include "readdir.m";
21	readdir : Readdir;
22include "sh.m";
23
24Install: module
25{
26	init: fn(nil: ref Draw->Context, nil: list of string);
27};
28
29# required dirs, usually in the standard inferno root.
30# The network download doesn't include them because of
31# problems with versions of tar that won't create empty dirs
32# so we'll make sure they exist.
33
34reqdirs := array [] of {
35	"/mnt",
36	"/mnt/wrap",
37	"/n",
38	"/n/remote",
39	"/tmp",
40};
41
42YES, NO, QUIT, ERR : con iota;
43INST : con "install/inst";	# actual install program
44MTPT : con "/n/remote";	# mount point for user's inferno root
45
46debug := 0;
47force := 0;
48exitemu := 0;
49uflag := 0;
50stderr : ref Sys->FD;
51installdir := "/install";
52platform := "Plan9";
53lcplatform : string;
54root := "/usr/inferno";
55local: int;
56global: int = 1;
57waitfd : ref Sys->FD;
58
59Product : adt {
60	name : string;
61	pkgs : ref Package;
62	nxt : ref Product;
63};
64
65Package : adt {
66	name : string;
67	nxt : ref Package;
68};
69
70instprods : ref Product;	# products/packages already installed
71
72# platform independent packages
73xpkgs := array[] of { "inferno", "utils", "src", "ipaq", "minitel", "sds" };
74ypkgs: list of string;
75
76init(nil: ref Draw->Context, args: list of string)
77{
78	sys = load Sys Sys->PATH;
79	stderr = sys->fildes(2);
80
81	# Hack for network download...
82	# make sure the dirs we need exist
83	for (dirix := 0; dirix < len reqdirs; dirix++) {
84		dir := reqdirs[dirix];
85		(exists, nil) := sys->stat(dir);
86		if (exists == -1) {
87			fd := sys->create(dir, Sys->OREAD, Sys->DMDIR + 8r7775);
88			if (fd == nil)
89				fatal(sys->sprint("cannot create directory %s: %r\n", dir));
90			fd = nil;
91		}
92	}
93
94	bufio = load Bufio Bufio->PATH;
95	if(bufio == nil)
96		fatal(sys->sprint("cannot load %s: %r\n", Bufio->PATH));
97	readdir = load Readdir Readdir->PATH;
98	if(readdir == nil)
99		fatal(sys->sprint("cannot load %s: %r\n", Readdir->PATH));
100	str = load String String->PATH;
101	if(str == nil)
102		fatal(sys->sprint("cannot load %s: %r\n", String->PATH));
103	arg = load Arg Arg->PATH;
104	if(arg == nil)
105		fatal(sys->sprint("cannot load %s: %r\n", Arg->PATH));
106	arg->init(args);
107	while((c := arg->opt()) != 0) {
108		case c {
109			'd' =>
110				debug = 1;
111			'F' =>
112				force = 1;
113			's' =>
114				exitemu = 1;
115			'i' =>
116				installdir = arg->arg();
117				if (installdir == nil)
118					fatal("install directory missing");
119			'p' =>
120				platform = arg->arg();
121				if (platform == nil)
122					fatal("platform missing");
123			'P' =>
124				pkg := arg->arg();
125				if (pkg == nil)
126					fatal("package missing");
127				ypkgs = pkg :: ypkgs;
128			'r' =>
129				root = arg->arg();
130				if (root == nil)
131					fatal("inferno root missing");
132			'u' =>
133				uflag = 1;
134			'g' =>
135				global = 0;
136			'*' =>
137				usage();
138		}
139	}
140	if (arg->argv() != nil)
141		usage();
142	lcplatform = str->tolower(platform);
143	(ok, dir) := sys->stat(installdir);
144	if (ok < 0)
145		fatal(sys->sprint("cannot open install directory %s", installdir));
146	nt := lcplatform == "nt";
147	if (nt) {
148		# root os of the form ?:/.........
149		if (len root < 3 || root[1] != ':' || root[2] != '/')
150			fatal(sys->sprint("root %s not of the form ?:/.......", root));
151		spec := root[0:2];
152		root = root[2:];
153		if (sys->bind("#U"+spec, MTPT, Sys->MREPL|Sys->MCREATE) < 0)
154			fatal(sys->sprint("cannot bind to drive %s", spec));
155	}
156	else {
157		if (root[0] != '/')
158			fatal(sys->sprint("root %s must be an absolute path name", root));
159		if (sys->bind("#U*", MTPT, Sys->MREPL|Sys->MCREATE) < 0)
160			fatal("cannot bind to system root");
161	}
162	(ok, dir) = sys->stat(MTPT+root);
163	if (ok >= 0) {
164		if ((dir.mode & Sys->DMDIR) == 0)
165			fatal(sys->sprint("inferno root %s is not a directory", root));
166	}
167	else if (sys->create(MTPT+root, Sys->OREAD, 8r775 | Sys->DMDIR) == nil)
168		fatal(sys->sprint("cannot create inferno root %s: %r", root));
169	# need a writable tmp directory /tmp in case installing from CD
170	(ok, dir) = sys->stat(MTPT+root+"/tmp");
171	if (ok >= 0) {
172		if ((dir.mode & Sys->DMDIR) == 0)
173			fatal(sys->sprint("inferno root tmp %s is not a directory", root+"/tmp"));
174	}
175	else if (sys->create(MTPT+root+"/tmp", Sys->OREAD, 8r775 | Sys->DMDIR) == nil)
176		fatal(sys->sprint("cannot create inferno root tmp %s: %r", root+"/tmp"));
177	if (sys->bind(MTPT+root, MTPT, Sys->MREPL | Sys->MCREATE) < 0)
178		fatal("cannot bind inferno root");
179	if (sys->bind(MTPT+"/tmp", "/tmp", Sys->MREPL | Sys->MCREATE) < 0)
180		fatal("cannot bind inferno root tmp");
181	root = MTPT;
182
183	if (nt || 1)
184		local = 1;
185	else {
186		sys->print("You can either install software specific to %s only or\n", platform);
187		sys->print(" install software for all platforms that we support.\n");
188		sys->print("If you are unsure what to do, answer yes to the question following.\n");
189		sys->print(" You can install the remainder of the software at a later date if desired.\n");
190		sys->print("\n");
191		b := bufio->fopen(sys->fildes(0), Bufio->OREAD);
192		if (b == nil)
193			fatal("cannot open stdin");
194		for (;;) {
195			sys->print("Install software specific to %s only ? (yes/no/quit) ", platform);
196			resp := getresponse(b);
197			ans := answer(resp);
198			if (ans == QUIT)
199				exit;
200			else if (ans == ERR)
201				sys->print("bad response %s\n\n", resp);
202			else {
203				local = ans == YES;
204				break;
205			}
206		}
207	}
208	instprods = dowraps(root+"/wrap");
209	doprods(installdir);
210	if (!nt)
211		sys->print("installation complete\n");
212	if (exitemu)
213		shutdown();
214}
215
216getresponse(b : ref Iobuf) : string
217{
218	s := b.gets('\n');
219	while (s != nil && (s[0] == ' ' || s[0] == '\t'))
220		s = s[1:];
221	while (s != nil && ((c := s[len s - 1]) == ' ' || c == '\t' || c == '\n'))
222		s = s[0: len s - 1];
223	return s;
224}
225
226answer(s : string) : int
227{
228	s = str->tolower(s);
229	if (s == "y" || s == "yes")
230		return YES;
231	if (s == "n" || s == "no")
232		return NO;
233	if (s == "q" || s == "quit")
234		return QUIT;
235	return ERR;
236}
237
238usage()
239{
240	fatal("Usage: install [-d] [-F] [-s] [-u] [-i installdir ] [-p platform ] [-r root]");
241}
242
243fatal(s : string)
244{
245	sys->fprint(stderr, "install: %s\n", s);
246	exit;
247}
248
249dowraps(d : string) : ref Product
250{
251	p : ref Product;
252
253	# make an inventory of what is already apparently installed
254	(dir, n) := readdir->init(d, Readdir->NAME|Readdir->COMPACT);
255	for (i := 0; i < n; i++) {
256		if (dir[i].mode & Sys->DMDIR) {
257			p = ref Product(str->tolower(dir[i].name), nil, p);
258			p.pkgs = dowrap(d + "/" + dir[i].name);
259		}
260	}
261	return p;
262}
263
264dowrap(d : string) : ref Package
265{
266	p : ref Package;
267
268	(dir, n) := readdir->init(d, Readdir->NAME|Readdir->COMPACT);
269	for (i := 0; i < n; i++)
270		p = ref Package(dir[i].name, p);
271	return p;
272}
273
274doprods(d : string)
275{
276	(dir, n) := readdir->init(d, Readdir->NAME|Readdir->COMPACT);
277	for (i := 0; i < n; i++) {
278		if (dir[i].mode & Sys->DMDIR)
279			doprod(str->tolower(dir[i].name), d + "/" + dir[i].name);
280	}
281}
282
283doprod(pr : string, d : string)
284{
285	# base package, updates and update packages have the name
286	# <timestamp> or <timestamp.gz>
287	if (!wanted(pr))
288		return;
289	(dir, n) := readdir->init(d, Readdir->NAME|Readdir->COMPACT);
290	for (i := 0; i < n; i++) {
291		pk := dir[i].name;
292		l := len pk;
293		if (l >= 4 && pk[l-3:l] == ".gz")
294			pk = pk[0:l-3];
295		else if (l >= 5 && (pk[l-4:] == ".tgz" || pk[l-4:] == ".9gz"))
296			pk = pk[0:l-4];
297		dopkg(pk, pr, d+"/"+dir[i].name);
298
299	}
300}
301
302dopkg(pk : string, pr : string, d : string)
303{
304	if (!installed(pk, pr))
305		install(d);
306}
307
308installed(pkg : string, prd : string) : int
309{
310	for (pr := instprods; pr != nil; pr = pr.nxt) {
311		if (pr.name == prd) {
312			for (pk := pr.pkgs; pk != nil; pk = pk.nxt) {
313				if (pk.name == pkg)
314					return 1;
315			}
316			return 0;
317		}
318	}
319	return 0;
320}
321
322lookup(pr : string) : int
323{
324	for (i := 0; i < len xpkgs; i++) {
325		if (xpkgs[i] == pr)
326			return i;
327	}
328	return -1;
329}
330
331plookup(pr: string): int
332{
333	for(ps := ypkgs; ps != nil; ps = tl ps)
334		if(pr == hd ps)
335			return 1;
336	return 0;
337}
338
339wanted(pr : string) : int
340{
341	if (!local || global)
342		return 1;
343	if(ypkgs != nil)	# overrides everything else
344		return plookup(pr);
345	found := lookup(pr);
346	if (found >= 0)
347		return 1;
348	return pr == lcplatform || prefix(lcplatform, pr);
349}
350
351install(d : string)
352{
353	if (waitfd == nil)
354		waitfd = openwait(sys->pctl(0, nil));
355	sys->fprint(stderr, "installing package %s\n", d);
356	if (debug)
357		return;
358	c := chan of int;
359	args := "-t" :: "-v" :: "-r" :: root :: d :: nil;
360	if (uflag)
361		args = "-u" :: args;
362	if (force)
363		args = "-F" :: args;
364	spawn exec(INST, INST :: args, c);
365	execpid := <- c;
366	wait(waitfd, execpid);
367}
368
369exec(cmd : string, argl : list of string, ci : chan of int)
370{
371	ci <-= sys->pctl(Sys->FORKNS|Sys->NEWFD|Sys->NEWPGRP, 0 :: 1 :: 2 :: stderr.fd :: nil);
372	file := cmd;
373	if(len file<4 || file[len file-4:] !=".dis")
374		file += ".dis";
375	c := load Command file;
376	if(c == nil) {
377		err := sys->sprint("%r");
378		if(file[0] !='/' && file[0:2] !="./") {
379			c = load Command "/dis/"+file;
380			if(c == nil)
381				err = sys->sprint("%r");
382		}
383		if(c == nil)
384			fatal(sys->sprint("%s: %s\n", cmd, err));
385	}
386	c->init(nil, argl);
387}
388
389openwait(pid : int) : ref Sys->FD
390{
391	w := sys->sprint("#p/%d/wait", pid);
392	fd := sys->open(w, Sys->OREAD);
393	if (fd == nil)
394		fatal("fd == nil in wait");
395	return fd;
396}
397
398wait(wfd : ref Sys->FD, wpid : int)
399{
400	n : int;
401
402	buf := array[Sys->WAITLEN] of byte;
403	status := "";
404	for(;;) {
405		if ((n = sys->read(wfd, buf, len buf)) < 0)
406			fatal("bad read in wait");
407		status = string buf[0:n];
408		break;
409	}
410	if (int status != wpid)
411		fatal("bad status in wait");
412	if(status[len status - 1] != ':')
413		fatal(sys->sprint("%s\n", status));
414}
415
416shutdown()
417{
418	fd := sys->open("/dev/sysctl", sys->OWRITE);
419	if(fd == nil)
420		fatal("cannot shutdown emu");
421	if (sys->write(fd, array of byte "halt", 4) < 0)
422		fatal(sys->sprint("shutdown: write failed: %r\n"));
423}
424
425prefix(s, t : string) : int
426{
427	if (len s <= len t)
428		return t[0:len s] == s;
429	return 0;
430}
431