]> CyberLeo.Net >> Repos - FreeBSD/releng/8.2.git/blob - usr.sbin/sysinstall/devices.c
MFC r216824:
[FreeBSD/releng/8.2.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 DISK(name, descr, max)                                          \
61         DEVICE_ENTRY(DEVICE_TYPE_DISK, name, descr, max)
62 #define FLOPPY(name, descr, max)                                        \
63         DEVICE_ENTRY(DEVICE_TYPE_FLOPPY, name, descr, max)
64 #define NETWORK(name, descr)                                            \
65         DEVICE_ENTRY(DEVICE_TYPE_NETWORK, name, descr, 0)
66 #define SERIAL(name, descr, max)                                        \
67         DEVICE_ENTRY(DEVICE_TYPE_NETWORK, name, descr, max)
68 #define USB(name, descr, max)                                           \
69         DEVICE_ENTRY(DEVICE_TYPE_USB, 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     DISK("da%d",        "SCSI disk device",                     16),
82     DISK("ad%d",        "ATA/IDE disk device",                  16),
83     DISK("ada%d",       "SATA disk device",                     16),
84     DISK("ar%d",        "ATA/IDE RAID device",                  16),
85     DISK("afd%d",       "ATAPI/IDE floppy device",              4),
86     DISK("mlxd%d",      "Mylex RAID disk",                      4),
87     DISK("amrd%d",      "AMI MegaRAID drive",                   4),
88     DISK("idad%d",      "Compaq RAID array",                    4),
89     DISK("twed%d",      "3ware ATA RAID array",                 4),
90     DISK("aacd%d",      "Adaptec FSA RAID array",               4),
91     DISK("ipsd%d",      "IBM ServeRAID RAID array",             4),
92     DISK("mfid%d",      "LSI MegaRAID SAS array",               4),
93     FLOPPY("fd%d",      "floppy drive unit A",                  4),
94     SERIAL("cuau%d",    "%s on device %s (COM%d)",              16),
95     USB("da%da",        "USB Mass Storage Device",              16),
96     NETWORK("ae",       "Attansic/Atheros L2 Fast Ethernet"),
97     NETWORK("age",      "Attansic/Atheros L1 Gigabit Ethernet"),
98     NETWORK("alc",      "Atheros AR8131/AR8132 PCIe Ethernet"),
99     NETWORK("ale",      "Atheros AR8121/AR8113/AR8114 PCIe Ethernet"),
100     NETWORK("an",       "Aironet 4500/4800 802.11 wireless adapter"),
101     NETWORK("ath",      "Atheros IEEE 802.11 wireless adapter"),
102     NETWORK("aue",      "ADMtek USB Ethernet adapter"),
103     NETWORK("axe",      "ASIX Electronics USB Ethernet adapter"),
104     NETWORK("bce",      "Broadcom NetXtreme II Gigabit Ethernet card"),
105     NETWORK("bfe",      "Broadcom BCM440x PCI Ethernet card"),
106     NETWORK("bge",      "Broadcom BCM570x PCI Gigabit Ethernet card"),
107     NETWORK("bm",       "Apple BMAC Built-in Ethernet"),
108     NETWORK("bwn",      "Broadcom BCM43xx IEEE 802.11 wireless adapter"),
109     NETWORK("cas",      "Sun Cassini/Cassini+ or NS DP83065 Saturn Ethernet"),
110     NETWORK("cue",      "CATC USB Ethernet adapter"),
111     NETWORK("cxgb",     "Chelsio T3 10Gb Ethernet card"),
112     NETWORK("fpa",      "DEC DEFPA PCI FDDI card"),
113     NETWORK("sr",       "SDL T1/E1 sync serial PCI card"),
114     NETWORK("cc3i",     "SDL HSSI sync serial PCI card"),
115     NETWORK("en",       "Efficient Networks ATM PCI card"),
116     NETWORK("dc",       "DEC/Intel 21143 (and clones) PCI Fast Ethernet card"),
117     NETWORK("de",       "DEC DE435 PCI NIC or other DC21040-AA based card"),
118     NETWORK("fxp",      "Intel EtherExpress Pro/100B PCI Fast Ethernet card"),
119     NETWORK("ed",       "Novell NE1000/2000; 3C503; NE2000-compatible PCMCIA"),
120     NETWORK("ep",       "3Com 3C509 Ethernet card/3C589 PCMCIA"),
121     NETWORK("em",       "Intel(R) PRO/1000 Ethernet card"),
122     NETWORK("et",       "Agere ET1310 based PCI Express Gigabit Ethernet card"),
123     NETWORK("ex",       "Intel EtherExpress Pro/10 Ethernet card"),
124     NETWORK("fe",       "Fujitsu MB86960A/MB86965A Ethernet card"),
125     NETWORK("gem",      "Apple GMAC or Sun ERI/GEM Ethernet adapter"),
126     NETWORK("hme",      "Sun HME (Happy Meal Ethernet) Ethernet adapter"),
127     NETWORK("ie",       "AT&T StarLAN 10 and EN100; 3Com 3C507; NI5210"),
128     NETWORK("igb",      "Intel(R) PRO/1000 PCI Express Gigabit Ethernet card"),
129     NETWORK("ipw",      "Intel PRO/Wireless 2100 IEEE 802.11 adapter"),
130     NETWORK("iwi",      "Intel PRO/Wireless 2200BG/2225BG/2915ABG adapter"),
131     NETWORK("iwn",      "Intel Wireless WiFi Link 4965AGN IEEE 802.11n adapter"),
132     NETWORK("ixgb",     "Intel(R) PRO/10Gb Ethernet card"),
133     NETWORK("ixgbe",    "Intel(R) PRO/10Gb Ethernet card"),
134     NETWORK("jme",      "JMicron JMC250 Gigabit/JMC260 Fast Ethernet"),
135     NETWORK("kue",      "Kawasaki LSI USB Ethernet adapter"),
136     NETWORK("le",       "AMD Am7900 LANCE or Am79C9xx PCnet Ethernet adapter"),
137     NETWORK("lge",      "Level 1 LXT1001 Gigabit Ethernet card"),
138     NETWORK("malo",     "Marvell Libertas 88W8335 802.11 wireless adapter"),
139     NETWORK("msk",      "Marvell/SysKonnect Yukon II Gigabit Ethernet"),
140     NETWORK("mxge",     "Myricom Myri10GE 10Gb Ethernet card"),
141     NETWORK("nfe",      "NVIDIA nForce MCP Ethernet"),
142     NETWORK("nge",      "NatSemi PCI Gigabit Ethernet card"),
143     NETWORK("nve",      "NVIDIA nForce MCP Ethernet"),
144     NETWORK("nxge",     "Neterion Xframe 10GbE Server/Storage adapter"),
145     NETWORK("pcn",      "AMD Am79c79x PCI Ethernet card"),
146     NETWORK("ral",      "Ralink Technology IEEE 802.11 wireless adapter"),
147     NETWORK("ray",      "Raytheon Raylink 802.11 wireless adapter"),
148     NETWORK("re",       "RealTek 8139C+/8169/8169S/8110S PCI Ethernet card"),
149     NETWORK("rl",       "RealTek 8129/8139 PCI Ethernet card"),
150     NETWORK("rue",      "RealTek USB Ethernet card"),
151     NETWORK("rum",      "Ralink Technology USB IEEE 802.11 wireless adapter"),
152     NETWORK("sf",       "Adaptec AIC-6915 PCI Ethernet card"),
153     NETWORK("sge",      "Silicon Integrated Systems SiS190/191 Ethernet"),
154     NETWORK("sis",      "SiS 900/SiS 7016 PCI Ethernet card"),
155 #ifdef PC98
156     NETWORK("snc",      "SONIC Ethernet card"),
157 #endif
158     NETWORK("sn",       "SMC/Megahertz Ethernet card"),
159     NETWORK("ste",      "Sundance ST201 PCI Ethernet card"),
160     NETWORK("stge",     "Sundance/Tamarack TC9021 Gigabit Ethernet"),
161     NETWORK("sk",       "SysKonnect PCI Gigabit Ethernet card"),
162     NETWORK("tx",       "SMC 9432TX Ethernet card"),
163     NETWORK("txp",      "3Com 3cR990 Ethernet card"),
164     NETWORK("ti",       "Alteon Networks PCI Gigabit Ethernet card"),
165     NETWORK("tl",       "Texas Instruments ThunderLAN PCI Ethernet card"),
166     NETWORK("uath",     "Atheros AR5005UG and AR5005UX USB wireless adapter"),
167     NETWORK("upgt",     "Conexant/Intersil PrismGT USB wireless adapter"),
168     NETWORK("ural",     "Ralink Technology RT2500USB 802.11 wireless adapter"),
169     NETWORK("urtw",     "Realtek 8187L USB wireless adapter"),
170     NETWORK("vge",      "VIA VT612x PCI Gigabit Ethernet card"),
171     NETWORK("vr",       "VIA VT3043/VT86C100A Rhine PCI Ethernet card"),
172     NETWORK("vlan",     "IEEE 802.1Q VLAN network interface"),
173     NETWORK("vx",       "3COM 3c590 / 3c595 Ethernet card"),
174     NETWORK("wb",       "Winbond W89C840F PCI Ethernet card"),
175     NETWORK("wi",       "Lucent WaveLAN/IEEE 802.11 wireless adapter"),
176     NETWORK("wpi",      "Intel 3945ABG IEEE 802.11 wireless adapter"),
177     NETWORK("xe",       "Xircom/Intel EtherExpress Pro100/16 Ethernet card"),
178     NETWORK("xl",       "3COM 3c90x / 3c90xB PCI Ethernet card"),
179     NETWORK("zyd",      "ZyDAS ZD1211/ZD1211B USB 802.11 wireless adapter"),
180     NETWORK("fwe",      "FireWire Ethernet emulation"),
181     NETWORK("fwip",     "IP over FireWire"),
182     NETWORK("plip",     "Parallel Port IP (PLIP) peer connection"),
183     NETWORK("lo",       "Loop-back (local) network interface"),
184     NETWORK("disc",     "Software discard network interface"),
185     { 0, NULL, NULL, 0 }
186 };
187
188 Device *
189 new_device(char *name)
190 {
191     Device *dev;
192
193     dev = safe_malloc(sizeof(Device));
194     bzero(dev, sizeof(Device));
195     if (name)
196         SAFE_STRCPY(dev->name, name);
197     return dev;
198 }
199
200 /* Stubs for unimplemented strategy routines */
201 Boolean
202 dummyInit(Device *dev)
203 {
204     return TRUE;
205 }
206
207 FILE *
208 dummyGet(Device *dev, char *dist, Boolean probe)
209 {
210     return NULL;
211 }
212
213 void
214 dummyShutdown(Device *dev)
215 {
216     return;
217 }
218
219 static int
220 deviceTry(struct _devname dev, char *try, int i)
221 {
222     int fd;
223     char unit[80];
224
225     snprintf(unit, sizeof unit, dev.name, i);
226     snprintf(try, FILENAME_MAX, "/dev/%s", unit);
227     if (isDebug())
228         msgDebug("deviceTry: attempting to open %s\n", try);
229     fd = open(try, O_RDONLY);
230     if (fd >= 0) {
231         if (isDebug())
232             msgDebug("deviceTry: open of %s succeeded on first try.\n", try);
233     } else {
234         if (isDebug())
235             msgDebug("deviceTry: open of %s failed.\n", try);
236     }
237     return fd;
238 }
239
240 /* Register a new device in the devices array */
241 Device *
242 deviceRegister(char *name, char *desc, char *devname, DeviceType type, Boolean enabled,
243                Boolean (*init)(Device *), FILE * (*get)(Device *, char *, Boolean),
244                void (*shutdown)(Device *), void *private)
245 {
246     Device *newdev = NULL;
247
248     if (numDevs == DEV_MAX)
249         msgFatal("Too many devices found!");
250     else {
251         newdev = new_device(name);
252         newdev->description = desc;
253         newdev->devname = devname;
254         newdev->type = type;
255         newdev->enabled = enabled;
256         newdev->init = init ? init : dummyInit;
257         newdev->get = get ? get : dummyGet;
258         newdev->shutdown = shutdown ? shutdown : dummyShutdown;
259         newdev->private = private;
260         Devices[numDevs] = newdev;
261         Devices[++numDevs] = NULL;
262     }
263     return newdev;
264 }
265
266 /* Reset the registered device chain */
267 void
268 deviceReset(void)
269 {
270     int i;
271
272     for (i = 0; i < numDevs; i++) {
273         DEVICE_SHUTDOWN(Devices[i]);
274
275         /* XXX this potentially leaks Devices[i]->private if it's being
276          * used to point to something dynamic, but you're not supposed
277          * to call this routine at such times that some open instance
278          * has its private ptr pointing somewhere anyway. XXX
279          */
280         free(Devices[i]);
281     }
282     Devices[numDevs = 0] = NULL;
283 }
284
285 /* Get all device information for devices we have attached */
286 void
287 deviceGetAll(void)
288 {
289     int i, j, fd, s;
290     struct ifconf ifc;
291     struct ifreq *ifptr, *end;
292     int ifflags;
293     char buffer[INTERFACE_MAX * sizeof(struct ifreq)];
294     char **names;
295
296     msgNotify("Probing devices, please wait (this can take a while)...");
297     /* First go for the network interfaces.  Stolen shamelessly from ifconfig! */
298     memset(&ifc, 0, sizeof(ifc));
299     memset(buffer, 0, INTERFACE_MAX * sizeof(struct ifreq));
300     ifc.ifc_len = sizeof(buffer);
301     ifc.ifc_buf = buffer;
302
303     s = socket(AF_INET, SOCK_DGRAM, 0);
304     if (s < 0)
305         goto skipif;    /* Jump over network iface probing */
306
307     if (ioctl(s, SIOCGIFCONF, (char *) &ifc) < 0)
308         goto skipif;    /* Jump over network iface probing */
309
310     close(s);
311     ifflags = ifc.ifc_req->ifr_flags;
312     end = (struct ifreq *) (ifc.ifc_buf + ifc.ifc_len);
313     for (ifptr = ifc.ifc_req; ifptr < end; ifptr++) {
314         char *descr;
315
316         /* If it's not a link entry, forget it */
317         if (ifptr->ifr_ifru.ifru_addr.sa_family != AF_LINK)
318             goto loopend;
319
320         /* Eliminate network devices that don't make sense */
321         if (!strncmp(ifptr->ifr_name, "lo", 2))
322             goto loopend;
323
324         /* Try and find its description */
325         for (i = 0, descr = NULL; device_names[i].name; i++) {
326             int len = strlen(device_names[i].name);
327
328             if (!ifptr->ifr_name || !ifptr->ifr_name[0])
329                 continue;
330             else if (!strncmp(ifptr->ifr_name, device_names[i].name, len)) {
331                 descr = device_names[i].description;
332                 break;
333             }
334         }
335         if (!descr)
336             descr = "<unknown network interface type>";
337
338         deviceRegister(ifptr->ifr_name, descr, strdup(ifptr->ifr_name), DEVICE_TYPE_NETWORK, TRUE,
339                        mediaInitNetwork, NULL, mediaShutdownNetwork, NULL);
340         if (isDebug())
341             msgDebug("Found a network device named %s\n", ifptr->ifr_name);
342         close(s);
343         if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
344             continue;
345
346 loopend:
347         if (ifptr->ifr_addr.sa_len)     /* I'm not sure why this is here - it's inherited */
348             ifptr = (struct ifreq *)((caddr_t)ifptr + ifptr->ifr_addr.sa_len - sizeof(struct sockaddr));
349         close(s);
350     }
351
352 skipif:
353     /* Next, try to find all the types of devices one might need
354      * during the second stage of the installation.
355      */
356     for (i = 0; device_names[i].name; i++) {
357         for (j = 0; j < device_names[i].max; j++) {
358             char try[FILENAME_MAX];
359
360             switch(device_names[i].type) {
361             case DEVICE_TYPE_CDROM:
362                 fd = deviceTry(device_names[i], try, j);
363                 if (fd >= 0 || errno == EBUSY) {        /* EBUSY if already mounted */
364                     char n[BUFSIZ];
365
366                     if (fd >= 0) close(fd);
367                     snprintf(n, sizeof n, device_names[i].name, j);
368                     deviceRegister(n, device_names[i].description, strdup(try),
369                                          DEVICE_TYPE_CDROM, TRUE, mediaInitCDROM, mediaGetCDROM,
370                                          mediaShutdownCDROM, NULL);
371                     if (isDebug())
372                         msgDebug("Found a CDROM device for %s\n", try);
373                 }
374                 break;
375
376             case DEVICE_TYPE_DISK:
377                 /* nothing to do */
378                 break;
379
380             case DEVICE_TYPE_FLOPPY:
381                 fd = deviceTry(device_names[i], try, j);
382                 if (fd >= 0) {
383                     char n[BUFSIZ];
384
385                     close(fd);
386                     snprintf(n, sizeof n, device_names[i].name, j);
387                     deviceRegister(n, device_names[i].description, strdup(try),
388                                    DEVICE_TYPE_FLOPPY, TRUE, mediaInitFloppy, mediaGetFloppy,
389                                    mediaShutdownFloppy, NULL);
390                     if (isDebug())
391                         msgDebug("Found a floppy device for %s\n", try);
392                 }
393                 break;
394
395             case DEVICE_TYPE_USB:
396                 fd = deviceTry(device_names[i], try, j);
397                 if (fd >= 0) {
398                         char n[BUFSIZ];
399
400                         close(fd);
401                         snprintf(n, sizeof(n), device_names[i].name, j);
402                         deviceRegister(n, device_names[i].description,
403                             strdup(try), DEVICE_TYPE_USB, TRUE, mediaInitUSB,
404                             mediaGetUSB, mediaShutdownUSB, NULL);
405
406                         if (isDebug())
407                                 msgDebug("Found a USB disk for %s\n", try);
408                 }
409                 break;
410
411             default:
412                 break;
413             }
414         }
415     }
416
417     /* Finally, go get the disks and look for partitions to register */
418     if ((names = Disk_Names()) != NULL) {
419         int i;
420
421         for (i = 0; names[i]; i++) {
422             Chunk *c1;
423             Disk *d;
424
425             /* Ignore memory disks */
426             if (!strncmp(names[i], "md", 2))
427                 continue;
428
429             /*
430              * XXX 
431              *  Due to unknown reasons, Disk_Names() returns SCSI CDROM as a
432              * valid disk. This is main reason why sysinstall presents SCSI
433              * CDROM to available disks in Fdisk/Label menu. In addition,
434              * adding a blank SCSI CDROM to the menu generates floating point
435              * exception in sparc64. Disk_Names() just extracts sysctl
436              * "kern.disks". Why GEOM treats SCSI CDROM as a disk is beyond
437              * me and that should be investigated.
438              * For temporary workaround, ignore SCSI CDROM device.
439              */
440             if (!strncmp(names[i], "cd", 2))
441                 continue;
442
443             d = Open_Disk(names[i]);
444             if (!d) {
445                 msgDebug("Unable to open disk %s\n", names[i]);
446                 continue;
447             }
448
449             deviceRegister(names[i], names[i], d->name, DEVICE_TYPE_DISK, FALSE,
450                            dummyInit, dummyGet, dummyShutdown, d);
451             if (isDebug())
452                 msgDebug("Found a disk device named %s\n", names[i]);
453
454             /* Look for existing DOS partitions to register as "DOS media devices"
455              * XXX: libdisks handling of extended partitions is too
456              * simplistic - it does not handle them containing (for
457              * example) UFS partitions
458              */
459             for (c1 = d->chunks->part; c1; c1 = c1->next) {
460                 if (c1->type == fat || c1->type == efi || c1->type == extended) {
461                     Device *dev;
462                     char devname[80];
463
464                     /* Got one! */
465                     snprintf(devname, sizeof devname, "/dev/%s", c1->name);
466                     dev = deviceRegister(c1->name, c1->name, strdup(devname), DEVICE_TYPE_DOS, TRUE,
467                                          mediaInitDOS, mediaGetDOS, mediaShutdownDOS, NULL);
468                     dev->private = c1;
469                     if (isDebug())
470                         msgDebug("Found a DOS partition %s\n", c1->name);
471                 } else if (c1->type == freebsd) {
472                     Device *dev;
473                     char devname[80];
474                     Chunk *c2;
475                         
476                     for (c2 = c1->part; c2; c2 = c2->next) {
477                         if (c2->type != part || c2->subtype != 7)
478                             continue;
479                         /* Got one! */
480                         snprintf(devname, sizeof devname, "/dev/%s", c1->name);
481                         dev = deviceRegister(c2->name, c2->name, strdup(devname), DEVICE_TYPE_UFS, TRUE,
482                                              mediaInitUFS, mediaGetUFS, mediaShutdownUFS, NULL);
483                         dev->private = c2;
484                         if (isDebug())
485                             msgDebug("Found a UFS sub-partition %s\n", c2->name);
486                     }
487                 }
488                 
489             }
490         }
491         free(names);
492     }
493     dialog_clear_norefresh();
494 }
495
496 /* Rescan all devices, after closing previous set - convenience function */
497 void
498 deviceRescan(void)
499 {
500     deviceReset();
501     deviceGetAll();
502 }
503
504 /*
505  * Find all devices that match the criteria, allowing "wildcarding" as well
506  * by allowing NULL or ANY values to match all.  The array returned is static
507  * and may be used until the next invocation of deviceFind().
508  */
509 Device **
510 deviceFind(char *name, DeviceType class)
511 {
512     static Device *found[DEV_MAX];
513     int i, j;
514
515     j = 0;
516     for (i = 0; i < numDevs; i++) {
517         if ((!name || !strcmp(Devices[i]->name, name))
518             && (class == DEVICE_TYPE_ANY || class == Devices[i]->type))
519             found[j++] = Devices[i];
520     }
521     found[j] = NULL;
522     return j ? found : NULL;
523 }
524
525 Device **
526 deviceFindDescr(char *name, char *desc, DeviceType class)
527 {
528     static Device *found[DEV_MAX];
529     int i, j;
530
531     j = 0;
532     for (i = 0; i < numDevs; i++) {
533         if ((!name || !strcmp(Devices[i]->name, name)) &&
534             (!desc || !strcmp(Devices[i]->description, desc)) &&
535             (class == DEVICE_TYPE_ANY || class == Devices[i]->type))
536             found[j++] = Devices[i];
537     }
538     found[j] = NULL;
539     return j ? found : NULL;
540 }
541
542 int
543 deviceCount(Device **devs)
544 {
545     int i;
546
547     if (!devs)
548         return 0;
549     for (i = 0; devs[i]; i++);
550     return i;
551 }
552
553 /*
554  * Create a menu listing all the devices of a certain type in the system.
555  * The passed-in menu is expected to be a "prototype" from which the new
556  * menu is cloned.
557  */
558 DMenu *
559 deviceCreateMenu(DMenu *menu, DeviceType type, int (*hook)(dialogMenuItem *d), int (*check)(dialogMenuItem *d))
560 {
561     Device **devs;
562     int numdevs;
563     DMenu *tmp = NULL;
564     int i, j;
565
566     devs = deviceFind(NULL, type);
567     numdevs = deviceCount(devs);
568     if (!numdevs)
569         return NULL;
570     tmp = (DMenu *)safe_malloc(sizeof(DMenu) + (sizeof(dialogMenuItem) * (numdevs + 1)));
571     bcopy(menu, tmp, sizeof(DMenu));
572     for (i = 0; devs[i]; i++) {
573         tmp->items[i].prompt = devs[i]->name;
574         for (j = 0; j < numDevs; j++) {
575             if (devs[i] == Devices[j]) {
576                 tmp->items[i].title = Devices[j]->description;
577                 break;
578             }
579         }
580         if (j == numDevs)
581             tmp->items[i].title = "<unknown device type>";
582         tmp->items[i].fire = hook;
583         tmp->items[i].checked = check;
584     }
585     tmp->items[i].title = NULL;
586     return tmp;
587 }