fsrvp: add fss_agent rpc server
authorDavid Disseldorp <ddiss@samba.org>
Tue, 10 Apr 2012 12:32:41 +0000 (14:32 +0200)
committerDavid Disseldorp <ddiss@samba.org>
Mon, 15 Apr 2013 16:15:19 +0000 (18:15 +0200)
The Samba fss_agent rpc server is an implementation the the File Server
Remote VSS (Volume Shadow Copy) Protocol, or FSRVP for short.

FSRVP is new to Windows Server 2012, and allows authenticated clients to
remotely request the creation, exposure and deletion of share snapshots.

The fss_agent rpc server processes requests on the FssAgentRpc named
pipe, and dispatches relavent snapshot creation and deletion requests
through to the VFS asynchronously.
The registry smb.conf backend is used to expose snapshot shares, with
configuration parameters cloned from the base share.

There are three FSRVP client implementations that I'm aware of:
- Samba rpcclient includes fss_X commands.
- Windows Server 2012 includes diskshadow.exe.
- System Center 2012.

librpc/idl/fsrvp.idl
source3/Makefile.in
source3/rpc_server/fss/srv_fss_agent.c [new file with mode: 0644]
source3/rpc_server/fss/srv_fss_agent.h [new file with mode: 0644]
source3/rpc_server/fssd.c [new file with mode: 0644]
source3/rpc_server/rpc_config.c
source3/rpc_server/rpc_config.h
source3/rpc_server/wscript_build
source3/smbd/server.c
source3/wscript_build

index d61545b2717a211e008c586d7a3acead0734f5ff..a823c161410fbd07f84bebb42466860cfd032281 100644 (file)
@@ -70,7 +70,7 @@ import "misc.idl";
                [in] [string,charset(UTF16)] uint16 *ShareName,
                [out] GUID *pShadowCopyId);
 
-       DWORD fss_CommitShadowCopySet(
+       [async] DWORD fss_CommitShadowCopySet(
                [in] GUID ShadowCopySetId,
                [in] uint32 TimeOutInMilliseconds);
 
@@ -102,7 +102,7 @@ import "misc.idl";
                [in] uint32 Level,
                [out,switch_is(Level)] fssagent_share_mapping *ShareMapping);
 
-       DWORD fss_DeleteShareMapping(
+       [async] DWORD fss_DeleteShareMapping(
                [in] GUID ShadowCopySetId,
                [in] GUID ShadowCopyId,
                [in] [string,charset(UTF16)] uint16 *ShareName);
index 6cbbb7187780bb9614590c0c7065f381adc348f9..eabc71ca2c37cf92ab18860e4678b316fafa3522 100644 (file)
@@ -793,6 +793,8 @@ RPC_EPMAPPER_OBJ = rpc_server/epmapper/srv_epmapper.o autoconf/librpc/gen_ndr/sr
 
 RPC_SERVER_REGISTER_OBJ = rpc_server/rpc_ep_register.o $(DCE_RPC_EP_OBJ)
 
+RPC_FSS_AGENT_OBJ = rpc_server/fss/srv_fss_agent.o autoconf/librpc/gen_ndr/srv_fsrvp.o
+
 RPC_SERVER_OBJ = $(RPC_LSARPC_OBJ) $(RPC_WINREG_OBJ) $(RPC_INITSHUTDOWN_OBJ) \
                 $(RPC_DSSETUP_OBJ) $(RPC_WKSSVC_OBJ) $(RPC_SVCCTL_OBJ) \
                 $(RPC_NTSVCS_OBJ) $(RPC_NETLOGON_OBJ) $(RPC_NETDFS_OBJ) \
@@ -805,6 +807,7 @@ RPC_SERVER_OBJ = $(RPC_LSARPC_OBJ) $(RPC_WINREG_OBJ) $(RPC_INITSHUTDOWN_OBJ) \
                 $(LIBCLI_LSA_OBJ) \
                 $(LIBCLI_SAMR_OBJ) \
                 $(RPC_SERVER_REGISTER_OBJ) \
+                $(RPC_FSS_AGENT_OBJ) \
                 $(RPC_CLIENT_SCHANNEL_OBJ) \
                 rpc_server/rpc_sock_helper.o \
                 rpc_server/rpc_service_setup.o \
@@ -949,6 +952,7 @@ SMBD_OBJ_SRV = smbd/server_reload.o \
               smbd/process.o smbd/service.o param/service.o smbd/error.o \
               rpc_server/epmd.o \
               rpc_server/lsasd.o \
+              rpc_server/fssd.o \
               printing/printspoolss.o printing/spoolssd.o \
               lib/sysquotas.o lib/sysquotas_linux.o \
               lib/sysquotas_xfs.o lib/sysquotas_4A.o lib/sysquotas_4B.o \
diff --git a/source3/rpc_server/fss/srv_fss_agent.c b/source3/rpc_server/fss/srv_fss_agent.c
new file mode 100644 (file)
index 0000000..c47ecdc
--- /dev/null
@@ -0,0 +1,1344 @@
+/*
+ * 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)) {
+               sc->sc_set->state = FSS_SC_COMMITED;
+               tevent_req_done(req);
+       } else {
+               /* TODO cleanup */
+               sc->sc_set->state = FSS_SC_ADDED;
+               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);
+
+       if (!NT_STATUS_IS_OK(commit_state->status)) {
+               uint32_t ret;
+               DEBUG(0, ("sc set commit failed: %s\n",
+                         nt_errstr(commit_state->status)));
+               ret = fss_ntstatus_map(commit_state->status);
+               tevent_req_received(req);
+               return ret;
+       }
+
+       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;
+};
+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)) {
+               tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
+               return tevent_req_post(req, ev);
+       }
+
+       sc_set = sc_set_lookup(fss_global.sc_sets, &r->in.ShadowCopySetId);
+       if (sc_set == NULL) {
+               /* docs say E_INVALIDARG */
+               tevent_req_nterror(req, NT_STATUS_OBJECT_NAME_INVALID);
+               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)) {
+               tevent_req_nterror(req, NT_STATUS_INVALID_SERVER_STATE);
+               return tevent_req_post(req, ev);
+       }
+
+       sc = sc_lookup(sc_set->scs, &r->in.ShadowCopyId);
+       if (sc == NULL) {
+               /* docs say E_INVALIDARG */
+               tevent_req_nterror(req, NT_STATUS_OBJECT_NAME_INVALID);
+               return tevent_req_post(req, ev);
+       }
+       delete_state->sc = sc;
+       delete_state->snum = sc->smaps->snum;
+
+       share = strrchr(r->in.ShareName, '\\');
+       if (share++ == NULL) {
+               tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+               return tevent_req_post(req, ev);
+       }
+
+       sc_smap = sc_smap_lookup(sc->smaps, share);
+       if (sc_smap == NULL) {
+               tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+               return tevent_req_post(req, ev);
+       }
+       delete_state->sc_smap = sc_smap;
+
+       status = sc_smap_unexpose(p->msg_ctx, sc_smap);
+       if (tevent_req_nterror(req, status)) {
+               DEBUG(0, ("failed to remove share %s: %s\n",
+                         sc_smap->sc_share_name, nt_errstr(status)));
+               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 */
+               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 (tevent_req_nterror(req, status)) {
+               return tevent_req_post(req, ev);
+       }
+
+       vfs_req = SMB_VFS_SNAP_DELETE_SEND(conn, delete_state, ev,
+                                          sc->volume_name, sc->sc_path);
+       fss_vfs_conn_unbecome(conn);
+       if (tevent_req_nomem(vfs_req, req)) {
+               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 (tevent_req_nterror(req, status)) {
+               return;
+       }
+
+       status = SMB_VFS_SNAP_DELETE_RECV(conn, subreq);
+       fss_vfs_conn_unbecome(conn);
+       if (tevent_req_nterror(req, status)) {
+               DEBUG(0, ("bad snap delete recv: %s\n",
+                         nt_errstr(status)));
+               return;
+       }
+
+       DEBUG(6, ("good snap delete recv\n"));
+       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);
+               }
+       }
+
+       tevent_req_done(req);
+}
+
+uint32_t _fss_DeleteShareMapping_recv(struct tevent_req *req)
+{
+       NTSTATUS status;
+
+       if (tevent_req_is_nterror(req, &status)) {
+               tevent_req_received(req);
+               return fss_ntstatus_map(status);
+       }
+
+       tevent_req_received(req);
+       return 0;
+}
+
+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;
+}
diff --git a/source3/rpc_server/fss/srv_fss_agent.h b/source3/rpc_server/fss/srv_fss_agent.h
new file mode 100644 (file)
index 0000000..d7a9b2b
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * 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/>.
+ */
+
+#ifndef _SRV_FSS_AGENT_H_
+#define _SRV_FSS_AGENT_H_
+
+void srv_fssa_start(void);
+void srv_fssa_cleanup(void);
+
+#endif /*_SRV_FSS_AGENT_H_ */
diff --git a/source3/rpc_server/fssd.c b/source3/rpc_server/fssd.c
new file mode 100644 (file)
index 0000000..5f044ba
--- /dev/null
@@ -0,0 +1,223 @@
+/*
+ *  Unix SMB/CIFS implementation.
+ *
+ *  File Server Shadow-Copy Daemon
+ *
+ *  Copyright (C) David Disseldorp     2012
+ *
+ *  Based on epmd.c:
+ *  Copyright (c) 2011      Andreas Schneider <asn@samba.org>
+ *
+ *  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 "serverid.h"
+#include "ntdomain.h"
+#include "messages.h"
+
+#include "librpc/rpc/dcerpc_ep.h"
+#include "../librpc/gen_ndr/srv_fsrvp.h"
+#include "rpc_server/rpc_server.h"
+#include "rpc_server/rpc_sock_helper.h"
+#include "rpc_server/fss/srv_fss_agent.h"
+
+#define DAEMON_NAME "fssd"
+
+void start_fssd(struct tevent_context *ev_ctx,
+               struct messaging_context *msg_ctx);
+
+static void fssd_reopen_logs(void)
+{
+       char *lfile = lp_logfile(NULL);
+       int rc;
+
+       if (lfile == NULL || lfile[0] == '\0') {
+               rc = asprintf(&lfile, "%s/log.%s", get_dyn_LOGFILEBASE(), DAEMON_NAME);
+               if (rc > 0) {
+                       lp_set_logfile(lfile);
+                       SAFE_FREE(lfile);
+               }
+       } else {
+               if (strstr(lfile, DAEMON_NAME) == NULL) {
+                       rc = asprintf(&lfile, "%s.%s", lp_logfile(NULL), DAEMON_NAME);
+                       if (rc > 0) {
+                               lp_set_logfile(lfile);
+                               SAFE_FREE(lfile);
+                       }
+               }
+       }
+
+       reopen_logs();
+}
+
+static void fssd_smb_conf_updated(struct messaging_context *msg,
+                                 void *private_data,
+                                 uint32_t msg_type,
+                                 struct server_id server_id,
+                                 DATA_BLOB *data)
+{
+       DEBUG(10, ("Got message saying smb.conf was updated. Reloading.\n"));
+       change_to_root_user();
+       fssd_reopen_logs();
+}
+
+static void fssd_sig_term_handler(struct tevent_context *ev,
+                                 struct tevent_signal *se,
+                                 int signum,
+                                 int count,
+                                 void *siginfo,
+                                 void *private_data)
+{
+       rpc_FileServerVssAgent_shutdown();
+
+       exit_server_cleanly("termination signal");
+}
+
+static void fssd_setup_sig_term_handler(struct tevent_context *ev_ctx)
+{
+       struct tevent_signal *se;
+
+       se = tevent_add_signal(ev_ctx,
+                              ev_ctx,
+                              SIGTERM, 0,
+                              fssd_sig_term_handler,
+                              NULL);
+       if (se == NULL) {
+               exit_server("failed to setup SIGTERM handler");
+       }
+}
+
+static void fssd_sig_hup_handler(struct tevent_context *ev,
+                                   struct tevent_signal *se,
+                                   int signum,
+                                   int count,
+                                   void *siginfo,
+                                   void *private_data)
+{
+       change_to_root_user();
+
+       DEBUG(1,("reopening logs after SIGHUP\n"));
+       fssd_reopen_logs();
+}
+
+static void fssd_setup_sig_hup_handler(struct tevent_context *ev_ctx,
+                                      struct messaging_context *msg_ctx)
+{
+       struct tevent_signal *se;
+
+       se = tevent_add_signal(ev_ctx,
+                              ev_ctx,
+                              SIGHUP, 0,
+                              fssd_sig_hup_handler,
+                              msg_ctx);
+       if (se == NULL) {
+               exit_server("failed to setup SIGHUP handler");
+       }
+}
+
+static bool fss_shutdown_cb(void *ptr)
+{
+       srv_fssa_cleanup();
+       return true;
+}
+
+static bool fss_init_cb(void *ptr)
+{
+       srv_fssa_start();
+       return true;
+}
+
+void start_fssd(struct tevent_context *ev_ctx,
+               struct messaging_context *msg_ctx)
+{
+       struct rpc_srv_callbacks fss_cb;
+       NTSTATUS status;
+       pid_t pid;
+       bool ok;
+       int rc;
+
+       fss_cb.init = fss_init_cb;
+       fss_cb.shutdown = fss_shutdown_cb;
+       fss_cb.private_data = NULL;
+
+       DEBUG(1, ("Forking File Server Shadow-copy Daemon\n"));
+
+       pid = fork();
+
+       if (pid == -1) {
+               DEBUG(0, ("failed to fork file server shadow-copy daemon [%s], "
+                         "aborting ...\n", strerror(errno)));
+               exit(1);
+       }
+
+       if (pid) {
+               /* parent */
+               return;
+       }
+
+       /* child */
+       status = reinit_after_fork(msg_ctx,
+                                  ev_ctx,
+                                  true);
+       if (!NT_STATUS_IS_OK(status)) {
+               DEBUG(0,("reinit_after_fork() failed\n"));
+               smb_panic("reinit_after_fork() failed");
+       }
+
+       fssd_reopen_logs();
+
+       fssd_setup_sig_term_handler(ev_ctx);
+       fssd_setup_sig_hup_handler(ev_ctx, msg_ctx);
+
+       ok = serverid_register(procid_self(),
+                              FLAG_MSG_GENERAL |
+                              FLAG_MSG_PRINT_GENERAL);
+       if (!ok) {
+               DEBUG(0, ("Failed to register serverid in fssd!\n"));
+               exit(1);
+       }
+
+       messaging_register(msg_ctx,
+                          ev_ctx,
+                          MSG_SMB_CONF_UPDATED,
+                          fssd_smb_conf_updated);
+
+       status = rpc_FileServerVssAgent_init(&fss_cb);
+       if (!NT_STATUS_IS_OK(status)) {
+               DEBUG(0, ("Failed to register fssd rpc inteface! (%s)\n",
+                         nt_errstr(status)));
+               exit(1);
+       }
+
+       /* case is normalized by smbd on connection */
+       ok = setup_named_pipe_socket("fssagentrpc", ev_ctx, msg_ctx);
+       if (!ok) {
+               DEBUG(0, ("Failed to open fssd named pipe!\n"));
+               exit(1);
+       }
+
+       DEBUG(1, ("File Server Shadow-copy Daemon Started (%d)\n", getpid()));
+
+       /* loop forever */
+       rc = tevent_loop_wait(ev_ctx);
+
+       /* should not be reached */
+       DEBUG(0,("tevent_loop_wait() exited with %d - %s\n",
+                rc, (rc == 0) ? "out of events" : strerror(errno)));
+
+       exit(1);
+}
+
index d3a46ddc612100ba2e3edc4baeb576a945f0228f..b9d199d7d1a8ace8ec3cdb8d70bbf922be36175b 100644 (file)
@@ -1,6 +1,6 @@
 /*
    Unix SMB/Netbios implementation.
-   Generic infrstructure for RPC Daemons
+   Generic infrastructure for RPC Daemons
    Copyright (C) Simo Sorce 2011
    Copyright (C) Andreas Schneider 2011
 
index d1c26dd3667811b58d18b00e6badcc9eed11d368..3bdae2a6c52a35b3ded32c5edde51aca151894bd 100644 (file)
@@ -33,7 +33,6 @@ enum rpc_service_mode_e {
  * @brief Get the mode in which service pipes are configured.
  *
  * @param name         Name of the service
- * @param def_mode     The default mode for the service
  *
  * @return The actual configured mode.
  */
@@ -44,6 +43,7 @@ enum rpc_service_mode_e rpc_service_mode(const char *name);
 #define rpc_lsarpc_mode() rpc_service_mode("lsarpc")
 #define rpc_samr_mode() rpc_service_mode("samr")
 #define rpc_netlogon_mode() rpc_service_mode("netlogon")
+#define rpc_fss_mode() rpc_service_mode("fss")
 
 
 
@@ -57,7 +57,6 @@ enum rpc_daemon_type_e {
  * @brief Get the mode in which a server is started.
  *
  * @param name         Name of the rpc server
- * @param def_type     The default type for the server
  *
  * @return The actual configured type.
  */
@@ -66,5 +65,6 @@ enum rpc_daemon_type_e rpc_daemon_type(const char *name);
 #define rpc_epmapper_daemon() rpc_daemon_type("epmd")
 #define rpc_spoolss_daemon() rpc_daemon_type("spoolssd")
 #define rpc_lsasd_daemon() rpc_daemon_type("lsasd")
+#define rpc_fss_daemon() rpc_daemon_type("fssd")
 
 #endif /* _RPC_CONFIG_H */
index bd96b92b25d9245d6a5de2941b450a6655a59477..1d09da72faf0603b034c14bd41577646b9ee8604 100755 (executable)
@@ -19,6 +19,7 @@ RPC_SPOOLSS_SRC = '''spoolss/srv_spoolss_nt.c ../../librpc/gen_ndr/srv_spoolss.c
 RPC_EVENTLOG_SRC = '''eventlog/srv_eventlog_nt.c eventlog/srv_eventlog_reg.c ../../librpc/gen_ndr/srv_eventlog.c'''
 RPC_RPCECHO_SRC = '''echo/srv_echo_nt.c ../../librpc/gen_ndr/srv_echo.c'''
 RPC_EPMAPPER_SRC = '''epmapper/srv_epmapper.c ../../librpc/gen_ndr/srv_epmapper.c'''
+RPC_FSS_AGENT_SRC = '''fss/srv_fss_agent.c ../../librpc/gen_ndr/srv_fsrvp.c'''
 
 bld.SAMBA3_SUBSYSTEM('rpc',
                     source='',
@@ -57,6 +58,10 @@ bld.SAMBA3_SUBSYSTEM('LSASD',
                     source='lsasd.c',
                     deps='samba-util')
 
+bld.SAMBA3_SUBSYSTEM('FSSD',
+                    source='fssd.c',
+                    deps='samba-util')
+
 bld.SAMBA3_SUBSYSTEM('SRV_ACCESS_CHECK',
                     source='srv_access_check.c',
                     deps='samba-util')
@@ -136,6 +141,11 @@ bld.SAMBA3_SUBSYSTEM('RPC_EPMAPPER',
                     deps='samba-util',
                     vars=locals())
 
+bld.SAMBA3_SUBSYSTEM('RPC_FSS_AGENT',
+                    source=RPC_FSS_AGENT_SRC,
+                    deps='samba-util',
+                    vars=locals())
+
 bld.SAMBA3_SUBSYSTEM('RPC_SERVER',
                     source='srv_pipe_hnd.c srv_pipe.c rpc_sock_helper.c rpc_service_setup.c',
                     deps='''RPC_CONFIG RPC_NCACN_NP RPC_SERVICE RPC_CRYPTO
@@ -143,4 +153,4 @@ bld.SAMBA3_SUBSYSTEM('RPC_SERVER',
                           RPC_DSSETUP RPC_WKSSVC RPC_SVCCTL RPC_NTSVCS
                           RPC_NETLOGON RPC_NETDFS RPC_SRVSVC RPC_SPOOLSS
                           RPC_EVENTLOG RPC_RPCECHO RPC_SERVER RPC_EPMAPPER
-                          RPC_SERVER_REGISTER NDR_NAMED_PIPE_AUTH''')
+                          RPC_SERVER_REGISTER NDR_NAMED_PIPE_AUTH RPC_FSS_AGENT''')
index 7113eaeb7256ff89aeecb6ec540183bf4e072138..d38cb200ec44e08e75920a36c87406c5c61e29e0 100644 (file)
@@ -83,6 +83,9 @@ extern void start_epmd(struct tevent_context *ev_ctx,
 extern void start_lsasd(struct event_context *ev_ctx,
                        struct messaging_context *msg_ctx);
 
+extern void start_fssd(struct event_context *ev_ctx,
+                      struct messaging_context *msg_ctx);
+
 #ifdef WITH_DFS
 extern int dcelogin_atmost_once;
 #endif /* WITH_DFS */
@@ -1496,6 +1499,10 @@ extern void build_options(bool screen);
                        start_lsasd(ev_ctx, msg_ctx);
                }
 
+               if (rpc_fss_daemon() == RPC_DAEMON_FORK) {
+                       start_fssd(ev_ctx, msg_ctx);
+               }
+
                if (!lp__disable_spoolss() &&
                    (rpc_spoolss_daemon() != RPC_DAEMON_DISABLED)) {
                        bool bgq = lp_parm_bool(-1, "smbd", "backgroundqueue", true);
index a3b4f0606f4424f56487ab1a51f4ebbf26acbf36..1cf6c0dc3feaa24f497a3ed71fb8871f7c6adfa3 100755 (executable)
@@ -1174,7 +1174,7 @@ bld.SAMBA3_SUBSYSTEM('INIT_SAMR',
 
 bld.SAMBA3_BINARY('smbd/smbd',
                  source='${SMBD_SRC_MAIN}',
-                 deps='smbd_base EPMD LSASD',
+                 deps='smbd_base EPMD LSASD FSSD',
                  install_path='${SBINDIR}',
                  vars=locals())