diff options
author | Rajesh Joseph <rjoseph@redhat.com> | 2016-07-13 18:59:18 +0000 |
---|---|---|
committer | Stefan Metzmacher <metze@samba.org> | 2016-07-28 10:06:13 +0200 |
commit | 71682e125c840225a25b21810119e20c96fd85d8 (patch) | |
tree | b1159e5313fe921d460b879881ab80e490bee080 /source3 | |
parent | c9b6e99ee17427138524b0ce676d720459ed23c3 (diff) | |
download | samba-71682e125c840225a25b21810119e20c96fd85d8.tar.gz |
shadow_copy2: allow configurable prefix for snapshot name
With growing number of snapshots file-systems need some mechanism
to differentiate one set of snapshots from other, e.g. monthly, weekly,
manual, special events, etc. Therefore these file-systems provide
different ways to tag snapshots, e.g. provide a configurable way to
name snapshots, which is not just based on time. With only shadow:format
it is very difficult to filter these snapshots.
As part of this change added two new options, shadow:snapprefix and
shadow:delimiter, in shadow_copy2 config. This option will accept regular
expression (BRE) as input. With this optional parameter, one can specify a
variable prefix component for names of the snapshot directories in the
file-system. If this parameter is set, together with the shadow:format and
shadow:delimiter parameters it determines the possible names of snapshot
directories in the file-system.
e.g.
shadow:snapprefix = [a-z]*[0-9]
When this option is provided then shadow:format option should always
start with <delimiter> string. This delimiter is configurable via a new option,
i.e. shadow:delimiter. Default value for this is "_GMT",
e.g. _GMT-%Y.%m.%d-%H.%M.%S
Signed-off-by: Rajesh Joseph <rjoseph@redhat.com>
Reviewed-by: Uri Simchoni <uri@samba.org>
Reviewed-by: Michael Adam <obnox@samba.org>
Diffstat (limited to 'source3')
-rw-r--r-- | source3/modules/vfs_shadow_copy2.c | 310 |
1 files changed, 303 insertions, 7 deletions
diff --git a/source3/modules/vfs_shadow_copy2.c b/source3/modules/vfs_shadow_copy2.c index 0185842ea24..123901cc91c 100644 --- a/source3/modules/vfs_shadow_copy2.c +++ b/source3/modules/vfs_shadow_copy2.c @@ -6,6 +6,7 @@ * Copyright (C) Volker Lendecke 2011 * Copyright (C) Christian Ambach 2011 * Copyright (C) Michael Adam 2013 + * Copyright (C) Rajesh Joseph 2016 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -40,6 +41,7 @@ struct shadow_copy2_config { bool use_sscanf; bool use_localtime; char *snapdir; + char *delimiter; bool snapdirseverywhere; bool crossmountpoints; bool fixinodes; @@ -50,15 +52,164 @@ struct shadow_copy2_config { char *snapshot_basepath; /* the absolute version of snapdir */ }; +/* Data-structure to hold the list of snap entries */ +struct shadow_copy2_snapentry { + char *snapname; + char *time_fmt; + struct shadow_copy2_snapentry *next; + struct shadow_copy2_snapentry *prev; +}; + +struct shadow_copy2_snaplist_info { + struct shadow_copy2_snapentry *snaplist; /* snapshot list */ + regex_t *regex; /* Regex to filter snaps */ + time_t fetch_time; /* snaplist update time */ +}; + /* * shadow_copy2 private structure. This structure will be * used to keep module specific information */ struct shadow_copy2_private { - struct shadow_copy2_config *config; + struct shadow_copy2_config *config; + struct shadow_copy2_snaplist_info *snaps; }; +static int shadow_copy2_get_shadow_copy_data( + vfs_handle_struct *handle, files_struct *fsp, + struct shadow_copy_data *shadow_copy2_data, + bool labels); + +/** + *This function will create a new snapshot list entry and + * return to the caller. This entry will also be added to + * the global snapshot list. + * + * @param[in] priv shadow_copy2 specific data structure + * @return Newly created snapshot entry or NULL on failure + */ +static struct shadow_copy2_snapentry *shadow_copy2_create_snapentry( + struct shadow_copy2_private *priv) +{ + struct shadow_copy2_snapentry *tmpentry = NULL; + + tmpentry = talloc_zero(priv->snaps, struct shadow_copy2_snapentry); + if (tmpentry == NULL) { + DBG_ERR("talloc_zero() failed\n"); + errno = ENOMEM; + return NULL; + } + + DLIST_ADD(priv->snaps->snaplist, tmpentry); + + return tmpentry; +} + +/** + *This function will delete the entire snaplist and reset + * priv->snaps->snaplist to NULL. + * + * @param[in] priv shadow_copye specific data structure + */ +static void shadow_copy2_delete_snaplist(struct shadow_copy2_private *priv) +{ + struct shadow_copy2_snapentry *tmp = NULL; + + while ((tmp = priv->snaps->snaplist) != NULL) { + DLIST_REMOVE(priv->snaps->snaplist, tmp); + talloc_free(tmp); + } +} + +/** + * Given a timestamp this function searches the global snapshot list + * and returns the complete snapshot directory name saved in the entry. + * + * @param[in] priv shadow_copy2 specific structure + * @param[in] timestamp timestamp corresponding to one of the snapshot + * @param[out] snap_str buffer to copy the actual snapshot name + * @param[in] len length of snap_str buffer + * + * @return Length of actual snapshot name, and -1 on failure + */ +static ssize_t shadow_copy2_saved_snapname(struct shadow_copy2_private *priv, + struct tm *timestamp, + char *snap_str, size_t len) +{ + ssize_t snaptime_len = -1; + struct shadow_copy2_snapentry *entry = NULL; + + snaptime_len = strftime(snap_str, len, GMT_FORMAT, timestamp); + if (snaptime_len == 0) { + DBG_ERR("strftime failed\n"); + return -1; + } + + snaptime_len = -1; + + for (entry = priv->snaps->snaplist; entry; entry = entry->next) { + if (strcmp(entry->time_fmt, snap_str) == 0) { + snaptime_len = snprintf(snap_str, len, "%s", + entry->snapname); + return snaptime_len; + } + } + + snap_str[0] = 0; + return snaptime_len; +} + + +/** + * This function will check if snaplist is updated or not. If snaplist + * is empty then it will create a new list. Each time snaplist is updated + * the time is recorded. If the snapshot time is greater than the snaplist + * update time then chances are we are working on an older list. Then discard + * the old list and fetch a new snaplist. + * + * @param[in] handle VFS handle struct + * @param[in] snap_time time of snapshot + * + * @return true if the list is updated else false + */ +static bool shadow_copy2_update_snaplist(struct vfs_handle_struct *handle, + time_t snap_time) +{ + int ret = -1; + bool snaplist_updated = false; + struct files_struct fsp = {0}; + struct smb_filename smb_fname = {0}; + double seconds = 0.0; + struct shadow_copy2_private *priv = NULL; + + SMB_VFS_HANDLE_GET_DATA(handle, priv, struct shadow_copy2_private, + return false); + + seconds = difftime(snap_time, priv->snaps->fetch_time); + + /* + * Fetch the snapshot list if either the snaplist is empty or the + * required snapshot time is greater than the last fetched snaplist + * time. + */ + if (seconds > 0 || (priv->snaps->snaplist == NULL)) { + smb_fname.base_name = "."; + fsp.fsp_name = &smb_fname; + + ret = shadow_copy2_get_shadow_copy_data(handle, &fsp, + NULL, false); + if (ret == 0) { + snaplist_updated = true; + } else { + DBG_ERR("Failed to get shadow copy data\n"); + } + + } + + return snaplist_updated; +} + static bool shadow_copy2_find_slashes(TALLOC_CTX *mem_ctx, const char *str, size_t **poffsets, unsigned *pnum_offsets) @@ -133,6 +284,28 @@ static ssize_t shadow_copy2_posix_gmt_string(struct vfs_handle_struct *handle, return -1; } } + + if (priv->snaps->regex != NULL) { + snaptime_len = shadow_copy2_saved_snapname(priv, + &snap_tm, snaptime_string, len); + if (snaptime_len >= 0) + return snaptime_len; + + /* + * If we fail to find the snapshot name, chances are + * that we have not updated our snaplist. Make sure the + * snaplist is updated. + */ + if (!shadow_copy2_update_snaplist(handle, snapshot)) { + DBG_DEBUG("shadow_copy2_update_snaplist " + "failed\n"); + return -1; + } + + return shadow_copy2_saved_snapname(priv, + &snap_tm, snaptime_string, len); + } + snaptime_len = strftime(snaptime_string, len, config->gmt_format, @@ -1354,6 +1527,10 @@ static bool shadow_copy2_snapshot_to_gmt(vfs_handle_struct *handle, const char *fmt; struct shadow_copy2_config *config; struct shadow_copy2_private *priv; + char *tmpstr = NULL; + char *tmp = NULL; + bool converted = false; + int ret = -1; SMB_VFS_HANDLE_GET_DATA(handle, priv, struct shadow_copy2_private, return NULL); @@ -1362,13 +1539,38 @@ static bool shadow_copy2_snapshot_to_gmt(vfs_handle_struct *handle, fmt = config->gmt_format; + /* + * If regex is provided, then we will have to parse the + * filename which will contain both the prefix and the time format. + * e.g. <prefix><delimiter><time_format> + */ + if (priv->snaps->regex != NULL) { + tmpstr = talloc_strdup(talloc_tos(), name); + /* point "name" to the time format */ + name = strstr(name, priv->config->delimiter); + if (name == NULL) { + goto done; + } + /* Extract the prefix */ + tmp = strstr(tmpstr, priv->config->delimiter); + *tmp = '\0'; + + /* Parse regex */ + ret = regexec(priv->snaps->regex, tmpstr, 0, NULL, 0); + if (ret) { + DBG_DEBUG("shadow_copy2_snapshot_to_gmt: " + "no regex match for %s\n", tmpstr); + goto done; + } + } + ZERO_STRUCT(timestamp); if (config->use_sscanf) { if (sscanf(name, fmt, ×tamp_long) != 1) { DEBUG(10, ("shadow_copy2_snapshot_to_gmt: " "no sscanf match %s: %s\n", fmt, name)); - return false; + goto done; } timestamp_t = timestamp_long; gmtime_r(×tamp_t, ×tamp); @@ -1377,7 +1579,7 @@ static bool shadow_copy2_snapshot_to_gmt(vfs_handle_struct *handle, DEBUG(10, ("shadow_copy2_snapshot_to_gmt: " "no match %s: %s\n", fmt, name)); - return false; + goto done; } DEBUG(10, ("shadow_copy2_snapshot_to_gmt: match %s: %s\n", fmt, name)); @@ -1390,7 +1592,11 @@ static bool shadow_copy2_snapshot_to_gmt(vfs_handle_struct *handle, } strftime(gmt, gmt_len, GMT_FORMAT, ×tamp); - return true; + converted = true; + +done: + TALLOC_FREE(tmpstr); + return converted; } static int shadow_copy2_label_cmp_asc(const void *x, const void *y) @@ -1448,6 +1654,9 @@ static int shadow_copy2_get_shadow_copy_data( struct smb_filename *snapdir_smb_fname = NULL; struct dirent *d; TALLOC_CTX *tmp_ctx = talloc_stackframe(); + struct shadow_copy2_private *priv = NULL; + struct shadow_copy2_snapentry *tmpentry = NULL; + bool get_snaplist = false; bool ret; snapdir = shadow_copy2_find_snapdir(tmp_ctx, handle, fsp->fsp_name); @@ -1487,8 +1696,32 @@ static int shadow_copy2_get_shadow_copy_data( return -1; } - shadow_copy2_data->num_volumes = 0; - shadow_copy2_data->labels = NULL; + if (shadow_copy2_data != NULL) { + shadow_copy2_data->num_volumes = 0; + shadow_copy2_data->labels = NULL; + } + + SMB_VFS_HANDLE_GET_DATA(handle, priv, struct shadow_copy2_private, + return -1); + + /* + * Normally this function is called twice once with labels = false and + * then with labels = true. When labels is false it will return the + * number of volumes so that the caller can allocate memory for that + * many labels. Therefore to eliminate snaplist both the times it is + * good to check if labels is set or not. + * + * shadow_copy2_data is NULL when we only want to update the list and + * don't want any labels. + */ + if ((priv->snaps->regex != NULL) && (labels || shadow_copy2_data == NULL)) { + get_snaplist = true; + /* Reset the global snaplist */ + shadow_copy2_delete_snaplist(priv); + + /* Set the current time as snaplist update time */ + time(&(priv->snaps->fetch_time)); + } while ((d = SMB_VFS_NEXT_READDIR(handle, p, NULL))) { char snapshot[GMT_NAME_LEN+1]; @@ -1509,6 +1742,25 @@ static int shadow_copy2_get_shadow_copy_data( DEBUG(6,("shadow_copy2_get_shadow_copy_data: %s -> %s\n", d->d_name, snapshot)); + if (get_snaplist) { + /* + * Create a snap entry for each successful + * pattern match. + */ + tmpentry = shadow_copy2_create_snapentry(priv); + if (tmpentry == NULL) { + DBG_ERR("talloc_zero() failed\n"); + talloc_free(tmp_ctx); + return -1; + } + tmpentry->snapname = talloc_strdup(tmpentry, d->d_name); + tmpentry->time_fmt = talloc_strdup(tmpentry, snapshot); + } + + if (shadow_copy2_data == NULL) { + continue; + } + if (!labels) { /* the caller doesn't want the labels */ shadow_copy2_data->num_volumes++; @@ -2061,6 +2313,8 @@ static int shadow_copy2_connect(struct vfs_handle_struct *handle, struct shadow_copy2_private *priv; int ret; const char *snapdir; + const char *snapprefix = NULL; + const char *delimiter; const char *gmt_format; const char *sort_order; const char *basedir = NULL; @@ -2078,7 +2332,14 @@ static int shadow_copy2_connect(struct vfs_handle_struct *handle, priv = talloc_zero(handle->conn, struct shadow_copy2_private); if (priv == NULL) { - DEBUG(0, ("talloc_zero() failed\n")); + DBG_ERR("talloc_zero() failed\n"); + errno = ENOMEM; + return -1; + } + + priv->snaps = talloc_zero(priv, struct shadow_copy2_snaplist_info); + if (priv->snaps == NULL) { + DBG_ERR("talloc_zero() failed\n"); errno = ENOMEM; return -1; } @@ -2119,6 +2380,37 @@ static int shadow_copy2_connect(struct vfs_handle_struct *handle, return -1; } + snapprefix = lp_parm_const_string(SNUM(handle->conn), + "shadow", "snapprefix", + NULL); + if (snapprefix != NULL) { + priv->snaps->regex = talloc_zero(priv->snaps, regex_t); + if (priv->snaps->regex == NULL) { + DBG_ERR("talloc_zero() failed\n"); + errno = ENOMEM; + return -1; + } + + /* pre-compute regex rule for matching pattern later */ + ret = regcomp(priv->snaps->regex, snapprefix, 0); + if (ret) { + DBG_ERR("Failed to create regex object\n"); + return -1; + } + } + + delimiter = lp_parm_const_string(SNUM(handle->conn), + "shadow", "delimiter", + "_GMT"); + if (delimiter != NULL) { + priv->config->delimiter = talloc_strdup(priv->config, delimiter); + if (priv->config->delimiter == NULL) { + DBG_ERR("talloc_strdup() failed\n"); + errno = ENOMEM; + return -1; + } + } + config->snapdirseverywhere = lp_parm_bool(SNUM(handle->conn), "shadow", "snapdirseverywhere", @@ -2298,6 +2590,8 @@ static int shadow_copy2_connect(struct vfs_handle_struct *handle, " mountpoint: '%s'\n" " rel share root: '%s'\n" " snapdir: '%s'\n" + " snapprefix: '%s'\n" + " delimiter: '%s'\n" " snapshot base path: '%s'\n" " format: '%s'\n" " use sscanf: %s\n" @@ -2310,6 +2604,8 @@ static int shadow_copy2_connect(struct vfs_handle_struct *handle, config->mount_point, config->rel_connectpath, config->snapdir, + snapprefix, + config->delimiter, config->snapshot_basepath, config->gmt_format, config->use_sscanf ? "yes" : "no", |