Windows: Service processing for Symlink creation
authorJeffrey Altman <jaltman@your-file-system.com>
Wed, 6 Mar 2013 14:53:41 +0000 (09:53 -0500)
committerJeffrey Altman <jaltman@your-file-system.com>
Tue, 12 Mar 2013 00:39:11 +0000 (17:39 -0700)
Initial support for symlink creation via the Win32 CreateSymbolicLink api.
Add support for AFS_REQUEST_TYPE_CREATE_SYMLINK redirector requests via
the new RDR_CreateSymlinkEntry() function.

Since CreateSymbolicLink api creates a new directory or file object and
then assigns the Microsoft reparse tag data to that object,
RDR_CreateSymlinkEntry must first delete the empty directory or file and
then create the new symlink object in its place.  If the empty object can
be removed but the symlink cannot be created, STATUS_FILE_DELETED is
returned to indicate to the redirector that a failure occurred that
changed the state of the directory without creating a new object.

If the empty object cannot be removed, a STATUS_ACCESS_DENIED error will
be returned and the empty object will unfortunately remain in the AFS
directory.

Target path translation is performed.  Absolute AFS paths are stored in
UNIX notation.  Absolute non-AFS UNC and device paths are prefaced with
"msdfs:".

Change-Id: If8b4729dd5fffddc71221750852b8be731c83cab
Reviewed-on: http://gerrit.openafs.org/9425
Tested-by: BuildBot <buildbot@rampaginggeek.com>
Reviewed-by: Jeffrey Altman <jaltman@your-file-system.com>
Tested-by: Jeffrey Altman <jaltman@your-file-system.com>

src/WINNT/afsrdr/user/RDRFunction.c
src/WINNT/afsrdr/user/RDRInit.cpp
src/WINNT/afsrdr/user/RDRPrototypes.h

index 8ee0ed8..3efcb04 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Copyright (c) 2008 Secure Endpoints, Inc.
- * Copyright (c) 2009-2011 Your File System, Inc.
+ * Copyright (c) 2009-2013 Your File System, Inc.
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -2859,6 +2859,285 @@ RDR_HardLinkFileEntry( IN cm_user_t *userp,
     return;
 }
 
+
+void
+RDR_CreateSymlinkEntry( IN cm_user_t *userp,
+                        IN AFSFileID FileId,
+                        IN WCHAR *FileNameCounted,
+                        IN DWORD FileNameLength,
+                        IN AFSCreateSymlinkCB *SymlinkCB,
+                        IN BOOL bWow64,
+                        IN DWORD ResultBufferLength,
+                        IN OUT AFSCommResult **ResultCB)
+{
+    AFSCreateSymlinkResultCB *pResultCB = NULL;
+    size_t size = sizeof(AFSCommResult) + ResultBufferLength - 1;
+    cm_fid_t            parentFid;
+    cm_fid_t            Fid;
+    afs_uint32          code;
+    cm_scache_t *       dscp = NULL;
+    afs_uint32          flags = 0;
+    cm_attr_t           setAttr;
+    cm_scache_t *       scp = NULL;
+    cm_req_t            req;
+    DWORD               status;
+    wchar_t             FileName[260];
+    char               *TargetPath = NULL;
+
+    StringCchCopyNW(FileName, 260, FileNameCounted, FileNameLength / sizeof(WCHAR));
+    TargetPath = cm_Utf16ToUtf8Alloc( SymlinkCB->TargetName,  SymlinkCB->TargetNameLength / sizeof(WCHAR), NULL);
+
+    osi_Log4( afsd_logp, "RDR_CreateSymlinkEntry parent FID cell=0x%x vol=0x%x vn=0x%x uniq=0x%x",
+              SymlinkCB->ParentId.Cell, SymlinkCB->ParentId.Volume,
+              SymlinkCB->ParentId.Vnode, SymlinkCB->ParentId.Unique);
+    osi_Log1(afsd_logp, "... name=%S", osi_LogSaveStringW(afsd_logp, FileName));
+
+    RDR_InitReq(&req, bWow64);
+    memset(&setAttr, 0, sizeof(cm_attr_t));
+
+    *ResultCB = (AFSCommResult *)malloc(size);
+    if (!(*ResultCB)) {
+        osi_Log0(afsd_logp, "RDR_CreateSymlinkEntry out of memory");
+        free(TargetPath);
+       return;
+    }
+
+    memset( *ResultCB,
+            '\0',
+            size);
+
+    parentFid.cell   = SymlinkCB->ParentId.Cell;
+    parentFid.volume = SymlinkCB->ParentId.Volume;
+    parentFid.vnode  = SymlinkCB->ParentId.Vnode;
+    parentFid.unique = SymlinkCB->ParentId.Unique;
+    parentFid.hash   = SymlinkCB->ParentId.Hash;
+
+    code = cm_GetSCache(&parentFid, NULL, &dscp, userp, &req);
+    if (code) {
+        smb_MapNTError(cm_MapRPCError(code, &req), &status, TRUE);
+        (*ResultCB)->ResultStatus = status;
+        if ( status == STATUS_INVALID_HANDLE)
+            status = STATUS_OBJECT_PATH_INVALID;
+        osi_Log2(afsd_logp, "RDR_CreateSymlinkEntry cm_GetSCache ParentFID failure code=0x%x status=0x%x",
+                  code, status);
+        free(TargetPath);
+        return;
+    }
+
+    lock_ObtainWrite(&dscp->rw);
+    code = cm_SyncOp(dscp, NULL, userp, &req, 0,
+                      CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_GETSTATUS);
+    if (code) {
+        smb_MapNTError(cm_MapRPCError(code, &req), &status, TRUE);
+        (*ResultCB)->ResultStatus = status;
+        lock_ReleaseWrite(&dscp->rw);
+        cm_ReleaseSCache(dscp);
+        osi_Log3(afsd_logp, "RDR_CreateSymlinkEntry cm_SyncOp failure (1) dscp=0x%p code=0x%x status=0x%x",
+                 dscp, code, status);
+        free(TargetPath);
+        return;
+    }
+
+    cm_SyncOpDone(dscp, NULL, CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_GETSTATUS);
+    lock_ReleaseWrite(&dscp->rw);
+
+    if (dscp->fileType != CM_SCACHETYPE_DIRECTORY) {
+        (*ResultCB)->ResultStatus = STATUS_NOT_A_DIRECTORY;
+        cm_ReleaseSCache(dscp);
+        osi_Log1(afsd_logp, "RDR_CreateSymlinkEntry Not a Directory dscp=0x%p",
+                 dscp);
+        free(TargetPath);
+        return;
+    }
+
+    Fid.cell   = FileId.Cell;
+    Fid.volume = FileId.Volume;
+    Fid.vnode  = FileId.Vnode;
+    Fid.unique = FileId.Unique;
+    Fid.hash   = FileId.Hash;
+
+    code = cm_GetSCache(&Fid, NULL, &scp, userp, &req);
+    if (code) {
+        smb_MapNTError(cm_MapRPCError(code, &req), &status, TRUE);
+        (*ResultCB)->ResultStatus = status;
+        if ( status == STATUS_INVALID_HANDLE)
+            status = STATUS_OBJECT_PATH_INVALID;
+        osi_Log2(afsd_logp, "RDR_CreateSymlinkEntry cm_GetSCache FID failure code=0x%x status=0x%x",
+                  code, status);
+        free(TargetPath);
+        return;
+    }
+
+    lock_ObtainWrite(&scp->rw);
+    code = cm_SyncOp(scp, NULL, userp, &req, 0,
+                      CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_GETSTATUS);
+    if (code) {
+        smb_MapNTError(cm_MapRPCError(code, &req), &status, TRUE);
+        (*ResultCB)->ResultStatus = status;
+        lock_ReleaseWrite(&scp->rw);
+        cm_ReleaseSCache(scp);
+        osi_Log3(afsd_logp, "RDR_CreateSymlinkEntry cm_SyncOp failure (1) scp=0x%p code=0x%x status=0x%x",
+                 scp, code, status);
+        free(TargetPath);
+        return;
+    }
+
+    cm_SyncOpDone(scp, NULL, CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_GETSTATUS);
+    lock_ReleaseWrite(&scp->rw);
+
+    /* Remove the temporary object */
+    if (scp->fileType == CM_SCACHETYPE_DIRECTORY)
+        code = cm_RemoveDir(dscp, NULL, FileName, userp, &req);
+    else
+        code = cm_Unlink(dscp, NULL, FileName, userp, &req);
+    cm_ReleaseSCache(scp);
+    scp = NULL;
+    if (code && code != CM_ERROR_NOSUCHFILE) {
+        smb_MapNTError(cm_MapRPCError(code, &req), &status, TRUE);
+        (*ResultCB)->ResultStatus = status;
+        cm_ReleaseSCache(dscp);
+        osi_Log2(afsd_logp, "RDR_CreateSymlinkEntry Unable to delete file dscp=0x%p code=0x%x",
+                 dscp, code);
+        free(TargetPath);
+        return;
+    }
+
+    /*
+     * The target path is going to be provided by the redirector in one of the following forms:
+     *
+     * 1. Relative path.
+     * 2. Absolute path prefaced as \??\UNC\<server>\<share>\<path>
+     * 3. Absolute path prefaced as \??\<drive-letter>:\<path>
+     *
+     * Relative paths can be used with just slash conversion.  Absolute paths must be converted.
+     * UNC paths with a server name that matches cm_NetbiosName then the path is an AFS path and
+     * it must be converted to /<server>/<share>/<path>.  Other UNC paths must be converted to
+     * msdfs:\\<server>\<share>\<path>.  Local disk paths should be converted to
+     * msdfs:<drive-letter>:<path>.
+     */
+
+    if ( TargetPath[0] == '\\' ) {
+        size_t nbNameLen = strlen(cm_NetbiosName);
+        size_t len;
+        char  *s;
+
+        if ( strncmp(TargetPath, "\\??\\UNC\\", 8) == 0) {
+
+            if (strncmp(&TargetPath[8], cm_NetbiosName, nbNameLen) == 0 &&
+                TargetPath[8 + nbNameLen] == '\\')
+            {
+                /* AFS path */
+                s = strdup(&TargetPath[8 + nbNameLen]);
+                free(TargetPath);
+                TargetPath = s;
+                for (; *s; s++) {
+                    if (*s == '\\')
+                        *s = '/';
+                }
+            } else {
+                /*
+                 * non-AFS UNC path (msdfs:\\server\share\path)
+                 * strlen("msdfs:\\") == 7 + 1 for the NUL
+                 */
+                len = 8 + strlen(&TargetPath[7]);
+                s = malloc(8 + strlen(&TargetPath[7]));
+                StringCbCopy(s, len, "msdfs:\\");
+                StringCbCat(s, len, &TargetPath[7]);
+                free(TargetPath);
+                TargetPath = s;
+            }
+        } else {
+            /* non-UNC path (msdfs:<drive>:\<path> */
+            s = strdup(&TargetPath[4]);
+            free(TargetPath);
+            TargetPath = s;
+        }
+
+    } else {
+        /* relative paths require slash conversion */
+        char *s = TargetPath;
+        for (; *s; s++) {
+            if (*s == '\\')
+                *s = '/';
+        }
+    }
+
+    /* Use current time */
+    setAttr.mask = CM_ATTRMASK_UNIXMODEBITS | CM_ATTRMASK_CLIENTMODTIME;
+    setAttr.unixModeBits = 0755;
+    setAttr.clientModTime = time(NULL);
+
+    code = cm_SymLink(dscp, FileName, TargetPath, flags, &setAttr, userp, &req, &scp);
+    free(TargetPath);
+
+    if (code == 0) {
+        wchar_t shortName[13]=L"";
+        cm_dirFid_t dfid;
+        DWORD dwRemaining;
+
+        if (dscp->flags & CM_SCACHEFLAG_ANYWATCH) {
+            smb_NotifyChange(FILE_ACTION_ADDED,
+                             FILE_NOTIFY_CHANGE_DIR_NAME,
+                             dscp, FileName, NULL, TRUE);
+        }
+
+        (*ResultCB)->ResultStatus = 0;  // We will be able to fit all the data in here
+
+        (*ResultCB)->ResultBufferLength = sizeof( AFSCreateSymlinkResultCB);
+
+        pResultCB = (AFSCreateSymlinkResultCB *)(*ResultCB)->ResultData;
+
+        dwRemaining = ResultBufferLength - sizeof( AFSCreateSymlinkResultCB) + sizeof( AFSDirEnumEntry);
+
+        lock_ObtainWrite(&dscp->rw);
+        code = cm_SyncOp(dscp, NULL, userp, &req, 0,
+                          CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_GETSTATUS);
+        if (code) {
+            smb_MapNTError(cm_MapRPCError(code, &req), &status, TRUE);
+            (*ResultCB)->ResultStatus = status;
+            lock_ReleaseWrite(&dscp->rw);
+            cm_ReleaseSCache(dscp);
+            cm_ReleaseSCache(scp);
+            osi_Log3(afsd_logp, "RDR_CreateSymlinkEntry cm_SyncOp failure (2) dscp=0x%p code=0x%x status=0x%x",
+                      dscp, code, status);
+            return;
+        }
+
+        pResultCB->ParentDataVersion.QuadPart = dscp->dataVersion;
+
+        cm_SyncOpDone(dscp, NULL, CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_GETSTATUS);
+        lock_ReleaseWrite(&dscp->rw);
+
+        if (cm_shortNames) {
+            dfid.vnode = htonl(scp->fid.vnode);
+            dfid.unique = htonl(scp->fid.unique);
+
+            if (!cm_Is8Dot3(FileName))
+                cm_Gen8Dot3NameIntW(FileName, &dfid, shortName, NULL);
+            else
+                shortName[0] = '\0';
+        }
+
+        code = RDR_PopulateCurrentEntry(&pResultCB->DirEnum, dwRemaining,
+                                        dscp, scp, userp, &req, FileName, shortName,
+                                        RDR_POP_FOLLOW_MOUNTPOINTS | RDR_POP_EVALUATE_SYMLINKS,
+                                        0, NULL, &dwRemaining);
+        cm_ReleaseSCache(scp);
+        (*ResultCB)->ResultBufferLength = ResultBufferLength - dwRemaining;
+        osi_Log0(afsd_logp, "RDR_CreateSymlinkEntry SUCCESS");
+    } else {
+        (*ResultCB)->ResultStatus = STATUS_FILE_DELETED;
+        (*ResultCB)->ResultBufferLength = 0;
+        osi_Log2(afsd_logp, "RDR_CreateSymlinkEntry FAILURE code=0x%x status=0x%x",
+                  code, STATUS_FILE_DELETED);
+    }
+
+    cm_ReleaseSCache(dscp);
+
+    return;
+}
+
+
 void
 RDR_FlushFileEntry( IN cm_user_t *userp,
                     IN AFSFileID FileId,
index 921bbbd..96d37b9 100644 (file)
@@ -779,6 +779,38 @@ RDR_ProcessRequest( AFSCommRequest *RequestBuffer)
             break;
         }
 
+        case AFS_REQUEST_TYPE_CREATE_SYMLINK:
+        {
+
+            AFSCreateSymlinkCB *pCreateSymlinkCB = (AFSCreateSymlinkCB *)((char *)RequestBuffer->Name + RequestBuffer->DataOffset);
+
+            WCHAR wchFileName[ 256];
+
+            if (afsd_logp->enabled) {
+                memset( wchFileName, '\0', 256 * sizeof( WCHAR));
+
+                memcpy( wchFileName,
+                        RequestBuffer->Name,
+                        RequestBuffer->NameLength);
+
+                swprintf( wchBuffer, L"ProcessRequest Processing AFS_REQUEST_TYPE_CREATE_SYMLINK Index %08lX File %S",
+                          RequestBuffer->RequestIndex, wchFileName);
+
+                osi_Log1(afsd_logp, "%S", osi_LogSaveStringW(afsd_logp, wchBuffer));
+            }
+
+            RDR_CreateSymlinkEntry( userp,
+                                    RequestBuffer->FileId,
+                                    RequestBuffer->Name,
+                                    RequestBuffer->NameLength,
+                                    pCreateSymlinkCB,
+                                    bWow64,
+                                    RequestBuffer->ResultBufferLength,
+                                    &pResultCB);
+
+            break;
+        }
+
         case AFS_REQUEST_TYPE_REQUEST_FILE_EXTENTS:
         {
 
index a29d9b5..21cab52 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Copyright (c) 2008 Secure Endpoints, Inc.
- * Copyright (c) 2009-2011 Your File System, Inc.
+ * Copyright (c) 2009-2013 Your File System, Inc.
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -144,6 +144,16 @@ RDR_HardLinkFileEntry( IN cm_user_t *userp,
                        IN OUT AFSCommResult **ResultCB);
 
 void
+RDR_CreateSymlinkEntry( IN cm_user_t *userp,
+                        IN AFSFileID FileId,
+                        IN WCHAR *FileName,
+                        IN DWORD FileNameLength,
+                        IN AFSCreateSymlinkCB *SymlinkCB,
+                        IN BOOL bWow64,
+                        IN DWORD ResultBufferLength,
+                        IN OUT AFSCommResult **ResultCB);
+
+void
 RDR_FlushFileEntry( IN cm_user_t *userp,
                     IN AFSFileID FileId,
                     IN BOOL bWow64,