diff --git a/src/core/posix_io.c b/src/core/posix_io.c new file mode 100644 index 00000000..2974a4c8 --- /dev/null +++ b/src/core/posix_io.c @@ -0,0 +1,332 @@ +/* + * Copyright (C) 2007 Michael Brown . + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/** @file + * + * POSIX-like I/O + * + * These functions provide traditional blocking I/O semantics. They + * are designed to be used by the PXE TFTP API. Because they block, + * they may not be used by most other portions of the gPXE codebase. + */ + +/** An open file */ +struct posix_file { + /** Reference count for this object */ + struct refcnt refcnt; + /** List of open files */ + struct list_head list; + /** File descriptor */ + int fd; + /** Overall status + * + * Set to -EINPROGRESS while data transfer is in progress. + */ + int rc; + /** Data transfer interface */ + struct xfer_interface xfer; + /** Current seek position */ + size_t pos; + /** File size */ + size_t filesize; + /** Received data queue */ + struct list_head data; +}; + +/** List of open files */ +static LIST_HEAD ( posix_files ); + +/** Minimum file descriptor that will ever be allocated */ +#define POSIX_FD_MIN ( 1 ) + +/** Maximum file descriptor that will ever be allocated */ +#define POSIX_FD_MAX ( 255 ) + +/** + * Free open file + * + * @v refcnt Reference counter + */ +static void posix_file_free ( struct refcnt *refcnt ) { + struct posix_file *file = + container_of ( refcnt, struct posix_file, refcnt ); + struct io_buffer *iobuf; + + list_for_each_entry ( iobuf, &file->data, list ) { + free_iob ( iobuf ); + } + free ( file ); +} + +/** + * Terminate file data transfer + * + * @v file POSIX file + * @v rc Reason for termination + */ +static void posix_file_finished ( struct posix_file *file, int rc ) { + xfer_nullify ( &file->xfer ); + xfer_close ( &file->xfer, rc ); + file->rc = rc; +} + +/** + * Handle close() event + * + * @v xfer POSIX file data transfer interface + * @v rc Reason for close + */ +static void posix_file_xfer_close ( struct xfer_interface *xfer, int rc ) { + struct posix_file *file = + container_of ( xfer, struct posix_file, xfer ); + + posix_file_finished ( file, rc ); +} + +/** + * Handle seek() event + * + * @v xfer POSIX file data transfer interface + * @v pos New position + * @ret rc Return status code + */ +static int posix_file_xfer_seek ( struct xfer_interface *xfer, off_t offset, + int whence ) { + struct posix_file *file = + container_of ( xfer, struct posix_file, xfer ); + + switch ( whence ) { + case SEEK_SET: + file->pos = offset; + break; + case SEEK_CUR: + file->pos += offset; + break; + } + + if ( file->filesize < file->pos ) + file->filesize = file->pos; + + return 0; +} + +/** + * Handle deliver_iob() event + * + * @v xfer POSIX file data transfer interface + * @v iobuf I/O buffer + * @ret rc Return status code + */ +static int posix_file_xfer_deliver_iob ( struct xfer_interface *xfer, + struct io_buffer *iobuf ) { + struct posix_file *file = + container_of ( xfer, struct posix_file, xfer ); + + list_add_tail ( &iobuf->list, &file->data ); + return 0; +} + +/** POSIX file data transfer interface operations */ +static struct xfer_interface_operations posix_file_xfer_operations = { + .close = posix_file_xfer_close, + .vredirect = xfer_vopen, + .request = ignore_xfer_request, + .seek = posix_file_xfer_seek, + .alloc_iob = default_xfer_alloc_iob, + .deliver_iob = posix_file_xfer_deliver_iob, + .deliver_raw = xfer_deliver_as_iob, +}; + +/** + * Identify file by file descriptor + * + * @v fd File descriptor + * @ret file Corresponding file, or NULL + */ +static struct posix_file * posix_fd_to_file ( int fd ) { + struct posix_file *file; + + list_for_each_entry ( file, &posix_files, list ) { + if ( file->fd == fd ) + return file; + } + return NULL; +} + +/** + * Find an available file descriptor + * + * @ret fd File descriptor, or negative error number + */ +static int posix_find_free_fd ( void ) { + int fd; + + for ( fd = POSIX_FD_MIN ; fd <= POSIX_FD_MAX ; fd++ ) { + if ( ! posix_fd_to_file ( fd ) ) + return fd; + } + return -ENFILE; +} + +/** + * Open file + * + * @v uri_string URI string + * @ret fd File descriptor, or negative error number + */ +int open ( const char *uri_string ) { + struct posix_file *file; + int fd; + int rc; + + /* Find a free file descriptor to use */ + fd = posix_find_free_fd(); + if ( fd < 0 ) + return fd; + + /* Allocate and initialise structure */ + file = malloc ( sizeof ( *file ) ); + if ( ! file ) + return -ENOMEM; + memset ( file, 0, sizeof ( *file ) ); + file->refcnt.free = posix_file_free; + file->fd = fd; + file->rc = -EINPROGRESS; + xfer_init ( &file->xfer, &posix_file_xfer_operations, + &file->refcnt ); + INIT_LIST_HEAD ( &file->data ); + + /* Open URI on data transfer interface */ + if ( ( rc = xfer_open_uri ( &file->xfer, uri_string ) ) != 0 ) + goto err; + + /* Wait for open to succeed or fail */ + while ( list_empty ( &file->data ) ) { + step(); + if ( file->rc != -EINPROGRESS ) { + rc = file->rc; + goto err; + } + } + + /* Add to list of open files. List takes reference ownership. */ + list_add ( &file->list, &posix_files ); + return fd; + + err: + posix_file_finished ( file, rc ); + ref_put ( &file->refcnt ); + return rc; +} + +/** + * Read data from file + * + * @v buffer Data buffer + * @v offset Starting offset within data buffer + * @v len Maximum length to read + * @ret len Actual length read, or negative error number + */ +ssize_t read_user ( int fd, userptr_t buffer, off_t offset, size_t max_len ) { + struct posix_file *file; + struct io_buffer *iobuf; + size_t frag_len; + ssize_t len = 0; + + /* Identify file */ + file = posix_fd_to_file ( fd ); + if ( ! file ) + return -EBADF; + + while ( 1 ) { + /* Try to fetch more data if none available */ + if ( list_empty ( &file->data ) ) + step(); + /* Dequeue at most one received I/O buffer into user buffer */ + list_for_each_entry ( iobuf, &file->data, list ) { + frag_len = iob_len ( iobuf ); + if ( frag_len > max_len ) + frag_len = max_len; + copy_to_user ( buffer, offset, iobuf->data, + frag_len ); + iob_pull ( iobuf, frag_len ); + if ( ! iob_len ( iobuf ) ) + free_iob ( iobuf ); + file->pos += frag_len; + len += frag_len; + offset += frag_len; + max_len -= frag_len; + break; + } + /* If buffer is full, return */ + if ( ! max_len ) + return len; + /* If file has completed, return */ + if ( file->rc != -EINPROGRESS ) + return ( file->rc ? file->rc : len ); + } +} + +/** + * Determine file size + * + * @v fd File descriptor + * @ret size File size, or negative error number + */ +ssize_t fsize ( int fd ) { + struct posix_file *file; + + /* Identify file */ + file = posix_fd_to_file ( fd ); + if ( ! file ) + return -EBADF; + + return file->filesize; +} + +/** + * Close file + * + * @v fd File descriptor + * @ret rc Return status code + */ +int close ( int fd ) { + struct posix_file *file; + + /* Identify file */ + file = posix_fd_to_file ( fd ); + if ( ! file ) + return -EBADF; + + /* Terminate data transfer */ + posix_file_finished ( file, 0 ); + + /* Remove from list of open files and drop reference */ + list_del ( &file->list ); + ref_put ( &file->refcnt ); + return 0; +} diff --git a/src/include/gpxe/posix_io.h b/src/include/gpxe/posix_io.h new file mode 100644 index 00000000..a5cf0c75 --- /dev/null +++ b/src/include/gpxe/posix_io.h @@ -0,0 +1,31 @@ +#ifndef _GPXE_POSIX_IO_H +#define _GPXE_POSIX_IO_H + +/** @file + * + * POSIX-like I/O + * + */ + +#include +#include + +extern int open ( const char *uri_string ); +extern ssize_t read_user ( int fd, userptr_t buffer, + off_t offset, size_t len ); +extern ssize_t fsize ( int fd ); +extern int close ( int fd ); + +/** + * Read data from file + * + * @v fd File descriptor + * @v buf Data buffer + * @v len Maximum length to read + * @ret len Actual length read, or negative error number + */ +static inline ssize_t read ( int fd, void *buf, size_t len ) { + return read_user ( fd, virt_to_user ( buf ), 0, len ); +} + +#endif /* _GPXE_POSIX_IO_H */