--- /dev/null
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * File Server Shadow-Copy service for the FSRVP pipe
+ *
+ * Copyright (C) David Disseldorp 2012
+ *
+ * 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
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "ntdomain.h"
+#include "include/messages.h"
+#include "../libcli/security/security.h"
+#include "../lib/tsocket/tsocket.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "../lib/smbconf/smbconf.h"
+#include "smbd/proto.h"
+#include "lib/smbconf/smbconf_init.h"
+#include "librpc/gen_ndr/srv_fsrvp.h"
+#include "srv_fss_agent.h"
+#include "auth.h"
+
+struct fss_sc_smap {
+ struct fss_sc_smap *next, *prev;
+ int snum;
+ char *share_name; /* name of the file share */
+ char *sc_share_name; /* share exposing the shadow copy */
+ char *sc_share_comment;
+ bool is_exposed; /* whether shadow copy is exposed */
+};
+
+struct fss_sc {
+ struct fss_sc *next, *prev;
+ struct tevent_req *vfs_req; /* vfs shadow copy send request */
+ struct GUID id; /* GUID of the shadow copy */
+ char *id_str;
+ char *volume_name; /* name uniquely identifying on the
+ * server object store on which this
+ * shadow copy is created. */
+ char *sc_path; /* path exposing the shadow copy */
+ time_t create_ts; /* timestamp of client initiation */
+ struct fss_sc_smap *smaps; /* shares mapped to this shadow copy */
+ uint32_t smaps_count;
+ struct fss_sc_set *sc_set; /* parent shadow copy set */
+};
+
+enum fss_sc_state {
+ FSS_SC_STARTED,
+ FSS_SC_ADDED,
+ FSS_SC_CREATING,
+ FSS_SC_COMMITED,
+ FSS_SC_EXPOSED,
+ FSS_SC_RECOVERED,
+};
+struct fss_sc_set {
+ struct fss_sc_set *next, *prev;
+ struct tevent_req *commit_req; /* valid while state = commiting */
+ struct GUID id; /* GUID of the shadow copy set. */
+ char *id_str;
+ enum fss_sc_state state; /* status of the shadow copy set */
+ uint32_t context; /* attributes used for set creation */
+ struct fss_sc *scs; /* list of ShadowCopy objects */
+ uint32_t scs_count;
+ /* XXX Timeout? */
+};
+
+struct fss_global {
+ uint32_t min_vers;
+ uint32_t max_vers;
+ uint32_t cur_ctx;
+ struct fss_sc_set *sc_sets;
+ uint32_t sc_sets_count;
+};
+static struct fss_global fss_global;
+
+/* errmap NTSTATUS->fsrvp */
+static const struct {
+ NTSTATUS status;
+ uint32_t fsrvp_err;
+} ntstatus_to_fsrvp_map[] = {
+ {NT_STATUS_INVALID_SERVER_STATE, FSRVP_E_BAD_STATE},
+ {NT_STATUS_INVALID_DISPOSITION, FSRVP_E_SHADOW_COPY_SET_IN_PROGRESS},
+ {NT_STATUS_NOT_SUPPORTED, FSRVP_E_NOT_SUPPORTED},
+ {NT_STATUS_IO_TIMEOUT, FSRVP_E_WAIT_TIMEOUT},
+ {NT_STATUS_CANT_WAIT, FSRVP_E_WAIT_FAILED},
+ {NT_STATUS_OBJECTID_EXISTS, FSRVP_E_OBJECT_ALREADY_EXISTS},
+ {NT_STATUS_OBJECTID_NOT_FOUND, FSRVP_E_OBJECT_NOT_FOUND},
+ {NT_STATUS_OBJECT_NAME_INVALID, FSRVP_E_BAD_ID},
+ {NT_STATUS_ACCESS_DENIED, E_ACCESSDENIED},
+ {NT_STATUS_INVALID_PARAMETER, E_INVALIDARG},
+ {NT_STATUS_NO_MEMORY, E_OUTOFMEMORY},
+};
+
+static uint32_t fss_ntstatus_map(NTSTATUS status)
+{
+ int i;
+
+ if (NT_STATUS_IS_OK(status))
+ return 0;
+
+ for (i = 0; i < ARRAY_SIZE(ntstatus_to_fsrvp_map); i++) {
+ if (NT_STATUS_EQUAL(status, ntstatus_to_fsrvp_map[i].status)) {
+ return ntstatus_to_fsrvp_map[i].fsrvp_err;
+ }
+ }
+
+ return E_OUTOFMEMORY; /* FIXME */
+}
+
+static NTSTATUS fss_vfs_conn_become(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ struct auth_session_info *session_info,
+ int snum,
+ struct connection_struct **conn_out)
+{
+ struct connection_struct *conn = NULL;
+ NTSTATUS status;
+ char *oldcwd;
+
+ status = create_conn_struct(mem_ctx, ev, msg_ctx, &conn,
+ snum, lp_pathname(mem_ctx, snum),
+ session_info,
+ &oldcwd);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("failed to create conn for vfs: %s\n",
+ nt_errstr(status)));
+ return status;
+ }
+
+ status = set_conn_force_user_group(conn, snum);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("failed set force user / group\n"));
+ goto err_free_conn;
+ }
+
+ if (!become_user_by_session(conn, session_info)) {
+ DEBUG(0, ("failed to become user\n"));
+ status = NT_STATUS_ACCESS_DENIED;
+ goto err_free_conn;
+ }
+ *conn_out = conn;
+
+ return NT_STATUS_OK;
+
+err_free_conn:
+ vfs_ChDir(conn, oldcwd);
+ SMB_VFS_DISCONNECT(conn);
+ conn_free(conn);
+ return status;
+}
+
+static void fss_vfs_conn_unbecome(struct connection_struct *conn)
+{
+ unbecome_user();
+ /* vfs_ChDir(conn, oldcwd); needed? */
+ SMB_VFS_DISCONNECT(conn);
+ conn_free(conn);
+}
+
+static struct fss_sc_set *sc_set_lookup(struct fss_sc_set *sc_set_head,
+ struct GUID *sc_set_id)
+{
+
+ struct fss_sc_set *sc_set;
+ char *guid_str;
+
+ for (sc_set = sc_set_head; sc_set; sc_set = sc_set->next) {
+ if (GUID_equal(&sc_set->id, sc_set_id)) {
+ return sc_set;
+ }
+ }
+ guid_str = GUID_string(sc_set_head, sc_set_id);
+ DEBUG(4, ("shadow copy set with GUID %s not found\n",
+ guid_str ? guid_str : "NO MEM"));
+ talloc_free(guid_str);
+
+ return NULL;
+}
+
+static struct fss_sc *sc_lookup(struct fss_sc *sc_head, struct GUID *sc_id)
+{
+
+ struct fss_sc *sc;
+ char *guid_str;
+
+ for (sc = sc_head; sc; sc = sc->next) {
+ if (GUID_equal(&sc->id, sc_id)) {
+ return sc;
+ }
+ }
+ guid_str = GUID_string(sc_head, sc_id);
+ DEBUG(4, ("shadow copy with GUID %s not found\n",
+ guid_str ? guid_str : "NO MEM"));
+ talloc_free(guid_str);
+
+ return NULL;
+}
+
+static struct fss_sc *sc_lookup_volname(struct fss_sc *sc_head,
+ const char *volname)
+{
+ struct fss_sc *sc;
+
+ for (sc = sc_head; sc; sc = sc->next) {
+ if (!strcmp(sc->volume_name, volname)) {
+ return sc;
+ }
+ }
+ DEBUG(4, ("shadow copy with base volume %s not found\n", volname));
+ return NULL;
+}
+
+static struct fss_sc_smap *sc_smap_lookup(struct fss_sc_smap *smaps_head,
+ const char *share)
+{
+ struct fss_sc_smap *sc_smap;
+ for (sc_smap = smaps_head; sc_smap; sc_smap = sc_smap->next) {
+ if (!strcmp(sc_smap->share_name, share)) {
+ return sc_smap;
+ }
+ }
+ DEBUG(4, ("shadow copy share mapping for %s not found\n", share));
+ return NULL;
+}
+
+void srv_fssa_cleanup(void)
+{
+ struct fss_sc_set *sc_set;
+ struct fss_sc_set *sc_set_n;
+
+ for (sc_set = fss_global.sc_sets; sc_set; sc_set = sc_set_n) {
+ sc_set_n = sc_set->next;
+ talloc_free(sc_set);
+ }
+ ZERO_STRUCT(fss_global);
+}
+
+void srv_fssa_start(void)
+{
+ fss_global.min_vers = FSRVP_RPC_VERSION_1;
+ fss_global.max_vers = FSRVP_RPC_VERSION_1;
+ /*
+ * TODO The server MUST populate the GlobalShadowCopySetTable with the
+ * ShadowCopySet entries read from the configuration store.
+ */
+}
+
+static bool fss_permitted(struct pipes_struct *p)
+{
+ /* Windows checks for Administrators or Backup Operators membership */
+ return true;
+}
+
+uint32_t _fss_GetSupportedVersion(struct pipes_struct *p,
+ struct fss_GetSupportedVersion *r)
+{
+ if (!fss_permitted(p)) {
+ return E_ACCESSDENIED;
+ }
+
+ *r->out.MinVersion = fss_global.min_vers;
+ *r->out.MaxVersion = fss_global.max_vers;
+
+ return 0;
+}
+
+uint32_t _fss_SetContext(struct pipes_struct *p,
+ struct fss_SetContext *r)
+{
+ if (!fss_permitted(p)) {
+ return E_ACCESSDENIED;
+ }
+
+ /* ATTR_AUTO_RECOVERY flag can be applied to any */
+ switch (r->in.Context & (~ATTR_AUTO_RECOVERY)) {
+ case FSRVP_CTX_BACKUP:
+ DEBUG(6, ("fss ctx set backup\n"));
+ break;
+ case FSRVP_CTX_FILE_SHARE_BACKUP:
+ DEBUG(6, ("fss ctx set file share backup\n"));
+ break;
+ case FSRVP_CTX_NAS_ROLLBACK:
+ DEBUG(6, ("fss ctx set nas rollback\n"));
+ break;
+ case FSRVP_CTX_APP_ROLLBACK:
+ DEBUG(6, ("fss ctx set app rollback\n"));
+ break;
+ default:
+ DEBUG(0, ("invalid fss ctx set value: 0x%x\n", r->in.Context));
+ return E_INVALIDARG;
+ break; /* not reached */
+ }
+
+ fss_global.cur_ctx = r->in.Context;
+
+ /* TODO start msg seq timer */
+
+ return 0;
+}
+
+static bool sc_set_active(struct fss_sc_set *sc_set_head)
+{
+
+ struct fss_sc_set *sc_set;
+
+ for (sc_set = sc_set_head; sc_set; sc_set = sc_set->next) {
+ if ((sc_set->state != FSS_SC_EXPOSED)
+ && (sc_set->state != FSS_SC_RECOVERED)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+uint32_t _fss_StartShadowCopySet(struct pipes_struct *p,
+ struct fss_StartShadowCopySet *r)
+{
+ struct fss_sc_set *sc_set;
+
+ if (!fss_permitted(p)) {
+ return E_ACCESSDENIED;
+ }
+
+ /*
+ * At any given time, Windows servers allow only one shadow copy set to
+ * be going through the creation process.
+ */
+ if (sc_set_active(fss_global.sc_sets)) {
+ DEBUG(3, ("StartShadowCopySet called while in progress\n"));
+ return FSRVP_E_SHADOW_COPY_SET_IN_PROGRESS;
+ }
+
+ /* stop msg seq timer */
+
+ sc_set = talloc_zero(NULL, struct fss_sc_set);
+ if (sc_set == NULL) {
+ return E_OUTOFMEMORY;
+ }
+
+ sc_set->id = GUID_random(); /* Windows servers ignore client ids */
+ sc_set->id_str = GUID_string(sc_set, &sc_set->id);
+ if (sc_set->id_str == NULL) {
+ talloc_free(sc_set);
+ return E_OUTOFMEMORY;
+ }
+ sc_set->state = FSS_SC_STARTED;
+ /* TODO check for 0 global context here?? */
+ sc_set->context = fss_global.cur_ctx;
+ DLIST_ADD_END(fss_global.sc_sets, sc_set, struct fss_sc_set *);
+ fss_global.sc_sets_count++;
+ DEBUG(6, ("%s: shadow-copy set %u added\n",
+ sc_set->id_str, fss_global.sc_sets_count));
+
+ r->out.pShadowCopySetId = &sc_set->id;
+ /* TODO start msg seq timer */
+
+ return 0;
+}
+
+uint32_t _fss_AddToShadowCopySet(struct pipes_struct *p,
+ struct fss_AddToShadowCopySet *r)
+{
+ struct fss_sc_set *sc_set;
+ struct fss_sc *sc;
+ struct fss_sc_smap *sc_smap;
+ int snum;
+ char *service;
+ char *base_vol;
+ char *share;
+ char *path_name;
+ struct connection_struct *conn;
+ NTSTATUS status;
+ TALLOC_CTX *tmp_ctx = talloc_new(p->mem_ctx);
+ if (tmp_ctx == NULL) {
+ return E_OUTOFMEMORY;
+ }
+
+ if (!fss_permitted(p)) {
+ talloc_free(tmp_ctx);
+ return E_ACCESSDENIED;
+ }
+
+ sc_set = sc_set_lookup(fss_global.sc_sets, &r->in.ShadowCopySetId);
+ if (sc_set == NULL) {
+ talloc_free(tmp_ctx);
+ return E_INVALIDARG;
+ }
+
+ share = strrchr(r->in.ShareName, '\\');
+ if (share++ == NULL) {
+ talloc_free(tmp_ctx);
+ return E_INVALIDARG;
+ }
+
+ snum = find_service(tmp_ctx, share, &service);
+ if ((snum == -1) || (service == NULL)) {
+ DEBUG(0, ("share at %s not found\n", r->in.ShareName));
+ talloc_free(tmp_ctx);
+ return E_INVALIDARG;
+ }
+
+ path_name = lp_pathname(tmp_ctx, snum);
+ if (path_name == NULL) {
+ talloc_free(tmp_ctx);
+ return E_OUTOFMEMORY;
+ }
+
+ status = fss_vfs_conn_become(tmp_ctx, server_event_context(),
+ p->msg_ctx, p->session_info, snum, &conn);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(tmp_ctx);
+ return E_ACCESSDENIED;
+ }
+ status = SMB_VFS_SNAP_CHECK_PATH(conn, tmp_ctx, path_name, &base_vol);
+ fss_vfs_conn_unbecome(conn);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(tmp_ctx);
+ return FSRVP_E_NOT_SUPPORTED;
+ }
+
+ if ((sc_set->state != FSS_SC_STARTED)
+ && (sc_set->state != FSS_SC_ADDED)) {
+ talloc_free(tmp_ctx);
+ return FSRVP_E_BAD_STATE;
+ }
+
+ /* TODO stop msg seq timer */
+
+ /*
+ * server MUST look up the ShadowCopy in ShadowCopySet.ShadowCopyList
+ * where ShadowCopy.VolumeName matches the file store on which the
+ * share identified by ShareName is hosted. If an entry is found, the
+ * server MUST fail the call with FSRVP_E_OBJECT_ALREADY_EXISTS.
+ * If no entry is found, the server MUST create a new ShadowCopy
+ * object
+ * XXX Windows appears to allow multiple mappings for the same vol!
+ */
+ sc = sc_lookup_volname(sc_set->scs, base_vol);
+ if (sc != NULL) {
+ talloc_free(tmp_ctx);
+ return FSRVP_E_OBJECT_ALREADY_EXISTS;
+ }
+
+ sc = talloc_zero(sc_set, struct fss_sc);
+ if (sc == NULL) {
+ talloc_free(tmp_ctx);
+ return E_OUTOFMEMORY;
+ }
+ talloc_steal(sc, base_vol);
+ sc->volume_name = base_vol;
+ sc->sc_set = sc_set;
+ sc->create_ts = time(NULL);
+
+ sc->id = GUID_random(); /* Windows servers ignore client ids */
+ sc->id_str = GUID_string(sc, &sc->id);
+ if (sc->id_str == NULL) {
+ talloc_free(sc);
+ talloc_free(tmp_ctx);
+ return E_OUTOFMEMORY;
+ }
+
+ sc_smap = talloc_zero(sc, struct fss_sc_smap);
+ if (sc_smap == NULL) {
+ talloc_free(sc);
+ talloc_free(tmp_ctx);
+ return E_OUTOFMEMORY;
+ }
+
+ sc_smap->snum = snum;
+ talloc_steal(sc_smap, service);
+ sc_smap->share_name = service;
+ sc_smap->is_exposed = false;
+
+ /* add share map to shadow-copy */
+ DLIST_ADD_END(sc->smaps, sc_smap, struct fss_sc_smap *);
+ sc->smaps_count++;
+ /* add shadow-copy to shadow-copy set */
+ DLIST_ADD_END(sc_set->scs, sc, struct fss_sc *);
+ sc_set->scs_count++;
+ sc_set->state = FSS_SC_ADDED;
+ DEBUG(4, ("added volume %s to shadow copy set with GUID %s\n",
+ sc->volume_name, sc_set->id_str));
+
+ r->out.pShadowCopyId = &sc->id;
+
+ /* TODO start the Message Sequence Timer with timeout of 180 seconds */
+ talloc_free(tmp_ctx);
+ return 0;
+}
+
+struct fss_commit_state {
+ struct auth_session_info *session_info;
+ struct GUID sc_set_id; /* use guid as handle in case of abort */
+ uint32_t dispatch_count;
+ uint32_t recv_count; /* total completions */
+ uint32_t bad_recv_count; /* number of failed completions */
+ NTSTATUS status;
+};
+static void fss_commit_vfs_done(struct tevent_req *subreq);
+
+struct tevent_req *_fss_CommitShadowCopySet_send(struct tevent_context *ev,
+ TALLOC_CTX *mem_ctx,
+ struct pipes_struct *p,
+ struct fss_CommitShadowCopySet *r)
+{
+ struct tevent_req *req;
+ struct fss_commit_state *commit_state = NULL;
+ struct fss_sc_set *sc_set;
+ struct fss_sc *sc;
+ bool rw;
+
+ req = tevent_req_create(mem_ctx, &commit_state,
+ struct fss_commit_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (!fss_permitted(p)) {
+ commit_state->status = NT_STATUS_ACCESS_DENIED;
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ sc_set = sc_set_lookup(fss_global.sc_sets, &r->in.ShadowCopySetId);
+ if (sc_set == NULL) {
+ commit_state->status = NT_STATUS_INVALID_PARAMETER;
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+ sc_set->commit_req = req;
+
+ if (sc_set->state != FSS_SC_ADDED) {
+ commit_state->status = NT_STATUS_INVALID_SERVER_STATE;
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ /* TODO stop Message Sequence Timer */
+ commit_state->session_info = p->session_info;
+ commit_state->sc_set_id = sc_set->id;
+ rw = ((sc_set->context & ATTR_AUTO_RECOVERY) == ATTR_AUTO_RECOVERY);
+
+ for (sc = sc_set->scs; sc; sc = sc->next) {
+ struct tevent_req *vfs_req = NULL;
+ struct connection_struct *conn;
+ NTSTATUS status;
+ /* XXX error path is a bit complex here */
+ status = fss_vfs_conn_become(commit_state,
+ ev, p->msg_ctx, p->session_info,
+ sc->smaps->snum, &conn);
+ if (NT_STATUS_IS_OK(status)) {
+ status = NT_STATUS_NO_MEMORY;
+ vfs_req = SMB_VFS_SNAP_CREATE_SEND(conn, commit_state,
+ ev, sc->volume_name,
+ &sc->create_ts, rw);
+ fss_vfs_conn_unbecome(conn);
+ }
+ if (vfs_req == NULL) {
+ commit_state->status = status;
+ if (commit_state->dispatch_count == 0) {
+ /* nothing dispatched, return immediately */
+ tevent_req_nterror(sc_set->commit_req,
+ commit_state->status);
+ return tevent_req_post(sc_set->commit_req, ev);
+ } else {
+ /*
+ * wait for dispatched to complete before
+ * returning error
+ */
+ break;
+ }
+ }
+ /* XXX set timeout r->in.TimeOutInMilliseconds */
+ tevent_req_set_callback(vfs_req, fss_commit_vfs_done, sc);
+ sc->vfs_req = vfs_req;
+ commit_state->dispatch_count++;
+ }
+
+ sc_set->state = FSS_SC_CREATING;
+ return sc_set->commit_req;
+}
+
+static void fss_commit_vfs_done(struct tevent_req *subreq)
+{
+ /* FIXME use a sc handle */
+ struct fss_sc *sc = tevent_req_callback_data(subreq,
+ struct fss_sc);
+ struct tevent_req *req = sc->sc_set->commit_req;
+ struct fss_commit_state *commit_state = tevent_req_data(req,
+ struct fss_commit_state);
+ char *snap_path;
+ char *base_path;
+ NTSTATUS status;
+ struct connection_struct *conn;
+
+ commit_state->recv_count++;
+ status = fss_vfs_conn_become(commit_state, server_event_context(),
+ server_messaging_context(),
+ commit_state->session_info,
+ sc->smaps->snum, &conn);
+ if (NT_STATUS_IS_OK(status)) {
+ status = SMB_VFS_SNAP_CREATE_RECV(conn, subreq, sc,
+ &base_path, &snap_path);
+ fss_vfs_conn_unbecome(conn);
+ }
+ if (NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("good snap create recv %d of %d\n",
+ commit_state->recv_count,
+ commit_state->dispatch_count));
+ sc->sc_path = snap_path;
+ } else {
+ DEBUG(0, ("snap create failed for shadow copy of "
+ "%s\n", base_path));
+ commit_state->bad_recv_count++;
+ commit_state->status = status; /* may overwrite previous failure */
+ }
+
+ if (commit_state->recv_count != commit_state->dispatch_count) {
+ DEBUG(0, ("awaiting %u more snapshot completions\n",
+ (commit_state->dispatch_count - commit_state->recv_count)));
+ return;
+ }
+ if (NT_STATUS_IS_OK(commit_state->status)) {
+ tevent_req_done(req);
+ } else {
+ /* TODO cleanup */
+ tevent_req_nterror(req, commit_state->status);
+ }
+}
+
+uint32_t _fss_CommitShadowCopySet_recv(struct tevent_req *req)
+{
+ struct fss_commit_state *commit_state
+ = tevent_req_data(req, struct fss_commit_state);
+ struct fss_sc_set *sc_set;
+
+ sc_set = sc_set_lookup(fss_global.sc_sets, &commit_state->sc_set_id);
+ if (sc_set == NULL) {
+ DEBUG(0, ("recv for non existent sc set\n"));
+ tevent_req_received(req);
+ return FSRVP_E_OBJECT_NOT_FOUND;
+ }
+
+ if (!NT_STATUS_IS_OK(commit_state->status)) {
+ uint32_t ret;
+ DEBUG(0, ("sc set commit failed: %s\n",
+ nt_errstr(commit_state->status)));
+ /* TODO map to fsrvp error */
+ /* commit failed, TODO cleanup */
+ sc_set->state = FSS_SC_ADDED;
+ ret = fss_ntstatus_map(commit_state->status);
+ tevent_req_received(req);
+ return ret;
+ }
+
+ sc_set->state = FSS_SC_COMMITED;
+ tevent_req_received(req);
+ return 0;
+}
+
+static uint32_t map_share_name(struct fss_sc_smap *sc_smap,
+ const struct fss_sc *sc)
+{
+ bool hidden_base = false;
+ char *time_str;
+
+ if (*(sc_smap->share_name + strlen(sc_smap->share_name) - 1) == '$') {
+ /*
+ * If MappedShare.ShareName ends with a $ character (meaning
+ * that the share is hidden), then the exposed share name will
+ * have the $ suffix appended.
+ * FIXME: turns out Windows doesn't do this, contrary to docs
+ */
+ hidden_base = true;
+ }
+
+ sc_smap->sc_share_name = talloc_asprintf(sc_smap, "%s@{%s}%s",
+ sc_smap->share_name,
+ sc->id_str,
+ hidden_base ? "$" : "");
+ if (sc_smap->sc_share_name == NULL) {
+ return E_OUTOFMEMORY;
+ }
+
+ time_str = http_timestring(sc_smap, sc->create_ts);
+ if (time_str == NULL) {
+ return E_OUTOFMEMORY;
+ }
+
+ sc_smap->sc_share_comment = talloc_asprintf(sc_smap, "Shadow copy of %s taken %s",
+ sc_smap->share_name, time_str);
+ if (sc_smap->sc_share_comment == NULL) {
+ return E_OUTOFMEMORY;
+ }
+
+ return 0;
+}
+
+static sbcErr fss_conf_get_share_def(struct smbconf_ctx *fconf_ctx,
+ struct smbconf_ctx *rconf_ctx,
+ TALLOC_CTX *mem_ctx,
+ char *share,
+ struct smbconf_service **service_def)
+{
+ sbcErr cerr;
+ struct smbconf_service *def;
+
+ *service_def = NULL;
+ cerr = smbconf_get_share(fconf_ctx, mem_ctx, share, &def);
+ if (SBC_ERROR_IS_OK(cerr)) {
+ *service_def = def;
+ return SBC_ERR_OK;
+ }
+
+ cerr = smbconf_get_share(rconf_ctx, mem_ctx, share, &def);
+ if (SBC_ERROR_IS_OK(cerr)) {
+ *service_def = def;
+ return SBC_ERR_OK;
+ }
+ return cerr;
+}
+
+/*
+ * Expose a new share using libsmbconf, cloning the existing configuration
+ * from the base share. The base share may be defined in either the registry
+ * or smb.conf.
+ * XXX this is called as root
+ */
+static uint32_t fss_sc_expose(struct smbconf_ctx *fconf_ctx,
+ struct smbconf_ctx *rconf_ctx,
+ TALLOC_CTX *mem_ctx,
+ struct fss_sc *sc)
+{
+ struct fss_sc_smap *sc_smap;
+ uint32_t err = 0;
+
+ for (sc_smap = sc->smaps; sc_smap; sc_smap = sc_smap->next) {
+ sbcErr cerr;
+ struct smbconf_service *base_service = NULL;
+
+ cerr = fss_conf_get_share_def(fconf_ctx, rconf_ctx, mem_ctx,
+ sc_smap->share_name, &base_service);
+ if (!SBC_ERROR_IS_OK(cerr)) {
+ DEBUG(0, ("failed to get base share %s definition: "
+ "%s\n", sc_smap->share_name,
+ sbcErrorString(cerr)));
+ err = E_OUTOFMEMORY; /* FIXME */
+ break;
+ }
+
+ err = map_share_name(sc_smap, sc);
+ if (err) {
+ DEBUG(0, ("failed to map share name\n"));
+ break;
+ }
+
+ base_service->name = sc_smap->sc_share_name;
+
+ cerr = smbconf_create_set_share(rconf_ctx, base_service->name,
+ base_service);
+ if (!SBC_ERROR_IS_OK(cerr)) {
+ DEBUG(0, ("failed to create share %s: %s\n",
+ base_service->name, sbcErrorString(cerr)));
+ err = E_OUTOFMEMORY; /* FIXME */
+ break;
+ }
+ cerr = smbconf_set_parameter(rconf_ctx, sc_smap->sc_share_name,
+ "path", sc->sc_path);
+ if (!SBC_ERROR_IS_OK(cerr)) {
+ DEBUG(0, ("failed to set path param: %s\n",
+ sbcErrorString(cerr)));
+ err = E_OUTOFMEMORY; /* FIXME */
+ break;
+ }
+ if (sc_smap->sc_share_comment != NULL) {
+ cerr = smbconf_set_parameter(rconf_ctx,
+ sc_smap->sc_share_name,
+ "comment",
+ sc_smap->sc_share_comment);
+ if (!SBC_ERROR_IS_OK(cerr)) {
+ DEBUG(0, ("failed to set comment param: %s\n",
+ sbcErrorString(cerr)));
+ err = E_OUTOFMEMORY; /* FIXME */
+ break;
+ }
+ }
+ talloc_free(base_service);
+ }
+
+ return err;
+}
+
+uint32_t _fss_ExposeShadowCopySet(struct pipes_struct *p,
+ struct fss_ExposeShadowCopySet *r)
+{
+ struct fss_sc_set *sc_set;
+ struct fss_sc *sc;
+ uint32_t ret;
+ struct smbconf_ctx *fconf_ctx;
+ struct smbconf_ctx *rconf_ctx;
+ sbcErr cerr;
+ char *fconf_path;
+ TALLOC_CTX *tmp_ctx = talloc_new(p->mem_ctx);
+ if (tmp_ctx == NULL) {
+ return E_OUTOFMEMORY;
+ }
+
+ if (!fss_permitted(p)) {
+ ret = E_ACCESSDENIED;
+ goto err_out;
+ }
+
+ sc_set = sc_set_lookup(fss_global.sc_sets, &r->in.ShadowCopySetId);
+ if (sc_set == NULL) {
+ ret = E_ACCESSDENIED;
+ goto err_out;
+ }
+
+ if (sc_set->state != FSS_SC_COMMITED) {
+ ret = FSRVP_E_BAD_STATE;
+ goto err_out;
+ }
+
+ /*
+ * Prepare to clone the base share definition for the snapshot share.
+ * Create both registry and file conf contexts, as the base share
+ * definition may be located in either. The snapshot share definition
+ * is always written to the registry.
+ */
+ cerr = smbconf_init(tmp_ctx, &rconf_ctx, "registry");
+ if (!SBC_ERROR_IS_OK(cerr)) {
+ DEBUG(0, ("failed registry smbconf init: %s\n",
+ sbcErrorString(cerr)));
+ ret = E_OUTOFMEMORY; /* FIXME */
+ goto err_out;
+ }
+ fconf_path = talloc_asprintf(tmp_ctx, "file:%s", get_dyn_CONFIGFILE());
+ if (fconf_path == NULL) {
+ ret = E_OUTOFMEMORY;
+ goto err_out;
+ }
+ cerr = smbconf_init(tmp_ctx, &fconf_ctx, fconf_path);
+ if (!SBC_ERROR_IS_OK(cerr)) {
+ DEBUG(0, ("failed %s smbconf init: %s\n",
+ fconf_path, sbcErrorString(cerr)));
+ ret = E_OUTOFMEMORY; /* FIXME */
+ goto err_out;
+ }
+
+ /* registry IO must be done as root */
+ become_root();
+ cerr = smbconf_transaction_start(rconf_ctx);
+ if (!SBC_ERROR_IS_OK(cerr)) {
+ DEBUG(0, ("error starting transaction: %s\n",
+ sbcErrorString(cerr)));
+ ret = E_OUTOFMEMORY; /* FIXME */
+ unbecome_root();
+ goto err_out;
+ }
+
+ for (sc = sc_set->scs; sc; sc = sc->next) {
+ ret = fss_sc_expose(fconf_ctx, rconf_ctx, tmp_ctx, sc);
+ if (ret) {
+ DEBUG(0,("failed to expose shadow copy of %s\n",
+ sc->volume_name));
+ goto err_cancel;
+ }
+ }
+
+ cerr = smbconf_transaction_commit(rconf_ctx);
+ if (!SBC_ERROR_IS_OK(cerr)) {
+ DEBUG(0, ("error committing transaction: %s\n",
+ sbcErrorString(cerr)));
+ ret = E_OUTOFMEMORY; /* FIXME */
+ goto err_cancel;
+ }
+ unbecome_root();
+
+ message_send_all(p->msg_ctx, MSG_SMB_CONF_UPDATED, NULL, 0, NULL);
+ for (sc = sc_set->scs; sc; sc = sc->next) {
+ struct fss_sc_smap *sm;
+ for (sm = sc->smaps; sm; sm = sm->next)
+ sm->is_exposed = true;
+ }
+ sc_set->state = FSS_SC_EXPOSED;
+ ret = 0;
+err_out:
+ talloc_free(tmp_ctx);
+ return ret;
+err_cancel:
+ smbconf_transaction_cancel(rconf_ctx);
+ talloc_free(tmp_ctx);
+ unbecome_root();
+ return ret;
+}
+
+uint32_t _fss_RecoveryCompleteShadowCopySet(struct pipes_struct *p,
+ struct fss_RecoveryCompleteShadowCopySet *r)
+{
+ struct fss_sc_set *sc_set;
+
+ if (!fss_permitted(p)) {
+ return E_ACCESSDENIED;
+ }
+
+ sc_set = sc_set_lookup(fss_global.sc_sets, &r->in.ShadowCopySetId);
+ if (sc_set == NULL) {
+ return E_INVALIDARG;
+ }
+
+ if (sc_set->state != FSS_SC_EXPOSED) {
+ return FSRVP_E_BAD_STATE;
+ }
+
+ if (sc_set->context | ATTR_NO_AUTO_RECOVERY) {
+ /* TODO set read-only */
+ }
+
+ sc_set->state = FSS_SC_RECOVERED;
+ fss_global.cur_ctx = 0;
+
+ return 0;
+}
+
+uint32_t _fss_AbortShadowCopySet(struct pipes_struct *p,
+ struct fss_AbortShadowCopySet *r)
+{
+ struct fss_sc_set *sc_set;
+
+ if (!fss_permitted(p)) {
+ return E_ACCESSDENIED;
+ }
+
+ sc_set = sc_set_lookup(fss_global.sc_sets, &r->in.ShadowCopySetId);
+ if (sc_set == NULL) {
+ return E_INVALIDARG;
+ }
+
+ DEBUG(6, ("%s: aborting shadow-copy set\n", sc_set->id_str));
+
+ if ((sc_set->state == FSS_SC_COMMITED)
+ || (sc_set->state == FSS_SC_EXPOSED)
+ || (sc_set->state == FSS_SC_RECOVERED)) {
+ return 0;
+ }
+
+ if (sc_set->state == FSS_SC_CREATING) {
+ /* TODO check how Window handles this case */
+ DEBUG(0, ("abort received while create is in progress\n"));
+ return FSRVP_E_BAD_STATE;
+ }
+
+ DLIST_REMOVE(fss_global.sc_sets, sc_set);
+ talloc_free(sc_set);
+ fss_global.sc_sets_count--;
+
+ return 0;
+}
+
+uint32_t _fss_IsPathSupported(struct pipes_struct *p,
+ struct fss_IsPathSupported *r)
+{
+ int snum;
+ char *service;
+ char *base_vol;
+ NTSTATUS status;
+ struct connection_struct *conn;
+ char *share;
+ char *addr;
+ TALLOC_CTX *tmp_ctx = talloc_new(p->mem_ctx);
+ if (tmp_ctx == NULL) {
+ return E_OUTOFMEMORY;
+ }
+
+ if (!fss_permitted(p)) {
+ talloc_free(tmp_ctx);
+ return E_ACCESSDENIED;
+ }
+
+ share = strrchr(r->in.ShareName, '\\');
+ if (share++ == NULL) {
+ talloc_free(tmp_ctx);
+ return E_INVALIDARG;
+ }
+
+ snum = find_service(tmp_ctx, share, &service);
+ if ((snum == -1) || (service == NULL)) {
+ DEBUG(0, ("share at %s not found\n", r->in.ShareName));
+ talloc_free(tmp_ctx);
+ return E_INVALIDARG;
+ }
+
+ status = fss_vfs_conn_become(tmp_ctx, server_event_context(),
+ p->msg_ctx, p->session_info, snum, &conn);
+ if (!NT_STATUS_IS_OK(status)) {
+ return E_ACCESSDENIED;
+ }
+ status = SMB_VFS_SNAP_CHECK_PATH(conn, tmp_ctx,
+ lp_pathname(tmp_ctx, snum),
+ &base_vol);
+ fss_vfs_conn_unbecome(conn);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(tmp_ctx);
+ return FSRVP_E_NOT_SUPPORTED;
+ }
+
+ addr = tsocket_address_inet_addr_string(p->local_address, p->mem_ctx);
+ if (addr == NULL) {
+ talloc_free(tmp_ctx);
+ return E_OUTOFMEMORY;
+ }
+ *r->out.OwnerMachineName = addr;
+ *r->out.SupportedByThisProvider = 1;
+ talloc_free(tmp_ctx);
+ return 0;
+}
+
+uint32_t _fss_IsPathShadowCopied(struct pipes_struct *p,
+ struct fss_IsPathShadowCopied *r)
+{
+ if (!fss_permitted(p)) {
+ return E_ACCESSDENIED;
+ }
+
+ /* not yet supported */
+ return FSRVP_E_NOT_SUPPORTED;
+}
+
+uint32_t _fss_GetShareMapping(struct pipes_struct *p,
+ struct fss_GetShareMapping *r)
+{
+ struct fss_sc_set *sc_set;
+ struct fss_sc *sc;
+ struct fss_sc_smap *sc_smap;
+ char *addr;
+ char *share;
+ struct fssagent_share_mapping_1 *sm_out;
+
+
+ if (!fss_permitted(p)) {
+ return E_ACCESSDENIED;
+ }
+
+ sc_set = sc_set_lookup(fss_global.sc_sets, &r->in.ShadowCopySetId);
+ if (sc_set == NULL) {
+ return E_INVALIDARG;
+ }
+
+ sc = sc_lookup(sc_set->scs, &r->in.ShadowCopyId);
+ if (sc == NULL) {
+ return E_INVALIDARG;
+ }
+
+ share = strrchr(r->in.ShareName, '\\');
+ if (share++ == NULL) {
+ return E_INVALIDARG;
+ }
+
+ sc_smap = sc_smap_lookup(sc->smaps, share);
+ if (sc_smap == NULL) {
+ return E_INVALIDARG;
+ }
+
+ if (r->in.Level != 1) {
+ return E_INVALIDARG;
+ }
+ addr = tsocket_address_inet_addr_string(p->local_address, p->mem_ctx);
+ if (addr == NULL) {
+ return E_OUTOFMEMORY;
+ }
+
+ sm_out = talloc_zero(p->mem_ctx, struct fssagent_share_mapping_1);
+ sm_out->ShadowCopySetId = sc_set->id;
+ sm_out->ShadowCopyId = sc->id;
+ sm_out->ShareNameUNC = talloc_asprintf(p->mem_ctx, "\\\\%s\\%s",
+ addr, sc_smap->share_name);
+ sm_out->ShadowCopyShareName = sc_smap->sc_share_name;
+ unix_to_nt_time(&sm_out->tstamp, sc->create_ts);
+ r->out.ShareMapping->ShareMapping1 = sm_out;
+
+ return 0;
+}
+
+static NTSTATUS sc_smap_unexpose(struct messaging_context *msg_ctx,
+ struct fss_sc_smap *sc_smap)
+{
+ NTSTATUS ret;
+ struct smbconf_ctx *conf_ctx;
+ sbcErr cerr;
+ TALLOC_CTX *tmp_ctx = talloc_new(sc_smap);
+ if (tmp_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ cerr = smbconf_init(tmp_ctx, &conf_ctx, "registry");
+ if (!SBC_ERROR_IS_OK(cerr)) {
+ DEBUG(0, ("failed registry smbconf init: %s\n",
+ sbcErrorString(cerr)));
+ ret = NT_STATUS_NO_MEMORY; /* FIXME */
+ goto err_tmp;
+ }
+
+ /* registry IO must be done as root */
+ become_root();
+ cerr = smbconf_transaction_start(conf_ctx);
+ if (!SBC_ERROR_IS_OK(cerr)) {
+ DEBUG(0, ("error starting transaction: %s\n",
+ sbcErrorString(cerr)));
+ ret = NT_STATUS_NO_MEMORY; /* FIXME */
+ goto err_conf;
+ }
+
+ cerr = smbconf_delete_share(conf_ctx, sc_smap->sc_share_name);
+ if (!SBC_ERROR_IS_OK(cerr)) {
+ ret = NT_STATUS_NO_MEMORY; /* FIXME */
+ goto err_cancel;
+ }
+
+ cerr = smbconf_transaction_commit(conf_ctx);
+ if (!SBC_ERROR_IS_OK(cerr)) {
+ DEBUG(0, ("error committing transaction: %s\n",
+ sbcErrorString(cerr)));
+ ret = NT_STATUS_NO_MEMORY; /* FIXME */
+ goto err_cancel;
+ }
+ message_send_all(msg_ctx, MSG_SMB_CONF_UPDATED, NULL, 0, NULL);
+ sc_smap->is_exposed = false;
+
+ ret = NT_STATUS_OK;
+err_conf:
+ talloc_free(conf_ctx);
+ unbecome_root();
+err_tmp:
+ talloc_free(tmp_ctx);
+ return ret;
+
+err_cancel:
+ smbconf_transaction_cancel(conf_ctx);
+ talloc_free(conf_ctx);
+ unbecome_root();
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+struct fss_delete_state {
+ struct auth_session_info *session_info;
+ struct fss_sc_set *sc_set;
+ struct fss_sc *sc;
+ struct fss_sc_smap *sc_smap;
+ int snum;
+ NTSTATUS status;
+};
+static void fss_delete_vfs_done(struct tevent_req *subreq);
+
+struct tevent_req *_fss_DeleteShareMapping_send(struct tevent_context *ev,
+ TALLOC_CTX *mem_ctx,
+ struct pipes_struct *p,
+ struct fss_DeleteShareMapping *r)
+{
+ struct fss_delete_state *delete_state = NULL;
+ struct fss_sc_set *sc_set;
+ struct fss_sc *sc;
+ struct tevent_req *req;
+ struct tevent_req *vfs_req = NULL;
+ struct fss_sc_smap *sc_smap;
+ char *share;
+ struct connection_struct *conn;
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &delete_state,
+ struct fss_delete_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ delete_state->session_info = p->session_info;
+
+ if (!fss_permitted(p)) {
+ delete_state->status = NT_STATUS_ACCESS_DENIED;
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ sc_set = sc_set_lookup(fss_global.sc_sets, &r->in.ShadowCopySetId);
+ if (sc_set == NULL) {
+ delete_state->status = NT_STATUS_OBJECT_NAME_INVALID; /* docs say E_INVALIDARG */
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+ delete_state->sc_set = sc_set;
+
+ if ((sc_set->state != FSS_SC_EXPOSED)
+ && (sc_set->state != FSS_SC_RECOVERED)) {
+ delete_state->status = NT_STATUS_INVALID_SERVER_STATE;
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ sc = sc_lookup(sc_set->scs, &r->in.ShadowCopyId);
+ if (sc == NULL) {
+ delete_state->status = NT_STATUS_OBJECT_NAME_INVALID; /* docs say E_INVALIDARG */
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+ delete_state->sc = sc;
+ delete_state->snum = sc->smaps->snum;
+
+ share = strrchr(r->in.ShareName, '\\');
+ if (share++ == NULL) {
+ delete_state->status = NT_STATUS_INVALID_PARAMETER;
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ sc_smap = sc_smap_lookup(sc->smaps, share);
+ if (sc_smap == NULL) {
+ delete_state->status = NT_STATUS_INVALID_PARAMETER;
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+ delete_state->sc_smap = sc_smap;
+
+ status = sc_smap_unexpose(p->msg_ctx, sc_smap);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("failed to remove share %s: %s\n",
+ sc_smap->sc_share_name, nt_errstr(status)));
+ delete_state->status = status;
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ message_send_all(p->msg_ctx, MSG_SMB_FORCE_TDIS, sc_smap->sc_share_name,
+ strlen(sc_smap->sc_share_name) + 1, NULL);
+ sleep(1); /* TODO wait until disconnected */
+
+ if (sc->smaps_count > 1) {
+ /* do not delete the underlying snapshot - still in use */
+ delete_state->status = NT_STATUS_OK;
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ status = fss_vfs_conn_become(delete_state, ev, p->msg_ctx,
+ delete_state->session_info,
+ delete_state->snum, &conn);
+ if (!NT_STATUS_IS_OK(status)) {
+ delete_state->status = status;
+ tevent_req_nterror(req,
+ delete_state->status);
+ return tevent_req_post(req, ev);
+ }
+
+ vfs_req = SMB_VFS_SNAP_DELETE_SEND(conn, delete_state, ev,
+ sc->sc_path);
+ fss_vfs_conn_unbecome(conn);
+ if (vfs_req == NULL) {
+ delete_state->status = NT_STATUS_NO_MEMORY;
+ tevent_req_nterror(req,
+ delete_state->status);
+ return tevent_req_post(req, ev);
+ }
+
+ /* XXX set timeout r->in.TimeOutInMilliseconds */
+ tevent_req_set_callback(vfs_req, fss_delete_vfs_done, req);
+ sc->vfs_req = vfs_req;
+
+ return req;
+}
+
+static void fss_delete_vfs_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct fss_delete_state *delete_state = tevent_req_data(req,
+ struct fss_delete_state);
+ NTSTATUS status;
+ struct connection_struct *conn;
+
+ status = fss_vfs_conn_become(delete_state, server_event_context(),
+ server_messaging_context(),
+ delete_state->session_info,
+ delete_state->snum, &conn);
+ if (!NT_STATUS_IS_OK(status)) {
+ delete_state->status = status;
+ tevent_req_nterror(req, delete_state->status);
+ return;
+ }
+
+ status = SMB_VFS_SNAP_DELETE_RECV(conn, subreq);
+ fss_vfs_conn_unbecome(conn);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("bad snap delete recv: %s\n",
+ nt_errstr(status)));
+ delete_state->status = status;
+ tevent_req_nterror(req, delete_state->status);
+ return;
+ }
+
+ DEBUG(6, ("good snap delete recv\n"));
+ delete_state->status = NT_STATUS_OK;
+ tevent_req_done(req);
+}
+
+uint32_t _fss_DeleteShareMapping_recv(struct tevent_req *req)
+{
+ struct fss_delete_state *delete_state = tevent_req_data(req,
+ struct fss_delete_state);
+ uint32_t ret;
+
+ if (NT_STATUS_IS_OK(delete_state->status)) {
+ DLIST_REMOVE(delete_state->sc->smaps, delete_state->sc_smap);
+ delete_state->sc->smaps_count--;
+ talloc_free(delete_state->sc_smap);
+ if (delete_state->sc->smaps_count == 0) {
+ DLIST_REMOVE(delete_state->sc_set->scs, delete_state->sc);
+ delete_state->sc_set->scs_count--;
+ talloc_free(delete_state->sc);
+
+ if (delete_state->sc_set->scs_count == 0) {
+ DLIST_REMOVE(fss_global.sc_sets, delete_state->sc_set);
+ fss_global.sc_sets_count--;
+ talloc_free(delete_state->sc_set);
+ }
+ }
+ }
+ ret = fss_ntstatus_map(delete_state->status);
+ tevent_req_received(req);
+ return ret;
+}
+
+uint32_t _fss_PrepareShadowCopySet(struct pipes_struct *p,
+ struct fss_PrepareShadowCopySet *r)
+{
+ struct fss_sc_set *sc_set;
+
+ if (!fss_permitted(p)) {
+ return E_ACCESSDENIED;
+ }
+
+ sc_set = sc_set_lookup(fss_global.sc_sets, &r->in.ShadowCopySetId);
+ if (sc_set == NULL) {
+ return E_INVALIDARG;
+ }
+
+ if (sc_set->state != FSS_SC_ADDED) {
+ return FSRVP_E_BAD_STATE;
+ }
+
+ /* TODO stop msg sequence timer */
+
+ /*
+ * Windows Server "8" Beta takes ~60s here, presumably flushing
+ * everything to disk. We may want to do something similar.
+ */
+
+ /* TODO start msg sequence timer */
+
+ return 0;
+}