From a2027c5d4c477546faea39a790b019c3480c9b9b Mon Sep 17 00:00:00 2001 From: Toomas Soome Date: Wed, 21 Jul 2021 23:32:24 +0300 Subject: 13965 loader: open file list should be dynamic Reviewed by: Andy Fiddaman Approved by: Robert Mustacchi --- usr/src/boot/Makefile.version | 2 +- usr/src/boot/lib/libstand/close.c | 33 ++++++--- usr/src/boot/lib/libstand/closeall.c | 59 ++++----------- usr/src/boot/lib/libstand/fstat.c | 5 +- usr/src/boot/lib/libstand/ioctl.c | 5 +- usr/src/boot/lib/libstand/iodesc.h | 4 +- usr/src/boot/lib/libstand/lseek.c | 138 ++++++++++++++++++----------------- usr/src/boot/lib/libstand/net.h | 4 +- usr/src/boot/lib/libstand/netif.c | 110 +++++++++++++++++++++++----- usr/src/boot/lib/libstand/open.c | 65 ++++++++++++++--- usr/src/boot/lib/libstand/read.c | 5 +- usr/src/boot/lib/libstand/readdir.c | 6 +- usr/src/boot/lib/libstand/stand.h | 8 +- usr/src/boot/lib/libstand/write.c | 5 +- 14 files changed, 282 insertions(+), 167 deletions(-) diff --git a/usr/src/boot/Makefile.version b/usr/src/boot/Makefile.version index ffa5854c66..a45290a429 100644 --- a/usr/src/boot/Makefile.version +++ b/usr/src/boot/Makefile.version @@ -34,4 +34,4 @@ LOADER_VERSION = 1.1 # Use date like formatting here, YYYY.MM.DD.XX, without leading zeroes. # The version is processed from left to right, the version number can only # be increased. -BOOT_VERSION = $(LOADER_VERSION)-2021.08.03.1 +BOOT_VERSION = $(LOADER_VERSION)-2021.08.03.2 diff --git a/usr/src/boot/lib/libstand/close.c b/usr/src/boot/lib/libstand/close.c index 573cef4dbd..546dd98600 100644 --- a/usr/src/boot/lib/libstand/close.c +++ b/usr/src/boot/lib/libstand/close.c @@ -67,23 +67,38 @@ int close(int fd) { - struct open_file *f = &files[fd]; + struct open_file *f, *last; int err1 = 0, err2 = 0; - if ((unsigned)fd >= SOPEN_MAX || f->f_flags == 0) { + f = fd2open_file(fd); + if (f == NULL) { errno = EBADF; return (-1); } free(f->f_rabuf); f->f_rabuf = NULL; - if (!(f->f_flags & F_RAW) && f->f_ops) - err1 = (f->f_ops->fo_close)(f); - if (!(f->f_flags & F_NODEV) && f->f_dev) - err2 = (f->f_dev->dv_close)(f); - if (f->f_devdata != NULL) - devclose(f); - f->f_flags = 0; + if (f->f_flags != 0) { + if (!(f->f_flags & F_RAW) && f->f_ops) + err1 = (f->f_ops->fo_close)(f); + if (!(f->f_flags & F_NODEV) && f->f_dev) + err2 = (f->f_dev->dv_close)(f); + if (f->f_devdata != NULL) + devclose(f); + f->f_flags = 0; + } else { + /* Attempt to close already closed file. */ + err1 = EBADF; + } + + /* free unused entries from tail. */ + TAILQ_FOREACH_REVERSE_SAFE(last, &files, file_list, f_link, f) { + if (last->f_flags != 0) + break; + TAILQ_REMOVE(&files, last, f_link); + free(last); + } + if (err1) { errno = err1; return (-1); diff --git a/usr/src/boot/lib/libstand/closeall.c b/usr/src/boot/lib/libstand/closeall.c index 13ff905a7e..425cd334b7 100644 --- a/usr/src/boot/lib/libstand/closeall.c +++ b/usr/src/boot/lib/libstand/closeall.c @@ -1,11 +1,7 @@ -/* $NetBSD: closeall.c,v 1.1 1996/01/13 22:25:36 leo Exp $ */ - -/* - * Copyright (c) 1993 - * The Regents of the University of California. All rights reserved. +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * - * This code is derived from software contributed to Berkeley by - * The Mach Operating System project at Carnegie-Mellon University. + * Copyright (c) 2021 Toomas Soome * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -15,14 +11,11 @@ * 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. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 THE REGENTS OR CONTRIBUTORS BE LIABLE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS 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, OR PROFITS; OR BUSINESS INTERRUPTION) @@ -30,34 +23,6 @@ * 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. - * - * @(#)close.c 8.1 (Berkeley) 6/11/93 - * - * - * Copyright (c) 1989, 1990, 1991 Carnegie Mellon University - * All Rights Reserved. - * - * Author: Alessandro Forin - * - * Permission to use, copy, modify and distribute this software and its - * documentation is hereby granted, provided that both the copyright - * notice and this permission notice appear in all copies of the - * software, derivative works or modified versions, and any portions - * thereof, and that both notices appear in supporting documentation. - * - * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" - * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR - * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. - * - * Carnegie Mellon requests users of this software to return to - * - * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU - * School of Computer Science - * Carnegie Mellon University - * Pittsburgh PA 15213-3890 - * - * any improvements or extensions that they make and grant Carnegie the - * rights to redistribute these changes. */ #include @@ -67,9 +32,15 @@ void closeall(void) { - int i; + struct open_file *f; - for (i = 0; i < SOPEN_MAX; i++) - if (files[i].f_flags != 0) - (void) close(i); + /* + * Pick up last entry and close it, this will also trigger + * the removal of this entry, and we end up with empty list. + */ + while ((f = TAILQ_LAST(&files, file_list)) != NULL) { + (void) close(f->f_id); + } + /* reset errno from close() */ + errno = 0; } diff --git a/usr/src/boot/lib/libstand/fstat.c b/usr/src/boot/lib/libstand/fstat.c index 3c33de0cc3..44030d085e 100644 --- a/usr/src/boot/lib/libstand/fstat.c +++ b/usr/src/boot/lib/libstand/fstat.c @@ -38,9 +38,10 @@ int fstat(int fd, struct stat *sb) { - struct open_file *f = &files[fd]; + struct open_file *f; - if ((unsigned)fd >= SOPEN_MAX || f->f_flags == 0) { + f = fd2open_file(fd); + if (f == NULL || f->f_flags == 0) { errno = EBADF; return (-1); } diff --git a/usr/src/boot/lib/libstand/ioctl.c b/usr/src/boot/lib/libstand/ioctl.c index 557cddf3ef..67c95c77d6 100644 --- a/usr/src/boot/lib/libstand/ioctl.c +++ b/usr/src/boot/lib/libstand/ioctl.c @@ -67,9 +67,10 @@ int ioctl(int fd, ulong_t cmd, char *arg) { - struct open_file *f = &files[fd]; + struct open_file *f; - if ((unsigned)fd >= SOPEN_MAX || f->f_flags == 0) { + f = fd2open_file(fd); + if (f == NULL || f->f_flags == 0) { errno = EBADF; return (-1); } diff --git a/usr/src/boot/lib/libstand/iodesc.h b/usr/src/boot/lib/libstand/iodesc.h index 37ae044af9..2086c586d2 100644 --- a/usr/src/boot/lib/libstand/iodesc.h +++ b/usr/src/boot/lib/libstand/iodesc.h @@ -1,7 +1,7 @@ /* $NetBSD: iodesc.h,v 1.4 1995/09/23 03:31:50 gwr Exp $ */ /* - * Copyright (c) 1993 Adam Glass + * Copyright (c) 1993 Adam Glass * Copyright (c) 1992 Regents of the University of California. * All rights reserved. * @@ -47,6 +47,8 @@ struct iodesc { u_long xid; /* transaction identification */ u_char myea[6]; /* my ethernet address */ struct netif *io_netif; + int io_id; /* descriptor id */ + TAILQ_ENTRY(iodesc) io_link; /* next entry in list */ }; #endif /* __SYS_LIBNETBOOT_IODESC_H */ diff --git a/usr/src/boot/lib/libstand/lseek.c b/usr/src/boot/lib/libstand/lseek.c index bfac8624a6..eb063394a1 100644 --- a/usr/src/boot/lib/libstand/lseek.c +++ b/usr/src/boot/lib/libstand/lseek.c @@ -32,30 +32,30 @@ * SUCH DAMAGE. * * @(#)lseek.c 8.1 (Berkeley) 6/11/93 - * + * * * Copyright (c) 1989, 1990, 1991 Carnegie Mellon University * All Rights Reserved. * * Author: Alessandro Forin - * + * * Permission to use, copy, modify and distribute this software and its * documentation is hereby granted, provided that both the copyright * notice and this permission notice appear in all copies of the * software, derivative works or modified versions, and any portions * thereof, and that both notices appear in supporting documentation. - * + * * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. - * + * * Carnegie Mellon requests users of this software to return to - * + * * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU * School of Computer Science * Carnegie Mellon University * Pittsburgh PA 15213-3890 - * + * * any improvements or extensions that they make and grant Carnegie the * rights to redistribute these changes. */ @@ -66,74 +66,76 @@ off_t lseek(int fd, off_t offset, int where) { - off_t bufpos, filepos, target; - struct open_file *f = &files[fd]; + off_t bufpos, filepos, target; + struct open_file *f; - if ((unsigned)fd >= SOPEN_MAX || f->f_flags == 0) { - errno = EBADF; - return (-1); - } - - if (f->f_flags & F_RAW) { - /* - * On RAW devices, update internal offset. - */ - switch (where) { - case SEEK_SET: - f->f_offset = offset; - break; - case SEEK_CUR: - f->f_offset += offset; - break; - default: - errno = EOFFSET; - return (-1); + f = fd2open_file(fd); + if (f == NULL || f->f_flags == 0) { + errno = EBADF; + return (-1); } - return (f->f_offset); - } - /* - * If there is some unconsumed data in the readahead buffer and it - * contains the desired offset, simply adjust the buffer offset and - * length. We don't bother with SEEK_END here, since the code to - * handle it would fail in the same cases where the non-readahead - * code fails (namely, for streams which cannot seek backward and whose - * size isn't known in advance). - */ - if (f->f_ralen != 0 && where != SEEK_END) { - if ((filepos = (f->f_ops->fo_seek)(f, (off_t)0, SEEK_CUR)) == -1) - return (-1); - bufpos = filepos - f->f_ralen; - switch (where) { - case SEEK_SET: - target = offset; - break; - case SEEK_CUR: - target = bufpos + offset; - break; - default: - errno = EINVAL; - return (-1); + if (f->f_flags & F_RAW) { + /* + * On RAW devices, update internal offset. + */ + switch (where) { + case SEEK_SET: + f->f_offset = offset; + break; + case SEEK_CUR: + f->f_offset += offset; + break; + default: + errno = EOFFSET; + return (-1); + } + return (f->f_offset); } - if (bufpos <= target && target < filepos) { - f->f_raoffset += target - bufpos; - f->f_ralen -= target - bufpos; - return (target); + + /* + * If there is some unconsumed data in the readahead buffer and it + * contains the desired offset, simply adjust the buffer offset and + * length. We don't bother with SEEK_END here, since the code to + * handle it would fail in the same cases where the non-readahead + * code fails (namely, for streams which cannot seek backward and whose + * size isn't known in advance). + */ + if (f->f_ralen != 0 && where != SEEK_END) { + filepos = (f->f_ops->fo_seek)(f, 0, SEEK_CUR); + if (filepos == -1) + return (-1); + bufpos = filepos - f->f_ralen; + switch (where) { + case SEEK_SET: + target = offset; + break; + case SEEK_CUR: + target = bufpos + offset; + break; + default: + errno = EINVAL; + return (-1); + } + if (bufpos <= target && target < filepos) { + f->f_raoffset += target - bufpos; + f->f_ralen -= target - bufpos; + return (target); + } } - } - /* - * If this is a relative seek, we need to correct the offset for - * bytes that we have already read but the caller doesn't know - * about. - */ - if (where == SEEK_CUR) - offset -= f->f_ralen; + /* + * If this is a relative seek, we need to correct the offset for + * bytes that we have already read but the caller doesn't know + * about. + */ + if (where == SEEK_CUR) + offset -= f->f_ralen; - /* - * Invalidate the readahead buffer. - */ - f->f_ralen = 0; + /* + * Invalidate the readahead buffer. + */ + f->f_ralen = 0; - return (f->f_ops->fo_seek)(f, offset, where); + return (f->f_ops->fo_seek)(f, offset, where); } diff --git a/usr/src/boot/lib/libstand/net.h b/usr/src/boot/lib/libstand/net.h index 99081a634a..93ab8d84e7 100644 --- a/usr/src/boot/lib/libstand/net.h +++ b/usr/src/boot/lib/libstand/net.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 1993 Adam Glass + * Copyright (c) 1993 Adam Glass * Copyright (c) 1992 Regents of the University of California. * All rights reserved. * @@ -93,8 +93,6 @@ extern u_int intf_mtu; extern int debug; /* defined in the machdep sources */ -extern struct iodesc sockets[SOPEN_MAX]; - /* ARP/RevARP functions: */ u_char *arpwhohas(struct iodesc *, struct in_addr); void arp_reply(struct iodesc *, void *); diff --git a/usr/src/boot/lib/libstand/netif.c b/usr/src/boot/lib/libstand/netif.c index 0e6c71569d..74364e9098 100644 --- a/usr/src/boot/lib/libstand/netif.c +++ b/usr/src/boot/lib/libstand/netif.c @@ -46,7 +46,19 @@ #include "net.h" #include "netif.h" -struct iodesc sockets[SOPEN_MAX]; +typedef TAILQ_HEAD(socket_list, iodesc) socket_list_t; + +/* + * Open socket list. The current implementation and assumption is, + * we only remove entries from tail and we only add new entries to tail. + * This decision is to keep iodesc id management simple - we get list + * entries ordered by continiously growing io_id field. + * If we do have multiple sockets open and we do close socket not from tail, + * this entry will be marked unused. netif_open() will reuse unused entry, or + * netif_close() will free all unused tail entries. + */ +static socket_list_t sockets = TAILQ_HEAD_INITIALIZER(sockets); + #ifdef NETIF_DEBUG int netif_debug = 0; #endif @@ -258,32 +270,67 @@ netif_put(struct iodesc *desc, void *pkt, size_t len) return (rv); } +/* + * socktodesc_impl: + * + * Walk socket list and return pointer to iodesc structure. + * if id is < 0, return first unused iodesc. + */ +static struct iodesc * +socktodesc_impl(int socket) +{ + struct iodesc *s; + + TAILQ_FOREACH(s, &sockets, io_link) { + /* search by socket id */ + if (socket >= 0) { + if (s->io_id == socket) + break; + continue; + } + /* search for first unused entry */ + if (s->io_netif == NULL) + break; + } + return (s); +} + struct iodesc * socktodesc(int sock) { - if (sock >= SOPEN_MAX) { + struct iodesc *desc; + + if (sock < 0) + desc = NULL; + else + desc = socktodesc_impl(sock); + + if (desc == NULL) errno = EBADF; - return (NULL); - } - return (&sockets[sock]); + + return (desc); } int netif_open(void *machdep_hint) { - int fd; struct iodesc *s; struct netif *nif; /* find a free socket */ - for (fd = 0, s = sockets; fd < SOPEN_MAX; fd++, s++) - if (s->io_netif == (struct netif *)0) - goto fnd; - errno = EMFILE; - return (-1); - -fnd: - bzero(s, sizeof (*s)); + s = socktodesc_impl(-1); + if (s == NULL) { + struct iodesc *last; + + s = calloc(1, sizeof (*s)); + if (s == NULL) + return (-1); + last = TAILQ_LAST(&sockets, socket_list); + if (last != NULL) + s->io_id = last->io_id + 1; + TAILQ_INSERT_TAIL(&sockets, s, io_link); + } + netif_init(); nif = netif_select(machdep_hint); if (!nif) @@ -296,18 +343,43 @@ fnd: } netif_attach(nif, s, machdep_hint); - return (fd); + return (s->io_id); } int netif_close(int sock) { - if (sock >= SOPEN_MAX) { - errno = EBADF; + struct iodesc *s, *last; + int err; + + err = 0; + s = socktodesc_impl(sock); + if (s == NULL || sock < 0) { + err = EBADF; + return (-1); + } + netif_detach(s->io_netif); + + bzero(&s->destip, sizeof (s->destip)); + bzero(&s->myip, sizeof (s->myip)); + s->destport = 0; + s->myport = 0; + s->xid = 0; + bzero(s->myea, sizeof (s->myea)); + s->io_netif = NULL; + + /* free unused entries from tail. */ + TAILQ_FOREACH_REVERSE_SAFE(last, &sockets, socket_list, io_link, s) { + if (last->io_netif != NULL) + break; + TAILQ_REMOVE(&sockets, last, io_link); + free(last); + } + + if (err) { + errno = err; return (-1); } - netif_detach(sockets[sock].io_netif); - sockets[sock].io_netif = (struct netif *)0; return (0); } diff --git a/usr/src/boot/lib/libstand/open.c b/usr/src/boot/lib/libstand/open.c index 5fd6c2a5fb..6d026f1ca6 100644 --- a/usr/src/boot/lib/libstand/open.c +++ b/usr/src/boot/lib/libstand/open.c @@ -66,17 +66,65 @@ struct fs_ops *exclusive_file_system; -struct open_file files[SOPEN_MAX]; +/* + * Open file list. The current implementation and assumption is, + * we only remove entries from tail and we only add new entries to tail. + * This decision is to keep file id management simple - we get list + * entries ordered by continiously growing f_id field. + * If we do have multiple files open and we do close file not from tail, + * this entry will be marked unused. open() will reuse unused entry, or + * close will free all unused tail entries. + * + * Only case we expect open file list to grow long, is with zfs pools with + * many disks. + */ +file_list_t files = TAILQ_HEAD_INITIALIZER(files); + +/* + * Walk file list and return pointer to open_file structure. + * if fd is < 0, return first unused open_file. + */ +struct open_file * +fd2open_file(int fd) +{ + struct open_file *f; + + TAILQ_FOREACH(f, &files, f_link) { + if (fd >= 0) { + if (f->f_id == fd) + break; + continue; + } + if (f->f_flags == 0) + break; + } + return (f); +} static int -o_gethandle(void) +o_gethandle(struct open_file **ptr) { - int fd; + struct open_file *f, *last; - for (fd = 0; fd < SOPEN_MAX; fd++) - if (files[fd].f_flags == 0) - return (fd); - return (-1); + /* Pick up unused entry */ + f = fd2open_file(-1); + if (f != NULL) { + *ptr = f; + return (f->f_id); + } + + /* Add new entry */ + f = calloc(1, sizeof (*f)); + if (f == NULL) + return (-1); + + last = TAILQ_LAST(&files, file_list); + if (last != NULL) + f->f_id = last->f_id + 1; + TAILQ_INSERT_TAIL(&files, f, f_link); + + *ptr = f; + return (f->f_id); } static void @@ -95,12 +143,11 @@ open(const char *fname, int mode) int fd, i, error, besterror; const char *file; - if ((fd = o_gethandle()) == -1) { + if ((fd = o_gethandle(&f)) == -1) { errno = EMFILE; return (-1); } - f = &files[fd]; f->f_flags = mode + 1; f->f_dev = NULL; f->f_ops = NULL; diff --git a/usr/src/boot/lib/libstand/read.c b/usr/src/boot/lib/libstand/read.c index 41e94a4f8b..ebbc082705 100644 --- a/usr/src/boot/lib/libstand/read.c +++ b/usr/src/boot/lib/libstand/read.c @@ -68,10 +68,11 @@ ssize_t read(int fd, void *dest, size_t bcount) { - struct open_file *f = &files[fd]; + struct open_file *f; size_t resid; - if ((unsigned)fd >= SOPEN_MAX || !(f->f_flags & F_READ)) { + f = fd2open_file(fd); + if (f == NULL || !(f->f_flags & F_READ)) { errno = EBADF; return (-1); } diff --git a/usr/src/boot/lib/libstand/readdir.c b/usr/src/boot/lib/libstand/readdir.c index e49d93d15e..6920f5b5fe 100644 --- a/usr/src/boot/lib/libstand/readdir.c +++ b/usr/src/boot/lib/libstand/readdir.c @@ -25,7 +25,6 @@ */ #include -__FBSDID("$FreeBSD$"); #include #include "stand.h" @@ -34,9 +33,10 @@ struct dirent * readdirfd(int fd) { static struct dirent dir; /* XXX not thread safe */ - struct open_file *f = &files[fd]; + struct open_file *f; - if ((unsigned)fd >= SOPEN_MAX || !(f->f_flags & F_READ)) { + f = fd2open_file(fd); + if (f == NULL || !(f->f_flags & F_READ)) { errno = EBADF; return (NULL); } diff --git a/usr/src/boot/lib/libstand/stand.h b/usr/src/boot/lib/libstand/stand.h index 1af513b5fa..408ebd73da 100644 --- a/usr/src/boot/lib/libstand/stand.h +++ b/usr/src/boot/lib/libstand/stand.h @@ -65,6 +65,7 @@ #include #include #include +#include /* this header intentionally exports NULL from */ #include @@ -183,11 +184,14 @@ struct open_file { char *f_rabuf; /* readahead buffer pointer */ size_t f_ralen; /* valid data in readahead buffer */ off_t f_raoffset; /* consumer offset in readahead buffer */ + int f_id; /* file number */ + TAILQ_ENTRY(open_file) f_link; /* next entry */ #define SOPEN_RASIZE 512 }; -#define SOPEN_MAX 64 -extern struct open_file files[]; +typedef TAILQ_HEAD(file_list, open_file) file_list_t; +extern file_list_t files; +extern struct open_file *fd2open_file(int); /* f_flags values */ #define F_READ 0x0001 /* file opened for reading */ diff --git a/usr/src/boot/lib/libstand/write.c b/usr/src/boot/lib/libstand/write.c index 86bc162fe5..3edab25428 100644 --- a/usr/src/boot/lib/libstand/write.c +++ b/usr/src/boot/lib/libstand/write.c @@ -68,10 +68,11 @@ ssize_t write(int fd, const void *dest, size_t bcount) { - struct open_file *f = &files[fd]; + struct open_file *f; size_t resid; - if ((unsigned)fd >= SOPEN_MAX || !(f->f_flags & F_WRITE)) { + f = fd2open_file(fd); + if (f == NULL || !(f->f_flags & F_WRITE)) { errno = EBADF; return (-1); } -- cgit v1.2.3