| /* | 
 |  * Write logging blk driver based on blkverify and blkdebug. | 
 |  * | 
 |  * Copyright (c) 2017 Tuomas Tynkkynen <tuomas@tuxera.com> | 
 |  * Copyright (c) 2018 Aapo Vienamo <aapo@tuxera.com> | 
 |  * Copyright (c) 2018-2024 Ari Sundholm <ari@tuxera.com> | 
 |  * | 
 |  * This work is licensed under the terms of the GNU GPL, version 2 or later. | 
 |  * See the COPYING file in the top-level directory. | 
 |  */ | 
 |  | 
 | #include "qemu/osdep.h" | 
 | #include "qapi/error.h" | 
 | #include "qemu/sockets.h" /* for EINPROGRESS on Windows */ | 
 | #include "block/block-io.h" | 
 | #include "block/block_int.h" | 
 | #include "qobject/qdict.h" | 
 | #include "qobject/qstring.h" | 
 | #include "qemu/cutils.h" | 
 | #include "qemu/module.h" | 
 | #include "qemu/option.h" | 
 |  | 
 | /* Disk format stuff - taken from Linux drivers/md/dm-log-writes.c */ | 
 |  | 
 | #define LOG_FLUSH_FLAG   (1 << 0) | 
 | #define LOG_FUA_FLAG     (1 << 1) | 
 | #define LOG_DISCARD_FLAG (1 << 2) | 
 | #define LOG_MARK_FLAG    (1 << 3) | 
 | #define LOG_FLAG_MASK    (LOG_FLUSH_FLAG \ | 
 |                          | LOG_FUA_FLAG \ | 
 |                          | LOG_DISCARD_FLAG \ | 
 |                          | LOG_MARK_FLAG) | 
 |  | 
 | #define WRITE_LOG_VERSION 1ULL | 
 | #define WRITE_LOG_MAGIC 0x6a736677736872ULL | 
 |  | 
 | /* All fields are little-endian. */ | 
 | struct log_write_super { | 
 |     uint64_t magic; | 
 |     uint64_t version; | 
 |     uint64_t nr_entries; | 
 |     uint32_t sectorsize; | 
 | } QEMU_PACKED; | 
 |  | 
 | struct log_write_entry { | 
 |     uint64_t sector; | 
 |     uint64_t nr_sectors; | 
 |     uint64_t flags; | 
 |     uint64_t data_len; | 
 | } QEMU_PACKED; | 
 |  | 
 | /* End of disk format structures. */ | 
 |  | 
 | typedef struct { | 
 |     BdrvChild *log_file; | 
 |     uint32_t sectorsize; | 
 |     uint32_t sectorbits; | 
 |     uint64_t update_interval; | 
 |  | 
 |     /* | 
 |      * The mutable state of the driver, consisting of the current log sector | 
 |      * and the number of log entries. | 
 |      * | 
 |      * May be read and/or written from multiple threads, and the mutex must be | 
 |      * held when accessing these fields. | 
 |      */ | 
 |     uint64_t cur_log_sector; | 
 |     uint64_t nr_entries; | 
 |     QemuMutex mutex; | 
 |  | 
 |     /* | 
 |      * The super block sequence number. Non-zero if a super block update is in | 
 |      * progress. | 
 |      * | 
 |      * The mutex must be held when accessing this field. | 
 |      */ | 
 |     uint64_t super_update_seq; | 
 |  | 
 |     /* | 
 |      * A coroutine-aware queue to serialize super block updates. | 
 |      * | 
 |      * Used with the mutex to ensure that only one thread be updating the super | 
 |      * block at a time. | 
 |      */ | 
 |     CoQueue super_update_queue; | 
 | } BDRVBlkLogWritesState; | 
 |  | 
 | static QemuOptsList runtime_opts = { | 
 |     .name = "blklogwrites", | 
 |     .head = QTAILQ_HEAD_INITIALIZER(runtime_opts.head), | 
 |     .desc = { | 
 |         { | 
 |             .name = "log-append", | 
 |             .type = QEMU_OPT_BOOL, | 
 |             .help = "Append to an existing log", | 
 |         }, | 
 |         { | 
 |             .name = "log-sector-size", | 
 |             .type = QEMU_OPT_SIZE, | 
 |             .help = "Log sector size", | 
 |         }, | 
 |         { | 
 |             .name = "log-super-update-interval", | 
 |             .type = QEMU_OPT_NUMBER, | 
 |             .help = "Log superblock update interval (# of write requests)", | 
 |         }, | 
 |         { /* end of list */ } | 
 |     }, | 
 | }; | 
 |  | 
 | static inline uint32_t blk_log_writes_log2(uint32_t value) | 
 | { | 
 |     assert(value > 0); | 
 |     return 31 - clz32(value); | 
 | } | 
 |  | 
 | static inline bool blk_log_writes_sector_size_valid(uint32_t sector_size) | 
 | { | 
 |     return is_power_of_2(sector_size) && | 
 |         sector_size >= sizeof(struct log_write_super) && | 
 |         sector_size >= sizeof(struct log_write_entry) && | 
 |         sector_size < (1ull << 24); | 
 | } | 
 |  | 
 | static uint64_t blk_log_writes_find_cur_log_sector(BdrvChild *log, | 
 |                                                    uint32_t sector_size, | 
 |                                                    uint64_t nr_entries, | 
 |                                                    Error **errp) | 
 | { | 
 |     uint64_t cur_sector = 1; | 
 |     uint64_t cur_idx = 0; | 
 |     uint32_t sector_bits = blk_log_writes_log2(sector_size); | 
 |     struct log_write_entry cur_entry; | 
 |  | 
 |     while (cur_idx < nr_entries) { | 
 |         int read_ret = bdrv_pread(log, cur_sector << sector_bits, | 
 |                                   sizeof(cur_entry), &cur_entry, 0); | 
 |         if (read_ret < 0) { | 
 |             error_setg_errno(errp, -read_ret, | 
 |                              "Failed to read log entry %"PRIu64, cur_idx); | 
 |             return (uint64_t)-1ull; | 
 |         } | 
 |  | 
 |         if (cur_entry.flags & ~cpu_to_le64(LOG_FLAG_MASK)) { | 
 |             error_setg(errp, "Invalid flags 0x%"PRIx64" in log entry %"PRIu64, | 
 |                        le64_to_cpu(cur_entry.flags), cur_idx); | 
 |             return (uint64_t)-1ull; | 
 |         } | 
 |  | 
 |         /* Account for the sector of the entry itself */ | 
 |         ++cur_sector; | 
 |  | 
 |         /* | 
 |          * Account for the data of the write. | 
 |          * For discards, this data is not present. | 
 |          */ | 
 |         if (!(cur_entry.flags & cpu_to_le64(LOG_DISCARD_FLAG))) { | 
 |             cur_sector += le64_to_cpu(cur_entry.nr_sectors); | 
 |         } | 
 |  | 
 |         ++cur_idx; | 
 |     } | 
 |  | 
 |     return cur_sector; | 
 | } | 
 |  | 
 | static int blk_log_writes_open(BlockDriverState *bs, QDict *options, int flags, | 
 |                                Error **errp) | 
 | { | 
 |     BDRVBlkLogWritesState *s = bs->opaque; | 
 |     QemuOpts *opts; | 
 |     Error *local_err = NULL; | 
 |     int ret; | 
 |     uint64_t log_sector_size; | 
 |     bool log_append; | 
 |  | 
 |     opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort); | 
 |     if (!qemu_opts_absorb_qdict(opts, options, errp)) { | 
 |         ret = -EINVAL; | 
 |         goto fail; | 
 |     } | 
 |  | 
 |     /* Open the file */ | 
 |     ret = bdrv_open_file_child(NULL, options, "file", bs, errp); | 
 |     if (ret < 0) { | 
 |         goto fail; | 
 |     } | 
 |  | 
 |     /* Open the log file */ | 
 |     s->log_file = bdrv_open_child(NULL, options, "log", bs, &child_of_bds, | 
 |                                   BDRV_CHILD_METADATA, false, errp); | 
 |     if (!s->log_file) { | 
 |         ret = -EINVAL; | 
 |         goto fail; | 
 |     } | 
 |  | 
 |     qemu_mutex_init(&s->mutex); | 
 |     qemu_co_queue_init(&s->super_update_queue); | 
 |  | 
 |     log_append = qemu_opt_get_bool(opts, "log-append", false); | 
 |  | 
 |     if (log_append) { | 
 |         struct log_write_super log_sb = { 0, 0, 0, 0 }; | 
 |  | 
 |         if (qemu_opt_find(opts, "log-sector-size")) { | 
 |             ret = -EINVAL; | 
 |             error_setg(errp, "log-append and log-sector-size are mutually " | 
 |                        "exclusive"); | 
 |             goto fail_log; | 
 |         } | 
 |  | 
 |         /* Read log superblock or fake one for an empty log */ | 
 |         if (!bdrv_getlength(s->log_file->bs)) { | 
 |             log_sb.magic      = cpu_to_le64(WRITE_LOG_MAGIC); | 
 |             log_sb.version    = cpu_to_le64(WRITE_LOG_VERSION); | 
 |             log_sb.nr_entries = cpu_to_le64(0); | 
 |             log_sb.sectorsize = cpu_to_le32(BDRV_SECTOR_SIZE); | 
 |         } else { | 
 |             ret = bdrv_pread(s->log_file, 0, sizeof(log_sb), &log_sb, 0); | 
 |             if (ret < 0) { | 
 |                 error_setg_errno(errp, -ret, "Could not read log superblock"); | 
 |                 goto fail_log; | 
 |             } | 
 |         } | 
 |  | 
 |         if (log_sb.magic != cpu_to_le64(WRITE_LOG_MAGIC)) { | 
 |             ret = -EINVAL; | 
 |             error_setg(errp, "Invalid log superblock magic"); | 
 |             goto fail_log; | 
 |         } | 
 |  | 
 |         if (log_sb.version != cpu_to_le64(WRITE_LOG_VERSION)) { | 
 |             ret = -EINVAL; | 
 |             error_setg(errp, "Unsupported log version %"PRIu64, | 
 |                        le64_to_cpu(log_sb.version)); | 
 |             goto fail_log; | 
 |         } | 
 |  | 
 |         log_sector_size = le32_to_cpu(log_sb.sectorsize); | 
 |         s->cur_log_sector = 1; | 
 |         s->nr_entries = 0; | 
 |  | 
 |         if (blk_log_writes_sector_size_valid(log_sector_size)) { | 
 |             s->cur_log_sector = | 
 |                 blk_log_writes_find_cur_log_sector(s->log_file, log_sector_size, | 
 |                                     le64_to_cpu(log_sb.nr_entries), &local_err); | 
 |             if (local_err) { | 
 |                 ret = -EINVAL; | 
 |                 error_propagate(errp, local_err); | 
 |                 goto fail_log; | 
 |             } | 
 |  | 
 |             s->nr_entries = le64_to_cpu(log_sb.nr_entries); | 
 |         } | 
 |     } else { | 
 |         log_sector_size = qemu_opt_get_size(opts, "log-sector-size", | 
 |                                             BDRV_SECTOR_SIZE); | 
 |         s->cur_log_sector = 1; | 
 |         s->nr_entries = 0; | 
 |     } | 
 |  | 
 |     s->super_update_seq = 0; | 
 |  | 
 |     if (!blk_log_writes_sector_size_valid(log_sector_size)) { | 
 |         ret = -EINVAL; | 
 |         error_setg(errp, "Invalid log sector size %"PRIu64, log_sector_size); | 
 |         goto fail_log; | 
 |     } | 
 |  | 
 |     s->sectorsize = log_sector_size; | 
 |     s->sectorbits = blk_log_writes_log2(log_sector_size); | 
 |     s->update_interval = qemu_opt_get_number(opts, "log-super-update-interval", | 
 |                                              4096); | 
 |     if (!s->update_interval) { | 
 |         ret = -EINVAL; | 
 |         error_setg(errp, "Invalid log superblock update interval %"PRIu64, | 
 |                    s->update_interval); | 
 |         goto fail_log; | 
 |     } | 
 |  | 
 |     ret = 0; | 
 | fail_log: | 
 |     if (ret < 0) { | 
 |         bdrv_graph_wrlock_drained(); | 
 |         bdrv_unref_child(bs, s->log_file); | 
 |         bdrv_graph_wrunlock(); | 
 |         s->log_file = NULL; | 
 |         qemu_mutex_destroy(&s->mutex); | 
 |     } | 
 | fail: | 
 |     qemu_opts_del(opts); | 
 |     return ret; | 
 | } | 
 |  | 
 | static void blk_log_writes_close(BlockDriverState *bs) | 
 | { | 
 |     BDRVBlkLogWritesState *s = bs->opaque; | 
 |  | 
 |     bdrv_graph_wrlock_drained(); | 
 |     bdrv_unref_child(bs, s->log_file); | 
 |     s->log_file = NULL; | 
 |     bdrv_graph_wrunlock(); | 
 |     qemu_mutex_destroy(&s->mutex); | 
 | } | 
 |  | 
 | static int64_t coroutine_fn GRAPH_RDLOCK | 
 | blk_log_writes_co_getlength(BlockDriverState *bs) | 
 | { | 
 |     return bdrv_co_getlength(bs->file->bs); | 
 | } | 
 |  | 
 | static void blk_log_writes_child_perm(BlockDriverState *bs, BdrvChild *c, | 
 |                                       BdrvChildRole role, | 
 |                                       BlockReopenQueue *ro_q, | 
 |                                       uint64_t perm, uint64_t shrd, | 
 |                                       uint64_t *nperm, uint64_t *nshrd) | 
 | { | 
 |     if (!c) { | 
 |         *nperm = perm & DEFAULT_PERM_PASSTHROUGH; | 
 |         *nshrd = (shrd & DEFAULT_PERM_PASSTHROUGH) | DEFAULT_PERM_UNCHANGED; | 
 |         return; | 
 |     } | 
 |  | 
 |     bdrv_default_perms(bs, c, role, ro_q, perm, shrd, | 
 |                        nperm, nshrd); | 
 | } | 
 |  | 
 | static void blk_log_writes_refresh_limits(BlockDriverState *bs, Error **errp) | 
 | { | 
 |     const BDRVBlkLogWritesState *s = bs->opaque; | 
 |     bs->bl.request_alignment = s->sectorsize; | 
 | } | 
 |  | 
 | static int coroutine_fn GRAPH_RDLOCK | 
 | blk_log_writes_co_preadv(BlockDriverState *bs, int64_t offset, int64_t bytes, | 
 |                          QEMUIOVector *qiov, BdrvRequestFlags flags) | 
 | { | 
 |     return bdrv_co_preadv(bs->file, offset, bytes, qiov, flags); | 
 | } | 
 |  | 
 | typedef struct BlkLogWritesFileReq { | 
 |     BlockDriverState *bs; | 
 |     uint64_t offset; | 
 |     uint64_t bytes; | 
 |     int file_flags; | 
 |     QEMUIOVector *qiov; | 
 |     int GRAPH_RDLOCK_PTR (*func)(struct BlkLogWritesFileReq *r); | 
 |     int file_ret; | 
 | } BlkLogWritesFileReq; | 
 |  | 
 | typedef struct { | 
 |     BlockDriverState *bs; | 
 |     QEMUIOVector *qiov; | 
 |     struct log_write_entry entry; | 
 |     uint64_t zero_size; | 
 |     int log_ret; | 
 | } BlkLogWritesLogReq; | 
 |  | 
 | static void coroutine_fn GRAPH_RDLOCK | 
 | blk_log_writes_co_do_log(BlkLogWritesLogReq *lr) | 
 | { | 
 |     BDRVBlkLogWritesState *s = lr->bs->opaque; | 
 |  | 
 |     /* | 
 |      * Determine the offsets and sizes of different parts of the entry, and | 
 |      * update the state of the driver. | 
 |      * | 
 |      * This needs to be done in one go, before any actual I/O is done, as the | 
 |      * log entry may have to be written in two parts, and the state of the | 
 |      * driver may be modified by other driver operations while waiting for the | 
 |      * I/O to complete. | 
 |      */ | 
 |     qemu_mutex_lock(&s->mutex); | 
 |     const uint64_t entry_start_sector = s->cur_log_sector; | 
 |     const uint64_t entry_offset = entry_start_sector << s->sectorbits; | 
 |     const uint64_t qiov_aligned_size = ROUND_UP(lr->qiov->size, s->sectorsize); | 
 |     const uint64_t entry_aligned_size = qiov_aligned_size + | 
 |         ROUND_UP(lr->zero_size, s->sectorsize); | 
 |     const uint64_t entry_nr_sectors = entry_aligned_size >> s->sectorbits; | 
 |     const uint64_t entry_seq = s->nr_entries + 1; | 
 |  | 
 |     s->nr_entries = entry_seq; | 
 |     s->cur_log_sector += entry_nr_sectors; | 
 |     qemu_mutex_unlock(&s->mutex); | 
 |  | 
 |     /* | 
 |      * Write the log entry. Note that if this is a "write zeroes" operation, | 
 |      * only the entry header is written here, with the zeroing being done | 
 |      * separately below. | 
 |      */ | 
 |     lr->log_ret = bdrv_co_pwritev(s->log_file, entry_offset, lr->qiov->size, | 
 |                                   lr->qiov, 0); | 
 |  | 
 |     /* Logging for the "write zeroes" operation */ | 
 |     if (lr->log_ret == 0 && lr->zero_size) { | 
 |         const uint64_t zeroes_offset = entry_offset + qiov_aligned_size; | 
 |  | 
 |         lr->log_ret = bdrv_co_pwrite_zeroes(s->log_file, zeroes_offset, | 
 |                                             lr->zero_size, 0); | 
 |     } | 
 |  | 
 |     /* Update super block on flush or every update interval */ | 
 |     if (lr->log_ret == 0 && ((lr->entry.flags & LOG_FLUSH_FLAG) | 
 |         || (entry_seq % s->update_interval == 0))) | 
 |     { | 
 |         struct log_write_super super = { | 
 |             .magic      = cpu_to_le64(WRITE_LOG_MAGIC), | 
 |             .version    = cpu_to_le64(WRITE_LOG_VERSION), | 
 |             .nr_entries = 0, /* updated below */ | 
 |             .sectorsize = cpu_to_le32(s->sectorsize), | 
 |         }; | 
 |         void *zeroes; | 
 |         QEMUIOVector qiov; | 
 |  | 
 |         /* | 
 |          * Wait if a super block update is already in progress. | 
 |          * Bail out if a newer update got its turn before us. | 
 |          */ | 
 |         WITH_QEMU_LOCK_GUARD(&s->mutex) { | 
 |             CoQueueWaitFlags wait_flags = 0; | 
 |             while (s->super_update_seq) { | 
 |                 if (entry_seq < s->super_update_seq) { | 
 |                     return; | 
 |                 } | 
 |                 qemu_co_queue_wait_flags(&s->super_update_queue, | 
 |                     &s->mutex, wait_flags); | 
 |  | 
 |                 /* | 
 |                  * In case the wait condition remains true after wakeup, | 
 |                  * to avoid starvation, make sure that this request is | 
 |                  * scheduled to rerun next by pushing it to the front of the | 
 |                  * queue. | 
 |                  */ | 
 |                 wait_flags = CO_QUEUE_WAIT_FRONT; | 
 |             } | 
 |             s->super_update_seq = entry_seq; | 
 |             super.nr_entries = cpu_to_le64(s->nr_entries); | 
 |         } | 
 |  | 
 |         zeroes = g_malloc0(s->sectorsize - sizeof(super)); | 
 |  | 
 |         qemu_iovec_init(&qiov, 2); | 
 |         qemu_iovec_add(&qiov, &super, sizeof(super)); | 
 |         qemu_iovec_add(&qiov, zeroes, s->sectorsize - sizeof(super)); | 
 |  | 
 |         lr->log_ret = | 
 |             bdrv_co_pwritev(s->log_file, 0, s->sectorsize, &qiov, 0); | 
 |         if (lr->log_ret == 0) { | 
 |             lr->log_ret = bdrv_co_flush(s->log_file->bs); | 
 |         } | 
 |  | 
 |         /* The super block has been updated. Let another request have a go. */ | 
 |         qemu_mutex_lock(&s->mutex); | 
 |         s->super_update_seq = 0; | 
 |         (void) qemu_co_queue_next(&s->super_update_queue); | 
 |         qemu_mutex_unlock(&s->mutex); | 
 |  | 
 |         qemu_iovec_destroy(&qiov); | 
 |         g_free(zeroes); | 
 |     } | 
 | } | 
 |  | 
 | static void coroutine_fn GRAPH_RDLOCK | 
 | blk_log_writes_co_do_file(BlkLogWritesFileReq *fr) | 
 | { | 
 |     fr->file_ret = fr->func(fr); | 
 | } | 
 |  | 
 | static int coroutine_fn GRAPH_RDLOCK | 
 | blk_log_writes_co_log(BlockDriverState *bs, uint64_t offset, uint64_t bytes, | 
 |                       QEMUIOVector *qiov, int flags, | 
 |                       int /*GRAPH_RDLOCK*/ (*file_func)(BlkLogWritesFileReq *r), | 
 |                       uint64_t entry_flags, bool is_zero_write) | 
 | { | 
 |     QEMUIOVector log_qiov; | 
 |     size_t niov = qiov ? qiov->niov : 0; | 
 |     const BDRVBlkLogWritesState *s = bs->opaque; | 
 |     BlkLogWritesFileReq fr = { | 
 |         .bs         = bs, | 
 |         .offset     = offset, | 
 |         .bytes      = bytes, | 
 |         .file_flags = flags, | 
 |         .qiov       = qiov, | 
 |         .func       = file_func, | 
 |     }; | 
 |     BlkLogWritesLogReq lr = { | 
 |         .bs             = bs, | 
 |         .qiov           = &log_qiov, | 
 |         .entry = { | 
 |             .sector     = cpu_to_le64(offset >> s->sectorbits), | 
 |             .nr_sectors = cpu_to_le64(bytes >> s->sectorbits), | 
 |             .flags      = cpu_to_le64(entry_flags), | 
 |             .data_len   = 0, | 
 |         }, | 
 |         .zero_size = is_zero_write ? bytes : 0, | 
 |     }; | 
 |     void *zeroes = g_malloc0(s->sectorsize - sizeof(lr.entry)); | 
 |  | 
 |     assert((1 << s->sectorbits) == s->sectorsize); | 
 |     assert(bs->bl.request_alignment == s->sectorsize); | 
 |     assert(QEMU_IS_ALIGNED(offset, bs->bl.request_alignment)); | 
 |     assert(QEMU_IS_ALIGNED(bytes, bs->bl.request_alignment)); | 
 |  | 
 |     qemu_iovec_init(&log_qiov, niov + 2); | 
 |     qemu_iovec_add(&log_qiov, &lr.entry, sizeof(lr.entry)); | 
 |     qemu_iovec_add(&log_qiov, zeroes, s->sectorsize - sizeof(lr.entry)); | 
 |     if (qiov) { | 
 |         qemu_iovec_concat(&log_qiov, qiov, 0, qiov->size); | 
 |     } | 
 |  | 
 |     blk_log_writes_co_do_file(&fr); | 
 |     blk_log_writes_co_do_log(&lr); | 
 |  | 
 |     qemu_iovec_destroy(&log_qiov); | 
 |     g_free(zeroes); | 
 |  | 
 |     if (lr.log_ret < 0) { | 
 |         return lr.log_ret; | 
 |     } | 
 |  | 
 |     return fr.file_ret; | 
 | } | 
 |  | 
 | static int coroutine_fn GRAPH_RDLOCK | 
 | blk_log_writes_co_do_file_pwritev(BlkLogWritesFileReq *fr) | 
 | { | 
 |     return bdrv_co_pwritev(fr->bs->file, fr->offset, fr->bytes, | 
 |                            fr->qiov, fr->file_flags); | 
 | } | 
 |  | 
 | static int coroutine_fn GRAPH_RDLOCK | 
 | blk_log_writes_co_do_file_pwrite_zeroes(BlkLogWritesFileReq *fr) | 
 | { | 
 |     return bdrv_co_pwrite_zeroes(fr->bs->file, fr->offset, fr->bytes, | 
 |                                  fr->file_flags); | 
 | } | 
 |  | 
 | static int coroutine_fn GRAPH_RDLOCK | 
 | blk_log_writes_co_do_file_flush(BlkLogWritesFileReq *fr) | 
 | { | 
 |     return bdrv_co_flush(fr->bs->file->bs); | 
 | } | 
 |  | 
 | static int coroutine_fn GRAPH_RDLOCK | 
 | blk_log_writes_co_do_file_pdiscard(BlkLogWritesFileReq *fr) | 
 | { | 
 |     return bdrv_co_pdiscard(fr->bs->file, fr->offset, fr->bytes); | 
 | } | 
 |  | 
 | static int coroutine_fn GRAPH_RDLOCK | 
 | blk_log_writes_co_pwritev(BlockDriverState *bs, int64_t offset, int64_t bytes, | 
 |                           QEMUIOVector *qiov, BdrvRequestFlags flags) | 
 | { | 
 |     return blk_log_writes_co_log(bs, offset, bytes, qiov, flags, | 
 |                                  blk_log_writes_co_do_file_pwritev, 0, false); | 
 | } | 
 |  | 
 | static int coroutine_fn GRAPH_RDLOCK | 
 | blk_log_writes_co_pwrite_zeroes(BlockDriverState *bs, int64_t offset, | 
 |                                 int64_t bytes, BdrvRequestFlags flags) | 
 | { | 
 |     return blk_log_writes_co_log(bs, offset, bytes, NULL, flags, | 
 |                                  blk_log_writes_co_do_file_pwrite_zeroes, 0, | 
 |                                  true); | 
 | } | 
 |  | 
 | static int coroutine_fn GRAPH_RDLOCK | 
 | blk_log_writes_co_flush_to_disk(BlockDriverState *bs) | 
 | { | 
 |     return blk_log_writes_co_log(bs, 0, 0, NULL, 0, | 
 |                                  blk_log_writes_co_do_file_flush, | 
 |                                  LOG_FLUSH_FLAG, false); | 
 | } | 
 |  | 
 | static int coroutine_fn GRAPH_RDLOCK | 
 | blk_log_writes_co_pdiscard(BlockDriverState *bs, int64_t offset, int64_t bytes) | 
 | { | 
 |     return blk_log_writes_co_log(bs, offset, bytes, NULL, 0, | 
 |                                  blk_log_writes_co_do_file_pdiscard, | 
 |                                  LOG_DISCARD_FLAG, false); | 
 | } | 
 |  | 
 | static const char *const blk_log_writes_strong_runtime_opts[] = { | 
 |     "log-append", | 
 |     "log-sector-size", | 
 |  | 
 |     NULL | 
 | }; | 
 |  | 
 | static BlockDriver bdrv_blk_log_writes = { | 
 |     .format_name            = "blklogwrites", | 
 |     .instance_size          = sizeof(BDRVBlkLogWritesState), | 
 |  | 
 |     .bdrv_open              = blk_log_writes_open, | 
 |     .bdrv_close             = blk_log_writes_close, | 
 |     .bdrv_co_getlength      = blk_log_writes_co_getlength, | 
 |     .bdrv_child_perm        = blk_log_writes_child_perm, | 
 |     .bdrv_refresh_limits    = blk_log_writes_refresh_limits, | 
 |  | 
 |     .bdrv_co_preadv         = blk_log_writes_co_preadv, | 
 |     .bdrv_co_pwritev        = blk_log_writes_co_pwritev, | 
 |     .bdrv_co_pwrite_zeroes  = blk_log_writes_co_pwrite_zeroes, | 
 |     .bdrv_co_flush_to_disk  = blk_log_writes_co_flush_to_disk, | 
 |     .bdrv_co_pdiscard       = blk_log_writes_co_pdiscard, | 
 |  | 
 |     .is_filter              = true, | 
 |     .strong_runtime_opts    = blk_log_writes_strong_runtime_opts, | 
 | }; | 
 |  | 
 | static void bdrv_blk_log_writes_init(void) | 
 | { | 
 |     bdrv_register(&bdrv_blk_log_writes); | 
 | } | 
 |  | 
 | block_init(bdrv_blk_log_writes_init); |