1/*
2 * ioconf: ioconf configuration file handling code
3 * Original code (C) 2004 by Red Hat (Charlie Bennett <ccb@redhat.com>)
4 *
5 * Modified and maintained by Sebastien GODARD (sysstat <at> orange.fr)
6 *
7 ***************************************************************************
8 * This program is free software; you can redistribute it and/or modify it *
9 * under the terms of the GNU General Public License as published  by  the *
10 * Free Software Foundation; either version 2 of the License, or (at  your *
11 * option) any later version.                                              *
12 *                                                                         *
13 * This program is distributed in the hope that it  will  be  useful,  but *
14 * WITHOUT ANY WARRANTY; without the implied warranty  of  MERCHANTABILITY *
15 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License *
16 * for more details.                                                       *
17 *                                                                         *
18 * You should have received a copy of the GNU General Public License along *
19 * with this program; if not, write to the Free Software Foundation, Inc., *
20 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA                   *
21 ***************************************************************************
22 */
23
24#include <stdio.h>
25#include <stdlib.h>
26#include <string.h>
27#include <errno.h>
28#include <dirent.h>
29#include <sys/stat.h>
30
31#include "ioconf.h"
32#include "common.h"
33
34#ifdef USE_NLS
35#include <locale.h>
36#include <libintl.h>
37#define _(string) gettext(string)
38#else
39#define _(string) (string)
40#endif
41
42static unsigned int ioc_parsed = 0;
43static struct ioc_entry *ioconf[MAX_BLKDEV + 1];
44static unsigned int ioc_refnr[MAX_BLKDEV + 1];
45
46/*
47 ***************************************************************************
48 * Free ioc_entry structures
49 ***************************************************************************
50 */
51static void ioc_free(void)
52{
53	unsigned int i;
54	struct ioc_entry **p;
55
56	/* Take out all of the references first */
57	for (i = 0, p = ioconf; i < MAX_BLKDEV; ++i, ++p) {
58		if ((*p == NULL) || ((*p)->live))
59			continue;
60
61		if ((*p)->desc != (*p)->blkp->desc) {
62			/* Not a shared description */
63			free((*p)->desc);
64		}
65		free(*p);
66		*p = NULL;
67	}
68
69	/* Now the live ones */
70	for (i = 0, p = ioconf; i < MAX_BLKDEV; ++i, ++p) {
71		if (*p == NULL)
72			continue;
73		free((*p)->blkp);
74		free(*p);
75		*p = NULL;
76	}
77}
78
79/*
80 ***************************************************************************
81 * ioc_conv - Turn a number into a string in radix <radix> using symbol
82 *   set (and ordering) syms.  Use nozero to generate strings
83 *   in which the number system uses a single sym for the
84 *   radix value (not 2, like decimal) and adds a column only
85 *   at radix+1.  If decimal were like this:
86 *
87 *   (no zero) 1 2 3 4 5 6 7 8 9 0 11 12 13 14 15 16 17 18 19 10 ...
88 ***************************************************************************
89 */
90static char *ioc_conv(int radix, int nozero, const char *syms,
91		      unsigned int val)
92{
93	static char out[17];
94	char *p;
95	int j;
96
97	*(p = out + 16) = '\0';
98
99	val += nozero;
100
101	if (val == 0) {
102		if (!nozero) {
103			*--p = '0';
104		}
105		return (p);	/* Empty string if nozero radix gets val == 0 */
106	}
107
108	while (val > 0) {
109		*--p = syms[j = val % radix];
110		val /= radix;
111		if (nozero && (j == 0)) {
112			/* Comp for 10 in nozero bases */
113			--val;
114		}
115	}
116	return (p);
117}
118
119char *ioc_ito10(unsigned int n)
120{
121	return (ioc_conv(10, 0, "0123456789", n));
122}
123
124char *ioc_ito26(unsigned int n)
125{
126	return (ioc_conv(26, 1, "zabcdefghijklmnopqrstuvwxy", n));
127}
128
129/*
130 ***************************************************************************
131 * ioc_init() - internalize the ioconf file
132 *
133 * given:    void
134 * does:     parses IOCONF into ioconf, an array of ioc_entry *
135 *           Only entries having lines in IOCONF will have valid pointers
136 * return:   1 on success
137 *           0 on failure
138 ***************************************************************************
139 */
140int ioc_init(void)
141{
142	FILE *fp;
143	unsigned int i, major, indirect, count = 0;
144	char buf[IOC_LINESIZ + 1];
145	char cfmt[IOC_FMTLEN + 1];
146	char dfmt[IOC_FMTLEN + 1];
147	char pfmt[IOC_FMTLEN + 1];
148	char desc[IOC_DESCLEN + 1];
149	struct ioc_entry  *iocp = NULL;
150	struct blk_config *blkp = NULL;
151	char ioconf_name[64];
152
153	if ((fp = fopen(IOCONF, "r")) == NULL) {
154		if ((fp = fopen(LOCAL_IOCONF, "r")) == NULL)
155			return 0;
156		strncpy(ioconf_name, LOCAL_IOCONF, 64);
157	}
158	else {
159		strncpy(ioconf_name, IOCONF, 64);
160	}
161	ioconf_name[63] = '\0';
162
163	/* Init ioc_refnr array */
164	memset(ioc_refnr, 0, sizeof(ioc_refnr));
165
166	while (fgets(buf, IOC_LINESIZ, fp)) {
167
168		if ((*buf == '#') || (*buf == '\n'))
169			continue;
170
171		/*
172		 * Preallocate some (probably) needed data structures
173		 */
174		IOC_ALLOC(blkp, struct blk_config, BLK_CONFIG_SIZE);
175		IOC_ALLOC(iocp, struct ioc_entry, IOC_ENTRY_SIZE);
176		memset(blkp, 0, BLK_CONFIG_SIZE);
177		memset(iocp, 0, IOC_ENTRY_SIZE);
178
179		i = sscanf(buf, "%u:%u:%u:%s",
180			   &major, &indirect, &iocp->ctrlno, desc);
181
182		if (i != 4) {
183			i = sscanf(buf, "%u:%u:%u",
184				   &major, &indirect, &iocp->ctrlno);
185		}
186
187		if ((i == 3) || (i == 4)) {
188			/* indirect record */
189			if (indirect == 0) {
190				/* conventional usage for unsupported device */
191				continue;
192			}
193			if (indirect >= MAX_BLKDEV) {
194				fprintf(stderr, "%s: Indirect major #%u out of range\n",
195					ioconf_name, indirect);
196				continue;
197			}
198			if (ioconf[indirect] == NULL) {
199				fprintf(stderr,
200					"%s: Indirect record '%u:%u:%u:...'"
201					" references not yet seen major %u\n",
202					ioconf_name, major, indirect, iocp->ctrlno, major);
203				continue;
204			}
205			/*
206			 * Cool. Point this device at its referent.
207			 * Skip last: (last field my be empty...)
208			 * if it was empty and : was in the sscanf spec
209			 * we'd only see 3 fields...
210			 */
211			if (i == 3) {
212				/* reference the mothership */
213				iocp->desc = ioconf[indirect]->blkp->desc;
214			}
215			else {
216				IOC_ALLOC(iocp->desc, char, IOC_DESCLEN + 1);
217				strncpy(iocp->desc, desc, IOC_DESCLEN);
218			}
219			ioc_refnr[indirect]++;
220			ioconf[major] = iocp;
221			iocp->basemajor = indirect;
222			iocp->blkp = ioconf[indirect]->blkp;
223			iocp->live = 0;
224			iocp = NULL;
225			continue;
226			/* all done with indirect record */
227		}
228
229		/* maybe it's a full record? */
230
231		i = sscanf(buf, "%u:%[^:]:%[^:]:%d:%[^:]:%u:%[^:]:%u:%s",
232			   &major, blkp->name,
233			   cfmt, &iocp->ctrlno,
234			   dfmt, &blkp->dcount,
235			   pfmt, &blkp->pcount,
236			   desc);
237
238		if (i != 9) {
239			fprintf(stderr, "%s: Malformed %d field record: %s\n",
240				ioconf_name, i, buf);
241			continue;
242		}
243
244		/* this is a full-fledged direct record */
245
246		if ((major == 0) || (major >= MAX_BLKDEV)) {
247			fprintf(stderr, "%s: major #%u out of range\n",
248				__FUNCTION__, major);
249			continue;
250		}
251
252		/* is this an exception record? */
253		if (*cfmt == 'x') {
254			struct blk_config *xblkp;
255
256			/*
257			 * device has an aliased minor
258			 * for now we only support on exception per major
259			 * (catering to initrd: (1,250))
260			 */
261			if (ioconf[major] == NULL) {
262				fprintf(stderr, "%s: type 'x' record for"
263					" major #%u must follow the base record - ignored\n",
264					ioconf_name, major);
265				continue;
266			}
267			xblkp = ioconf[major]->blkp;
268
269			if (xblkp->ext) {
270				/*
271				 * Enforce one minor exception per major policy
272				 * note: this applies to each major number and
273				 * all of it's indirect (short form) majors
274				 */
275				fprintf(stderr, "%s: duplicate 'x' record for"
276					" major #%u - ignored\ninput line: %s\n",
277					ioconf_name, major, buf);
278				continue;
279			}
280			/*
281			 * Decorate the base major struct with the
282			 * exception info
283			 */
284			xblkp->ext_minor = iocp->ctrlno;
285			strcpy(xblkp->ext_name, blkp->name);
286			xblkp->ext = 1;
287			continue;
288		}
289
290		/*
291		 * Preformat the sprintf format strings for generating
292		 * c-d-p info in ioc_name()
293		 */
294
295		/* basename of device + provided string + controller # */
296		if (*cfmt == '*') {
297			strcpy(blkp->cfmt, blkp->name);
298		}
299		else {
300			sprintf(blkp->cfmt, "%s%s%%d", blkp->name, cfmt);
301			++(blkp->ctrl_explicit);
302		}
303
304		/* Disk */
305		*blkp->dfmt = '\0';
306		switch (*dfmt) {
307		case 'a':
308			blkp->cconv = ioc_ito26;
309			strcpy(blkp->dfmt, "%s");
310			break;
311
312		case '%':
313			strcpy(blkp->dfmt, dfmt + 1);
314		case 'd':
315			blkp->cconv = ioc_ito10;
316			strcat(blkp->dfmt, "%s");
317			break;
318		}
319
320		/* Partition */
321		sprintf(blkp->pfmt, "%s%%d", (*pfmt == '*') ? "" : pfmt);
322
323		/*
324		 * We're good to go.
325		 * Stuff the ioc_entry and ref it.
326		 */
327		iocp->live = 1;
328		iocp->blkp = blkp;
329		iocp->desc = NULL;
330		iocp->basemajor = major;
331		ioconf[major] = iocp;
332		strncpy(blkp->desc, desc, IOC_DESCLEN);
333		blkp = NULL; iocp = NULL;
334		++count;
335	}
336	fclose(fp);
337
338	/*
339	 * These will become leaks if we ever 'continue'
340	 * after IOC_ALLOC( blkp->desc ... ).
341	 * Right now, we don't.
342	 */
343	if (blkp != NULL)
344		free(blkp);
345	if (iocp != NULL)
346		free(iocp);
347
348	/* Indicate that ioconf file has been parsed */
349	ioc_parsed = 1;
350
351	return (count);
352}
353
354/*
355 ***************************************************************************
356 *  ioc_name() - Generate a name from a maj,min pair
357 *
358 * IN:
359 * @major	Device major number.
360 * @minor	Device minor number.
361 *
362 * RETURNS:
363 * Returns NULL if major or minor are out of range
364 * otherwise returns a pointer to a static string containing
365 * the generated name.
366 ***************************************************************************
367 */
368
369char *ioc_name(unsigned int major, unsigned int minor)
370{
371	static char name[IOC_DEVLEN + 1];
372	struct ioc_entry *p;
373	int base, offset;
374
375	if ((MAX_BLKDEV <= major) || (IOC_MAXMINOR <= minor)) {
376		return (NULL);
377	}
378
379	if (!ioc_parsed && !ioc_init())
380		return (NULL);
381
382	p = ioconf[major];
383
384	/* Invalid major or minor numbers? */
385	if ((p == NULL) || ((minor & 0xff) >= (p->blkp->dcount * p->blkp->pcount))) {
386		/*
387		 * That minor test is only there for IDE-style devices
388		 * that have no minors over 128.
389		 */
390		strcpy(name, K_NODEV);
391		return (name);
392	}
393
394	/* Is this an extension record? */
395	if (p->blkp->ext && (p->blkp->ext_minor == minor)) {
396		strcpy(name, p->blkp->ext_name);
397		return (name);
398	}
399
400	/* OK.  we're doing an actual device name... */
401
402	/*
403	 * Assemble base + optional controller info
404	 * this is of course too clever by half
405	 * the parser has already cooked cfmt, dfmt to make this easy
406	 * (we parse once but may generate lots of names)
407	 */
408	base = p->ctrlno * p->blkp->dcount;
409	if (minor >= 256) {
410		base += p->blkp->dcount * (ioc_refnr[p->basemajor] + 1) * (minor >> 8);
411	}
412
413	offset = (minor & 0xff) / p->blkp->pcount;
414	if (!p->blkp->ctrl_explicit) {
415		offset += base;
416	}
417
418	/*
419	 * These sprintfs can't be coalesced because the first might
420	 * ignore its first arg
421	 */
422	sprintf(name, p->blkp->cfmt, p->ctrlno);
423	sprintf(name + strlen(name), p->blkp->dfmt, p->blkp->cconv(offset));
424
425	if (!IS_WHOLE(major, minor)) {
426		/*
427		 * Tack on partition info, format string cooked (curried?) by
428		 * the parser
429		 */
430		sprintf(name + strlen(name), p->blkp->pfmt, minor % p->blkp->pcount);
431	}
432	return (name);
433}
434
435/*
436 ***************************************************************************
437 * Check whether a device is a whole disk device or not.
438 *
439 * IN:
440 * @major	Device major number.
441 * @minor	Device minor number.
442 *
443 * RETURNS:
444 * Predicate: Returns 1 if dev (major,minor) is a whole disk device.
445 *            Returns 0 otherwise.
446 ***************************************************************************
447 */
448int ioc_iswhole(unsigned int major, unsigned int minor)
449{
450	if (!ioc_parsed && !ioc_init())
451		return 0;
452
453	if (major >= MAX_BLKDEV)
454		/*
455		 * Later: Handle Linux long major numbers here.
456		 * Now: This is an error.
457		 */
458		return 0;
459
460	if (ioconf[major] == NULL)
461		/* Device not registered */
462		return 0 ;
463
464	return (IS_WHOLE(major, minor));
465}
466
467/*
468 ***************************************************************************
469 * Transform device mapper name: Get the user assigned name of the logical
470 * device instead of the internal device mapper numbering.
471 *
472 * IN:
473 * @major	Device major number.
474 * @minor	Device minor number.
475 *
476 * RETURNS:
477 * Assigned name of the logical device.
478 ***************************************************************************
479 */
480char *transform_devmapname(unsigned int major, unsigned int minor)
481{
482	DIR *dm_dir;
483	struct dirent *dp;
484	char filen[MAX_FILE_LEN];
485	char *dm_name = NULL;
486	static char name[MAX_NAME_LEN];
487	struct stat aux;
488	unsigned int dm_major, dm_minor;
489
490	if ((dm_dir = opendir(DEVMAP_DIR)) == NULL) {
491		fprintf(stderr, _("Cannot open %s: %s\n"), DEVMAP_DIR, strerror(errno));
492		exit(4);
493	}
494
495	while ((dp = readdir(dm_dir)) != NULL) {
496		/* For each file in DEVMAP_DIR */
497
498		snprintf(filen, MAX_FILE_LEN, "%s/%s", DEVMAP_DIR, dp->d_name);
499		filen[MAX_FILE_LEN - 1] = '\0';
500
501		if (stat(filen, &aux) == 0) {
502			/* Get its minor and major numbers */
503
504			dm_major = major(aux.st_rdev);
505			dm_minor = minor(aux.st_rdev);
506
507			if ((dm_minor == minor) && (dm_major == major)) {
508				strncpy(name, dp->d_name, MAX_NAME_LEN);
509				name[MAX_NAME_LEN - 1] = '\0';
510				dm_name = name;
511				break;
512			}
513		}
514	}
515	closedir(dm_dir);
516
517	return dm_name;
518}
519