~cdv/snapmgr

55d965c2b1bd63023096bc3c2fce82fadadee7fd — Chris Vittal 4 years ago 72de655 0.0.3
Add prune functionality.

Prune takes two arguments:
    1: <volume>, the path to the parent volume
    2: <path>, the path to the directory containing snapshots

Snapshots are pruned if they are older than 30 days.
2 files changed, 132 insertions(+), 13 deletions(-)

M meson.build
M snapmgr.c
M meson.build => meson.build +2 -1
@@ 25,9 25,10 @@ add_project_arguments(cc.get_supported_arguments([
	'-Wno-unused-parameter',
]), language: 'c')

uuid = dependency('uuid')
btrfs = cc.find_library('btrfsutil')

exe = executable('snapmgr',
	'snapmgr.c',
	dependencies : btrfs,
	dependencies : [btrfs, uuid],
	install : true)

M snapmgr.c => snapmgr.c +130 -12
@@ 1,5 1,8 @@
#include <btrfsutil.h>
#include <dirent.h>
#include <fcntl.h>
#include <libgen.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


@@ 8,8 11,7 @@
#include <syslog.h>
#include <time.h>
#include <unistd.h>

#include <btrfsutil.h>
#include <uuid/uuid.h>

static void
assert_cap(void)


@@ 44,11 46,12 @@ take(const char *vol, const char *path)
	err = btrfs_util_create_snapshot_fd2(vfd, dfd, name, 0, NULL, NULL);
	int ret;
	if (err) {
		syslog(LOG_ERR, "snapmgr: could not create snapshot %s - %s\n",
			name, btrfs_util_strerror(err));
		syslog(LOG_ERR, "could not create snapshot of '%s' at '%s': %s\n",
			vol, name, btrfs_util_strerror(err));
		ret = 1;
	} else {
		syslog(LOG_INFO, "created snapshot %s/%s\n", path, name);
		syslog(LOG_INFO, "created snapshot of %s at %s/%s\n",
			vol, path, name);
		ret = 0;
	}



@@ 58,10 61,121 @@ take(const char *vol, const char *path)
}

static int
prune(const char *path)
maybe_prune_subvolume(int dfd, char *fullpath, struct dirent *dirent,
		time_t cur_time, struct btrfs_util_subvolume_info *subvol,
		const char *subvol_path)
{
	// time_t t = time(NULL);
	return 0;
	enum btrfs_util_error err;
	struct btrfs_util_subvolume_info info;
	int efd = openat(dfd, dirent->d_name, O_DIRECTORY);
	if (efd < 0) {
		return 0;
	}

	err = btrfs_util_subvolume_info_fd(efd, 0, &info);
	close(efd);

	if (err || uuid_compare(subvol->uuid, info.parent_uuid)) {
		return 0;
	}

	time_t otime = info.otime.tv_sec;
	double since = difftime(cur_time, otime);
	static const double ONE_MONTH = 30 * 24 * 60 * 60;
	if (since < ONE_MONTH) {
		return 0;
	}

	char *path;
	err = btrfs_util_subvolume_path_fd(dfd, info.id, &path);
	if (err) {
		syslog(LOG_WARNING, "cannot get subvol path: %s",
			btrfs_util_strerror(err));
		path = fullpath;
	}

	err = btrfs_util_delete_subvolume_fd(dfd, dirent->d_name, 0);
	int ret;
	if (err) {
		syslog(LOG_ERR, "cannot delete snapshot '%s': %s",
			path, btrfs_util_strerror(err));
		ret = 1;
	} else {
		syslog(LOG_INFO, "deleted snapshot '%s' of '%s'", path,
			subvol_path);
		ret = 0;
	}

	if (path != fullpath) {
		free(path);
	}
	return ret;
}

static int
prune(const char *vol, const char *path)
{
	time_t cur_time = time(NULL);

	char *fullpath = realpath(path, NULL);
	if (!fullpath) {
		syslog(LOG_ERR, "realpath('%s'): %m", path);
		return 1;
	}

	int vfd = open(vol, O_RDONLY | O_DIRECTORY);
	if (vfd < 0) {
		syslog(LOG_ERR, "open('%s'): %m", vol);
		free(fullpath);
		return 1;
	}

	enum btrfs_util_error err;
	struct btrfs_util_subvolume_info subvol;
	char *subvol_path = fullpath;

	err = btrfs_util_subvolume_info_fd(vfd, 0, &subvol);
	if (err) {
		syslog(LOG_ERR, "prune: can't get subvolume info: %s",
			btrfs_util_strerror(err));
		goto prune_err;
	}
	err = btrfs_util_subvolume_path_fd(vfd, subvol.id, &subvol_path);
	if (err) {
		syslog(LOG_WARNING, "prune: can't get subvolume path: %s",
			btrfs_util_strerror(err));
		subvol_path = fullpath;
	}

	DIR *dir = opendir(fullpath);
	if (!dir) {
		syslog(LOG_ERR, "prune: opendir('%s'): %m", fullpath);
		goto prune_err;
	}
	int dfd = dirfd(dir);

	int ret = 0;
	for (struct dirent *dirent = readdir(dir); dirent != NULL;
			dirent = readdir(dir)) {
		ret = ret ? ret
			: maybe_prune_subvolume(dfd, fullpath, dirent,
				cur_time, &subvol, subvol_path);
	}

	closedir(dir);
	close(vfd);
	if (fullpath != subvol_path) {
		free(subvol_path);
	}
	free(fullpath);
	return ret;
prune_err:
	close(vfd);
	if (fullpath != subvol_path) {
		free(subvol_path);
	}
	free(fullpath);
	return 1;
}

static void


@@ 75,15 189,16 @@ usage(const char *argv0)
		"\t\tprint this help\n"
		"\t%1$s take <volume> <path>\n"
		"\t\ttake a snapshot of <volume>, placing it in directory <path>\n"
		"\t%1$s prune <path>\n"
		"\t\tprunes old snapshots in directory <path>\n",
		"\t%1$s prune <volume> <path>\n"
		"\t\tprunes snapshots of <volume> present in directory <path>\n"
		"\t\tthat are older than 30 days\n",
		argv0);
}

int
main(int argc, char *argv[])
{
	openlog("snapmgr", LOG_NDELAY | LOG_PERROR, LOG_USER);
	openlog("snapmgr", LOG_PID | LOG_NDELAY | LOG_PERROR, LOG_USER);
	atexit(closelog);
	int ret = 1;
	if (argc == 1) {


@@ 97,8 212,11 @@ main(int argc, char *argv[])
		assert_cap();
		return take(argv[2], argv[3]);
	} else if (strcmp(argv[1], "prune") == 0) {
		if (argc != 4) {
			fprintf(stderr, "error: prune requires two arguments");
		}
		assert_cap();
		return prune(argv[2]);
		return prune(argv[2], argv[3]);
	} else if (strcmp(argv[1], "help") == 0) {
		ret = 0;
	} else {