[PATCH] Implement file syncing as data direction
authorJens Axboe <jens.axboe@oracle.com>
Tue, 24 Oct 2006 12:41:26 +0000 (14:41 +0200)
committerJens Axboe <jens.axboe@oracle.com>
Tue, 24 Oct 2006 12:41:26 +0000 (14:41 +0200)
Instead of defining a seperate ->sync() operation for the io engine,
reuse the io_u infrastructure for committing and reaping sync
events as well. It's a nice cleanup as well.

Signed-off-by: Jens Axboe <jens.axboe@oracle.com>
engines/fio-engine-libaio.c
engines/fio-engine-mmap.c
engines/fio-engine-posixaio.c
engines/fio-engine-sg.c
engines/fio-engine-splice.c
engines/fio-engine-sync.c
fio.c
fio.h
io_u.c
ioengines.c

index 57daf1b6b49a5c8b2933eac060b025318594803e..5e394e3e477f0961ad65c8f65a619b1151c30ee6 100644 (file)
@@ -17,20 +17,18 @@ struct libaio_data {
        struct io_event *aio_events;
 };
 
        struct io_event *aio_events;
 };
 
-static int fio_libaio_sync(struct thread_data fio_unused *td,
-                          struct fio_file *f)
-{
-       return fsync(f->fd);
-}
-
 static int fio_libaio_prep(struct thread_data fio_unused *td, struct io_u *io_u)
 {
        struct fio_file *f = io_u->file;
 
        if (io_u->ddir == DDIR_READ)
                io_prep_pread(&io_u->iocb, f->fd, io_u->buf, io_u->buflen, io_u->offset);
 static int fio_libaio_prep(struct thread_data fio_unused *td, struct io_u *io_u)
 {
        struct fio_file *f = io_u->file;
 
        if (io_u->ddir == DDIR_READ)
                io_prep_pread(&io_u->iocb, f->fd, io_u->buf, io_u->buflen, io_u->offset);
-       else
+       else if (io_u->ddir == DDIR_WRITE)
                io_prep_pwrite(&io_u->iocb, f->fd, io_u->buf, io_u->buflen, io_u->offset);
                io_prep_pwrite(&io_u->iocb, f->fd, io_u->buf, io_u->buflen, io_u->offset);
+       else if (io_u->ddir == DDIR_SYNC)
+               io_prep_fsync(&io_u->iocb, f->fd);
+       else
+               return 1;
 
        return 0;
 }
 
        return 0;
 }
@@ -59,7 +57,7 @@ static int fio_libaio_getevents(struct thread_data *td, int min, int max,
                        break;
        } while (1);
 
                        break;
        } while (1);
 
-       return (int) r;
+       return (int) -r;
 }
 
 static int fio_libaio_queue(struct thread_data *td, struct io_u *io_u)
 }
 
 static int fio_libaio_queue(struct thread_data *td, struct io_u *io_u)
@@ -82,7 +80,7 @@ static int fio_libaio_queue(struct thread_data *td, struct io_u *io_u)
 
        assert(ret);
 
 
        assert(ret);
 
-       return (int) ret;
+       return (int) -ret;
 }
 
 static int fio_libaio_cancel(struct thread_data *td, struct io_u *io_u)
 }
 
 static int fio_libaio_cancel(struct thread_data *td, struct io_u *io_u)
@@ -132,5 +130,4 @@ struct ioengine_ops ioengine = {
        .getevents      = fio_libaio_getevents,
        .event          = fio_libaio_event,
        .cleanup        = fio_libaio_cleanup,
        .getevents      = fio_libaio_getevents,
        .event          = fio_libaio_event,
        .cleanup        = fio_libaio_cleanup,
-       .sync           = fio_libaio_sync,
 };
 };
index d203d6afc40db3d4ccca1cbcdbdaff4f94c22aab..c85f6617309da24d4fca7bcc79dc3c443708f69c 100644 (file)
@@ -48,8 +48,10 @@ static int fio_mmapio_queue(struct thread_data *td, struct io_u *io_u)
 
        if (io_u->ddir == DDIR_READ)
                memcpy(io_u->buf, f->mmap + real_off, io_u->buflen);
 
        if (io_u->ddir == DDIR_READ)
                memcpy(io_u->buf, f->mmap + real_off, io_u->buflen);
-       else
+       else if (io_u->ddir == DDIR_WRITE)
                memcpy(f->mmap + real_off, io_u->buf, io_u->buflen);
                memcpy(f->mmap + real_off, io_u->buf, io_u->buflen);
+       else if (io_u->ddir == DDIR_SYNC)
+               return msync(f->mmap, f->file_size, MS_SYNC);
 
        /*
         * not really direct, but should drop the pages from the cache
 
        /*
         * not really direct, but should drop the pages from the cache
@@ -67,12 +69,6 @@ static int fio_mmapio_queue(struct thread_data *td, struct io_u *io_u)
        return io_u->error;
 }
 
        return io_u->error;
 }
 
-static int fio_mmapio_sync(struct thread_data fio_unused *td,
-                          struct fio_file *f)
-{
-       return msync(f->mmap, f->file_size, MS_SYNC);
-}
-
 static void fio_mmapio_cleanup(struct thread_data *td)
 {
        if (td->io_ops->data) {
 static void fio_mmapio_cleanup(struct thread_data *td)
 {
        if (td->io_ops->data) {
@@ -98,6 +94,5 @@ struct ioengine_ops ioengine = {
        .getevents      = fio_mmapio_getevents,
        .event          = fio_mmapio_event,
        .cleanup        = fio_mmapio_cleanup,
        .getevents      = fio_mmapio_getevents,
        .event          = fio_mmapio_event,
        .cleanup        = fio_mmapio_cleanup,
-       .sync           = fio_mmapio_sync,
        .flags          = FIO_SYNCIO | FIO_MMAPIO,
 };
        .flags          = FIO_SYNCIO | FIO_MMAPIO,
 };
index 260551a4ee8b506f9c1fa84cc30711bddf168255..2d0fd3114b6469729997721643f8d66e3826451f 100644 (file)
@@ -45,12 +45,6 @@ static unsigned long long ts_utime_since_now(struct timespec *t)
        return sec + nsec;
 }
 
        return sec + nsec;
 }
 
-static int fio_posixaio_sync(struct thread_data fio_unused *td,
-                            struct fio_file *f)
-{
-       return fsync(f->fd);
-}
-
 static int fio_posixaio_cancel(struct thread_data fio_unused *td,
                               struct io_u *io_u)
 {
 static int fio_posixaio_cancel(struct thread_data fio_unused *td,
                               struct io_u *io_u)
 {
@@ -149,8 +143,10 @@ static int fio_posixaio_queue(struct thread_data fio_unused *td,
 
        if (io_u->ddir == DDIR_READ)
                ret = aio_read(aiocb);
 
        if (io_u->ddir == DDIR_READ)
                ret = aio_read(aiocb);
-       else
+       else if (io_u->ddir == DDIR_WRITE)
                ret = aio_write(aiocb);
                ret = aio_write(aiocb);
+       else
+               ret = aio_fsync(O_SYNC, aiocb);
 
        if (ret)
                io_u->error = errno;
 
        if (ret)
                io_u->error = errno;
@@ -189,5 +185,4 @@ struct ioengine_ops ioengine = {
        .getevents      = fio_posixaio_getevents,
        .event          = fio_posixaio_event,
        .cleanup        = fio_posixaio_cleanup,
        .getevents      = fio_posixaio_getevents,
        .event          = fio_posixaio_event,
        .cleanup        = fio_posixaio_cleanup,
-       .sync           = fio_posixaio_sync,
 };
 };
index 01eba8d58391ebe957f4213452946a5cef09bf93..112f027869a840aebe43bc2f16c5e0a9a75873f5 100644 (file)
@@ -151,28 +151,6 @@ static int fio_sgio_doio(struct thread_data *td, struct io_u *io_u, int sync)
        return fio_sgio_rw_doio(f, io_u, sync);
 }
 
        return fio_sgio_rw_doio(f, io_u, sync);
 }
 
-static int fio_sgio_sync(struct thread_data *td, struct fio_file fio_unused *f)
-{
-       struct sgio_data *sd = td->io_ops->data;
-       struct sg_io_hdr *hdr;
-       struct io_u *io_u;
-       int ret;
-
-       io_u = __get_io_u(td);
-       if (!io_u)
-               return ENOMEM;
-
-       hdr = &io_u->hdr;
-       sgio_hdr_init(sd, hdr, io_u, 0);
-       hdr->dxfer_direction = SG_DXFER_NONE;
-
-       hdr->cmdp[0] = 0x35;
-
-       ret = fio_sgio_doio(td, io_u, 1);
-       put_io_u(td, io_u);
-       return ret;
-}
-
 static int fio_sgio_prep(struct thread_data *td, struct io_u *io_u)
 {
        struct sg_io_hdr *hdr = &io_u->hdr;
 static int fio_sgio_prep(struct thread_data *td, struct io_u *io_u)
 {
        struct sg_io_hdr *hdr = &io_u->hdr;
@@ -184,24 +162,34 @@ static int fio_sgio_prep(struct thread_data *td, struct io_u *io_u)
                return EINVAL;
        }
 
                return EINVAL;
        }
 
-       sgio_hdr_init(sd, hdr, io_u, 1);
-
        if (io_u->ddir == DDIR_READ) {
        if (io_u->ddir == DDIR_READ) {
+               sgio_hdr_init(sd, hdr, io_u, 1);
+
                hdr->dxfer_direction = SG_DXFER_FROM_DEV;
                hdr->cmdp[0] = 0x28;
                hdr->dxfer_direction = SG_DXFER_FROM_DEV;
                hdr->cmdp[0] = 0x28;
-       } else {
+       } else if (io_u->ddir == DDIR_WRITE) {
+               sgio_hdr_init(sd, hdr, io_u, 1);
+
                hdr->dxfer_direction = SG_DXFER_TO_DEV;
                hdr->cmdp[0] = 0x2a;
                hdr->dxfer_direction = SG_DXFER_TO_DEV;
                hdr->cmdp[0] = 0x2a;
+       } else {
+               sgio_hdr_init(sd, hdr, io_u, 0);
+
+               hdr->dxfer_direction = SG_DXFER_NONE;
+               hdr->cmdp[0] = 0x35;
+       }
+
+       if (hdr->dxfer_direction != SG_DXFER_NONE) {
+               nr_blocks = io_u->buflen / sd->bs;
+               lba = io_u->offset / sd->bs;
+               hdr->cmdp[2] = (lba >> 24) & 0xff;
+               hdr->cmdp[3] = (lba >> 16) & 0xff;
+               hdr->cmdp[4] = (lba >>  8) & 0xff;
+               hdr->cmdp[5] = lba & 0xff;
+               hdr->cmdp[7] = (nr_blocks >> 8) & 0xff;
+               hdr->cmdp[8] = nr_blocks & 0xff;
        }
 
        }
 
-       nr_blocks = io_u->buflen / sd->bs;
-       lba = io_u->offset / sd->bs;
-       hdr->cmdp[2] = (lba >> 24) & 0xff;
-       hdr->cmdp[3] = (lba >> 16) & 0xff;
-       hdr->cmdp[4] = (lba >>  8) & 0xff;
-       hdr->cmdp[5] = lba & 0xff;
-       hdr->cmdp[7] = (nr_blocks >> 8) & 0xff;
-       hdr->cmdp[8] = nr_blocks & 0xff;
        return 0;
 }
 
        return 0;
 }
 
@@ -210,7 +198,7 @@ static int fio_sgio_queue(struct thread_data *td, struct io_u *io_u)
        struct sg_io_hdr *hdr = &io_u->hdr;
        int ret;
 
        struct sg_io_hdr *hdr = &io_u->hdr;
        int ret;
 
-       ret = fio_sgio_doio(td, io_u, 0);
+       ret = fio_sgio_doio(td, io_u, io_u->ddir == DDIR_SYNC);
 
        if (ret < 0)
                io_u->error = errno;
 
        if (ret < 0)
                io_u->error = errno;
@@ -324,6 +312,5 @@ struct ioengine_ops ioengine = {
        .getevents      = fio_sgio_getevents,
        .event          = fio_sgio_event,
        .cleanup        = fio_sgio_cleanup,
        .getevents      = fio_sgio_getevents,
        .event          = fio_sgio_event,
        .cleanup        = fio_sgio_cleanup,
-       .sync           = fio_sgio_sync,
        .flags          = FIO_SYNCIO | FIO_RAWIO,
 };
        .flags          = FIO_SYNCIO | FIO_RAWIO,
 };
index cb39b7225946fc55b618b8b65cf0bef8d2500de0..3553d646c0a1f15a4a6e1540a214febf328d0abd 100644 (file)
@@ -16,12 +16,6 @@ struct spliceio_data {
        int pipe[2];
 };
 
        int pipe[2];
 };
 
-static int fio_spliceio_sync(struct thread_data fio_unused *td,
-                            struct fio_file *f)
-{
-       return fsync(f->fd);
-}
-
 static int fio_spliceio_getevents(struct thread_data *td, int fio_unused min,
                                  int max, struct timespec fio_unused *t)
 {
 static int fio_spliceio_getevents(struct thread_data *td, int fio_unused min,
                                  int max, struct timespec fio_unused *t)
 {
@@ -139,8 +133,10 @@ static int fio_spliceio_queue(struct thread_data *td, struct io_u *io_u)
 
        if (io_u->ddir == DDIR_READ)
                ret = fio_splice_read(td, io_u);
 
        if (io_u->ddir == DDIR_READ)
                ret = fio_splice_read(td, io_u);
-       else
+       else if (io_u->ddir == DDIR_WRITE)
                ret = fio_splice_write(td, io_u);
                ret = fio_splice_write(td, io_u);
+       else
+               ret = fsync(io_u->file->fd);
 
        if ((unsigned int) ret != io_u->buflen) {
                if (ret > 0) {
 
        if ((unsigned int) ret != io_u->buflen) {
                if (ret > 0) {
@@ -191,6 +187,5 @@ struct ioengine_ops ioengine = {
        .getevents      = fio_spliceio_getevents,
        .event          = fio_spliceio_event,
        .cleanup        = fio_spliceio_cleanup,
        .getevents      = fio_spliceio_getevents,
        .event          = fio_spliceio_event,
        .cleanup        = fio_spliceio_cleanup,
-       .sync           = fio_spliceio_sync,
        .flags          = FIO_SYNCIO,
 };
        .flags          = FIO_SYNCIO,
 };
index d5be4c8256ecda8415cbe438883d141758acfda7..8bc990d72bcd3de7457169e9b27b1f1389719bd9 100644 (file)
@@ -14,12 +14,6 @@ struct syncio_data {
        struct io_u *last_io_u;
 };
 
        struct io_u *last_io_u;
 };
 
-static int fio_syncio_sync(struct thread_data fio_unused *td,
-                          struct fio_file *f)
-{
-       return fsync(f->fd);
-}
-
 static int fio_syncio_getevents(struct thread_data *td, int fio_unused min,
                                int max, struct timespec fio_unused *t)
 {
 static int fio_syncio_getevents(struct thread_data *td, int fio_unused min,
                                int max, struct timespec fio_unused *t)
 {
@@ -48,6 +42,9 @@ static int fio_syncio_prep(struct thread_data *td, struct io_u *io_u)
 {
        struct fio_file *f = io_u->file;
 
 {
        struct fio_file *f = io_u->file;
 
+       if (io_u->ddir == DDIR_SYNC)
+               return 0;
+
        if (lseek(f->fd, io_u->offset, SEEK_SET) == -1) {
                td_verror(td, errno);
                return 1;
        if (lseek(f->fd, io_u->offset, SEEK_SET) == -1) {
                td_verror(td, errno);
                return 1;
@@ -64,8 +61,10 @@ static int fio_syncio_queue(struct thread_data *td, struct io_u *io_u)
 
        if (io_u->ddir == DDIR_READ)
                ret = read(f->fd, io_u->buf, io_u->buflen);
 
        if (io_u->ddir == DDIR_READ)
                ret = read(f->fd, io_u->buf, io_u->buflen);
-       else
+       else if (io_u->ddir == DDIR_WRITE)
                ret = write(f->fd, io_u->buf, io_u->buflen);
                ret = write(f->fd, io_u->buf, io_u->buflen);
+       else
+               ret = fsync(f->fd);
 
        if ((unsigned int) ret != io_u->buflen) {
                if (ret > 0) {
 
        if ((unsigned int) ret != io_u->buflen) {
                if (ret > 0) {
@@ -107,6 +106,5 @@ struct ioengine_ops ioengine = {
        .getevents      = fio_syncio_getevents,
        .event          = fio_syncio_event,
        .cleanup        = fio_syncio_cleanup,
        .getevents      = fio_syncio_getevents,
        .event          = fio_syncio_event,
        .cleanup        = fio_syncio_cleanup,
-       .sync           = fio_syncio_sync,
        .flags          = FIO_SYNCIO,
 };
        .flags          = FIO_SYNCIO,
 };
diff --git a/fio.c b/fio.c
index 5673d9ef690cc8e8c6d6bbe31dccc5fb3cc937ba..fc653b3d9016e9b7553ac5cadbb37a870ba00a9d 100644 (file)
--- a/fio.c
+++ b/fio.c
@@ -45,8 +45,6 @@ int shm_id = 0;
 int temp_stall_ts;
 char *fio_inst_prefix = _INST_PREFIX;
 
 int temp_stall_ts;
 char *fio_inst_prefix = _INST_PREFIX;
 
-#define should_fsync(td)       ((td_write(td) || td_rw(td)) && (!(td)->odirect || (td)->override_sync))
-
 static volatile int startup_sem;
 
 #define TERMINATE_ALL          (-1)
 static volatile int startup_sem;
 
 #define TERMINATE_ALL          (-1)
@@ -372,7 +370,7 @@ static void do_io(struct thread_data *td)
 
                ret = td_io_getevents(td, min_evts, td->cur_depth, timeout);
                if (ret < 0) {
 
                ret = td_io_getevents(td, min_evts, td->cur_depth, timeout);
                if (ret < 0) {
-                       td_verror(td, -ret);
+                       td_verror(td, ret);
                        break;
                } else if (!ret)
                        continue;
                        break;
                } else if (!ret)
                        continue;
@@ -406,10 +404,6 @@ static void do_io(struct thread_data *td)
 
                if (td->thinktime)
                        usec_sleep(td, td->thinktime);
 
                if (td->thinktime)
                        usec_sleep(td, td->thinktime);
-
-               if (should_fsync(td) && td->fsync_blocks &&
-                   (td->io_blocks[DDIR_WRITE] % td->fsync_blocks) == 0)
-                       td_io_sync(td, f);
        }
 
        if (!ret) {
        }
 
        if (!ret) {
diff --git a/fio.h b/fio.h
index 921282cc61cb8bcc741934689c4381217221456b..967c42e678b899030df7b6b0229a8e39cc8706ad 100644 (file)
--- a/fio.h
+++ b/fio.h
@@ -113,6 +113,7 @@ struct group_run_stats {
 enum fio_ddir {
        DDIR_READ = 0,
        DDIR_WRITE,
 enum fio_ddir {
        DDIR_READ = 0,
        DDIR_WRITE,
+       DDIR_SYNC,
 };
 
 /*
 };
 
 /*
@@ -184,6 +185,7 @@ struct thread_data {
        enum fio_ddir ddir;
        unsigned int iomix;
        unsigned int ioprio;
        enum fio_ddir ddir;
        unsigned int iomix;
        unsigned int ioprio;
+       unsigned int last_was_sync;
 
        unsigned char sequential;
        unsigned char odirect;
 
        unsigned char sequential;
        unsigned char odirect;
@@ -365,6 +367,18 @@ extern struct thread_data *threads;
 
 #define MAX_JOBS       (1024)
 
 
 #define MAX_JOBS       (1024)
 
+static inline int should_fsync(struct thread_data *td)
+{
+       if (td->last_was_sync)
+               return 0;
+       if (td->odirect)
+               return 0;
+       if (td_write(td) || td_rw(td) || td->override_sync)
+               return 1;
+
+       return 0;
+}
+
 struct disk_util_stat {
        unsigned ios[2];
        unsigned merges[2];
 struct disk_util_stat {
        unsigned ios[2];
        unsigned merges[2];
@@ -554,12 +568,11 @@ struct ioengine_ops {
        struct io_u *(*event)(struct thread_data *, int);
        int (*cancel)(struct thread_data *, struct io_u *);
        void (*cleanup)(struct thread_data *);
        struct io_u *(*event)(struct thread_data *, int);
        int (*cancel)(struct thread_data *, struct io_u *);
        void (*cleanup)(struct thread_data *);
-       int (*sync)(struct thread_data *, struct fio_file *);
        void *data;
        void *dlhandle;
 };
 
        void *data;
        void *dlhandle;
 };
 
-#define FIO_IOOPS_VERSION      2
+#define FIO_IOOPS_VERSION      3
 
 extern struct ioengine_ops *load_ioengine(struct thread_data *, char *);
 extern void close_ioengine(struct thread_data *);
 
 extern struct ioengine_ops *load_ioengine(struct thread_data *, char *);
 extern void close_ioengine(struct thread_data *);
diff --git a/io_u.c b/io_u.c
index 962c17a9e8ddeb7c0ffa70926aa30142b417e947..738f3e20101eee5e3535f1ff52fd344286c45745 100644 (file)
--- a/io_u.c
+++ b/io_u.c
@@ -189,6 +189,16 @@ static int fill_io_u(struct thread_data *td, struct fio_file *f,
        if (td->read_iolog)
                return read_iolog_get(td, io_u);
 
        if (td->read_iolog)
                return read_iolog_get(td, io_u);
 
+       /*
+        * see if it's time to sync
+        */
+       if (td->fsync_blocks && !(td->io_blocks[DDIR_WRITE] % td->fsync_blocks)
+           && should_fsync(td)) {
+               io_u->ddir = DDIR_SYNC;
+               io_u->file = f;
+               return 0;
+       }
+
        /*
         * No log, let the seq/rand engine retrieve the next position.
         */
        /*
         * No log, let the seq/rand engine retrieve the next position.
         */
@@ -260,18 +270,20 @@ struct io_u *get_io_u(struct thread_data *td, struct fio_file *f)
                io_u->buflen = f->file_size - io_u->offset;
        }
 
                io_u->buflen = f->file_size - io_u->offset;
        }
 
-       if (!io_u->buflen) {
-               put_io_u(td, io_u);
-               return NULL;
-       }
+       if (io_u->ddir != DDIR_SYNC) {
+               if (!io_u->buflen) {
+                       put_io_u(td, io_u);
+                       return NULL;
+               }
 
 
-       if (!td->read_iolog && !td->sequential)
-               mark_random_map(td, f, io_u);
+               if (!td->read_iolog && !td->sequential)
+                       mark_random_map(td, f, io_u);
 
 
-       f->last_pos += io_u->buflen;
+               f->last_pos += io_u->buflen;
 
 
-       if (td->verify != VERIFY_NONE)
-               populate_verify_io_u(td, io_u);
+               if (td->verify != VERIFY_NONE)
+                       populate_verify_io_u(td, io_u);
+       }
 
        if (td_io_prep(td, io_u)) {
                put_io_u(td, io_u);
 
        if (td_io_prep(td, io_u)) {
                put_io_u(td, io_u);
@@ -288,6 +300,13 @@ void io_completed(struct thread_data *td, struct io_u *io_u,
        struct timeval e;
        unsigned long msec;
 
        struct timeval e;
        unsigned long msec;
 
+       if (io_u->ddir == DDIR_SYNC) {
+               td->last_was_sync = 1;
+               return;
+       }
+
+       td->last_was_sync = 0;
+
        gettimeofday(&e, NULL);
 
        if (!io_u->error) {
        gettimeofday(&e, NULL);
 
        if (!io_u->error) {
index 9b1ad60692e0e7b76bfd3296e9bab64bd566dcd6..bb46c293e3aed69dffd681095381fc1cd4a39344 100644 (file)
@@ -113,8 +113,40 @@ int td_io_prep(struct thread_data *td, struct io_u *io_u)
 
 int td_io_sync(struct thread_data *td, struct fio_file *f)
 {
 
 int td_io_sync(struct thread_data *td, struct fio_file *f)
 {
-       if (td->io_ops->sync)
-               return td->io_ops->sync(td, f);
+       struct io_u *io_u = __get_io_u(td);
+       struct io_completion_data icd;
+       int ret;
+
+       if (!io_u)
+               return 1;
+
+       io_u->ddir = DDIR_SYNC;
+       io_u->file = f;
+
+       if (td_io_prep(td, io_u)) {
+               put_io_u(td, io_u);
+               return 1;
+       }
+
+       ret = td_io_queue(td, io_u);
+       if (ret) {
+               put_io_u(td, io_u);
+               td_verror(td, ret);
+               return 1;
+       }
+
+       ret = td_io_getevents(td, 1, td->cur_depth, NULL);
+       if (ret < 0) {
+               td_verror(td, -ret);
+               return 1;
+       }
+
+       icd.nr = ret;
+       ios_completed(td, &icd);
+       if (icd.error) {
+               td_verror(td, icd.error);
+               return 1;
+       }
 
        return 0;
 }
 
        return 0;
 }