diff --git a/block/qcow2-dirty-bitmap.c b/block/qcow2-dirty-bitmap.c
index 5e38a3d..979e17e 100644
--- a/block/qcow2-dirty-bitmap.c
+++ b/block/qcow2-dirty-bitmap.c
@@ -74,6 +74,15 @@ static void bitmap_header_to_cpu(QCow2BitmapHeader *h)
     be32_to_cpus(&h->extra_data_size);
 }
 
+static void bitmap_header_to_be(QCow2BitmapHeader *h)
+{
+    cpu_to_be64s(&h->bitmap_table_offset);
+    cpu_to_be32s(&h->bitmap_table_size);
+    cpu_to_be32s(&h->flags);
+    cpu_to_be16s(&h->name_size);
+    cpu_to_be32s(&h->extra_data_size);
+}
+
 static int calc_dir_entry_size(size_t name_size)
 {
     return align_offset(sizeof(QCow2BitmapHeader) + name_size, 8);
@@ -84,6 +93,17 @@ static int dir_entry_size(QCow2BitmapHeader *h)
     return calc_dir_entry_size(h->name_size);
 }
 
+static void directory_to_be(uint8_t *dir, size_t size)
+{
+    uint8_t *end = dir + size;
+    while (dir < end) {
+        QCow2BitmapHeader *h = (QCow2BitmapHeader *)dir;
+        dir += dir_entry_size(h);
+
+        bitmap_header_to_be(h);
+    }
+}
+
 static int check_constraints(QCow2BitmapHeader *h, int cluster_size,
                              uint64_t disk_size)
 {
@@ -318,3 +338,426 @@ BdrvDirtyBitmap *qcow2_bitmap_load(BlockDriverState *bs, const char *name,
 
     return load_bitmap(bs, bm, errp);
 }
+
+static int update_header_sync(BlockDriverState *bs)
+{
+    int ret;
+
+    ret = qcow2_update_header(bs);
+    if (ret < 0) {
+        return ret;
+    }
+
+    ret = bdrv_flush(bs);
+    if (ret < 0) {
+        return ret;
+    }
+
+    return 0;
+}
+
+/* write bitmap directory from the state to new allocated clusters */
+static int64_t directory_write(BlockDriverState *bs, const uint8_t *dir,
+                               size_t size)
+{
+    int ret = 0;
+    uint8_t *dir_be = NULL;
+    int64_t dir_offset = 0;
+
+    dir_be = g_try_malloc(size);
+    if (dir_be == NULL) {
+        return -ENOMEM;
+    }
+    memcpy(dir_be, dir, size);
+    directory_to_be(dir_be, size);
+
+    /* Allocate space for the new bitmap directory */
+    dir_offset = qcow2_alloc_clusters(bs, size);
+    if (dir_offset < 0) {
+        ret = dir_offset;
+        goto out;
+    }
+
+    /* The bitmap directory position has not yet been updated, so these
+     * clusters must indeed be completely free */
+    ret = qcow2_pre_write_overlap_check(bs, 0, dir_offset, size);
+    if (ret < 0) {
+        goto out;
+    }
+
+    ret = bdrv_pwrite(bs->file->bs, dir_offset, dir_be, size);
+    if (ret < 0) {
+        goto out;
+    }
+
+out:
+    g_free(dir_be);
+
+    if (ret < 0) {
+        if (dir_offset > 0) {
+            qcow2_free_clusters(bs, dir_offset, size, QCOW2_DISCARD_ALWAYS);
+        }
+
+        return ret;
+    }
+
+    return dir_offset;
+}
+
+static int directory_push_entry(BlockDriverState *bs, QCow2BitmapHeader *header)
+{
+    BDRVQcow2State *s = bs->opaque;
+    int ret;
+    int entry_size = dir_entry_size(header);
+    int64_t new_offset = 0, old_offset = 0;
+    uint64_t new_size = s->bitmap_directory_size + entry_size, old_size = 0;
+    void *p;
+    int64_t nb_sectors = bdrv_nb_sectors(bs);
+
+    if (nb_sectors < 0) {
+        return nb_sectors;
+    }
+
+    if (new_size > QCOW_MAX_DIRTY_BITMAP_DIRECTORY_SIZE) {
+        return -EINVAL;
+    }
+
+    ret = check_constraints(header, s->cluster_size,
+                            nb_sectors << BDRV_SECTOR_BITS);
+    if (ret < 0) {
+        return -EINVAL;
+    }
+
+    old_offset = s->bitmap_directory_offset;
+    old_size = s->bitmap_directory_size;
+
+    uint8_t *new_dir = g_try_malloc(new_size);
+    if (new_dir == NULL) {
+        return -ENOMEM;
+    }
+    memcpy(new_dir, s->bitmap_directory, s->bitmap_directory_size);
+    memcpy(new_dir + s->bitmap_directory_size, header, entry_size);
+
+    new_offset = directory_write(bs, new_dir, new_size);
+    if (new_offset < 0) {
+        ret = new_offset;
+        goto fail;
+    }
+
+    ret = bdrv_flush(bs);
+    if (ret < 0) {
+        goto fail;
+    }
+
+    s->bitmap_directory_offset = new_offset;
+    s->bitmap_directory_size = new_size;
+
+    ret = update_header_sync(bs);
+    if (ret < 0) {
+        goto fail;
+    }
+
+    if (old_size) {
+        qcow2_free_clusters(bs, old_offset, old_size, QCOW2_DISCARD_ALWAYS);
+    }
+
+    g_free(s->bitmap_directory);
+    s->bitmap_directory = new_dir;
+
+    return 0;
+
+fail:
+    g_free(new_dir);
+    if (new_offset > 0) {
+        qcow2_free_clusters(bs, new_offset, new_size, QCOW2_DISCARD_ALWAYS);
+        s->bitmap_directory_offset = old_offset;
+        s->bitmap_directory_size = old_size;
+    }
+
+    p = g_try_realloc(s->bitmap_directory, s->bitmap_directory_size);
+    if (p != NULL) {
+        s->bitmap_directory = p;
+    }
+
+    return ret;
+}
+
+/* store_bitmap()
+ * update bitmap table by storing bitmap to it.
+ * Bitmap table entries are assumed to be in big endian format
+ * On the error, the resulting bitmap table is valid for clearing, but
+ * may contain invalid bitmap */
+static int store_bitmap(BlockDriverState *bs, uint64_t *bitmap_table,
+                        uint32_t bitmap_table_size,
+                        const BdrvDirtyBitmap *bitmap)
+{
+    int ret;
+    BDRVQcow2State *s = bs->opaque;
+    int cl_size = s->cluster_size;
+    uint32_t i, tab_size = 0;
+    uint64_t *tab;
+    uint32_t j;
+
+    bdrv_dirty_bitmap_prepare_store(bitmap, s->cluster_size, NULL, &tab_size);
+    tab = g_try_malloc(tab_size * sizeof(tab[0]));
+    if (tab == NULL) {
+        return -ENOMEM;
+    }
+
+    ret = bdrv_dirty_bitmap_prepare_store(bitmap, s->cluster_size,
+                                          tab, &tab_size);
+    if (ret != 0) {
+        g_free(tab);
+        return ret;
+    }
+
+    for (i = 0, j = 0; i < bitmap_table_size; ++i) {
+        if (tab[i] <= 1) {
+            continue;
+        }
+
+        for ( ; j < bitmap_table_size && bitmap_table[j] <= 1; ++j) {
+            ;
+        }
+
+        if (j < bitmap_table_size) {
+            tab[i] = be64_to_cpu(bitmap_table[j]);
+        } else {
+            tab[i] = qcow2_alloc_clusters(bs, cl_size);
+        }
+    }
+
+    for ( ; j < bitmap_table_size; ++j) {
+        uint64_t addr = be64_to_cpu(bitmap_table[j]);
+        if (addr <= 1) {
+            continue;
+        }
+
+        qcow2_free_clusters(bs, addr, cl_size, QCOW2_DISCARD_ALWAYS);
+        bitmap_table[j] = 0;
+    }
+
+    bdrv_dirty_bitmap_store(bitmap, bs->file->bs, tab,
+                            bitmap_table_size, s->cluster_size);
+
+    for (i = 0; i < bitmap_table_size; ++i) {
+        bitmap_table[i] = cpu_to_be64(tab[i]);
+    }
+
+    g_free(tab);
+
+    return 0;
+}
+
+static int64_t alloc_zeroed_clusters(BlockDriverState *bs, uint64_t size)
+{
+    int ret = 0;
+    void *buf = NULL;
+    int64_t offset = qcow2_alloc_clusters(bs, size);
+    if (offset < 0) {
+        return offset;
+    }
+
+    buf = g_try_malloc0(size);
+    if (buf == NULL) {
+        ret = -ENOMEM;
+        goto out;
+    }
+
+    ret = qcow2_pre_write_overlap_check(bs, 0, offset, size);
+    if (ret < 0) {
+        goto out;
+    }
+
+    ret = bdrv_pwrite(bs->file->bs, offset, buf, size);
+    if (ret < 0) {
+        goto out;
+    }
+
+    ret = bdrv_flush(bs);
+    if (ret < 0) {
+        goto out;
+    }
+
+out:
+    g_free(buf);
+
+    if (ret < 0) {
+        qcow2_free_clusters(bs, offset, size, QCOW2_DISCARD_ALWAYS);
+        return ret;
+    }
+
+    return offset;
+}
+
+static int directory_push(BlockDriverState *bs, const char *name,
+                          uint64_t size, int granularity)
+{
+    int ret;
+    BDRVQcow2State *s = bs->opaque;
+    int sector_granularity = granularity >> BDRV_SECTOR_BITS;
+    size_t name_size = strlen(name);
+    size_t entry_size = calc_dir_entry_size(name_size);
+    QCow2BitmapHeader *entry = g_malloc0(entry_size);
+    int64_t table_offset = 0;
+
+    entry->granularity_bits = ctz32(granularity);
+    entry->type = BT_DIRTY_TRACKING_BITMAP;
+    entry->name_size = name_size;
+    memcpy(entry + 1, name, name_size);
+
+    entry->bitmap_table_size =
+            size_to_clusters(s, (((size - 1) / sector_granularity) >> 3) + 1);
+    table_offset = alloc_zeroed_clusters(bs, entry->bitmap_table_size *
+                                  sizeof(uint64_t));
+    if (table_offset < 0) {
+        ret = table_offset;
+        goto out;
+    }
+    entry->bitmap_table_offset = table_offset;
+
+    ret = directory_push_entry(bs, entry);
+    if (ret < 0) {
+        goto out;
+    }
+
+out:
+    g_free(entry);
+    if (ret < 0 && table_offset > 0) {
+        qcow2_free_clusters(bs, table_offset, entry->bitmap_table_size *
+                            sizeof(uint64_t), QCOW2_DISCARD_ALWAYS);
+    }
+
+    return ret;
+}
+
+static int bitmaps_push(BDRVQcow2State *s, const char *name, uint64_t offset)
+{
+    QCow2Bitmap *bm;
+    QCow2Bitmap *p;
+
+    printf("dirty bitmaps push\n");
+    p = g_try_renew(QCow2Bitmap, s->bitmaps, s->nb_bitmaps + 1);
+    if (p == NULL) {
+        return -ENOMEM;
+    }
+    s->bitmaps = p;
+    s->nb_bitmaps++;
+
+    bm = s->bitmaps + s->nb_bitmaps - 1;
+    bm->name = g_strdup(name);
+    bm->offset = offset;
+
+
+    return 0;
+}
+
+static void bitmaps_pop(BDRVQcow2State *s)
+{
+    QCow2Bitmap *p;
+
+    if (s->nb_bitmaps == 0) {
+        return;
+    }
+
+    p = g_try_renew(QCow2Bitmap, s->bitmaps, s->nb_bitmaps - 1);
+    if (p != NULL) {
+        s->bitmaps = p;
+    }
+
+    s->nb_bitmaps--;
+}
+
+/* if no id is provided, a new one is constructed */
+static int qcow2_bitmap_create(BlockDriverState *bs, const char *name,
+                               uint64_t size, int granularity)
+{
+    int ret;
+    BDRVQcow2State *s = bs->opaque;
+
+    if (s->nb_bitmaps >= QCOW_MAX_DIRTY_BITMAPS) {
+        return -EFBIG;
+    }
+
+    /* Check that the name is unique */
+    if (find_bitmap_by_name(bs, name) != NULL) {
+        return -EEXIST;
+    }
+
+    ret = bitmaps_push(s, name, s->bitmap_directory_size);
+    if (ret < 0) {
+        return ret;
+    }
+
+    ret = directory_push(bs, name, size, granularity);
+    if (ret < 0) {
+        bitmaps_pop(s);
+        return ret;
+    }
+
+    return 0;
+}
+
+void qcow2_bitmap_store(BlockDriverState *bs,
+                        const BdrvDirtyBitmap *bitmap, Error **errp)
+{
+    BDRVQcow2State *s = bs->opaque;
+    int ret = 0;
+    uint64_t *bitmap_table;
+    QCow2Bitmap *bm;
+    QCow2BitmapHeader *bmh;
+    const char *name = bdrv_dirty_bitmap_name(bitmap);
+    uint64_t size = bdrv_dirty_bitmap_size(bitmap);
+    int granularity = bdrv_dirty_bitmap_granularity(bitmap);
+
+    /* find/create dirty bitmap */
+    bm = find_bitmap_by_name(bs, name);
+    if (bm == NULL) {
+        ret = qcow2_bitmap_create(bs, name, size, granularity);
+        if (ret < 0) {
+            error_setg_errno(errp, ret, "Can't create dirty bitmap in qcow2.");
+        }
+        bm = s->bitmaps + s->nb_bitmaps - 1;
+        bmh = bitmap_header(s, bm);
+    } else {
+        bmh = bitmap_header(s, bm);
+
+        if (granularity != (1U << bmh->granularity_bits)) {
+            error_setg(errp,
+                       "The bitmap with same name (but other granularity) "
+                       "already exists.");
+            return;
+        }
+    }
+
+    bitmap_table = g_try_new(uint64_t, bmh->bitmap_table_size);
+    if (bitmap_table == NULL) {
+        error_setg(errp, "No memory.");
+        return;
+    }
+    ret = bdrv_pread(bs->file->bs, bmh->bitmap_table_offset,
+                     bitmap_table,
+                     bmh->bitmap_table_size * sizeof(uint64_t));
+    if (ret < 0) {
+        error_setg_errno(errp, ret, "Can't read dirty bitmap table.");
+        goto finish;
+    }
+
+    ret = store_bitmap(bs, bitmap_table, bmh->bitmap_table_size,
+                       bitmap);
+    if (ret < 0) {
+        error_setg_errno(errp, ret, "Can't store bitmap table.");
+        goto finish;
+    }
+
+    ret = bdrv_pwrite(bs->file->bs, bmh->bitmap_table_offset,
+                      bitmap_table,
+                      bmh->bitmap_table_size * sizeof(uint64_t));
+    if (ret < 0) {
+        error_setg_errno(errp, ret, "Can't write dirty bitmap table.");
+        goto finish;
+    }
+
+finish:
+    g_free(bitmap_table);
+}
diff --git a/block/qcow2.c b/block/qcow2.c
index fe53338..75ec4c0 100644
--- a/block/qcow2.c
+++ b/block/qcow2.c
@@ -3347,6 +3347,7 @@ BlockDriver bdrv_qcow2 = {
     .bdrv_get_specific_info = qcow2_get_specific_info,
 
     .bdrv_dirty_bitmap_load = qcow2_bitmap_load,
+    .bdrv_dirty_bitmap_store = qcow2_bitmap_store,
 
     .bdrv_save_vmstate    = qcow2_save_vmstate,
     .bdrv_load_vmstate    = qcow2_load_vmstate,
diff --git a/block/qcow2.h b/block/qcow2.h
index cc4c776..e4a517c 100644
--- a/block/qcow2.h
+++ b/block/qcow2.h
@@ -622,6 +622,8 @@ int qcow2_read_bitmaps(BlockDriverState *bs, Error **errp);
 
 BdrvDirtyBitmap *qcow2_bitmap_load(BlockDriverState *bs, const char *name,
                                    Error **errp);
+void qcow2_bitmap_store(BlockDriverState *bs, const BdrvDirtyBitmap *bitmap,
+                        Error **errp);
 
 /* qcow2-cache.c functions */
 Qcow2Cache *qcow2_cache_create(BlockDriverState *bs, int num_tables);
diff --git a/include/block/block_int.h b/include/block/block_int.h
index 7c0290e..be8f8a3 100644
--- a/include/block/block_int.h
+++ b/include/block/block_int.h
@@ -217,6 +217,9 @@ struct BlockDriver {
     BdrvDirtyBitmap *(*bdrv_dirty_bitmap_load)(BlockDriverState *bs,
                                                const char *name,
                                                Error **errp);
+    void (*bdrv_dirty_bitmap_store)(BlockDriverState *bs,
+                                    const BdrvDirtyBitmap *bitmap,
+                                    Error **errp);
 
     int (*bdrv_save_vmstate)(BlockDriverState *bs, QEMUIOVector *qiov,
                              int64_t pos);
