]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - release/sysinstall/devices.c
This commit adds device driver support for the Sundance Technologies ST201
[FreeBSD/FreeBSD.git] / release / 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  * $Id: devices.c,v 1.98 1999/07/25 04:32:50 wpaul Exp $
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
52 /* how much to bias minor number for a given /dev/<ct#><un#>s<s#> slice */
53 #define SLICE_DELTA     (0x10000)
54
55 static Device *Devices[DEV_MAX];
56 static int numDevs;
57
58 static struct _devname {
59     DeviceType type;
60     char *name;
61     char *description;
62     int major, minor, delta, max;
63     char dev_type;
64 } device_names[] = {
65     { DEVICE_TYPE_CDROM,        "cd%dc",        "SCSI CDROM drive",     6, 2, 8, 4, 'b'                         },
66     { DEVICE_TYPE_CDROM,        "mcd%da",       "Mitsumi (old model) CDROM drive",      7, 0, 8, 4, 'b'         },
67     { DEVICE_TYPE_CDROM,        "scd%da",       "Sony CDROM drive - CDU31/33A type",    16, 0, 8, 4, 'b'        },
68 #ifdef notdef
69     { DEVICE_TYPE_CDROM,        "matcd%da",     "Matsushita CDROM ('sound blaster' type)", 17, 0, 8, 4, 'b'     },
70 #endif
71     { DEVICE_TYPE_CDROM,        "acd%dc",       "ATAPI IDE CDROM",      19, 2, 8, 4, 'b'                        },
72     { DEVICE_TYPE_TAPE,         "rsa%d",        "SCSI tape drive",      14, 0, 16, 4, 'c'                       },
73     { DEVICE_TYPE_TAPE,         "rwt%d",        "Wangtek tape drive",   10, 0, 1, 4, 'c'                        },
74     { DEVICE_TYPE_DISK,         "da%d",         "SCSI disk device",     4, 65538, 8, 16, 'b'                    },
75     { DEVICE_TYPE_DISK,         "rda%d",        "SCSI disk device",     13, 65538, 8, 16, 'c'                   },
76     { DEVICE_TYPE_DISK,         "wd%d",         "IDE/ESDI/MFM/ST506 disk device",       0, 65538, 8, 16, 'b'    },
77     { DEVICE_TYPE_DISK,         "rwd%d",        "IDE/ESDI/MFM/ST506 disk device",       3, 65538, 8, 16, 'c'    },
78     { DEVICE_TYPE_DISK,         "fla%d",        "M-Systems DiskOnChip Flash device", 28, 65538, 8, 16, 'b'      },
79     { DEVICE_TYPE_DISK,         "rfla%d",       "M-Systems DiskOnChip Flash devicee",   102, 65538, 8, 16, 'c'  },
80     { DEVICE_TYPE_DISK,         "wfd%d",        "ATAPI floppy device",  1, 65538, 8, 4, 'b'             },
81     { DEVICE_TYPE_DISK,         "rwfd%d",       "ATAPI floppy device",  87, 65538, 8, 4, 'c'            },
82     { DEVICE_TYPE_FLOPPY,       "fd%d",         "floppy drive unit A",  2, 0, 64, 4, 'b'                        },
83     { DEVICE_TYPE_FLOPPY,       "wfd%d",        "ATAPI floppy drive unit A",    1, 0, 8, 4, 'b'                 },
84     { DEVICE_TYPE_FLOPPY,       "worm%d",       "SCSI optical disk / CDR",      23, 0, 1, 4, 'b'                },
85     { DEVICE_TYPE_NETWORK,      "al",           "ADMtek AL981 PCI ethernet card"                                },
86     { DEVICE_TYPE_NETWORK,      "ax",           "ASIX AX88140A PCI ethernet card"                               },
87     { DEVICE_TYPE_NETWORK,      "fpa",          "DEC DEFPA PCI FDDI card"                                       },
88     { DEVICE_TYPE_NETWORK,      "sr",           "SDL T1/E1 sync serial PCI card"                                },
89     { DEVICE_TYPE_NETWORK,      "cc3i",         "SDL HSSI sync serial PCI card"                                 },
90     { DEVICE_TYPE_NETWORK,      "en",           "Efficient Networks ATM PCI card"                               },
91     { DEVICE_TYPE_NETWORK,      "de",           "DEC DE435 PCI NIC or other DC21040-AA based card"              },
92     { DEVICE_TYPE_NETWORK,      "fxp",          "Intel EtherExpress Pro/100B PCI Fast Ethernet card"            },
93     { DEVICE_TYPE_NETWORK,      "ed",           "Novell NE1000/2000; 3C503; NE2000-compatible PCMCIA"           },
94     { DEVICE_TYPE_NETWORK,      "ep",           "3Com 3C509 ethernet card/3C589 PCMCIA"                         },
95     { DEVICE_TYPE_NETWORK,      "el",           "3Com 3C501 ethernet card"                                      },
96     { DEVICE_TYPE_NETWORK,      "ex",           "Intel EtherExpress Pro/10 ethernet card"                       },
97     { DEVICE_TYPE_NETWORK,      "fe",           "Fujitsu MB86960A/MB86965A ethernet card"                       },
98     { DEVICE_TYPE_NETWORK,      "ie",           "AT&T StarLAN 10 and EN100; 3Com 3C507; NI5210"                 },
99     { DEVICE_TYPE_NETWORK,      "ix",           "Intel Etherexpress ethernet card"                              },
100     { DEVICE_TYPE_NETWORK,      "le",           "DEC EtherWorks 2 or 3 ethernet card"                           },
101     { DEVICE_TYPE_NETWORK,      "lnc",          "Lance/PCnet (Isolan/Novell NE2100/NE32-VL) ethernet"           },
102     { DEVICE_TYPE_NETWORK,      "mx",           "Macronix 98713/98715/98725 PCI ethernet card"          },
103     { DEVICE_TYPE_NETWORK,      "pn",           "Lite-On 82168/82169 PNIC PCI ethernet card"            },
104     { DEVICE_TYPE_NETWORK,      "rl",           "RealTek 8129/8139 PCI ethernet card"           },
105     { DEVICE_TYPE_NETWORK,      "sf",           "Adaptec AIC-6915 PCI ethernet card"            },
106     { DEVICE_TYPE_NETWORK,      "ste",          "Sundance ST201 PCI ethernet card"              },
107     { DEVICE_TYPE_NETWORK,      "sk",           "SysKonnect PCI gigabit ethernet card"          },
108     { DEVICE_TYPE_NETWORK,      "tx",           "SMC 9432TX ethernet card"                                      },
109     { DEVICE_TYPE_NETWORK,      "ti",           "Alteon Networks PCI gigabit ethernet card"             },
110     { DEVICE_TYPE_NETWORK,      "tl",           "Texas Instruments ThunderLAN PCI ethernet card"                },
111     { DEVICE_TYPE_NETWORK,      "vr",           "VIA VT3043/VT86C100A Rhine PCI ethernet card"                          },
112     { DEVICE_TYPE_NETWORK,      "vx",           "3COM 3c590 / 3c595 ethernet card"                              },
113     { DEVICE_TYPE_NETWORK,      "wb",           "Winbond W89C840F PCI ethernet card"                            },
114     { DEVICE_TYPE_NETWORK,      "xl",           "3COM 3c90x / 3c90xB PCI ethernet card"                         },
115     { DEVICE_TYPE_NETWORK,      "ze",           "IBM/National Semiconductor PCMCIA ethernet card"               },
116     { DEVICE_TYPE_NETWORK,      "zp",           "3Com Etherlink III PCMCIA ethernet card"                       },
117     { DEVICE_TYPE_NETWORK,      "cuaa%d",       "%s on device %s (COM%d)",      28, 128, 1, 16, 'c'             },
118     { DEVICE_TYPE_NETWORK,      "lp",           "Parallel Port IP (PLIP) peer connection"                       },
119     { DEVICE_TYPE_NETWORK,      "lo",           "Loop-back (local) network interface"                           },
120     { 0 },
121 };
122
123 Device *
124 new_device(char *name)
125 {
126     Device *dev;
127
128     dev = safe_malloc(sizeof(Device));
129     bzero(dev, sizeof(Device));
130     if (name)
131         SAFE_STRCPY(dev->name, name);
132     return dev;
133 }
134
135 /* Stubs for unimplemented strategy routines */
136 Boolean
137 dummyInit(Device *dev)
138 {
139     return TRUE;
140 }
141
142 FILE *
143 dummyGet(Device *dev, char *dist, Boolean probe)
144 {
145     return NULL;
146 }
147
148 void
149 dummyShutdown(Device *dev)
150 {
151     return;
152 }
153
154 static int
155 deviceTry(struct _devname dev, char *try, int i)
156 {
157     int fd;
158     char unit[80];
159     mode_t m;
160     dev_t d;
161     int fail;
162
163     snprintf(unit, sizeof unit, dev.name, i);
164     snprintf(try, FILENAME_MAX, "/dev/%s", unit);
165     fd = open(try, O_RDONLY);
166     if (fd >= 0)
167         return fd;
168     m = 0640;
169     if (dev.dev_type == 'c')
170         m |= S_IFCHR;
171     else
172         m |= S_IFBLK;
173     d = makedev(dev.major, dev.minor + (i * dev.delta));
174     fail = mknod(try, m, d);
175     fd = open(try, O_RDONLY);
176     if (fd >= 0)
177         return fd;
178     else if (!fail)
179         (void)unlink(try);
180     /* Don't try a "make-under" here since we're using a fixit floppy in this case */
181     snprintf(try, FILENAME_MAX, "/mnt/dev/%s", unit);
182     fd = open(try, O_RDONLY);
183     return fd;
184 }
185
186 /* Register a new device in the devices array */
187 Device *
188 deviceRegister(char *name, char *desc, char *devname, DeviceType type, Boolean enabled,
189                Boolean (*init)(Device *), FILE * (*get)(Device *, char *, Boolean),
190                void (*shutdown)(Device *), void *private)
191 {
192     Device *newdev = NULL;
193
194     if (numDevs == DEV_MAX)
195         msgFatal("Too many devices found!");
196     else {
197         newdev = new_device(name);
198         newdev->description = desc;
199         newdev->devname = devname;
200         newdev->type = type;
201         newdev->enabled = enabled;
202         newdev->init = init ? init : dummyInit;
203         newdev->get = get ? get : dummyGet;
204         newdev->shutdown = shutdown ? shutdown : dummyShutdown;
205         newdev->private = private;
206         Devices[numDevs] = newdev;
207         Devices[++numDevs] = NULL;
208     }
209     return newdev;
210 }
211
212 /* Reset the registered device chain */
213 void
214 deviceReset(void)
215 {
216     int i;
217
218     for (i = 0; i < numDevs; i++) {
219         Devices[i]->shutdown(Devices[i]);
220
221         /* XXX this potentially leaks Devices[i]->private if it's being
222          * used to point to something dynamic, but you're not supposed
223          * to call this routine at such times that some open instance
224          * has its private ptr pointing somewhere anyway. XXX
225          */
226         free(Devices[i]);
227     }
228     Devices[numDevs = 0] = NULL;
229 }
230
231 /* Get all device information for devices we have attached */
232 void
233 deviceGetAll(void)
234 {
235     int i, j, fd, s;
236     struct ifconf ifc;
237     struct ifreq *ifptr, *end;
238     int ifflags;
239     char buffer[INTERFACE_MAX * sizeof(struct ifreq)];
240     char **names;
241
242     msgNotify("Probing devices, please wait (this can take a while)...");
243     /* First go for the network interfaces.  Stolen shamelessly from ifconfig! */
244     ifc.ifc_len = sizeof(buffer);
245     ifc.ifc_buf = buffer;
246
247     s = socket(AF_INET, SOCK_DGRAM, 0);
248     if (s < 0)
249         goto skipif;    /* Jump over network iface probing */
250
251     if (ioctl(s, SIOCGIFCONF, (char *) &ifc) < 0)
252         goto skipif;    /* Jump over network iface probing */
253
254     ifflags = ifc.ifc_req->ifr_flags;
255     end = (struct ifreq *) (ifc.ifc_buf + ifc.ifc_len);
256     for (ifptr = ifc.ifc_req; ifptr < end; ifptr++) {
257         char *descr;
258
259         /* If it's not a link entry, forget it */
260         if (ifptr->ifr_ifru.ifru_addr.sa_family != AF_LINK)
261             goto loopend;
262
263         /* Eliminate network devices that don't make sense */
264         if (!strncmp(ifptr->ifr_name, "lo", 2))
265             goto loopend;
266
267         /* If we have a slip device, don't register it */
268         if (!strncmp(ifptr->ifr_name, "sl", 2)) {
269             goto loopend;
270         }
271         /* And the same for ppp */
272         if (!strncmp(ifptr->ifr_name, "tun", 3) || !strncmp(ifptr->ifr_name, "ppp", 3)) {
273             goto loopend;
274         }
275         /* Try and find its description */
276         for (i = 0, descr = NULL; device_names[i].name; i++) {
277             int len = strlen(device_names[i].name);
278
279             if (!ifptr->ifr_name || !ifptr->ifr_name[0])
280                 continue;
281             else if (!strncmp(ifptr->ifr_name, device_names[i].name, len)) {
282                 descr = device_names[i].description;
283                 break;
284             }
285         }
286         if (!descr)
287             descr = "<unknown network interface type>";
288
289         deviceRegister(ifptr->ifr_name, descr, strdup(ifptr->ifr_name), DEVICE_TYPE_NETWORK, TRUE,
290                        mediaInitNetwork, NULL, mediaShutdownNetwork, NULL);
291         msgDebug("Found a network device named %s\n", ifptr->ifr_name);
292         close(s);
293         if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
294             continue;
295
296 loopend:
297         if (ifptr->ifr_addr.sa_len)     /* I'm not sure why this is here - it's inherited */
298             ifptr = (struct ifreq *)((caddr_t)ifptr + ifptr->ifr_addr.sa_len - sizeof(struct sockaddr));
299     }
300
301 skipif:
302     /* Next, try to find all the types of devices one might need
303      * during the second stage of the installation.
304      */
305     for (i = 0; device_names[i].name; i++) {
306         for (j = 0; j < device_names[i].max; j++) {
307             char try[FILENAME_MAX];
308
309             switch(device_names[i].type) {
310             case DEVICE_TYPE_CDROM:
311                 fd = deviceTry(device_names[i], try, j);
312                 if (fd >= 0 || errno == EBUSY) {        /* EBUSY if already mounted */
313                     char n[BUFSIZ];
314
315                     if (fd >= 0) close(fd);
316                     snprintf(n, sizeof n, device_names[i].name, j);
317                     deviceRegister(strdup(n), device_names[i].description, strdup(try),
318                                          DEVICE_TYPE_CDROM, TRUE, mediaInitCDROM, mediaGetCDROM,
319                                          mediaShutdownCDROM, NULL);
320                     msgDebug("Found a CDROM device for %s\n", try);
321                 }
322                 break;
323
324             case DEVICE_TYPE_TAPE:
325                 fd = deviceTry(device_names[i], try, j);
326                 if (fd >= 0) {
327                     char n[BUFSIZ];
328
329                     close(fd);
330                     snprintf(n, sizeof n, device_names[i].name, j);
331                     deviceRegister(strdup(n), device_names[i].description, strdup(try),
332                                    DEVICE_TYPE_TAPE, TRUE, mediaInitTape, mediaGetTape, mediaShutdownTape, NULL);
333                     msgDebug("Found a TAPE device for %s\n", try);
334                 }
335                 break;
336
337             case DEVICE_TYPE_DISK:
338                 fd = deviceTry(device_names[i], try, j);
339                 if (fd >= 0 && RunningAsInit) {
340                     dev_t d;
341                     mode_t m;
342                     int s, fail;
343                     char unit[80], slice[80];
344
345                     close(fd);
346                     /* Make associated slice entries */
347                     for (s = 1; s < 8; s++) {
348                         snprintf(unit, sizeof unit, device_names[i].name, j);
349                         snprintf(slice, sizeof slice, "/dev/%ss%d", unit, s);
350                         d = makedev(device_names[i].major, device_names[i].minor +
351                                     (j * device_names[i].delta) + (s * SLICE_DELTA));
352                         m = 0640;
353                         if (device_names[i].dev_type == 'c')
354                             m |= S_IFCHR;
355                         else
356                             m |= S_IFBLK;
357                         fail = mknod(slice, m, d);
358                         fd = open(slice, O_RDONLY);
359                         if (fd >= 0)
360                             close(fd);
361                         else if (!fail)
362                             (void)unlink(slice);
363                     }
364                 }
365                 break;
366
367             case DEVICE_TYPE_FLOPPY:
368                 fd = deviceTry(device_names[i], try, j);
369                 if (fd >= 0) {
370                     char n[BUFSIZ];
371
372                     close(fd);
373                     snprintf(n, sizeof n, device_names[i].name, j);
374                     deviceRegister(strdup(n), device_names[i].description, strdup(try),
375                                    DEVICE_TYPE_FLOPPY, TRUE, mediaInitFloppy, mediaGetFloppy,
376                                    mediaShutdownFloppy, NULL);
377                     msgDebug("Found a floppy device for %s\n", try);
378                 }
379                 break;
380
381             case DEVICE_TYPE_NETWORK:
382                 fd = deviceTry(device_names[i], try, j);
383                 /* The only network devices that you can open this way are serial ones */
384                 if (fd >= 0) {
385                     char *newdesc, *cp;
386
387                     close(fd);
388                     cp = device_names[i].description;
389                     /* Serial devices get a slip and ppp device each, if supported */
390                     newdesc = safe_malloc(strlen(cp) + 40);
391                     sprintf(newdesc, cp, "SLIP interface", try, j + 1);
392                     deviceRegister("sl0", newdesc, strdup(try), DEVICE_TYPE_NETWORK, TRUE, mediaInitNetwork,
393                                    NULL, mediaShutdownNetwork, NULL);
394                     msgDebug("Add mapping for %s to sl0\n", try);
395                     newdesc = safe_malloc(strlen(cp) + 50);
396                     sprintf(newdesc, cp, "PPP interface", try, j + 1);
397                     deviceRegister("ppp0", newdesc, strdup(try), DEVICE_TYPE_NETWORK, TRUE, mediaInitNetwork,
398                                    NULL, mediaShutdownNetwork, NULL);
399                     msgDebug("Add mapping for %s to ppp0\n", try);
400                 }
401                 break;
402
403             default:
404                 break;
405             }
406         }
407     }
408
409     /* Finally, go get the disks and look for DOS partitions to register */
410     if ((names = Disk_Names()) != NULL) {
411         int i;
412
413         for (i = 0; names[i]; i++) {
414             Chunk *c1;
415             Disk *d;
416
417             d = Open_Disk(names[i]);
418             if (!d)
419                 msgFatal("Unable to open disk %s", names[i]);
420
421             deviceRegister(names[i], names[i], d->name, DEVICE_TYPE_DISK, FALSE,
422                            dummyInit, dummyGet, dummyShutdown, d);
423             msgDebug("Found a disk device named %s\n", names[i]);
424
425             /* Look for existing DOS partitions to register as "DOS media devices" */
426             for (c1 = d->chunks->part; c1; c1 = c1->next) {
427                 if (c1->type == fat || c1->type == extended) {
428                     Device *dev;
429                     char devname[80];
430
431                     /* Got one! */
432                     snprintf(devname, sizeof devname, "/dev/%s", c1->name);
433                     dev = deviceRegister(c1->name, c1->name, strdup(devname), DEVICE_TYPE_DOS, TRUE,
434                                          mediaInitDOS, mediaGetDOS, mediaShutdownDOS, NULL);
435                     dev->private = c1;
436                     msgDebug("Found a DOS partition %s on drive %s\n", c1->name, d->name);
437                 }
438             }
439         }
440         free(names);
441     }
442 }
443
444 /* Rescan all devices, after closing previous set - convenience function */
445 void
446 deviceRescan(void)
447 {
448     deviceReset();
449     deviceGetAll();
450 }
451
452 /*
453  * Find all devices that match the criteria, allowing "wildcarding" as well
454  * by allowing NULL or ANY values to match all.  The array returned is static
455  * and may be used until the next invocation of deviceFind().
456  */
457 Device **
458 deviceFind(char *name, DeviceType class)
459 {
460     static Device *found[DEV_MAX];
461     int i, j;
462
463     j = 0;
464     for (i = 0; i < numDevs; i++) {
465         if ((!name || !strcmp(Devices[i]->name, name))
466             && (class == DEVICE_TYPE_ANY || class == Devices[i]->type))
467             found[j++] = Devices[i];
468     }
469     found[j] = NULL;
470     return j ? found : NULL;
471 }
472
473 Device **
474 deviceFindDescr(char *name, char *desc, DeviceType class)
475 {
476     static Device *found[DEV_MAX];
477     int i, j;
478
479     j = 0;
480     for (i = 0; i < numDevs; i++) {
481         if ((!name || !strcmp(Devices[i]->name, name)) &&
482             (!desc || !strcmp(Devices[i]->description, desc)) &&
483             (class == DEVICE_TYPE_ANY || class == Devices[i]->type))
484             found[j++] = Devices[i];
485     }
486     found[j] = NULL;
487     return j ? found : NULL;
488 }
489
490 int
491 deviceCount(Device **devs)
492 {
493     int i;
494
495     if (!devs)
496         return 0;
497     for (i = 0; devs[i]; i++);
498     return i;
499 }
500
501 /*
502  * Create a menu listing all the devices of a certain type in the system.
503  * The passed-in menu is expected to be a "prototype" from which the new
504  * menu is cloned.
505  */
506 DMenu *
507 deviceCreateMenu(DMenu *menu, DeviceType type, int (*hook)(dialogMenuItem *d), int (*check)(dialogMenuItem *d))
508 {
509     Device **devs;
510     int numdevs;
511     DMenu *tmp = NULL;
512     int i, j;
513
514     devs = deviceFind(NULL, type);
515     numdevs = deviceCount(devs);
516     if (!numdevs)
517         return NULL;
518     tmp = (DMenu *)safe_malloc(sizeof(DMenu) + (sizeof(dialogMenuItem) * (numdevs + 1)));
519     bcopy(menu, tmp, sizeof(DMenu));
520     for (i = 0; devs[i]; i++) {
521         tmp->items[i].prompt = devs[i]->name;
522         for (j = 0; j < numDevs; j++) {
523             if (devs[i] == Devices[j]) {
524                 tmp->items[i].title = Devices[j]->description;
525                 break;
526             }
527         }
528         if (j == numDevs)
529             tmp->items[i].title = "<unknown device type>";
530         tmp->items[i].fire = hook;
531         tmp->items[i].checked = check;
532     }
533     tmp->items[i].title = NULL;
534     return tmp;
535 }