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