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