diff options
Diffstat (limited to 'usr.sbin/sysinstall/devices.c')
-rw-r--r-- | usr.sbin/sysinstall/devices.c | 590 |
1 files changed, 0 insertions, 590 deletions
diff --git a/usr.sbin/sysinstall/devices.c b/usr.sbin/sysinstall/devices.c deleted file mode 100644 index 18a33b5..0000000 --- a/usr.sbin/sysinstall/devices.c +++ /dev/null @@ -1,590 +0,0 @@ -/* - * The new sysinstall program. - * - * This is probably the last program in the `sysinstall' line - the next - * generation being essentially a complete rewrite. - * - * $FreeBSD$ - * - * Copyright (c) 1995 - * Jordan Hubbard. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer, - * verbatim and that no modifications are made prior to this - * point in the file. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY JORDAN HUBBARD ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL JORDAN HUBBARD OR HIS PETS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, LIFE OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - */ - -#include "sysinstall.h" -#include <sys/fcntl.h> -#include <sys/param.h> -#include <sys/socket.h> -#include <sys/ioctl.h> -#include <sys/errno.h> -#include <sys/time.h> -#include <net/if.h> -#include <net/if_var.h> -#include <net/if_dl.h> -#include <netinet/in.h> -#include <netinet/in_var.h> -#include <arpa/inet.h> -#include <ctype.h> -#include <libdisk.h> - -static Device *Devices[DEV_MAX]; -static int numDevs; - -#define DEVICE_ENTRY(type, name, descr, max) { type, name, descr, max } - -#define CDROM(name, descr, max) \ - DEVICE_ENTRY(DEVICE_TYPE_CDROM, name, descr, max) -#define DISK(name, descr, max) \ - DEVICE_ENTRY(DEVICE_TYPE_DISK, name, descr, max) -#define FLOPPY(name, descr, max) \ - DEVICE_ENTRY(DEVICE_TYPE_FLOPPY, name, descr, max) -#define NETWORK(name, descr) \ - DEVICE_ENTRY(DEVICE_TYPE_NETWORK, name, descr, 0) -#define SERIAL(name, descr, max) \ - DEVICE_ENTRY(DEVICE_TYPE_NETWORK, name, descr, max) -#define USB(name, descr, max) \ - DEVICE_ENTRY(DEVICE_TYPE_USB, name, descr, max) - -static struct _devname { - DeviceType type; - char *name; - char *description; - int max; -} device_names[] = { - CDROM("cd%d", "SCSI CDROM drive", 4), - CDROM("mcd%d", "Mitsumi (old model) CDROM drive", 4), - CDROM("scd%d", "Sony CDROM drive - CDU31/33A type", 4), - CDROM("acd%d", "ATAPI/IDE CDROM", 4), - DISK("da%d", "SCSI disk device", 16), - DISK("ad%d", "ATA/IDE disk device", 16), - DISK("ada%d", "SATA disk device", 16), - DISK("ar%d", "ATA/IDE RAID device", 16), - DISK("afd%d", "ATAPI/IDE floppy device", 4), - DISK("mlxd%d", "Mylex RAID disk", 4), - DISK("amrd%d", "AMI MegaRAID drive", 4), - DISK("idad%d", "Compaq RAID array", 4), - DISK("twed%d", "3ware ATA RAID array", 4), - DISK("aacd%d", "Adaptec FSA RAID array", 4), - DISK("ipsd%d", "IBM ServeRAID RAID array", 4), - DISK("mfid%d", "LSI MegaRAID SAS array", 4), - FLOPPY("fd%d", "floppy drive unit A", 4), - SERIAL("cuau%d", "%s on device %s (COM%d)", 16), - USB("da%da", "USB Mass Storage Device", 16), - NETWORK("ae", "Attansic/Atheros L2 Fast Ethernet"), - NETWORK("age", "Attansic/Atheros L1 Gigabit Ethernet"), - NETWORK("alc", "Atheros AR8131/AR8132 PCIe Ethernet"), - NETWORK("ale", "Atheros AR8121/AR8113/AR8114 PCIe Ethernet"), - NETWORK("an", "Aironet 4500/4800 802.11 wireless adapter"), - NETWORK("ath", "Atheros IEEE 802.11 wireless adapter"), - NETWORK("aue", "ADMtek USB Ethernet adapter"), - NETWORK("axe", "ASIX Electronics USB Ethernet adapter"), - NETWORK("bce", "Broadcom NetXtreme II Gigabit Ethernet card"), - NETWORK("bfe", "Broadcom BCM440x PCI Ethernet card"), - NETWORK("bge", "Broadcom BCM570x PCI Gigabit Ethernet card"), - NETWORK("bm", "Apple BMAC Built-in Ethernet"), - NETWORK("bwn", "Broadcom BCM43xx IEEE 802.11 wireless adapter"), - NETWORK("bxe", "Broadcom NetXtreme II 10Gb Ethernet card"), - NETWORK("cas", "Sun Cassini/Cassini+ or NS DP83065 Saturn Ethernet"), - NETWORK("cue", "CATC USB Ethernet adapter"), - NETWORK("cxgb", "Chelsio T3 10Gb Ethernet card"), - NETWORK("cxgbe", "Chelsio T4 10Gb Ethernet card"), - NETWORK("fpa", "DEC DEFPA PCI FDDI card"), - NETWORK("sr", "SDL T1/E1 sync serial PCI card"), - NETWORK("cc3i", "SDL HSSI sync serial PCI card"), - NETWORK("en", "Efficient Networks ATM PCI card"), - NETWORK("dc", "DEC/Intel 21143 (and clones) PCI Fast Ethernet card"), - NETWORK("de", "DEC DE435 PCI NIC or other DC21040-AA based card"), - NETWORK("fxp", "Intel EtherExpress Pro/100B PCI Fast Ethernet card"), - NETWORK("ed", "Novell NE1000/2000; 3C503; NE2000-compatible PCMCIA"), - NETWORK("ep", "3Com 3C509 Ethernet card/3C589 PCMCIA"), - NETWORK("em", "Intel(R) PRO/1000 Ethernet card"), - NETWORK("et", "Agere ET1310 based PCI Express Gigabit Ethernet card"), - NETWORK("ex", "Intel EtherExpress Pro/10 Ethernet card"), - NETWORK("fe", "Fujitsu MB86960A/MB86965A Ethernet card"), - NETWORK("gem", "Apple GMAC or Sun ERI/GEM Ethernet adapter"), - NETWORK("hme", "Sun HME (Happy Meal Ethernet) Ethernet adapter"), - NETWORK("ie", "AT&T StarLAN 10 and EN100; 3Com 3C507; NI5210"), - NETWORK("igb", "Intel(R) PRO/1000 PCI Express Gigabit Ethernet card"), - NETWORK("ipw", "Intel PRO/Wireless 2100 IEEE 802.11 adapter"), - NETWORK("iwi", "Intel PRO/Wireless 2200BG/2225BG/2915ABG adapter"), - NETWORK("iwn", "Intel Wireless WiFi Link 4965AGN IEEE 802.11n adapter"), - NETWORK("ixgb", "Intel(R) PRO/10Gb Ethernet card"), - NETWORK("ixgbe", "Intel(R) PRO/10Gb Ethernet card"), - NETWORK("jme", "JMicron JMC250 Gigabit/JMC260 Fast Ethernet"), - NETWORK("kue", "Kawasaki LSI USB Ethernet adapter"), - NETWORK("le", "AMD Am7900 LANCE or Am79C9xx PCnet Ethernet adapter"), - NETWORK("lge", "Level 1 LXT1001 Gigabit Ethernet card"), - NETWORK("malo", "Marvell Libertas 88W8335 802.11 wireless adapter"), - NETWORK("msk", "Marvell/SysKonnect Yukon II Gigabit Ethernet"), - NETWORK("mxge", "Myricom Myri10GE 10Gb Ethernet card"), - NETWORK("nfe", "NVIDIA nForce MCP Ethernet"), - NETWORK("nge", "NatSemi PCI Gigabit Ethernet card"), - NETWORK("nve", "NVIDIA nForce MCP Ethernet"), - NETWORK("nxge", "Neterion Xframe 10GbE Server/Storage adapter"), - NETWORK("pcn", "AMD Am79c79x PCI Ethernet card"), - NETWORK("ral", "Ralink Technology IEEE 802.11 wireless adapter"), - NETWORK("ray", "Raytheon Raylink 802.11 wireless adapter"), - NETWORK("re", "RealTek 8139C+/8169/8169S/8110S PCI Ethernet card"), - NETWORK("rl", "RealTek 8129/8139 PCI Ethernet card"), - NETWORK("rue", "RealTek USB Ethernet card"), - NETWORK("rum", "Ralink Technology USB IEEE 802.11 wireless adapter"), - NETWORK("sf", "Adaptec AIC-6915 PCI Ethernet card"), - NETWORK("sge", "Silicon Integrated Systems SiS190/191 Ethernet"), - NETWORK("sis", "SiS 900/SiS 7016 PCI Ethernet card"), -#ifdef PC98 - NETWORK("snc", "SONIC Ethernet card"), -#endif - NETWORK("sn", "SMC/Megahertz Ethernet card"), - NETWORK("ste", "Sundance ST201 PCI Ethernet card"), - NETWORK("stge", "Sundance/Tamarack TC9021 Gigabit Ethernet"), - NETWORK("sk", "SysKonnect PCI Gigabit Ethernet card"), - NETWORK("tx", "SMC 9432TX Ethernet card"), - NETWORK("txp", "3Com 3cR990 Ethernet card"), - NETWORK("ti", "Alteon Networks PCI Gigabit Ethernet card"), - NETWORK("tl", "Texas Instruments ThunderLAN PCI Ethernet card"), - NETWORK("uath", "Atheros AR5005UG and AR5005UX USB wireless adapter"), - NETWORK("upgt", "Conexant/Intersil PrismGT USB wireless adapter"), - NETWORK("ural", "Ralink Technology RT2500USB 802.11 wireless adapter"), - NETWORK("urtw", "Realtek 8187L USB wireless adapter"), - NETWORK("vge", "VIA VT612x PCI Gigabit Ethernet card"), - NETWORK("vr", "VIA VT3043/VT86C100A Rhine PCI Ethernet card"), - NETWORK("vte", "DM&P Vortex86 RDC R6040 Fast Ethernet"), - NETWORK("vlan", "IEEE 802.1Q VLAN network interface"), - NETWORK("vx", "3COM 3c590 / 3c595 Ethernet card"), - NETWORK("wb", "Winbond W89C840F PCI Ethernet card"), - NETWORK("wi", "Lucent WaveLAN/IEEE 802.11 wireless adapter"), - NETWORK("wpi", "Intel 3945ABG IEEE 802.11 wireless adapter"), - NETWORK("xe", "Xircom/Intel EtherExpress Pro100/16 Ethernet card"), - NETWORK("xl", "3COM 3c90x / 3c90xB PCI Ethernet card"), - NETWORK("zyd", "ZyDAS ZD1211/ZD1211B USB 802.11 wireless adapter"), - NETWORK("fwe", "FireWire Ethernet emulation"), - NETWORK("fwip", "IP over FireWire"), - NETWORK("plip", "Parallel Port IP (PLIP) peer connection"), - NETWORK("lo", "Loop-back (local) network interface"), - NETWORK("disc", "Software discard network interface"), - { 0, NULL, NULL, 0 } -}; - -Device * -new_device(char *name) -{ - Device *dev; - - dev = safe_malloc(sizeof(Device)); - bzero(dev, sizeof(Device)); - if (name) - SAFE_STRCPY(dev->name, name); - return dev; -} - -/* Stubs for unimplemented strategy routines */ -Boolean -dummyInit(Device *dev) -{ - return TRUE; -} - -FILE * -dummyGet(Device *dev, char *dist, Boolean probe) -{ - return NULL; -} - -void -dummyShutdown(Device *dev) -{ - return; -} - -static int -deviceTry(struct _devname dev, char *try, int i) -{ - int fd; - char unit[80]; - - snprintf(unit, sizeof unit, dev.name, i); - snprintf(try, FILENAME_MAX, "/dev/%s", unit); - if (isDebug()) - msgDebug("deviceTry: attempting to open %s\n", try); - fd = open(try, O_RDONLY); - if (fd >= 0) { - if (isDebug()) - msgDebug("deviceTry: open of %s succeeded on first try.\n", try); - } else { - if (isDebug()) - msgDebug("deviceTry: open of %s failed.\n", try); - } - return fd; -} - -/* Register a new device in the devices array */ -Device * -deviceRegister(char *name, char *desc, char *devname, DeviceType type, Boolean enabled, - Boolean (*init)(Device *), FILE * (*get)(Device *, char *, Boolean), - void (*shutdown)(Device *), void *private) -{ - Device *newdev = NULL; - - if (numDevs == DEV_MAX) - msgFatal("Too many devices found!"); - else { - newdev = new_device(name); - newdev->description = desc; - newdev->devname = devname; - newdev->type = type; - newdev->enabled = enabled; - newdev->init = init ? init : dummyInit; - newdev->get = get ? get : dummyGet; - newdev->shutdown = shutdown ? shutdown : dummyShutdown; - newdev->private = private; - Devices[numDevs] = newdev; - Devices[++numDevs] = NULL; - } - return newdev; -} - -/* Reset the registered device chain */ -void -deviceReset(void) -{ - int i; - - for (i = 0; i < numDevs; i++) { - DEVICE_SHUTDOWN(Devices[i]); - - /* XXX this potentially leaks Devices[i]->private if it's being - * used to point to something dynamic, but you're not supposed - * to call this routine at such times that some open instance - * has its private ptr pointing somewhere anyway. XXX - */ - free(Devices[i]); - } - Devices[numDevs = 0] = NULL; -} - -/* Get all device information for devices we have attached */ -void -deviceGetAll(void) -{ - int i, j, fd, s; - struct ifconf ifc; - struct ifreq *ifptr, *end; - int ifflags; - char buffer[INTERFACE_MAX * sizeof(struct ifreq)]; - char **names; - - msgNotify("Probing devices, please wait (this can take a while)..."); - /* First go for the network interfaces. Stolen shamelessly from ifconfig! */ - memset(&ifc, 0, sizeof(ifc)); - memset(buffer, 0, INTERFACE_MAX * sizeof(struct ifreq)); - ifc.ifc_len = sizeof(buffer); - ifc.ifc_buf = buffer; - - s = socket(AF_INET, SOCK_DGRAM, 0); - if (s < 0) - goto skipif; /* Jump over network iface probing */ - - if (ioctl(s, SIOCGIFCONF, (char *) &ifc) < 0) - goto skipif; /* Jump over network iface probing */ - - close(s); - ifflags = ifc.ifc_req->ifr_flags; - end = (struct ifreq *) (ifc.ifc_buf + ifc.ifc_len); - for (ifptr = ifc.ifc_req; ifptr < end; ifptr++) { - char *descr; - - /* If it's not a link entry, forget it */ - if (ifptr->ifr_ifru.ifru_addr.sa_family != AF_LINK) - goto loopend; - - /* Eliminate network devices that don't make sense */ - if (!strncmp(ifptr->ifr_name, "lo", 2)) - goto loopend; - - /* Try and find its description */ - for (i = 0, descr = NULL; device_names[i].name; i++) { - int len = strlen(device_names[i].name); - - if (!ifptr->ifr_name || !ifptr->ifr_name[0]) - continue; - else if (!strncmp(ifptr->ifr_name, device_names[i].name, len)) { - descr = device_names[i].description; - break; - } - } - if (!descr) - descr = "<unknown network interface type>"; - - deviceRegister(ifptr->ifr_name, descr, strdup(ifptr->ifr_name), DEVICE_TYPE_NETWORK, TRUE, - mediaInitNetwork, NULL, mediaShutdownNetwork, NULL); - if (isDebug()) - msgDebug("Found a network device named %s\n", ifptr->ifr_name); - close(s); - if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) - continue; - -loopend: - if (ifptr->ifr_addr.sa_len) /* I'm not sure why this is here - it's inherited */ - ifptr = (struct ifreq *)((caddr_t)ifptr + ifptr->ifr_addr.sa_len - sizeof(struct sockaddr)); - close(s); - } - -skipif: - /* Next, try to find all the types of devices one might need - * during the second stage of the installation. - */ - for (i = 0; device_names[i].name; i++) { - for (j = 0; j < device_names[i].max; j++) { - char try[FILENAME_MAX]; - - switch(device_names[i].type) { - case DEVICE_TYPE_CDROM: - fd = deviceTry(device_names[i], try, j); - if (fd >= 0 || errno == EBUSY) { /* EBUSY if already mounted */ - char n[BUFSIZ]; - - if (fd >= 0) close(fd); - snprintf(n, sizeof n, device_names[i].name, j); - deviceRegister(n, device_names[i].description, strdup(try), - DEVICE_TYPE_CDROM, TRUE, mediaInitCDROM, mediaGetCDROM, - mediaShutdownCDROM, NULL); - if (isDebug()) - msgDebug("Found a CDROM device for %s\n", try); - } - break; - - case DEVICE_TYPE_DISK: - /* nothing to do */ - break; - - case DEVICE_TYPE_FLOPPY: - fd = deviceTry(device_names[i], try, j); - if (fd >= 0) { - char n[BUFSIZ]; - - close(fd); - snprintf(n, sizeof n, device_names[i].name, j); - deviceRegister(n, device_names[i].description, strdup(try), - DEVICE_TYPE_FLOPPY, TRUE, mediaInitFloppy, mediaGetFloppy, - mediaShutdownFloppy, NULL); - if (isDebug()) - msgDebug("Found a floppy device for %s\n", try); - } - break; - - case DEVICE_TYPE_USB: - fd = deviceTry(device_names[i], try, j); - if (fd >= 0) { - char n[BUFSIZ]; - - close(fd); - snprintf(n, sizeof(n), device_names[i].name, j); - deviceRegister(n, device_names[i].description, - strdup(try), DEVICE_TYPE_USB, TRUE, mediaInitUSB, - mediaGetUSB, mediaShutdownUSB, NULL); - - if (isDebug()) - msgDebug("Found a USB disk for %s\n", try); - } - break; - - default: - break; - } - } - } - - /* Finally, go get the disks and look for partitions to register */ - if ((names = Disk_Names()) != NULL) { - int i; - - for (i = 0; names[i]; i++) { - Chunk *c1; - Disk *d; - - /* Ignore memory disks */ - if (!strncmp(names[i], "md", 2)) - continue; - - /* - * XXX - * Due to unknown reasons, Disk_Names() returns SCSI CDROM as a - * valid disk. This is main reason why sysinstall presents SCSI - * CDROM to available disks in Fdisk/Label menu. In addition, - * adding a blank SCSI CDROM to the menu generates floating point - * exception in sparc64. Disk_Names() just extracts sysctl - * "kern.disks". Why GEOM treats SCSI CDROM as a disk is beyond - * me and that should be investigated. - * For temporary workaround, ignore SCSI CDROM device. - */ - if (!strncmp(names[i], "cd", 2)) - continue; - - d = Open_Disk(names[i]); - if (!d) { - msgDebug("Unable to open disk %s\n", names[i]); - continue; - } - - deviceRegister(names[i], names[i], d->name, DEVICE_TYPE_DISK, FALSE, - dummyInit, dummyGet, dummyShutdown, d); - if (isDebug()) - msgDebug("Found a disk device named %s\n", names[i]); - - /* Look for existing DOS partitions to register as "DOS media devices" - * XXX: libdisks handling of extended partitions is too - * simplistic - it does not handle them containing (for - * example) UFS partitions - */ - for (c1 = d->chunks->part; c1; c1 = c1->next) { - if (c1->type == fat || c1->type == efi || c1->type == extended) { - Device *dev; - char devname[80]; - - /* Got one! */ - snprintf(devname, sizeof devname, "/dev/%s", c1->name); - dev = deviceRegister(c1->name, c1->name, strdup(devname), DEVICE_TYPE_DOS, TRUE, - mediaInitDOS, mediaGetDOS, mediaShutdownDOS, NULL); - dev->private = c1; - if (isDebug()) - msgDebug("Found a DOS partition %s\n", c1->name); - } else if (c1->type == freebsd) { - Device *dev; - char devname[80]; - Chunk *c2; - - for (c2 = c1->part; c2; c2 = c2->next) { - if (c2->type != part || c2->subtype != 7) - continue; - /* Got one! */ - snprintf(devname, sizeof devname, "/dev/%s", c1->name); - dev = deviceRegister(c2->name, c2->name, strdup(devname), DEVICE_TYPE_UFS, TRUE, - mediaInitUFS, mediaGetUFS, mediaShutdownUFS, NULL); - dev->private = c2; - if (isDebug()) - msgDebug("Found a UFS sub-partition %s\n", c2->name); - } - } - - } - } - free(names); - } - dialog_clear_norefresh(); -} - -/* Rescan all devices, after closing previous set - convenience function */ -void -deviceRescan(void) -{ - deviceReset(); - deviceGetAll(); -} - -/* - * Find all devices that match the criteria, allowing "wildcarding" as well - * by allowing NULL or ANY values to match all. The array returned is static - * and may be used until the next invocation of deviceFind(). - */ -Device ** -deviceFind(char *name, DeviceType class) -{ - static Device *found[DEV_MAX]; - int i, j; - - j = 0; - for (i = 0; i < numDevs; i++) { - if ((!name || !strcmp(Devices[i]->name, name)) - && (class == DEVICE_TYPE_ANY || class == Devices[i]->type)) - found[j++] = Devices[i]; - } - found[j] = NULL; - return j ? found : NULL; -} - -Device ** -deviceFindDescr(char *name, char *desc, DeviceType class) -{ - static Device *found[DEV_MAX]; - int i, j; - - j = 0; - for (i = 0; i < numDevs; i++) { - if ((!name || !strcmp(Devices[i]->name, name)) && - (!desc || !strcmp(Devices[i]->description, desc)) && - (class == DEVICE_TYPE_ANY || class == Devices[i]->type)) - found[j++] = Devices[i]; - } - found[j] = NULL; - return j ? found : NULL; -} - -int -deviceCount(Device **devs) -{ - int i; - - if (!devs) - return 0; - for (i = 0; devs[i]; i++); - return i; -} - -/* - * Create a menu listing all the devices of a certain type in the system. - * The passed-in menu is expected to be a "prototype" from which the new - * menu is cloned. - */ -DMenu * -deviceCreateMenu(DMenu *menu, DeviceType type, int (*hook)(dialogMenuItem *d), int (*check)(dialogMenuItem *d)) -{ - Device **devs; - int numdevs; - DMenu *tmp = NULL; - int i, j; - - devs = deviceFind(NULL, type); - numdevs = deviceCount(devs); - if (!numdevs) - return NULL; - tmp = (DMenu *)safe_malloc(sizeof(DMenu) + (sizeof(dialogMenuItem) * (numdevs + 1))); - bcopy(menu, tmp, sizeof(DMenu)); - for (i = 0; devs[i]; i++) { - tmp->items[i].prompt = devs[i]->name; - for (j = 0; j < numDevs; j++) { - if (devs[i] == Devices[j]) { - tmp->items[i].title = Devices[j]->description; - break; - } - } - if (j == numDevs) - tmp->items[i].title = "<unknown device type>"; - tmp->items[i].fire = hook; - tmp->items[i].checked = check; - } - tmp->items[i].title = NULL; - return tmp; -} |