]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - usr.sbin/sysinstall/devices.c
add -n option to suppress clearing the build tree and add -DNO_CLEAN
[FreeBSD/FreeBSD.git] / usr.sbin / sysinstall / devices.c
1 /*
2  * The new sysinstall program.
3  *
4  * This is probably the last program in the `sysinstall' line - the next
5  * generation being essentially a complete rewrite.
6  *
7  * $FreeBSD$
8  *
9  * Copyright (c) 1995
10  *      Jordan Hubbard.  All rights reserved.
11  *
12  * Redistribution and use in source and binary forms, with or without
13  * modification, are permitted provided that the following conditions
14  * are met:
15  * 1. Redistributions of source code must retain the above copyright
16  *    notice, this list of conditions and the following disclaimer,
17  *    verbatim and that no modifications are made prior to this
18  *    point in the file.
19  * 2. Redistributions in binary form must reproduce the above copyright
20  *    notice, this list of conditions and the following disclaimer in the
21  *    documentation and/or other materials provided with the distribution.
22  *
23  * THIS SOFTWARE IS PROVIDED BY JORDAN HUBBARD ``AS IS'' AND
24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26  * ARE DISCLAIMED.  IN NO EVENT SHALL JORDAN HUBBARD OR HIS PETS BE LIABLE
27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29  * OR SERVICES; LOSS OF USE, DATA, LIFE OR PROFITS; OR BUSINESS INTERRUPTION)
30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33  * SUCH DAMAGE.
34  *
35  */
36
37 #include "sysinstall.h"
38 #include <sys/fcntl.h>
39 #include <sys/param.h>
40 #include <sys/socket.h>
41 #include <sys/ioctl.h>
42 #include <sys/errno.h>
43 #include <sys/time.h>
44 #include <net/if.h>
45 #include <net/if_var.h>
46 #include <net/if_dl.h>
47 #include <netinet/in.h>
48 #include <netinet/in_var.h>
49 #include <arpa/inet.h>
50 #include <ctype.h>
51 #include <libdisk.h>
52
53 static Device *Devices[DEV_MAX];
54 static int numDevs;
55
56 #define DEVICE_ENTRY(type, name, descr, max)    { type, name, descr, max }
57
58 #define CDROM(name, descr, max)                                 \
59         DEVICE_ENTRY(DEVICE_TYPE_CDROM, name, descr, max)
60 #define TAPE(name, descr, max)                                          \
61         DEVICE_ENTRY(DEVICE_TYPE_TAPE, name, descr, max)
62 #define DISK(name, descr, max)                                          \
63         DEVICE_ENTRY(DEVICE_TYPE_DISK, name, descr, max)
64 #define FLOPPY(name, descr, max)                                        \
65         DEVICE_ENTRY(DEVICE_TYPE_FLOPPY, name, descr, max)
66 #define NETWORK(name, descr)                                            \
67         DEVICE_ENTRY(DEVICE_TYPE_NETWORK, name, descr, 0)
68 #define SERIAL(name, descr, max)                                        \
69         DEVICE_ENTRY(DEVICE_TYPE_NETWORK, name, descr, max)
70
71 static struct _devname {
72     DeviceType type;
73     char *name;
74     char *description;
75     int max;
76 } device_names[] = {
77     CDROM("cd%d",       "SCSI CDROM drive",                     4),
78     CDROM("mcd%d",      "Mitsumi (old model) CDROM drive",      4),
79     CDROM("scd%d",      "Sony CDROM drive - CDU31/33A type",    4),
80     CDROM("acd%d",      "ATAPI/IDE CDROM",                      4),
81     TAPE("sa%d",        "SCSI tape drive",                      4),
82     TAPE("rwt%d",       "Wangtek tape drive",                   4),
83     DISK("da%d",        "SCSI disk device",                     16),
84     DISK("ad%d",        "ATA/IDE disk device",                  16),
85     DISK("ar%d",        "ATA/IDE RAID device",                  16),
86     DISK("afd%d",       "ATAPI/IDE floppy device",              4),
87     DISK("mlxd%d",      "Mylex RAID disk",                      4),
88     DISK("amrd%d",      "AMI MegaRAID drive",                   4),
89     DISK("idad%d",      "Compaq RAID array",                    4),
90     DISK("twed%d",      "3ware ATA RAID array",                 4),
91     DISK("aacd%d",      "Adaptec FSA RAID array",               4),
92     DISK("ipsd%d",      "IBM ServeRAID RAID array",             4),
93     DISK("mfid%d",      "LSI MegaRAID SAS array",               4),
94     FLOPPY("fd%d",      "floppy drive unit A",                  4),
95     SERIAL("cuad%d",    "%s on device %s (COM%d)",              16),
96     NETWORK("ae",       "Attansic/Atheros L2 FastEthernet"),
97     NETWORK("age",      "Attansic/Atheros L1 Gigabit Ethernet"),
98     NETWORK("an",       "Aironet 4500/4800 802.11 wireless adapter"),
99     NETWORK("ath",      "Atheros IEEE 802.11 wireless adapter"),
100     NETWORK("aue",      "ADMtek USB Ethernet adapter"),
101     NETWORK("axe",      "ASIX Electronics USB Ethernet adapter"),
102     NETWORK("bce",      "Broadcom NetXtreme II Gigabit Ethernet card"),
103     NETWORK("bfe",      "Broadcom BCM440x PCI Ethernet card"),
104     NETWORK("bge",      "Broadcom BCM570x PCI Gigabit Ethernet card"),
105     NETWORK("cue",      "CATC USB Ethernet adapter"),
106     NETWORK("cxgb",     "Chelsio T3 10Gb Ethernet card"),
107     NETWORK("fpa",      "DEC DEFPA PCI FDDI card"),
108     NETWORK("sr",       "SDL T1/E1 sync serial PCI card"),
109     NETWORK("cc3i",     "SDL HSSI sync serial PCI card"),
110     NETWORK("en",       "Efficient Networks ATM PCI card"),
111     NETWORK("dc",       "DEC/Intel 21143 (and clones) PCI Fast Ethernet card"),
112     NETWORK("de",       "DEC DE435 PCI NIC or other DC21040-AA based card"),
113     NETWORK("fxp",      "Intel EtherExpress Pro/100B PCI Fast Ethernet card"),
114     NETWORK("ed",       "Novell NE1000/2000; 3C503; NE2000-compatible PCMCIA"),
115     NETWORK("ep",       "3Com 3C509 Ethernet card/3C589 PCMCIA"),
116     NETWORK("em",       "Intel(R) PRO/1000 Ethernet card"),
117     NETWORK("ex",       "Intel EtherExpress Pro/10 Ethernet card"),
118     NETWORK("fe",       "Fujitsu MB86960A/MB86965A Ethernet card"),
119     NETWORK("gem",      "Apple GMAC or Sun ERI/GEM Ethernet adapter"),
120     NETWORK("hme",      "Sun HME (Happy Meal Ethernet) Ethernet adapter"),
121     NETWORK("ie",       "AT&T StarLAN 10 and EN100; 3Com 3C507; NI5210"),
122     NETWORK("ipw",      "Intel PRO/Wireless 2100 IEEE 802.11 adapter"),
123     NETWORK("iwi",      "Intel PRO/Wireless 2200BG/2225BG/2915ABG adapter"),
124     NETWORK("ixgb",     "Intel(R) PRO/10Gb Ethernet card"),
125     NETWORK("jme",      "JMicron JMC250 Gigabit/JMC260 Fast Ethernet"),
126     NETWORK("kue",      "Kawasaki LSI USB Ethernet adapter"),
127     NETWORK("le",       "AMD Am7900 LANCE or Am79C9xx PCnet Ethernet adapter"),
128     NETWORK("lge",      "Level 1 LXT1001 Gigabit Ethernet card"),
129     NETWORK("malo",     "Marvell Libertas 88W8335 802.11 wireless adapter"),
130     NETWORK("msk",      "Marvell/SysKonnect Yukon II Gigabit Ethernet"),
131     NETWORK("mxge",     "Myricom Myri10GE 10Gb Ethernet card"),
132     NETWORK("nfe",      "NVIDIA nForce MCP Ethernet"),
133     NETWORK("nge",      "NatSemi PCI Gigabit Ethernet card"),
134     NETWORK("nve",      "NVIDIA nForce MCP Ethernet"),
135     NETWORK("pcn",      "AMD Am79c79x PCI Ethernet card"),
136     NETWORK("ral",      "Ralink Technology IEEE 802.11 wireless adapter"),
137     NETWORK("ray",      "Raytheon Raylink 802.11 wireless adapter"),
138     NETWORK("re",       "RealTek 8139C+/8169/8169S/8110S PCI Ethernet card"),
139     NETWORK("rl",       "RealTek 8129/8139 PCI Ethernet card"),
140     NETWORK("rue",      "RealTek USB Ethernet card"),
141     NETWORK("rum",      "Ralink Technology USB IEEE 802.11 wireless adapter"),
142     NETWORK("sf",       "Adaptec AIC-6915 PCI Ethernet card"),
143     NETWORK("sis",      "SiS 900/SiS 7016 PCI Ethernet card"),
144 #ifdef PC98
145     NETWORK("snc",      "SONIC Ethernet card"),
146 #endif
147     NETWORK("sn",       "SMC/Megahertz Ethernet card"),
148     NETWORK("ste",      "Sundance ST201 PCI Ethernet card"),
149     NETWORK("stge",     "Sundance/Tamarack TC9021 Gigabit Ethernet"),
150     NETWORK("sk",       "SysKonnect PCI Gigabit Ethernet card"),
151     NETWORK("tx",       "SMC 9432TX Ethernet card"),
152     NETWORK("txp",      "3Com 3cR990 Ethernet card"),
153     NETWORK("ti",       "Alteon Networks PCI Gigabit Ethernet card"),
154     NETWORK("tl",       "Texas Instruments ThunderLAN PCI Ethernet card"),
155     NETWORK("upgt",     "Conexant/Intersil PrismGT USB wireless adapter"),
156     NETWORK("ural",     "Ralink Technology RT2500USB 802.11 wireless adapter"),
157     NETWORK("vge",      "VIA VT612x PCI Gigabit Ethernet card"),
158     NETWORK("vr",       "VIA VT3043/VT86C100A Rhine PCI Ethernet card"),
159     NETWORK("vlan",     "IEEE 802.1Q VLAN network interface"),
160     NETWORK("vx",       "3COM 3c590 / 3c595 Ethernet card"),
161     NETWORK("wb",       "Winbond W89C840F PCI Ethernet card"),
162     NETWORK("wi",       "Lucent WaveLAN/IEEE 802.11 wireless adapter"),
163     NETWORK("wpi",      "Intel 3945ABG IEEE 802.11 wireless adapter"),
164     NETWORK("xe",       "Xircom/Intel EtherExpress Pro100/16 Ethernet card"),
165     NETWORK("xl",       "3COM 3c90x / 3c90xB PCI Ethernet card"),
166     NETWORK("zyd",      "ZyDAS ZD1211/ZD1211B USB 802.11 wireless adapter"),
167     NETWORK("fwe",      "FireWire Ethernet emulation"),
168     NETWORK("fwip",     "IP over FireWire"),
169     NETWORK("plip",     "Parallel Port IP (PLIP) peer connection"),
170     NETWORK("lo",       "Loop-back (local) network interface"),
171     NETWORK("disc",     "Software discard network interface"),
172     { 0, NULL, NULL, 0 }
173 };
174
175 Device *
176 new_device(char *name)
177 {
178     Device *dev;
179
180     dev = safe_malloc(sizeof(Device));
181     bzero(dev, sizeof(Device));
182     if (name)
183         SAFE_STRCPY(dev->name, name);
184     return dev;
185 }
186
187 /* Stubs for unimplemented strategy routines */
188 Boolean
189 dummyInit(Device *dev)
190 {
191     return TRUE;
192 }
193
194 FILE *
195 dummyGet(Device *dev, char *dist, Boolean probe)
196 {
197     return NULL;
198 }
199
200 void
201 dummyShutdown(Device *dev)
202 {
203     return;
204 }
205
206 static int
207 deviceTry(struct _devname dev, char *try, int i)
208 {
209     int fd;
210     char unit[80];
211
212     snprintf(unit, sizeof unit, dev.name, i);
213     snprintf(try, FILENAME_MAX, "/dev/%s", unit);
214     if (isDebug())
215         msgDebug("deviceTry: attempting to open %s\n", try);
216     fd = open(try, O_RDONLY);
217     if (fd >= 0) {
218         if (isDebug())
219             msgDebug("deviceTry: open of %s succeeded on first try.\n", try);
220     } else {
221         if (isDebug())
222             msgDebug("deviceTry: open of %s failed.\n", try);
223     }
224     return fd;
225 }
226
227 /* Register a new device in the devices array */
228 Device *
229 deviceRegister(char *name, char *desc, char *devname, DeviceType type, Boolean enabled,
230                Boolean (*init)(Device *), FILE * (*get)(Device *, char *, Boolean),
231                void (*shutdown)(Device *), void *private)
232 {
233     Device *newdev = NULL;
234
235     if (numDevs == DEV_MAX)
236         msgFatal("Too many devices found!");
237     else {
238         newdev = new_device(name);
239         newdev->description = desc;
240         newdev->devname = devname;
241         newdev->type = type;
242         newdev->enabled = enabled;
243         newdev->init = init ? init : dummyInit;
244         newdev->get = get ? get : dummyGet;
245         newdev->shutdown = shutdown ? shutdown : dummyShutdown;
246         newdev->private = private;
247         Devices[numDevs] = newdev;
248         Devices[++numDevs] = NULL;
249     }
250     return newdev;
251 }
252
253 /* Reset the registered device chain */
254 void
255 deviceReset(void)
256 {
257     int i;
258
259     for (i = 0; i < numDevs; i++) {
260         DEVICE_SHUTDOWN(Devices[i]);
261
262         /* XXX this potentially leaks Devices[i]->private if it's being
263          * used to point to something dynamic, but you're not supposed
264          * to call this routine at such times that some open instance
265          * has its private ptr pointing somewhere anyway. XXX
266          */
267         free(Devices[i]);
268     }
269     Devices[numDevs = 0] = NULL;
270 }
271
272 /* Get all device information for devices we have attached */
273 void
274 deviceGetAll(void)
275 {
276     int i, j, fd, s;
277     struct ifconf ifc;
278     struct ifreq *ifptr, *end;
279     int ifflags;
280     char buffer[INTERFACE_MAX * sizeof(struct ifreq)];
281     char **names;
282
283     msgNotify("Probing devices, please wait (this can take a while)...");
284     /* First go for the network interfaces.  Stolen shamelessly from ifconfig! */
285     ifc.ifc_len = sizeof(buffer);
286     ifc.ifc_buf = buffer;
287
288     s = socket(AF_INET, SOCK_DGRAM, 0);
289     if (s < 0)
290         goto skipif;    /* Jump over network iface probing */
291
292     if (ioctl(s, SIOCGIFCONF, (char *) &ifc) < 0)
293         goto skipif;    /* Jump over network iface probing */
294
295     close(s);
296     ifflags = ifc.ifc_req->ifr_flags;
297     end = (struct ifreq *) (ifc.ifc_buf + ifc.ifc_len);
298     for (ifptr = ifc.ifc_req; ifptr < end; ifptr++) {
299         char *descr;
300
301         /* If it's not a link entry, forget it */
302         if (ifptr->ifr_ifru.ifru_addr.sa_family != AF_LINK)
303             goto loopend;
304
305         /* Eliminate network devices that don't make sense */
306         if (!strncmp(ifptr->ifr_name, "lo", 2))
307             goto loopend;
308
309         /* If we have a slip device, don't register it */
310         if (!strncmp(ifptr->ifr_name, "sl", 2)) {
311             goto loopend;
312         }
313         /* And the same for ppp */
314         if (!strncmp(ifptr->ifr_name, "tun", 3) || !strncmp(ifptr->ifr_name, "ppp", 3)) {
315             goto loopend;
316         }
317         /* Try and find its description */
318         for (i = 0, descr = NULL; device_names[i].name; i++) {
319             int len = strlen(device_names[i].name);
320
321             if (!ifptr->ifr_name || !ifptr->ifr_name[0])
322                 continue;
323             else if (!strncmp(ifptr->ifr_name, device_names[i].name, len)) {
324                 descr = device_names[i].description;
325                 break;
326             }
327         }
328         if (!descr)
329             descr = "<unknown network interface type>";
330
331         deviceRegister(ifptr->ifr_name, descr, strdup(ifptr->ifr_name), DEVICE_TYPE_NETWORK, TRUE,
332                        mediaInitNetwork, NULL, mediaShutdownNetwork, NULL);
333         if (isDebug())
334             msgDebug("Found a network device named %s\n", ifptr->ifr_name);
335         close(s);
336         if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
337             continue;
338
339 loopend:
340         if (ifptr->ifr_addr.sa_len)     /* I'm not sure why this is here - it's inherited */
341             ifptr = (struct ifreq *)((caddr_t)ifptr + ifptr->ifr_addr.sa_len - sizeof(struct sockaddr));
342         close(s);
343     }
344
345 skipif:
346     /* Next, try to find all the types of devices one might need
347      * during the second stage of the installation.
348      */
349     for (i = 0; device_names[i].name; i++) {
350         for (j = 0; j < device_names[i].max; j++) {
351             char try[FILENAME_MAX];
352
353             switch(device_names[i].type) {
354             case DEVICE_TYPE_CDROM:
355                 fd = deviceTry(device_names[i], try, j);
356                 if (fd >= 0 || errno == EBUSY) {        /* EBUSY if already mounted */
357                     char n[BUFSIZ];
358
359                     if (fd >= 0) close(fd);
360                     snprintf(n, sizeof n, device_names[i].name, j);
361                     deviceRegister(strdup(n), device_names[i].description, strdup(try),
362                                          DEVICE_TYPE_CDROM, TRUE, mediaInitCDROM, mediaGetCDROM,
363                                          mediaShutdownCDROM, NULL);
364                     if (isDebug())
365                         msgDebug("Found a CDROM device for %s\n", try);
366                 }
367                 break;
368
369             case DEVICE_TYPE_TAPE:
370                 fd = deviceTry(device_names[i], try, j);
371                 if (fd >= 0) {
372                     char n[BUFSIZ];
373
374                     close(fd);
375                     snprintf(n, sizeof n, device_names[i].name, j);
376                     deviceRegister(strdup(n), device_names[i].description, strdup(try),
377                                    DEVICE_TYPE_TAPE, TRUE, mediaInitTape, mediaGetTape, mediaShutdownTape, NULL);
378                     if (isDebug())
379                         msgDebug("Found a TAPE device for %s\n", try);
380                 }
381                 break;
382
383             case DEVICE_TYPE_DISK:
384                 /* nothing to do */
385                 break;
386
387             case DEVICE_TYPE_FLOPPY:
388                 fd = deviceTry(device_names[i], try, j);
389                 if (fd >= 0) {
390                     char n[BUFSIZ];
391
392                     close(fd);
393                     snprintf(n, sizeof n, device_names[i].name, j);
394                     deviceRegister(strdup(n), device_names[i].description, strdup(try),
395                                    DEVICE_TYPE_FLOPPY, TRUE, mediaInitFloppy, mediaGetFloppy,
396                                    mediaShutdownFloppy, NULL);
397                     if (isDebug())
398                         msgDebug("Found a floppy device for %s\n", try);
399                 }
400                 break;
401
402             case DEVICE_TYPE_NETWORK:
403                 fd = deviceTry(device_names[i], try, j);
404                 /* The only network devices that you can open this way are serial ones */
405                 if (fd >= 0) {
406                     char *newdesc, *cp;
407
408                     close(fd);
409                     cp = device_names[i].description;
410                     /* Serial devices get a slip and ppp device each, if supported */
411                     newdesc = safe_malloc(strlen(cp) + 40);
412                     sprintf(newdesc, cp, "SLIP interface", try, j + 1);
413                     deviceRegister("sl0", newdesc, strdup(try), DEVICE_TYPE_NETWORK, TRUE, mediaInitNetwork,
414                                    NULL, mediaShutdownNetwork, NULL);
415                     msgDebug("Add mapping for %s to sl0\n", try);
416                     newdesc = safe_malloc(strlen(cp) + 50);
417                     sprintf(newdesc, cp, "PPP interface", try, j + 1);
418                     deviceRegister("ppp0", newdesc, strdup(try), DEVICE_TYPE_NETWORK, TRUE, mediaInitNetwork,
419                                    NULL, mediaShutdownNetwork, NULL);
420                     if (isDebug())
421                         msgDebug("Add mapping for %s to ppp0\n", try);
422                 }
423                 break;
424
425             default:
426                 break;
427             }
428         }
429     }
430
431     /* Finally, go get the disks and look for DOS partitions to register */
432     if ((names = Disk_Names()) != NULL) {
433         int i;
434
435         for (i = 0; names[i]; i++) {
436             Chunk *c1;
437             Disk *d;
438
439             /* Ignore memory disks */
440             if (!strncmp(names[i], "md", 2))
441                 continue;
442
443             /*
444              * XXX 
445              *  Due to unknown reasons, Disk_Names() returns SCSI CDROM as a
446              * valid disk. This is main reason why sysinstall presents SCSI
447              * CDROM to available disks in Fdisk/Label menu. In addition,
448              * adding a blank SCSI CDROM to the menu generates floating point
449              * exception in sparc64. Disk_Names() just extracts sysctl
450              * "kern.disks". Why GEOM treats SCSI CDROM as a disk is beyond
451              * me and that should be investigated.
452              * For temporary workaround, ignore SCSI CDROM device.
453              */
454             if (!strncmp(names[i], "cd", 2))
455                 continue;
456
457             d = Open_Disk(names[i]);
458             if (!d) {
459                 msgDebug("Unable to open disk %s\n", names[i]);
460                 continue;
461             }
462
463             deviceRegister(names[i], names[i], d->name, DEVICE_TYPE_DISK, FALSE,
464                            dummyInit, dummyGet, dummyShutdown, d);
465             if (isDebug())
466                 msgDebug("Found a disk device named %s\n", names[i]);
467
468             /* Look for existing DOS partitions to register as "DOS media devices" */
469             for (c1 = d->chunks->part; c1; c1 = c1->next) {
470                 if (c1->type == fat || c1->type == efi || c1->type == extended) {
471                     Device *dev;
472                     char devname[80];
473
474                     /* Got one! */
475                     snprintf(devname, sizeof devname, "/dev/%s", c1->name);
476                     dev = deviceRegister(c1->name, c1->name, strdup(devname), DEVICE_TYPE_DOS, TRUE,
477                                          mediaInitDOS, mediaGetDOS, mediaShutdownDOS, NULL);
478                     dev->private = c1;
479                     if (isDebug())
480                         msgDebug("Found a DOS partition %s on drive %s\n", c1->name, d->name);
481                 }
482             }
483         }
484         free(names);
485     }
486     dialog_clear_norefresh();
487 }
488
489 /* Rescan all devices, after closing previous set - convenience function */
490 void
491 deviceRescan(void)
492 {
493     deviceReset();
494     deviceGetAll();
495 }
496
497 /*
498  * Find all devices that match the criteria, allowing "wildcarding" as well
499  * by allowing NULL or ANY values to match all.  The array returned is static
500  * and may be used until the next invocation of deviceFind().
501  */
502 Device **
503 deviceFind(char *name, DeviceType class)
504 {
505     static Device *found[DEV_MAX];
506     int i, j;
507
508     j = 0;
509     for (i = 0; i < numDevs; i++) {
510         if ((!name || !strcmp(Devices[i]->name, name))
511             && (class == DEVICE_TYPE_ANY || class == Devices[i]->type))
512             found[j++] = Devices[i];
513     }
514     found[j] = NULL;
515     return j ? found : NULL;
516 }
517
518 Device **
519 deviceFindDescr(char *name, char *desc, DeviceType class)
520 {
521     static Device *found[DEV_MAX];
522     int i, j;
523
524     j = 0;
525     for (i = 0; i < numDevs; i++) {
526         if ((!name || !strcmp(Devices[i]->name, name)) &&
527             (!desc || !strcmp(Devices[i]->description, desc)) &&
528             (class == DEVICE_TYPE_ANY || class == Devices[i]->type))
529             found[j++] = Devices[i];
530     }
531     found[j] = NULL;
532     return j ? found : NULL;
533 }
534
535 int
536 deviceCount(Device **devs)
537 {
538     int i;
539
540     if (!devs)
541         return 0;
542     for (i = 0; devs[i]; i++);
543     return i;
544 }
545
546 /*
547  * Create a menu listing all the devices of a certain type in the system.
548  * The passed-in menu is expected to be a "prototype" from which the new
549  * menu is cloned.
550  */
551 DMenu *
552 deviceCreateMenu(DMenu *menu, DeviceType type, int (*hook)(dialogMenuItem *d), int (*check)(dialogMenuItem *d))
553 {
554     Device **devs;
555     int numdevs;
556     DMenu *tmp = NULL;
557     int i, j;
558
559     devs = deviceFind(NULL, type);
560     numdevs = deviceCount(devs);
561     if (!numdevs)
562         return NULL;
563     tmp = (DMenu *)safe_malloc(sizeof(DMenu) + (sizeof(dialogMenuItem) * (numdevs + 1)));
564     bcopy(menu, tmp, sizeof(DMenu));
565     for (i = 0; devs[i]; i++) {
566         tmp->items[i].prompt = devs[i]->name;
567         for (j = 0; j < numDevs; j++) {
568             if (devs[i] == Devices[j]) {
569                 tmp->items[i].title = Devices[j]->description;
570                 break;
571             }
572         }
573         if (j == numDevs)
574             tmp->items[i].title = "<unknown device type>";
575         tmp->items[i].fire = hook;
576         tmp->items[i].checked = check;
577     }
578     tmp->items[i].title = NULL;
579     return tmp;
580 }