Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Roll back an LVM2 logical volume to match an earlier snapshot
#define _GNU_SOURCE
#include <byteswap.h>
#include <endian.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define BLOCK_SIZE 512
#define SNAP_MAGIC 0x70416e53
#define SNAPSHOT_DISK_VERSION 1
struct disk_header {
uint32_t magic;
uint32_t valid;
uint32_t version;
uint32_t chunk_size;
};
struct disk_exception {
uint64_t old_chunk;
uint64_t new_chunk;
};
#if BYTE_ORDER == LITTLE_ENDIAN
#define le32_to_cpu(x) (x)
#define le64_to_cpu(x) (x)
#elif BYTE_ORDER == BIG_ENDIAN
#define le32_to_cpu(x) bswap_32(x)
#define le64_to_cpu(x) bswap_64(x)
#else
#error BYTE_ORDER not defined
#endif
void *pagealloc(size_t size) {
void *block;
if (posix_memalign(&block, sysconf(_SC_PAGESIZE), size) == 0)
return block;
perror("posix_memalign");
exit(EXIT_FAILURE);
}
void process(int origin, int snap, struct disk_header *header) {
int n, m;
size_t chunk_size = le32_to_cpu(header->chunk_size)*BLOCK_SIZE;
struct disk_exception *table = pagealloc(chunk_size);
void *data = pagealloc(chunk_size);
for (n = 0; pread(snap, table, chunk_size,
(n*(chunk_size/16 + 1) + 1)*chunk_size) == chunk_size;
n++) {
for (m = 0; 2*m < chunk_size/sizeof(uint64_t); m++) {
if (table[m].new_chunk == 0)
return;
/* TODO: coalesce these copy operations when they are adjacent */
if (pread(snap, data, chunk_size,
le64_to_cpu(table[m].new_chunk)*chunk_size) != chunk_size) {
perror("pread");
exit(EXIT_FAILURE);
}
if (pwrite(origin, data, chunk_size,
le64_to_cpu(table[m].old_chunk)*chunk_size) != chunk_size) {
perror("pwrite");
exit(EXIT_FAILURE);
}
}
}
perror("pread");
exit(EXIT_FAILURE);
}
void usage(char *name) {
fprintf(stderr, "Usage: %s ORIGIN-DEVICE COW-DEVICE\n", name);
exit(EXIT_FAILURE);
}
int main(int argc, char **argv) {
int origin, snap;
struct disk_header *header;
if (argc != 3)
usage(argv[0]);
snap = open(argv[2], O_RDONLY|O_DIRECT);
if (snap < 0) {
perror(argv[2]);
return EXIT_FAILURE;
}
header = pagealloc(BLOCK_SIZE);
if (pread(snap, header, BLOCK_SIZE, 0) != BLOCK_SIZE) {
perror("pread");
return EXIT_FAILURE;
}
if (le32_to_cpu(header->magic) != SNAP_MAGIC) {
fprintf(stderr, "%s is not a snapshot cow device\n", argv[2]);
return EXIT_FAILURE;
} else if (le32_to_cpu(header->version) != SNAPSHOT_DISK_VERSION) {
fprintf(stderr, "%s: unknown snapshot version %d\n", argv[2],
le32_to_cpu(header->version));
return EXIT_FAILURE;
} else if (!header->valid) {
fprintf(stderr, "%s: invalid snapshot\n", argv[2]);
return EXIT_FAILURE;
}
origin = open(argv[1], O_RDWR);
if (origin < 0) {
perror(argv[2]);
return EXIT_FAILURE;
}
process(origin, snap, header);
fsync(origin);
close(origin);
close(snap);
return EXIT_SUCCESS;
}
#!/bin/bash
shopt -s extglob
error() {
echo "$2" >&2
exit $1
}
[ $# -eq 1 ] || error 1 "Usage: lvrollback SNAPSHOT"
if ! read NAME VG ATTR ORIGIN \
< <( lvs --options=name,vg_name,attr,origin "$1" \
--noheadings --unbuffered 2>/dev/null ); then
error 1 "Logical volume $1 not found"
fi
case "$ATTR" in
*s*)
;;
*)
error 1 "Logical volume $1 is not a snapshot"
;;
esac
PREFIX="/dev/mapper/`echo "$VG" | sed 's/-/--/g'`-"
ORIGIN="$PREFIX`echo "$ORIGIN" | sed 's/-/--/g'`"
COW="$PREFIX`echo "$NAME" | sed 's/-/--/g'`-cow"
[ -e "$ORIGIN" ] || error 1 "$ORIGIN: origin mapper device not found"
[ -e "$COW" ] || error 1 "$COW: cow mapper device not found"
exec dmrollback "$ORIGIN" "$COW"
@arachsys
Copy link
Author

arachsys commented Jun 12, 2012

This code was written in June 2008, and will only work for the original device-mapper snapshot format. It has since been entirely superseded by the snapshot-merge target and lvconvert --merge. It is only posted for archival purposes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment