xref: /plan9-contrib/sys/src/9/pcboot/diskload.c (revision 178702b161d3fe3e021aa6cb2f305be898e56ca0)
1 /*
2  * 9load - load next kernel from disk and start it
3  */
4 #include	"u.h"
5 #include	"../port/lib.h"
6 #include	"mem.h"
7 #include	"dat.h"
8 #include	"fns.h"
9 #include	"io.h"
10 #include	"ureg.h"
11 #include	"pool.h"
12 #include	"../port/error.h"
13 #include	"../port/netif.h"
14 #include	"dosfs.h"
15 #include	"../port/sd.h"
16 
17 /* from <libc.h> */
18 #define	DIRMAX	(sizeof(Dir)+STATMAX)	/* max length of Dir structure */
19 #define	STATMAX	65535U	/* max length of machine-independent stat structure */
20 
21 enum {
22 	Bufsize = 8192,
23 };
24 
25 int	dosdirread(File *f, char ***nmarray);
26 int	isconf(char *name);
27 
28 static int progress = 1;
29 static Bootfs fs;
30 
31 /*
32  * from 9load's bootp.c:
33  */
34 
35 static int
36 dumpfile(char *file)
37 {
38 	int n;
39 	char *buf;
40 
41 	buf = smalloc(Maxfile + 1);
42 	n = readfile(file, buf, Maxfile);
43 	if (n < 0)
44 		return -1;
45 	buf[n] = 0;
46 	print("%s (%d bytes):\n", file, n);
47 	print("%s\n", buf);
48 	free(buf);
49 	return 0;
50 }
51 
52 long
53 dirread0(Chan *c, uchar *p, long n)
54 {
55 	long nn, nnn;
56 	vlong off;
57 
58 	/*
59 	 * The offset is passed through on directories, normally.
60 	 * Sysseek complains, but pread is used by servers like exportfs,
61 	 * that shouldn't need to worry about this issue.
62 	 *
63 	 * Notice that c->devoffset is the offset that c's dev is seeing.
64 	 * The number of bytes read on this fd (c->offset) may be different
65 	 * due to rewritings in rockfix.
66 	 */
67 	/* use and maintain channel's offset */
68 	off = c->offset;
69 	if(off < 0)
70 		error(Enegoff);
71 
72 	if(off == 0){	/* rewind to the beginning of the directory */
73 		c->offset = 0;
74 		c->devoffset = 0;
75 		mountrewind(c);
76 		unionrewind(c);
77 	}
78 
79 	if(c->qid.type & QTDIR){
80 		if(mountrockread(c, p, n, &nn)){
81 			/* do nothing: mountrockread filled buffer */
82 		}else if(c->umh)
83 			nn = unionread(c, p, n);
84 		else{
85 			if(off != c->offset)
86 				error(Edirseek);
87 			nn = devtab[c->type]->read(c, p, n, c->devoffset);
88 		}
89 		nnn = mountfix(c, p, nn, n);
90 	}else
91 		nnn = nn = devtab[c->type]->read(c, p, n, off);
92 
93 	lock(c);
94 	c->devoffset += nn;
95 	c->offset += nnn;
96 	unlock(c);
97 
98 	/* nnn == 54, sizeof(Dir) == 60 */
99 	return nnn;
100 }
101 
102 long
103 dirread(Chan *c, Dir **d)
104 {
105 	uchar *buf;
106 	long ts;
107 
108 	buf = malloc(DIRMAX);
109 	if(buf == nil)
110 		return -1;
111 	ts = dirread0(c, buf, DIRMAX);
112 	if(ts >= 0)
113 		/* convert machine-independent representation to Dirs */
114 		ts = dirpackage(buf, ts, d);
115 	free(buf);
116 	return ts;
117 }
118 
119 static int
120 addsdev(Dir *dirp)
121 {
122 	int n, f, lines, flds;
123 	vlong start, end;
124 	char *buf, *part;
125 	char *line[64], *fld[5];
126 	char ctl[64], disk[64];
127 
128 	buf = smalloc(Maxfile + 1);
129 	snprint(ctl, sizeof ctl, "#S/%s/ctl", dirp->name);
130 	n = readfile(ctl, buf, Maxfile);
131 	if (n < 0) {
132 		free(buf);
133 		return -1;
134 	}
135 	buf[n] = 0;
136 
137 	lines = getfields(buf, line, nelem(line), 0, "\r\n");
138 	part = nil;
139 	for (f = 0; f < lines; f++) {
140 		flds = tokenize(line[f], fld, nelem(fld));
141 		if (flds < 4 || strcmp(fld[0], "part") != 0)
142 			continue;
143 		kstrdup(&part, fld[1]);
144 		start = strtoull(fld[2], nil, 0);
145 		end   = strtoull(fld[3], nil, 0);
146 		if (end > (vlong)100*(vlong)MB*MB) {
147 			print("addsdev: implausible partition #S/%s/%s %lld %lld\n",
148 				dirp->name, part, start, end);
149 			continue;
150 		}
151 		/*
152 		 * We are likely to only see a "data" partition on each disk.
153 		 *
154 		 * Read the on-disk partition tables & set in-core partitions
155 		 * (disk, part, start, end).
156 		 */
157 		print("found partition #S/%s/%s %,lld %,lld\n",
158 			dirp->name, part, start, end);
159 		snprint(disk, sizeof disk, "#S/%s", dirp->name);
160 		readparts(disk);
161 	}
162 	free(buf);
163 	return 0;
164 }
165 
166 static File file;
167 
168 /*
169  * look for kernels on a 9fat; if there's just one, return it.
170  * could treat x and x.gz as one kernel.
171  */
172 static char *
173 findonekernel(Bootfs *fs)
174 {
175 	int n, kerns;
176 	char *bootfile, *name;
177 	char **array;
178 
179 	if(fswalk(fs, "", &file) <= 0) {
180 		print("can't walk to ''\n");
181 		return nil;
182 	}
183 	dosdirread(&file, &array);
184 	bootfile = nil;
185 	kerns = 0;
186 	for (n = 0; (name = array[n]) != nil; n++)
187 		if(strncmp(name, "9pc", 3) == 0 ||
188 		   strncmp(name, "9k8", 3) == 0){
189 			bootfile = name;
190 			kerns++;
191 		}
192 	if (kerns > 1) {
193 		print("found these kernels:");
194 		for (n = 0; (name = array[n]) != nil; n++)
195 			print(" %s", name);
196 		print("\n");
197 	}
198 	return kerns == 1? bootfile: nil;
199 }
200 
201 int
202 partboot(char *path)
203 {
204 	long n;
205 	char *buf;
206 	Boot boot;
207 	Boot *b;
208 	Chan *ch;
209 
210 	b = &boot;
211 	memset(b, 0, sizeof *b);
212 	b->state = INITKERNEL;
213 	ch = namecopen(path, OREAD);
214 	if (ch == nil) {
215 		print("can't open partition %s\n", path);
216 		return -1;
217 	}
218 	print("loading %s\n", path);
219 	buf = smalloc(Bufsize);
220 	while((n = devtab[ch->type]->read(ch, buf, Bufsize, ch->offset)) > 0)
221 		if(bootpass(b, buf, n) != MORE)
222 			break;
223 	bootpass(b, nil, 0);		/* attempts to boot */
224 
225 	free(buf);
226 	cclose(ch);
227 	return -1;
228 }
229 
230 /* fsroot must be nil or a fat root directory already dosinit'ed */
231 static void
232 trybootfile(char *bootfile, Bootfs *fsroot)
233 {
234 	int nf;
235 	char fat[64];
236 	char *disk, *part, *file, *bootcopy;
237 	char *fields[4];
238 	Boot boot;
239 	static int didaddconf;
240 
241 	bootcopy = file = nil;
242 	kstrdup(&bootcopy, bootfile);
243 	nf = getfields(bootcopy, fields, nelem(fields), 0, "!");
244 	switch(nf){
245 	case 3:
246 		file = fields[2];
247 		/* fall through */
248 	case 2:
249 		disk = fields[0];
250 		part = fields[1];
251 		break;
252 	default:
253 		print("bad bootfile syntax: %s\n", bootfile);
254 		return;
255 	}
256 
257 	if(didaddconf == 0) {
258 		didaddconf = 1;
259 		sdaddallconfs(sdaddconf);
260 	}
261 
262 	snprint(fat, sizeof fat, "#S/%s/%s", disk, part);
263 	if (file == nil) { /* if no file, try to load from partition directly */
264 		partboot(fat);
265 		return;
266 	}
267 
268 	if (fsroot == nil) {
269 		fsroot = &fs;
270 		memset(fsroot, 0, sizeof *fsroot);
271 		if (dosinit(fsroot, fat) < 0) {
272 			print("dosinit %s failed\n", fat);
273 			return;
274 		}
275 	}
276 
277 	/* load kernel and jump to it */
278 	memset(&boot, 0, sizeof boot);
279 	boot.state = INITKERNEL;
280 	fsboot(fsroot, file, &boot);
281 
282 	/* failed to boot */
283 }
284 
285 /*
286  * for a given disk's 9fat, find & load plan9.ini, parse it,
287  * extract kernel filename, load that kernel and jump to it.
288  */
289 static void
290 trydiskboot(char *disk)
291 {
292 	int n;
293 	char fat[80];
294 	char *ini, *bootfile;
295 
296 	/* mount the disk's 9fat */
297 	memset(&fs, 0, sizeof fs);
298 	snprint(fat, sizeof fat, "#S/%s/9fat", disk);
299 	if (dosinit(&fs, fat) < 0) {
300 		print("dosinit %s failed\n", fat);
301 		return;
302 	}
303 
304 	/* open plan9.ini, read it */
305 	ini = smalloc(Maxfile+1);
306 	if(fswalk(&fs, "plan9.ini", &file) <= 0) {
307 		print("no plan9.ini in %s\n", fat);
308 		n = 0;
309 	} else {
310 		n = fsread(&file, ini, Maxfile);
311 		if (n < 0)
312 			panic("error reading %s", ini);
313 	}
314 	ini[n] = 0;
315 
316 	/*
317 	 * take note of plan9.ini contents.  consumes ini to make config vars,
318 	 * thus we can't free ini.
319 	 */
320 	dotini(ini);
321 	i8250console();			/* (re)configure serial port */
322 
323 	bootfile = nil;			/* for kstrdup in askbootfile */
324 	if(isconf("bootfile")) {
325 		kstrdup(&bootfile, getconf("bootfile"));
326 		if(strcmp(bootfile, "manual") == 0)
327 			askbootfile(fat, sizeof fat, &bootfile, 0, "");
328 
329 		/* pass arguments to kernels that can use them */
330 		strecpy(BOOTLINE, BOOTLINE+BOOTLINELEN, bootfile);
331 	} else if ((bootfile = findonekernel(&fs)) != nil) {  /* look in fat */
332 		snprint(fat, sizeof fat, "%s!9fat!%s", disk, bootfile);
333 		bootfile = fat;
334 		print("no bootfile named in plan9.ini; found %s\n", bootfile);
335 	} else {
336 		/* if #S/disk/kernel partition exists, load from it. */
337 		snprint(fat, sizeof fat, "#S/%s/kernel", disk);
338 		partboot(fat);
339 		/* last resort: ask the user */
340 		askbootfile(fat, sizeof fat, &bootfile, Promptsecs,
341 			"sdC0!9fat!9pccpu");
342 	}
343 	trybootfile(bootfile, &fs);
344 
345 	/* failed; try again */
346 }
347 
348 /*
349  * find all the disks in #S, read their partition tables and set those
350  * partitions in core, mainly so that we can access 9fat file systems.
351  * for each disk's 9fat, read plan9.ini and boot the named kernel.
352  */
353 void
354 bootloadproc(void *)
355 {
356 	int n, dirs, sdev;
357 	char kern[64];
358 	char *sdevs[128];
359 	Chan *sdch;
360 	Dir *dirp, *dp;
361 
362 	memset(sdevs, 0, sizeof sdevs);
363 	sdch = nil;
364 	while(waserror()) {
365 		print("error caught at top level in bootload\n");
366 		if(sdch) {
367 			cclose(sdch);
368 			sdch = nil;
369 		}
370 	}
371 	bind("#S", "/dev", MAFTER);		/* try to force an attach */
372 	sdch = namecopen("#S", OREAD);
373 	if (sdch == nil)
374 		panic("no disks (no #S)");
375 	sdev = 0;
376 	while ((dirs = dirread(sdch, &dirp)) > 0) {
377 		for (dp = dirp; dirs-- > 0; dp++)
378 			if (strcmp(dp->name, "sdctl") != 0) {
379 				addsdev(dp);
380 				if (sdev >= nelem(sdevs))
381 					print("too many sdevs; ignoring %s\n",
382 						dp->name);
383 				else
384 					kstrdup(&sdevs[sdev++], dp->name);
385 			}
386 		free(dirp);
387 	}
388 	cclose(sdch);
389 	sdch = nil;
390 	if (sdev == 0)
391 		panic("no disks (in #S)");
392 
393 	print("disks:");
394 	for (n = 0; n < sdev; n++)
395 		print(" %s", sdevs[n]);
396 	print("\n");
397 
398 	for (n = 0; n < sdev; n++) {
399 		print("trying %s...", sdevs[n]);
400 		trydiskboot(sdevs[n]);
401 	}
402 	USED(sdch);
403 	for (;;) {
404 		askbootfile(kern, sizeof kern, nil, 0, "");
405 		trybootfile(kern, nil);
406 	}
407 	// poperror();
408 }
409