]> granicus.if.org Git - sysstat/blob - ioconf.c
Replace strcpy() with strncpy() to avoid buffer overflows
[sysstat] / ioconf.c
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
42 static unsigned int ioc_parsed = 0;
43 static struct ioc_entry *ioconf[MAX_BLKDEV + 1];
44 static unsigned int ioc_refnr[MAX_BLKDEV + 1];
45
46 /*
47  ***************************************************************************
48  * Free ioc_entry structures
49  ***************************************************************************
50  */
51 static 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  */
90 static 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
119 char *ioc_ito10(unsigned int n)
120 {
121         return (ioc_conv(10, 0, "0123456789", n));
122 }
123
124 char *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  */
140 int 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 (major > MAX_BLKDEV) {
199                                 fprintf(stderr, "%s: Major #%u out of range\n",
200                                         ioconf_name, major);
201                                 continue;
202                         }
203                         if (ioconf[indirect] == NULL) {
204                                 fprintf(stderr,
205                                         "%s: Indirect record '%u:%u:%u:...'"
206                                         " references not yet seen major %u\n",
207                                         ioconf_name, major, indirect, iocp->ctrlno, major);
208                                 continue;
209                         }
210                         /*
211                          * Cool. Point this device at its referent.
212                          * Skip last: (last field my be empty...)
213                          * if it was empty and : was in the sscanf spec
214                          * we'd only see 3 fields...
215                          */
216                         if (i == 3) {
217                                 /* reference the mothership */
218                                 iocp->desc = ioconf[indirect]->blkp->desc;
219                         }
220                         else {
221                                 IOC_ALLOC(iocp->desc, char, IOC_DESCLEN + 1);
222                                 strncpy(iocp->desc, desc, IOC_DESCLEN);
223                         }
224                         ioc_refnr[indirect]++;
225                         ioconf[major] = iocp;
226                         iocp->basemajor = indirect;
227                         iocp->blkp = ioconf[indirect]->blkp;
228                         iocp->live = 0;
229                         iocp = NULL;
230                         continue;
231                         /* all done with indirect record */
232                 }
233
234                 /* maybe it's a full record? */
235
236                 i = sscanf(buf, "%u:%[^:]:%[^:]:%u:%[^:]:%u:%[^:]:%u:%s",
237                            &major, blkp->name,
238                            cfmt, &iocp->ctrlno,
239                            dfmt, &blkp->dcount,
240                            pfmt, &blkp->pcount,
241                            desc);
242
243                 if (i != 9) {
244                         fprintf(stderr, "%s: Malformed %u field record: %s\n",
245                                 ioconf_name, i, buf);
246                         continue;
247                 }
248
249                 /* this is a full-fledged direct record */
250
251                 if ((major == 0) || (major >= MAX_BLKDEV)) {
252                         fprintf(stderr, "%s: major #%u out of range\n",
253                                 __FUNCTION__, major);
254                         continue;
255                 }
256
257                 /* is this an exception record? */
258                 if (*cfmt == 'x') {
259                         struct blk_config *xblkp;
260
261                         /*
262                          * device has an aliased minor
263                          * for now we only support on exception per major
264                          * (catering to initrd: (1,250))
265                          */
266                         if (ioconf[major] == NULL) {
267                                 fprintf(stderr, "%s: type 'x' record for"
268                                         " major #%u must follow the base record - ignored\n",
269                                         ioconf_name, major);
270                                 continue;
271                         }
272                         xblkp = ioconf[major]->blkp;
273
274                         if (xblkp->ext) {
275                                 /*
276                                  * Enforce one minor exception per major policy
277                                  * note: this applies to each major number and
278                                  * all of it's indirect (short form) majors
279                                  */
280                                 fprintf(stderr, "%s: duplicate 'x' record for"
281                                         " major #%u - ignored\ninput line: %s\n",
282                                         ioconf_name, major, buf);
283                                 continue;
284                         }
285                         /*
286                          * Decorate the base major struct with the
287                          * exception info
288                          */
289                         xblkp->ext_minor = iocp->ctrlno;
290                         strncpy(xblkp->ext_name, blkp->name, IOC_NAMELEN + 1);
291                         xblkp->ext_name[IOC_NAMELEN] = '\0';
292                         xblkp->ext = 1;
293                         continue;
294                 }
295
296                 /*
297                  * Preformat the sprintf format strings for generating
298                  * c-d-p info in ioc_name()
299                  */
300
301                 /* basename of device + provided string + controller # */
302                 if (*cfmt == '*') {
303                         strncpy(blkp->cfmt, blkp->name, IOC_FMTLEN);
304                         blkp->cfmt[IOC_FMTLEN] = '\0';
305                 }
306                 else {
307                         sprintf(blkp->cfmt, "%s%s%%d", blkp->name, cfmt);
308                         ++(blkp->ctrl_explicit);
309                 }
310
311                 /* Disk */
312                 *blkp->dfmt = '\0';
313                 switch (*dfmt) {
314                 case 'a':
315                         blkp->cconv = ioc_ito26;
316                         strcpy(blkp->dfmt, "%s");
317                         break;
318
319                 case '%':
320                         strncpy(blkp->dfmt, dfmt + 1, IOC_FMTLEN);
321                         /* fallthrough to next case */
322                 case 'd':
323                         blkp->cconv = ioc_ito10;
324                         strcat(blkp->dfmt, "%s");
325                         break;
326                 }
327
328                 /* Partition */
329                 sprintf(blkp->pfmt, "%s%%d", (*pfmt == '*') ? "" : pfmt);
330
331                 /*
332                  * We're good to go.
333                  * Stuff the ioc_entry and ref it.
334                  */
335                 iocp->live = 1;
336                 iocp->blkp = blkp;
337                 iocp->desc = NULL;
338                 iocp->basemajor = major;
339                 ioconf[major] = iocp;
340                 strncpy(blkp->desc, desc, IOC_DESCLEN);
341                 blkp = NULL; iocp = NULL;
342                 ++count;
343         }
344         fclose(fp);
345
346         /*
347          * These will become leaks if we ever 'continue'
348          * after IOC_ALLOC( blkp->desc ... ).
349          * Right now, we don't.
350          */
351         free(blkp);
352         free(iocp);
353
354         /* Indicate that ioconf file has been parsed */
355         ioc_parsed = 1;
356
357         return (count);
358
359 free_and_return:
360         /* Free pointers and return */
361         fclose(fp);
362         if (blkp) {
363                 free(blkp);
364         }
365         if (iocp) {
366                 free(iocp);
367         }
368
369         return 0;
370 }
371
372 /*
373  ***************************************************************************
374  *  ioc_name() - Generate a name from a maj,min pair
375  *
376  * IN:
377  * @major       Device major number.
378  * @minor       Device minor number.
379  *
380  * RETURNS:
381  * Returns NULL if major or minor are out of range
382  * otherwise returns a pointer to a static string containing
383  * the generated name.
384  ***************************************************************************
385  */
386
387 char *ioc_name(unsigned int major, unsigned int minor)
388 {
389         static char name[IOC_DEVLEN + 1];
390         struct ioc_entry *p;
391         int base, offset;
392
393         if ((MAX_BLKDEV <= major) || (IOC_MAXMINOR <= minor)) {
394                 return (NULL);
395         }
396
397         if (!ioc_parsed && !ioc_init())
398                 return (NULL);
399
400         p = ioconf[major];
401
402         /* Invalid major or minor numbers? */
403         if ((p == NULL) || ((minor & 0xff) >= (p->blkp->dcount * p->blkp->pcount))) {
404                 /*
405                  * That minor test is only there for IDE-style devices
406                  * that have no minors over 128.
407                  */
408                 strcpy(name, K_NODEV);
409                 return (name);
410         }
411
412         /* Is this an extension record? */
413         if (p->blkp->ext && (p->blkp->ext_minor == minor)) {
414                 strncpy(name, p->blkp->ext_name, IOC_DEVLEN + 1);
415                 name[IOC_DEVLEN] = '\0';
416                 return (name);
417         }
418
419         /* OK.  we're doing an actual device name... */
420
421         /*
422          * Assemble base + optional controller info
423          * this is of course too clever by half
424          * the parser has already cooked cfmt, dfmt to make this easy
425          * (we parse once but may generate lots of names)
426          */
427         base = p->ctrlno * p->blkp->dcount;
428         if (minor >= 256) {
429                 base += p->blkp->dcount * (ioc_refnr[p->basemajor] + 1) * (minor >> 8);
430         }
431
432         offset = (minor & 0xff) / p->blkp->pcount;
433         if (!p->blkp->ctrl_explicit) {
434                 offset += base;
435         }
436
437         /*
438          * These sprintfs can't be coalesced because the first might
439          * ignore its first arg
440          */
441         sprintf(name, p->blkp->cfmt, p->ctrlno);
442         sprintf(name + strlen(name), p->blkp->dfmt, p->blkp->cconv(offset));
443
444         if (!IS_WHOLE(major, minor)) {
445                 /*
446                  * Tack on partition info, format string cooked (curried?) by
447                  * the parser
448                  */
449                 sprintf(name + strlen(name), p->blkp->pfmt, minor % p->blkp->pcount);
450         }
451         return (name);
452 }
453
454 /*
455  ***************************************************************************
456  * Check whether a device is a whole disk device or not.
457  *
458  * IN:
459  * @major       Device major number.
460  * @minor       Device minor number.
461  *
462  * RETURNS:
463  * Predicate: Returns 1 if dev (major,minor) is a whole disk device.
464  *            Returns 0 otherwise.
465  ***************************************************************************
466  */
467 int ioc_iswhole(unsigned int major, unsigned int minor)
468 {
469         if (!ioc_parsed && !ioc_init())
470                 return 0;
471
472         if (major >= MAX_BLKDEV)
473                 /*
474                  * Later: Handle Linux long major numbers here.
475                  * Now: This is an error.
476                  */
477                 return 0;
478
479         if (ioconf[major] == NULL)
480                 /* Device not registered */
481                 return 0 ;
482
483         return (IS_WHOLE(major, minor));
484 }
485
486 /*
487  ***************************************************************************
488  * Transform device mapper name: Get the user assigned name of the logical
489  * device instead of the internal device mapper numbering.
490  *
491  * IN:
492  * @major       Device major number.
493  * @minor       Device minor number.
494  *
495  * RETURNS:
496  * Assigned name of the logical device.
497  ***************************************************************************
498  */
499 char *transform_devmapname(unsigned int major, unsigned int minor)
500 {
501         DIR *dm_dir;
502         struct dirent *dp;
503         char filen[MAX_FILE_LEN];
504         char *dm_name = NULL;
505         static char name[MAX_NAME_LEN];
506         struct stat aux;
507         unsigned int dm_major, dm_minor;
508
509         if ((dm_dir = opendir(DEVMAP_DIR)) == NULL) {
510                 fprintf(stderr, _("Cannot open %s: %s\n"), DEVMAP_DIR, strerror(errno));
511                 exit(4);
512         }
513
514         while ((dp = readdir(dm_dir)) != NULL) {
515                 /* For each file in DEVMAP_DIR */
516
517                 snprintf(filen, MAX_FILE_LEN, "%s/%s", DEVMAP_DIR, dp->d_name);
518                 filen[MAX_FILE_LEN - 1] = '\0';
519
520                 if (stat(filen, &aux) == 0) {
521                         /* Get its minor and major numbers */
522
523                         dm_major = major(aux.st_rdev);
524                         dm_minor = minor(aux.st_rdev);
525
526                         if ((dm_minor == minor) && (dm_major == major)) {
527                                 strncpy(name, dp->d_name, MAX_NAME_LEN);
528                                 name[MAX_NAME_LEN - 1] = '\0';
529                                 dm_name = name;
530                                 break;
531                         }
532                 }
533         }
534         closedir(dm_dir);
535
536         return dm_name;
537 }