From d6e48869a41b61bb2f4eb0a787c08225630feb9e Mon Sep 17 00:00:00 2001 From: "Daniel P. Berrange" Date: Fri, 27 Feb 2015 18:25:25 +0000 Subject: [PATCH] io: add QIOChannelFile class Add a QIOChannel subclass that is capable of operating on things that are files, such as plain files, pipes, character/block devices, but notably not sockets. Signed-off-by: Daniel P. Berrange --- include/io/channel-file.h | 93 ++++++++++++++++++ io/Makefile.objs | 1 + io/channel-file.c | 225 +++++++++++++++++++++++++++++++++++++++++++ tests/.gitignore | 2 + tests/Makefile | 3 + tests/test-io-channel-file.c | 100 +++++++++++++++++++ trace-events | 4 + 7 files changed, 428 insertions(+) create mode 100644 include/io/channel-file.h create mode 100644 io/channel-file.c create mode 100644 tests/test-io-channel-file.c diff --git a/include/io/channel-file.h b/include/io/channel-file.h new file mode 100644 index 0000000000..308e6d44d6 --- /dev/null +++ b/include/io/channel-file.h @@ -0,0 +1,93 @@ +/* + * QEMU I/O channels files driver + * + * Copyright (c) 2015 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + * + */ + +#ifndef QIO_CHANNEL_FILE_H__ +#define QIO_CHANNEL_FILE_H__ + +#include "io/channel.h" + +#define TYPE_QIO_CHANNEL_FILE "qio-channel-file" +#define QIO_CHANNEL_FILE(obj) \ + OBJECT_CHECK(QIOChannelFile, (obj), TYPE_QIO_CHANNEL_FILE) + +typedef struct QIOChannelFile QIOChannelFile; + +/** + * QIOChannelFile: + * + * The QIOChannelFile object provides a channel implementation + * that is able to perform I/O on block devices, character + * devices, FIFOs, pipes and plain files. While it is technically + * able to work on sockets too on the UNIX platform, this is not + * portable to Windows and lacks some extra sockets specific + * functionality. So the QIOChannelSocket object is recommended + * for that use case. + * + */ + +struct QIOChannelFile { + QIOChannel parent; + int fd; +}; + + +/** + * qio_channel_file_new_fd: + * @fd: the file descriptor + * + * Create a new IO channel object for a file represented + * by the @fd parameter. @fd can be associated with a + * block device, character device, fifo, pipe, or a + * regular file. For sockets, the QIOChannelSocket class + * should be used instead, as this provides greater + * functionality and cross platform portability. + * + * The channel will own the passed in file descriptor + * and will take responsibility for closing it, so the + * caller must not close it. If appropriate the caller + * should dup() its FD before opening the channel. + * + * Returns: the new channel object + */ +QIOChannelFile * +qio_channel_file_new_fd(int fd); + +/** + * qio_channel_file_new_path: + * @fd: the file descriptor + * @flags: the open flags (O_RDONLY|O_WRONLY|O_RDWR, etc) + * @mode: the file creation mode if O_WRONLY is set in @flags + * @errp: pointer to initialized error object + * + * Create a new IO channel object for a file represented + * by the @path parameter. @path can point to any + * type of file on which sequential I/O can be + * performed, whether it be a plain file, character + * device or block device. + * + * Returns: the new channel object + */ +QIOChannelFile * +qio_channel_file_new_path(const char *path, + int flags, + mode_t mode, + Error **errp); + +#endif /* QIO_CHANNEL_FILE_H__ */ diff --git a/io/Makefile.objs b/io/Makefile.objs index e9d77aaaa5..3d2f232480 100644 --- a/io/Makefile.objs +++ b/io/Makefile.objs @@ -1,4 +1,5 @@ io-obj-y = channel.o +io-obj-y += channel-file.o io-obj-y += channel-socket.o io-obj-y += channel-watch.o io-obj-y += task.o diff --git a/io/channel-file.c b/io/channel-file.c new file mode 100644 index 0000000000..13609005f9 --- /dev/null +++ b/io/channel-file.c @@ -0,0 +1,225 @@ +/* + * QEMU I/O channels files driver + * + * Copyright (c) 2015 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + * + */ + +#include "io/channel-file.h" +#include "io/channel-watch.h" +#include "qemu/sockets.h" +#include "trace.h" + +QIOChannelFile * +qio_channel_file_new_fd(int fd) +{ + QIOChannelFile *ioc; + + ioc = QIO_CHANNEL_FILE(object_new(TYPE_QIO_CHANNEL_FILE)); + + ioc->fd = fd; + + trace_qio_channel_file_new_fd(ioc, fd); + + return ioc; +} + + +QIOChannelFile * +qio_channel_file_new_path(const char *path, + int flags, + mode_t mode, + Error **errp) +{ + QIOChannelFile *ioc; + + ioc = QIO_CHANNEL_FILE(object_new(TYPE_QIO_CHANNEL_FILE)); + + if (flags & O_WRONLY) { + ioc->fd = open(path, flags, mode); + } else { + ioc->fd = open(path, flags); + } + if (ioc->fd < 0) { + object_unref(OBJECT(ioc)); + error_setg_errno(errp, errno, + "Unable to open %s", path); + return NULL; + } + + trace_qio_channel_file_new_path(ioc, path, flags, mode, ioc->fd); + + return ioc; +} + + +static void qio_channel_file_init(Object *obj) +{ + QIOChannelFile *ioc = QIO_CHANNEL_FILE(obj); + ioc->fd = -1; +} + +static void qio_channel_file_finalize(Object *obj) +{ + QIOChannelFile *ioc = QIO_CHANNEL_FILE(obj); + if (ioc->fd != -1) { + close(ioc->fd); + ioc->fd = -1; + } +} + + +static ssize_t qio_channel_file_readv(QIOChannel *ioc, + const struct iovec *iov, + size_t niov, + int **fds, + size_t *nfds, + Error **errp) +{ + QIOChannelFile *fioc = QIO_CHANNEL_FILE(ioc); + ssize_t ret; + + retry: + ret = readv(fioc->fd, iov, niov); + if (ret < 0) { + if (errno == EAGAIN || + errno == EWOULDBLOCK) { + return QIO_CHANNEL_ERR_BLOCK; + } + if (errno == EINTR) { + goto retry; + } + + error_setg_errno(errp, errno, + "Unable to read from file"); + return -1; + } + + return ret; +} + +static ssize_t qio_channel_file_writev(QIOChannel *ioc, + const struct iovec *iov, + size_t niov, + int *fds, + size_t nfds, + Error **errp) +{ + QIOChannelFile *fioc = QIO_CHANNEL_FILE(ioc); + ssize_t ret; + + retry: + ret = writev(fioc->fd, iov, niov); + if (ret <= 0) { + if (errno == EAGAIN || + errno == EWOULDBLOCK) { + return QIO_CHANNEL_ERR_BLOCK; + } + if (errno == EINTR) { + goto retry; + } + error_setg_errno(errp, errno, + "Unable to write to file"); + return -1; + } + return ret; +} + +static int qio_channel_file_set_blocking(QIOChannel *ioc, + bool enabled, + Error **errp) +{ + QIOChannelFile *fioc = QIO_CHANNEL_FILE(ioc); + + if (enabled) { + qemu_set_block(fioc->fd); + } else { + qemu_set_nonblock(fioc->fd); + } + return 0; +} + + +static off_t qio_channel_file_seek(QIOChannel *ioc, + off_t offset, + int whence, + Error **errp) +{ + QIOChannelFile *fioc = QIO_CHANNEL_FILE(ioc); + off_t ret; + + ret = lseek(fioc->fd, offset, whence); + if (ret == (off_t)-1) { + error_setg_errno(errp, errno, + "Unable to seek to offset %lld whence %d in file", + (long long int)offset, whence); + return -1; + } + return ret; +} + + +static int qio_channel_file_close(QIOChannel *ioc, + Error **errp) +{ + QIOChannelFile *fioc = QIO_CHANNEL_FILE(ioc); + + if (close(fioc->fd) < 0) { + error_setg_errno(errp, errno, + "Unable to close file"); + return -1; + } + return 0; +} + + +static GSource *qio_channel_file_create_watch(QIOChannel *ioc, + GIOCondition condition) +{ + QIOChannelFile *fioc = QIO_CHANNEL_FILE(ioc); + return qio_channel_create_fd_watch(ioc, + fioc->fd, + condition); +} + +static void qio_channel_file_class_init(ObjectClass *klass, + void *class_data G_GNUC_UNUSED) +{ + QIOChannelClass *ioc_klass = QIO_CHANNEL_CLASS(klass); + + ioc_klass->io_writev = qio_channel_file_writev; + ioc_klass->io_readv = qio_channel_file_readv; + ioc_klass->io_set_blocking = qio_channel_file_set_blocking; + ioc_klass->io_seek = qio_channel_file_seek; + ioc_klass->io_close = qio_channel_file_close; + ioc_klass->io_create_watch = qio_channel_file_create_watch; +} + +static const TypeInfo qio_channel_file_info = { + .parent = TYPE_QIO_CHANNEL, + .name = TYPE_QIO_CHANNEL_FILE, + .instance_size = sizeof(QIOChannelFile), + .instance_init = qio_channel_file_init, + .instance_finalize = qio_channel_file_finalize, + .class_init = qio_channel_file_class_init, +}; + +static void qio_channel_file_register_types(void) +{ + type_register_static(&qio_channel_file_info); +} + +type_init(qio_channel_file_register_types); diff --git a/tests/.gitignore b/tests/.gitignore index 6164cfa2c5..6160003e75 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -24,6 +24,8 @@ test-cutils test-hbitmap test-int128 test-iov +test-io-channel-file +test-io-channel-file.txt test-io-channel-socket test-io-task test-mul64 diff --git a/tests/Makefile b/tests/Makefile index b2e987cf56..b7c9989090 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -86,6 +86,7 @@ check-unit-$(CONFIG_LINUX) += tests/test-qga$(EXESUF) check-unit-y += tests/test-timed-average$(EXESUF) check-unit-y += tests/test-io-task$(EXESUF) check-unit-y += tests/test-io-channel-socket$(EXESUF) +check-unit-y += tests/test-io-channel-file$(EXESUF) check-block-$(CONFIG_POSIX) += tests/qemu-iotests-quick.sh @@ -475,6 +476,8 @@ tests/test-crypto-tlssession$(EXESUF): tests/test-crypto-tlssession.o \ tests/test-io-task$(EXESUF): tests/test-io-task.o $(test-io-obj-y) tests/test-io-channel-socket$(EXESUF): tests/test-io-channel-socket.o \ tests/io-channel-helpers.o $(test-io-obj-y) +tests/test-io-channel-file$(EXESUF): tests/test-io-channel-file.o \ + tests/io-channel-helpers.o $(test-io-obj-y) libqos-obj-y = tests/libqos/pci.o tests/libqos/fw_cfg.o tests/libqos/malloc.o libqos-obj-y += tests/libqos/i2c.o tests/libqos/libqos.o diff --git a/tests/test-io-channel-file.c b/tests/test-io-channel-file.c new file mode 100644 index 0000000000..f276a32de0 --- /dev/null +++ b/tests/test-io-channel-file.c @@ -0,0 +1,100 @@ +/* + * QEMU I/O channel file test + * + * Copyright (c) 2015 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + * + */ + +#include "io/channel-file.h" +#include "io-channel-helpers.h" + + +static void test_io_channel_file(void) +{ + QIOChannel *src, *dst; + QIOChannelTest *test; + +#define TEST_FILE "tests/test-io-channel-file.txt" + unlink(TEST_FILE); + src = QIO_CHANNEL(qio_channel_file_new_path( + TEST_FILE, + O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0600, + &error_abort)); + dst = QIO_CHANNEL(qio_channel_file_new_path( + TEST_FILE, + O_RDONLY | O_BINARY, 0, + &error_abort)); + + test = qio_channel_test_new(); + qio_channel_test_run_writer(test, src); + qio_channel_test_run_reader(test, dst); + qio_channel_test_validate(test); + + unlink(TEST_FILE); + object_unref(OBJECT(src)); + object_unref(OBJECT(dst)); +} + + +#ifndef _WIN32 +static void test_io_channel_pipe(bool async) +{ + QIOChannel *src, *dst; + QIOChannelTest *test; + int fd[2]; + + if (pipe(fd) < 0) { + perror("pipe"); + abort(); + } + + src = QIO_CHANNEL(qio_channel_file_new_fd(fd[1])); + dst = QIO_CHANNEL(qio_channel_file_new_fd(fd[0])); + + test = qio_channel_test_new(); + qio_channel_test_run_threads(test, async, src, dst); + qio_channel_test_validate(test); + + object_unref(OBJECT(src)); + object_unref(OBJECT(dst)); +} + + +static void test_io_channel_pipe_async(void) +{ + test_io_channel_pipe(true); +} + +static void test_io_channel_pipe_sync(void) +{ + test_io_channel_pipe(false); +} +#endif /* ! _WIN32 */ + + +int main(int argc, char **argv) +{ + module_call_init(MODULE_INIT_QOM); + + g_test_init(&argc, &argv, NULL); + + g_test_add_func("/io/channel/file", test_io_channel_file); +#ifndef _WIN32 + g_test_add_func("/io/channel/pipe/sync", test_io_channel_pipe_sync); + g_test_add_func("/io/channel/pipe/async", test_io_channel_pipe_async); +#endif + return g_test_run(); +} diff --git a/trace-events b/trace-events index b335193011..88c83f4431 100644 --- a/trace-events +++ b/trace-events @@ -1836,3 +1836,7 @@ qio_channel_socket_dgram_complete(void *ioc, int fd) "Socket dgram complete ioc= qio_channel_socket_accept(void *ioc) "Socket accept start ioc=%p" qio_channel_socket_accept_fail(void *ioc) "Socket accept fail ioc=%p" qio_channel_socket_accept_complete(void *ioc, void *cioc, int fd) "Socket accept complete ioc=%p cioc=%p fd=%d" + +# io/channel-file.c +qio_channel_file_new_fd(void *ioc, int fd) "File new fd ioc=%p fd=%d" +qio_channel_file_new_path(void *ioc, const char *path, int flags, int mode, int fd) "File new fd ioc=%p path=%s flags=%d mode=%d fd=%d" -- 2.11.4.GIT