xref: /netbsd/sbin/raidctl/rf_configure.c (revision 8aeeb9be)
1 /*	$NetBSD: rf_configure.c,v 1.37 2022/07/21 09:19:53 kre Exp $ */
2 
3 /*
4  * Copyright (c) 1995 Carnegie-Mellon University.
5  * All rights reserved.
6  *
7  * Author: Mark Holland
8  *
9  * Permission to use, copy, modify and distribute this software and
10  * its documentation is hereby granted, provided that both the copyright
11  * notice and this permission notice appear in all copies of the
12  * software, derivative works or modified versions, and any portions
13  * thereof, and that both notices appear in supporting documentation.
14  *
15  * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS"
16  * CONDITION.  CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND
17  * FOR ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
18  *
19  * Carnegie Mellon requests users of this software to return to
20  *
21  *  Software Distribution Coordinator  or  Software.Distribution@CS.CMU.EDU
22  *  School of Computer Science
23  *  Carnegie Mellon University
24  *  Pittsburgh PA 15213-3890
25  *
26  * any improvements or extensions that they make and grant Carnegie the
27  * rights to redistribute these changes.
28  */
29 
30 /***************************************************************
31  *
32  * rf_configure.c -- code related to configuring the raidframe system
33  *
34  * configuration is complicated by the fact that we want the same
35  * driver to work both in the kernel and at user level.  In the
36  * kernel, we can't read the configuration file, so we configure
37  * by running a user-level program that reads the config file,
38  * creates a data structure describing the configuration and
39  * passes it into the kernel via an ioctl.  Since we want the config
40  * code to be common between the two versions of the driver, we
41  * configure using the same two-step process when running at
42  * user level.  Of course, at user level, the config structure is
43  * passed directly to the config routine, rather than via ioctl.
44  *
45  * This file is not compiled into the kernel, so we have no
46  * need for KERNEL ifdefs.
47  *
48  **************************************************************/
49 #include <sys/cdefs.h>
50 
51 #ifndef lint
52 __RCSID("$NetBSD: rf_configure.c,v 1.37 2022/07/21 09:19:53 kre Exp $");
53 #endif
54 
55 
56 #include <stdio.h>
57 #include <stdlib.h>
58 #include <errno.h>
59 #include <strings.h>
60 #include <err.h>
61 #include <util.h>
62 #include <assert.h>
63 #include <sys/types.h>
64 #include <sys/stat.h>
65 
66 #include <dev/raidframe/raidframevar.h>
67 #include <dev/raidframe/raidframeio.h>
68 #include "rf_configure.h"
69 
70 static char   *rf_find_non_white(char *, int);
71 static char   *rf_find_white(char *);
72 static int rf_search_file_for_start_of(const char *, char *, int, FILE *);
73 static int rf_get_next_nonblank_line(char *, int, FILE *, const char *);
74 
75 #define RF_MIN(a,b) (((a) < (b)) ? (a) : (b))
76 
77 static int     distSpareYes = 1;
78 static int     distSpareNo = 0;
79 
80 /*
81  * The mapsw[] table below contains all the various RAID types that might
82  * be supported by the kernel.  The actual supported types are found
83  * in sys/dev/raidframe/rf_layout.c.
84  */
85 
86 static const RF_LayoutSW_t mapsw[] = {
87 	/* parity declustering */
88 	{'T', "Parity declustering",
89 	 rf_MakeLayoutSpecificDeclustered, &distSpareNo},
90 	/* parity declustering with distributed sparing */
91 	{'D', "Distributed sparing parity declustering",
92 	 rf_MakeLayoutSpecificDeclustered, &distSpareYes},
93 	/* declustered P+Q */
94 	{'Q', "Declustered P+Q",
95 	 rf_MakeLayoutSpecificDeclustered, &distSpareNo},
96 	/* RAID 5 with rotated sparing */
97 	{'R', "RAID Level 5 rotated sparing", rf_MakeLayoutSpecificNULL, NULL},
98 	/* Chained Declustering */
99 	{'C', "Chained Declustering", rf_MakeLayoutSpecificNULL, NULL},
100 	/* Interleaved Declustering */
101 	{'I', "Interleaved Declustering", rf_MakeLayoutSpecificNULL, NULL},
102 	/* RAID level 0 */
103 	{'0', "RAID Level 0", rf_MakeLayoutSpecificNULL, NULL},
104 	/* RAID level 1 */
105 	{'1', "RAID Level 1", rf_MakeLayoutSpecificNULL, NULL},
106 	/* RAID level 4 */
107 	{'4', "RAID Level 4", rf_MakeLayoutSpecificNULL, NULL},
108 	/* RAID level 5 */
109 	{'5', "RAID Level 5", rf_MakeLayoutSpecificNULL, NULL},
110 	/* Evenodd */
111 	{'E', "EvenOdd", rf_MakeLayoutSpecificNULL, NULL},
112 	/* Declustered Evenodd */
113 	{'e', "Declustered EvenOdd",
114 	 rf_MakeLayoutSpecificDeclustered, &distSpareNo},
115 	/* parity logging */
116 	{'L', "Parity logging", rf_MakeLayoutSpecificNULL, NULL},
117 	/* end-of-list marker */
118 	{'\0', NULL, NULL, NULL}
119 };
120 
121 static const RF_LayoutSW_t *
rf_GetLayout(RF_ParityConfig_t parityConfig)122 rf_GetLayout(RF_ParityConfig_t parityConfig)
123 {
124 	const RF_LayoutSW_t *p;
125 
126 	/* look up the specific layout */
127 	for (p = &mapsw[0]; p->parityConfig; p++)
128 		if (p->parityConfig == parityConfig)
129 			break;
130 	if (!p->parityConfig)
131 		return NULL;
132 	return p;
133 }
134 
135 /*
136  * called from user level to read the configuration file and create
137  * a configuration control structure.  This is used in the user-level
138  * version of the driver, and in the user-level program that configures
139  * the system via ioctl.
140  */
141 int
rf_MakeConfig(char * configname,RF_Config_t * cfgPtr)142 rf_MakeConfig(char *configname, RF_Config_t *cfgPtr)
143 {
144 	int numscanned, val, c, retcode, aa, bb, cc;
145 	char buf[BUFSIZ], buf1[BUFSIZ], *cp;
146 	const RF_LayoutSW_t *lp;
147 	FILE *fp;
148 
149 	memset(cfgPtr, 0, sizeof(*cfgPtr));
150 
151 	fp = fopen(configname, "r");
152 	if (!fp) {
153 		warnx("Can't open config file %s", configname);
154 		return -1;
155 	}
156 	rewind(fp);
157 	if (rf_search_file_for_start_of("array", buf, sizeof(buf), fp)) {
158 		warnx("Unable to find start of \"array\" params in config "
159 		    "file %s", configname);
160 		retcode = -1;
161 		goto out;
162 	}
163 	rf_get_next_nonblank_line(buf, sizeof(buf), fp,
164 	    "Config file error (\"array\" section):  unable to get numRow "
165 	    "and numCol");
166 
167 	/*
168 	 * wackiness with aa, bb, cc to get around size problems on
169 	 * different platforms
170 	 */
171 
172 	/*
173 	 * Allow both "numCol numSpare" as well as old-style
174 	 * "numRow numCol numSpare".
175 	 * Note that numRow has always been ignored.
176 	 */
177 	numscanned = sscanf(buf, "%d %d %d", &aa, &bb, &cc);
178 	if (numscanned != 3) {
179 		numscanned = sscanf(buf, "%d %d", &bb, &cc);
180 		if (numscanned != 2) {
181 			warnx("Config file error (\"array\" section): unable "
182 			    "to get numCol, numSpare");
183 			retcode = -1;
184 			goto out;
185 		}
186 	}
187 	cfgPtr->numCol = (RF_RowCol_t) bb;
188 	cfgPtr->numSpare = (RF_RowCol_t) cc;
189 
190 	/* debug section is optional */
191 	for (c = 0; c < RF_MAXDBGV; c++)
192 		cfgPtr->debugVars[c][0] = '\0';
193 	rewind(fp);
194 	if (!rf_search_file_for_start_of("debug", buf, sizeof(buf), fp)) {
195 		for (c = 0; c < RF_MAXDBGV; c++) {
196 			if (rf_get_next_nonblank_line(buf, sizeof(buf), fp,
197 			    NULL))
198 				break;
199 			cp = rf_find_non_white(buf, 0);
200 			if (!strncmp(cp, "START", sizeof("START") - 1))
201 				break;
202 			(void) strlcpy(cfgPtr->debugVars[c], cp,
203 			    sizeof(cfgPtr->debugVars[c]));
204 		}
205 	}
206 	rewind(fp);
207 	strlcpy(cfgPtr->diskQueueType, "fifo", sizeof(cfgPtr->diskQueueType));
208 	cfgPtr->maxOutstandingDiskReqs = 1;
209 
210 	/* scan the file for the block related to disk queues */
211 	if (rf_search_file_for_start_of("queue", buf, sizeof(buf), fp) ||
212 	    rf_get_next_nonblank_line(buf, sizeof(buf), fp, NULL)) {
213 		warnx("[No disk queue discipline specified in config file %s. "
214 		    "Using %s.]", configname, cfgPtr->diskQueueType);
215 	}
216     else {
217 
218 	/*
219 	 * the queue specifier line contains two entries: 1st char of first
220 	 * word specifies queue to be used 2nd word specifies max num reqs
221 	 * that can be outstanding on the disk itself (typically 1)
222 	 */
223 	assert(64 < sizeof buf1);
224 	if (sscanf(buf, "%64s %d", buf1, &val) != 2 || strlen(buf1) >= 60) {
225 		warnx("Can't determine queue type and/or max outstanding "
226 		    "reqs from line: %.*s", (int)(sizeof(buf) - 1), buf);
227 		warnx("Using %s-%d", cfgPtr->diskQueueType,
228 		    cfgPtr->maxOutstandingDiskReqs);
229 	} else {
230 #if 0
231 		char *ch;
232 #endif
233 		memcpy(cfgPtr->diskQueueType, buf1,
234 		    RF_MIN(sizeof(cfgPtr->diskQueueType), strlen(buf1) + 1));
235 		cfgPtr->diskQueueType[sizeof cfgPtr->diskQueueType - 1] = '\0';
236 
237 #if 0	/* this indicates a lack of understanding of how scanf() works */
238 	/* it was also pointless as buf1 data was (b4) never used again */
239 		for (ch = buf1; *ch; ch++) {
240 			if (*ch == ' ') {
241 				*ch = '\0';
242 				break;
243 			}
244 		}
245 #endif
246 		cfgPtr->maxOutstandingDiskReqs = val;
247 		if (cfgPtr->maxOutstandingDiskReqs != val) {
248 			warnx("Queue length for %s out of range"
249 			    " [%d interp as %d], assuming 1",
250 			    buf1, val, cfgPtr->maxOutstandingDiskReqs);
251 			cfgPtr->maxOutstandingDiskReqs = 1;
252 		}
253 	}
254     }
255 
256 	rewind(fp);
257 
258 	if (rf_search_file_for_start_of("disks", buf, sizeof(buf), fp)) {
259 		warnx("Can't find \"disks\" section in config file %s",
260 		    configname);
261 		retcode = -1;
262 		goto out;
263 	}
264 	for (c = 0; c < cfgPtr->numCol; c++) {
265 		char b1[MAXPATHLEN];
266 		const char *b;
267 
268 		if (rf_get_next_nonblank_line(
269 		    buf, sizeof(buf), fp, NULL)) {
270 			warnx("Config file error: unable to find device "
271 			    "file name for disk at col %d", c);
272 			retcode = -1;
273 			goto out;
274 		}
275 
276 		b = getfsspecname(b1, sizeof(b1), buf);
277 		if (b == NULL) {
278 			warnx("Config file error: warning: unable to "
279 			    "get device file for disk at col %d: %s",
280 			    c, b1);
281 			b = "absent";
282 		}
283 
284 		strlcpy(cfgPtr->devnames[0][c], b,
285 		    sizeof(cfgPtr->devnames[0][c]));
286 	}
287 
288 	/* "spare" section is optional */
289 	rewind(fp);
290 	if (rf_search_file_for_start_of("spare", buf, sizeof(buf), fp))
291 		cfgPtr->numSpare = 0;
292 	for (c = 0; c < cfgPtr->numSpare; c++) {
293 		char b1[MAXPATHLEN];
294 		const char *b;
295 
296 		if (rf_get_next_nonblank_line(buf, sizeof(buf), fp, NULL)) {
297 			warnx("Config file error: unable to get device file "
298 			    "for spare disk %d", c);
299 			retcode = -1;
300 			goto out;
301 		}
302 
303 		b = getfsspecname(b1, sizeof(b1), buf);
304 		if (b == NULL) {
305 			warnx("Config file error: warning: unable to get "
306 			    "device file for spare disk %d: %s", c, buf);
307 			b = buf;
308 		}
309 
310 	        strlcpy(cfgPtr->spare_names[c], b,
311 		    sizeof(cfgPtr->spare_names[c]));
312 	}
313 
314 	/* scan the file for the block related to layout */
315 	rewind(fp);
316 	if (rf_search_file_for_start_of("layout", buf, sizeof(buf), fp)) {
317 		warnx("Can't find \"layout\" section in configuration file %s",
318 		    configname);
319 		retcode = -1;
320 		goto out;
321 	}
322 	if (rf_get_next_nonblank_line(buf, sizeof(buf), fp, NULL)) {
323 		warnx("Config file error (\"layout\" section): unable to find "
324 		    "common layout param line");
325 		retcode = -1;
326 		goto out;
327 	}
328 	c = sscanf(buf, "%d %d %d %c", &aa, &bb, &cc, &cfgPtr->parityConfig);
329 	cfgPtr->sectPerSU = (RF_SectorNum_t) aa;
330 	cfgPtr->SUsPerPU = (RF_StripeNum_t) bb;
331 	cfgPtr->SUsPerRU = (RF_StripeNum_t) cc;
332 	if (c != 4) {
333 		warnx("Unable to scan common layout line");
334 		retcode = -1;
335 		goto out;
336 	}
337 	lp = rf_GetLayout(cfgPtr->parityConfig);
338 	if (lp == NULL) {
339 		warnx("Unknown parity config '%c'",
340 		    cfgPtr->parityConfig);
341 		retcode = -1;
342 		goto out;
343 	}
344 
345 	retcode = lp->MakeLayoutSpecific(fp, cfgPtr,
346 	    lp->makeLayoutSpecificArg);
347 out:
348 	fclose(fp);
349 	if (retcode < 0)
350 		retcode = errno = EINVAL;
351 	else
352 		errno = retcode;
353 	return retcode;
354 }
355 
356 
357 /*
358  * used in architectures such as RAID0 where there is no layout-specific
359  * information to be passed into the configuration code.
360  */
361 int
rf_MakeLayoutSpecificNULL(FILE * fp,RF_Config_t * cfgPtr,void * ignored)362 rf_MakeLayoutSpecificNULL(FILE *fp, RF_Config_t *cfgPtr, void *ignored)
363 {
364 	cfgPtr->layoutSpecificSize = 0;
365 	cfgPtr->layoutSpecific = NULL;
366 	return 0;
367 }
368 
369 int
rf_MakeLayoutSpecificDeclustered(FILE * configfp,RF_Config_t * cfgPtr,void * arg)370 rf_MakeLayoutSpecificDeclustered(FILE *configfp, RF_Config_t *cfgPtr, void *arg)
371 {
372 	int b, v, k, r, lambda, norotate, i, val, distSpare;
373 	char *cfgBuf, *bdfile, *p, *smname;
374 	char buf[BUFSIZ], smbuf[BUFSIZ];
375 	FILE *fp;
376 
377 	distSpare = *((int *) arg);
378 
379 	/* get the block design file name */
380 	if (rf_get_next_nonblank_line(buf, sizeof(buf), configfp,
381 	    "Can't find block design file name in config file"))
382 		return EINVAL;
383 	bdfile = rf_find_non_white(buf, 1);
384 	/* open bd file, check validity of configuration */
385 	if ((fp = fopen(bdfile, "r")) == NULL) {
386 		warn("RAID: config error: Can't open layout table file %s",
387 		    bdfile);
388 		return EINVAL;
389 	}
390 	if (fgets(buf, sizeof(buf), fp) == NULL) {
391 		warnx("RAID: config error: Can't read layout from layout "
392 		    "table file %s", bdfile);
393 		fclose(fp);
394 		return EINVAL;
395 	}
396 	i = sscanf(buf, "%u %u %u %u %u %u",
397 	    &b, &v, &k, &r, &lambda, &norotate);
398 	if (i == 5)
399 		norotate = 0;	/* no-rotate flag is optional */
400 	else if (i != 6) {
401 		warnx("Unable to parse header line in block design file");
402 		fclose(fp);
403 		return EINVAL;
404 	}
405 	/*
406 	 * set the sparemap directory.  In the in-kernel version, there's a
407 	 * daemon that's responsible for finding the sparemaps
408 	 */
409 	if (distSpare) {
410 		if (rf_get_next_nonblank_line(smbuf, sizeof(smbuf), configfp,
411 		    "Can't find sparemap file name in config file")) {
412 			fclose(fp);
413 			return EINVAL;
414 		}
415 		smname = rf_find_non_white(smbuf, 1);
416 		if (strlen(smname) >= RF_SPAREMAP_NAME_LEN) {
417 			warnx("sparemap file name '%s' too long (max %d)",
418 			    smname, RF_SPAREMAP_NAME_LEN - 1);
419 			fclose(fp);
420 			return EINVAL;
421 		}
422 	} else {
423 		smbuf[0] = '\0';
424 		smname = smbuf;
425 	}
426 
427 	/* allocate a buffer to hold the configuration info */
428 	cfgPtr->layoutSpecificSize = RF_SPAREMAP_NAME_LEN +
429 	    6 * sizeof(int) + b * k;
430 
431 	cfgBuf = (char *) malloc(cfgPtr->layoutSpecificSize);
432 	if (cfgBuf == NULL) {
433 		fclose(fp);
434 		return ENOMEM;
435 	}
436 	cfgPtr->layoutSpecific = (void *) cfgBuf;
437 	p = cfgBuf;
438 
439 	/* install name of sparemap file */
440 	for (i = 0; smname[i]; i++)
441 		*p++ = smname[i];
442 	/* pad with zeros */
443 	while (i < RF_SPAREMAP_NAME_LEN) {
444 		*p++ = '\0';
445 		i++;
446 	}
447 	if ((i & (sizeof(int) - 1)) != 0) {
448 		/* panic, unaligned data; RF_SPAREMAP_NAME_LEN invalid */
449 		warnx("Program Bug: (RF_SPAREMAP_NAME_LEN(%d) %% %zd) != 0",
450 		    RF_SPAREMAP_NAME_LEN, sizeof(int));
451 		fclose(fp);
452 		return EINVAL;
453 	}
454 
455 	/*
456 	 * fill in the buffer with the block design parameters
457 	 * and then the block design itself
458 	 */
459 	*((int *) p) = b;
460 	p += sizeof(int);
461 	*((int *) p) = v;
462 	p += sizeof(int);
463 	*((int *) p) = k;
464 	p += sizeof(int);
465 	*((int *) p) = r;
466 	p += sizeof(int);
467 	*((int *) p) = lambda;
468 	p += sizeof(int);
469 	*((int *) p) = norotate;
470 	p += sizeof(int);
471 
472 	while (fscanf(fp, "%d", &val) == 1)
473 		*p++ = (char) val;
474 	fclose(fp);
475 	if ((unsigned int)(p - cfgBuf) != cfgPtr->layoutSpecificSize) {
476 		warnx("Size mismatch creating layout specific data: is %tu sb "
477 		    "%zu bytes", p - cfgBuf, 6 * sizeof(int) + b * k);
478 		return EINVAL;
479 	}
480 	return 0;
481 }
482 
483 /****************************************************************************
484  *
485  * utilities
486  *
487  ***************************************************************************/
488 
489 /* finds a non-white character in the line */
490 static char *
rf_find_non_white(char * p,int eatnl)491 rf_find_non_white(char *p, int eatnl)
492 {
493 	while (*p == ' ' || *p == '\t')
494 		p++;
495 	if (*p == '\n' && eatnl)
496 		*p = '\0';
497 	return p;
498 }
499 
500 /* finds a white character in the line */
501 static char *
rf_find_white(char * p)502 rf_find_white(char *p)
503 {
504 	while (*p != '\0' && *p != ' ' && *p != '\t')
505 		p++;
506 	return p;
507 }
508 
509 /*
510  * searches a file for a line that says "START string", where string is
511  * specified as a parameter
512  */
513 static int
rf_search_file_for_start_of(const char * string,char * buf,int len,FILE * fp)514 rf_search_file_for_start_of(const char *string, char *buf, int len, FILE *fp)
515 {
516 	char *p;
517 
518 	while (1) {
519 		if (fgets(buf, len, fp) == NULL)
520 			return -1;
521 		p = rf_find_non_white(buf, 0);
522 		if (!strncmp(p, "START", strlen("START"))) {
523 			p = rf_find_white(p);
524 			p = rf_find_non_white(p, 0);
525 			if (!strncmp(p, string, strlen(string)))
526 				return 0;
527 		}
528 	}
529 }
530 
531 /* reads from file fp into buf until it finds an interesting line */
532 static int
rf_get_next_nonblank_line(char * buf,int len,FILE * fp,const char * errmsg)533 rf_get_next_nonblank_line(char *buf, int len, FILE *fp, const char *errmsg)
534 {
535 	char *p;
536 	size_t l;
537 
538 	while (fgets(buf, len, fp) != NULL) {
539 		p = rf_find_non_white(buf, 0);
540 		if (*p == '\n' || *p == '\0' || *p == '#')
541 			continue;
542 		l = strlen(buf);
543 		while (l > 0 && (buf[--l] == ' ' || buf[l] == '\n'))
544 			buf[l] = '\0';
545 		return 0;
546 	}
547 	if (errmsg)
548 		warnx("%s", errmsg);
549 	return 1;
550 }
551 
552 /*
553  * Allocates an array for the spare table, and initializes it from a file.
554  * In the user-level version, this is called when recon is initiated.
555  * When/if I move recon into the kernel, there'll be a daemon that does
556  * an ioctl into raidframe which will block until a spare table is needed.
557  * When it returns, it will read a spare table from the file system,
558  * pass it into the kernel via a different ioctl, and then block again
559  * on the original ioctl.
560  *
561  * This is specific to the declustered layout, but doesn't belong in
562  * rf_decluster.c because it uses stuff that can't be compiled into
563  * the kernel, and it needs to be compiled into the user-level sparemap daemon.
564  */
565 void *
rf_ReadSpareTable(RF_SparetWait_t * req,char * fname)566 rf_ReadSpareTable(RF_SparetWait_t *req, char *fname)
567 {
568 	int i, j, numFound, linecount, tableNum, tupleNum,
569 	    spareDisk, spareBlkOffset;
570 	char buf[BUFSIZ], targString[BUFSIZ], errString[BUFSIZ];
571 	RF_SpareTableEntry_t **table;
572 	FILE *fp = NULL;
573 	size_t len;
574 
575 	/* allocate and initialize the table */
576 	table = calloc(req->TablesPerSpareRegion, sizeof(*table));
577 	if (table == NULL) {
578 		warn("%s: Unable to allocate table", __func__);
579 		return NULL;
580 	}
581 	for (i = 0; i < req->TablesPerSpareRegion; i++) {
582 		table[i] = calloc(req->BlocksPerTable, sizeof(**table));
583 		if (table[i] == NULL) {
584 			warn("%s: Unable to allocate table:%d", __func__, i);
585 			goto out;
586 		}
587 		for (j = 0; j < req->BlocksPerTable; j++)
588 			table[i][j].spareDisk =
589 			    table[i][j].spareBlockOffsetInSUs = -1;
590 	}
591 
592 	/* 2.  open sparemap file, sanity check */
593 	if ((fp = fopen(fname, "r")) == NULL) {
594 		warn("%s: Can't open sparemap file %s", __func__, fname);
595 		goto out;
596 	}
597 	if (rf_get_next_nonblank_line(buf, 1024, fp,
598 	    "Invalid sparemap file:  can't find header line"))
599 		goto out;
600 
601 	len = strlen(buf);
602 	if (len != 0 && buf[len - 1] == '\n')
603 		buf[len - 1] = '\0';
604 
605 	snprintf(targString, sizeof(targString), "fdisk %d\n", req->fcol);
606 	snprintf(errString, sizeof(errString),
607 	    "Invalid sparemap file: Can't find \"fdisk %d\" line", req->fcol);
608 	for (;;) {
609 		rf_get_next_nonblank_line(buf, sizeof(buf), fp, errString);
610 		if (!strncmp(buf, targString, strlen(targString)))
611 			break;
612 	}
613 
614 	/* no more blank lines or comments allowed now */
615 	linecount = req->TablesPerSpareRegion * req->TableDepthInPUs;
616 	for (i = 0; i < linecount; i++) {
617 		char linebuf[BUFSIZ];
618 
619 		if (fgets(linebuf, BUFSIZ, fp) == NULL) {
620 			warnx("Sparemap file prematurely exhausted after %d "
621 			    "of %d lines", i, linecount);
622 			goto out;
623 		}
624 		numFound = sscanf(linebuf, " %d %d %d %d", &tableNum, &tupleNum,
625 		    &spareDisk, &spareBlkOffset);
626 		if (numFound != 4) {
627 			warnx("Sparemap file format error - "
628 			    "line %d of %d lines",
629 			    i + 1, linecount);
630 			goto out;
631 		}
632 
633 		table[tableNum][tupleNum].spareDisk = spareDisk;
634 		table[tableNum][tupleNum].spareBlockOffsetInSUs =
635 		    spareBlkOffset * req->SUsPerPU;
636 	}
637 
638 	fclose(fp);
639 	return (void *) table;
640 out:
641 	if (fp)
642 		fclose(fp);
643 	for (i = 0; i < req->TablesPerSpareRegion; i++)
644 		free(table[i]);
645 	free(table);
646 	return NULL;
647 }
648