Windows: pioctl path retrying with \\afs\all
[openafs.git] / src / sys / pioctl_nt.c
index 22125fc..6d851c2 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * Copyright 2000, International Business Machines Corporation and others.
  * All Rights Reserved.
- * 
+ *
  * This software has been released under the terms of the IBM Public
  * License.  For details, see the LICENSE file in the top-level source
  * directory or online at http://www.openafs.org/dl/license10.html
@@ -9,17 +9,11 @@
 
 #include <afsconfig.h>
 #include <afs/param.h>
+#include <afs/stds.h>
 
-RCSID
-    ("$Header$");
+#include <roken.h>
 
-#include <afs/stds.h>
 #include <windows.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <errno.h>
-#include <malloc.h>
-#include <string.h>
 #include <winioctl.h>
 #include <winsock2.h>
 #define SECURITY_WIN32
@@ -39,7 +33,7 @@ RCSID
 #include <cm_dir.h>
 #include <cm_utils.h>
 #include <cm_ioctl.h>
-
+#include <smb_iocons.h>
 #include <smb.h>
 #include <pioctl_nt.h>
 #include <WINNT/afsreg.h>
@@ -128,7 +122,7 @@ IoctlDebug(void)
     if ( !init ) {
         HKEY hk;
 
-        if (RegOpenKey (HKEY_LOCAL_MACHINE, 
+        if (RegOpenKey (HKEY_LOCAL_MACHINE,
                          TEXT("Software\\OpenAFS\\Client"), &hk) == 0)
         {
             DWORD dwSize = sizeof(BOOL);
@@ -152,7 +146,7 @@ DisableServiceManagerCheck(void)
     if ( !init ) {
         HKEY hk;
 
-        if (RegOpenKey (HKEY_LOCAL_MACHINE, 
+        if (RegOpenKey (HKEY_LOCAL_MACHINE,
                          TEXT("Software\\OpenAFS\\Client"), &hk) == 0)
         {
             DWORD dwSize = sizeof(BOOL);
@@ -167,61 +161,61 @@ DisableServiceManagerCheck(void)
     return smcheck;
 }
 
-static DWORD 
+static DWORD
 GetServiceStatus(
-    LPSTR lpszMachineName, 
+    LPSTR lpszMachineName,
     LPSTR lpszServiceName,
-    DWORD *lpdwCurrentState) 
-{ 
-    DWORD           hr               = NOERROR; 
-    SC_HANDLE       schSCManager     = NULL; 
-    SC_HANDLE       schService       = NULL; 
-    DWORD           fdwDesiredAccess = 0; 
-    SERVICE_STATUS  ssServiceStatus  = {0}; 
-    BOOL            fRet             = FALSE; 
-
-    *lpdwCurrentState = 0; 
-    fdwDesiredAccess = GENERIC_READ; 
-    schSCManager = OpenSCManager(lpszMachineName,  
+    DWORD *lpdwCurrentState)
+{
+    DWORD           hr               = NOERROR;
+    SC_HANDLE       schSCManager     = NULL;
+    SC_HANDLE       schService       = NULL;
+    DWORD           fdwDesiredAccess = 0;
+    SERVICE_STATUS  ssServiceStatus  = {0};
+    BOOL            fRet             = FALSE;
+
+    *lpdwCurrentState = 0;
+
+    fdwDesiredAccess = GENERIC_READ;
+
+    schSCManager = OpenSCManager(lpszMachineName,
                                  NULL,
-                                 fdwDesiredAccess); 
-    if(schSCManager == NULL) 
-    { 
+                                 fdwDesiredAccess);
+
+    if(schSCManager == NULL)
+    {
         hr = GetLastError();
-        goto cleanup; 
-    } 
+        goto cleanup;
+    }
+
     schService = OpenService(schSCManager,
                              lpszServiceName,
-                             fdwDesiredAccess); 
-    if(schService == NULL) 
-    { 
+                             fdwDesiredAccess);
+
+    if(schService == NULL)
+    {
         hr = GetLastError();
-        goto cleanup; 
-    } 
+        goto cleanup;
+    }
+
     fRet = QueryServiceStatus(schService,
-                              &ssServiceStatus); 
-    if(fRet == FALSE) 
-    { 
-        hr = GetLastError(); 
-        goto cleanup; 
-    } 
-    *lpdwCurrentState = ssServiceStatus.dwCurrentState; 
-cleanup: 
-    CloseServiceHandle(schService); 
-    CloseServiceHandle(schSCManager); 
-    return(hr); 
-} 
+                              &ssServiceStatus);
+
+    if(fRet == FALSE)
+    {
+        hr = GetLastError();
+        goto cleanup;
+    }
+
+    *lpdwCurrentState = ssServiceStatus.dwCurrentState;
+
+cleanup:
+
+    CloseServiceHandle(schService);
+    CloseServiceHandle(schSCManager);
+
+    return(hr);
+}
 
 // krb5 functions
 DECL_FUNC_PTR(krb5_cc_default_name);
@@ -397,6 +391,59 @@ GetLSAPrincipalName(char * szUser, DWORD *dwSize)
     return success;
 }
 
+//
+// Recursively evaluate drivestr to find the final
+// dos drive letter to which the source is mapped.
+//
+static BOOL
+DriveSubstitution(char *drivestr, char *subststr, size_t substlen)
+{
+    char device[MAX_PATH];
+
+    if ( QueryDosDevice(drivestr, device, MAX_PATH) )
+    {
+        if ( device[0] == '\\' &&
+             device[1] == '?' &&
+             device[2] == '?' &&
+             device[3] == '\\' &&
+             isalpha(device[4]) &&
+             device[5] == ':')
+        {
+            device[0] = device[4];
+            device[1] = ':';
+            device[2] = '\0';
+            if ( DriveSubstitution(device, subststr, substlen) )
+            {
+                return TRUE;
+            } else {
+                subststr[0] = device[0];
+                subststr[1] = ':';
+                subststr[2] = '\0';
+                return TRUE;
+            }
+        } else
+        if ( device[0] == '\\' &&
+             device[1] == '?' &&
+             device[2] == '?' &&
+             device[3] == '\\' &&
+             device[4] == 'U' &&
+             device[5] == 'N' &&
+             device[6] == 'C' &&
+             device[7] == '\\')
+        {
+             subststr[0] = '\\';
+             strncpy(&subststr[1], &device[7], substlen-1);
+             subststr[substlen-1] = '\0';
+             return TRUE;
+        }
+    }
+
+    return FALSE;
+}
+
+//
+// drivestr - is "<drive-letter>:"
+//
 static BOOL
 DriveIsMappedToAFS(char *drivestr, char *NetbiosName)
 {
@@ -407,6 +454,25 @@ DriveIsMappedToAFS(char *drivestr, char *NetbiosName)
     LPNETRESOURCE lpnrLocal;    // pointer to enumerated structures
     DWORD i;
     BOOL  bIsAFS = FALSE;
+    char  subststr[MAX_PATH];
+
+    //
+    // Handle drive letter substitution created with "SUBST <drive> <path>".
+    // If a substitution has occurred, use the target drive letter instead
+    // of the source.
+    //
+    if ( DriveSubstitution(drivestr, subststr, MAX_PATH) )
+    {
+        if (subststr[0] == '\\' &&
+            subststr[1] == '\\')
+        {
+            if (_strnicmp( &subststr[2], NetbiosName, strlen(NetbiosName)) == 0)
+                return TRUE;
+            else
+                return FALSE;
+        }
+        drivestr = subststr;
+    }
 
     //
     // Call the WNetOpenEnum function to begin the enumeration.
@@ -465,7 +531,7 @@ DriveIsMappedToAFS(char *drivestr, char *NetbiosName)
             break;
     }
     while (dwResultEnum != ERROR_NO_MORE_ITEMS);
-    
+
     //
     // Call the GlobalFree function to free the memory.
     //
@@ -478,6 +544,32 @@ DriveIsMappedToAFS(char *drivestr, char *NetbiosName)
     return bIsAFS;
 }
 
+static BOOL
+DriveIsGlobalAutoMapped(char *drivestr)
+{
+    DWORD dwResult;
+    HKEY hKey;
+    DWORD dwSubMountSize;
+    char szSubMount[260];
+    DWORD dwType;
+
+    dwResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
+                            AFSREG_CLT_SVC_PARAM_SUBKEY "\\GlobalAutoMapper",
+                            0, KEY_QUERY_VALUE, &hKey);
+    if (dwResult != ERROR_SUCCESS)
+        return FALSE;
+
+    dwSubMountSize = sizeof(szSubMount);
+    dwType = REG_SZ;
+    dwResult = RegQueryValueEx(hKey, drivestr, 0, &dwType, szSubMount, &dwSubMountSize);
+    RegCloseKey(hKey);
+
+    if (dwResult == ERROR_SUCCESS && dwType == REG_SZ)
+        return TRUE;
+    else
+        return FALSE;
+}
+
 static long
 GetIoctlHandle(char *fileNamep, HANDLE * handlep)
 {
@@ -497,6 +589,8 @@ GetIoctlHandle(char *fileNamep, HANDLE * handlep)
     DWORD gle;
     DWORD dwSize = sizeof(szUser);
     int saveerrno;
+    UINT driveType;
+    int sharingViolation;
 
     memset(HostName, '\0', sizeof(HostName));
     gethostname(HostName, sizeof(HostName));
@@ -511,25 +605,27 @@ GetIoctlHandle(char *fileNamep, HANDLE * handlep)
     if (fileNamep) {
         drivep = strchr(fileNamep, ':');
         if (drivep && (drivep - fileNamep) >= 1) {
-            UINT driveType;
             tbuffer[0] = *(drivep - 1);
             tbuffer[1] = ':';
-            tbuffer[2] = '\\';
-            tbuffer[3] = '\0';
+            tbuffer[2] = '\0';
 
             driveType = GetDriveType(tbuffer);
             switch (driveType) {
             case DRIVE_UNKNOWN:
             case DRIVE_REMOTE:
-                if (DriveIsMappedToAFS(tbuffer, netbiosName))
+                if (DriveIsMappedToAFS(tbuffer, netbiosName) ||
+                    DriveIsGlobalAutoMapped(tbuffer))
                     strcpy(&tbuffer[2], SMB_IOCTL_FILENAME);
-                else 
+                else
                     return -1;
                 break;
             default:
-                return -1;
+                if (DriveIsGlobalAutoMapped(tbuffer))
+                    strcpy(&tbuffer[2], SMB_IOCTL_FILENAME);
+                else
+                    return -1;
             }
-        } else if (fileNamep[0] == fileNamep[1] && 
+        } else if (fileNamep[0] == fileNamep[1] &&
                   (fileNamep[0] == '\\' || fileNamep[0] == '/'))
         {
             int count = 0, i = 0;
@@ -541,10 +637,10 @@ GetIoctlHandle(char *fileNamep, HANDLE * handlep)
                     count++;
                i++;
             }
-            if (fileNamep[i] == 0)
+            if (fileNamep[i] == 0 || (fileNamep[i-1] != '\\' && fileNamep[i-1] != '/'))
                 tbuffer[i++] = '\\';
             tbuffer[i] = 0;
-            strcat(tbuffer, SMB_IOCTL_FILENAME);
+            strcat(tbuffer, SMB_IOCTL_FILENAME_NOSLASH);
         } else {
             char curdir[MAX_PATH]="";
 
@@ -552,9 +648,26 @@ GetIoctlHandle(char *fileNamep, HANDLE * handlep)
             if ( curdir[1] == ':' ) {
                 tbuffer[0] = curdir[0];
                 tbuffer[1] = ':';
-                strcpy(tbuffer + 2, SMB_IOCTL_FILENAME);
+                tbuffer[2] = '\0';
+
+                driveType = GetDriveType(tbuffer);
+                switch (driveType) {
+                case DRIVE_UNKNOWN:
+                case DRIVE_REMOTE:
+                    if (DriveIsMappedToAFS(tbuffer, netbiosName) ||
+                        DriveIsGlobalAutoMapped(tbuffer))
+                        strcpy(&tbuffer[2], SMB_IOCTL_FILENAME);
+                    else
+                        return -1;
+                    break;
+                default:
+                    if (DriveIsGlobalAutoMapped(tbuffer))
+                        strcpy(&tbuffer[2], SMB_IOCTL_FILENAME);
+                    else
+                        return -1;
+                }
             } else if (curdir[0] == curdir[1] &&
-                       (curdir[0] == '\\' || curdir[0] == '/')) 
+                       (curdir[0] == '\\' || curdir[0] == '/'))
             {
                 int count = 0, i = 0;
 
@@ -565,7 +678,7 @@ GetIoctlHandle(char *fileNamep, HANDLE * handlep)
                        count++;
                    i++;
                 }
-                if (tbuffer[i] == 0)
+                if (curdir[i] == 0 || (curdir[i-1] != '\\' && curdir[i-1] != '/'))
                     tbuffer[i++] = '\\';
                 tbuffer[i] = 0;
                 strcat(tbuffer, SMB_IOCTL_FILENAME_NOSLASH);
@@ -579,19 +692,24 @@ GetIoctlHandle(char *fileNamep, HANDLE * handlep)
 
     fflush(stdout);
     /* now open the file */
-    fh = CreateFile(tbuffer, GENERIC_READ | GENERIC_WRITE,
-                   FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
-                   FILE_FLAG_WRITE_THROUGH, NULL);
-
-       fflush(stdout);
+    sharingViolation = 0;
+    do {
+        if (sharingViolation)
+            Sleep(100);
+        fh = CreateFile(tbuffer, FILE_READ_DATA | FILE_WRITE_DATA,
+                        FILE_SHARE_READ, NULL, OPEN_EXISTING,
+                        FILE_FLAG_WRITE_THROUGH, NULL);
+        sharingViolation++;
+    } while (fh == INVALID_HANDLE_VALUE &&
+             GetLastError() == ERROR_SHARING_VIOLATION &&
+             sharingViolation < 100);
+    fflush(stdout);
 
     if (fh == INVALID_HANDLE_VALUE) {
-        int  gonext = 0;
-
         gle = GetLastError();
         if (gle && ioctlDebug ) {
             char buf[4096];
-            
+
             saveerrno = errno;
             if ( FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,
                                NULL,
@@ -606,11 +724,17 @@ GetIoctlHandle(char *fileNamep, HANDLE * handlep)
                         tbuffer,gle,buf);
             }
             errno = saveerrno;
+            SetLastError(gle);
         }
+    }
+
+    if (fh == INVALID_HANDLE_VALUE &&
+        GetLastError() != ERROR_SHARING_VIOLATION) {
+        int  gonext = 0;
 
         lana_GetNetbiosName(szClient, LANA_NETBIOS_NAME_FULL);
 
-        if (RegOpenKey (HKEY_CURRENT_USER, 
+        if (RegOpenKey (HKEY_CURRENT_USER,
                          TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer"), &hk) == 0)
         {
             DWORD dwType = REG_SZ;
@@ -655,9 +779,17 @@ GetIoctlHandle(char *fileNamep, HANDLE * handlep)
             if (gonext)
                 goto try_lsa_principal;
 
-            fh = CreateFile(tbuffer, GENERIC_READ | GENERIC_WRITE,
-                             FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
-                             FILE_FLAG_WRITE_THROUGH, NULL);
+            sharingViolation = 0;
+            do {
+                if (sharingViolation)
+                    Sleep(100);
+                fh = CreateFile(tbuffer, FILE_READ_DATA | FILE_WRITE_DATA,
+                                FILE_SHARE_READ, NULL, OPEN_EXISTING,
+                                FILE_FLAG_WRITE_THROUGH, NULL);
+                sharingViolation++;
+            } while (fh == INVALID_HANDLE_VALUE &&
+                     GetLastError() == ERROR_SHARING_VIOLATION &&
+                     sharingViolation < 100);
             fflush(stdout);
             if (fh == INVALID_HANDLE_VALUE) {
                 gle = GetLastError();
@@ -678,13 +810,15 @@ GetIoctlHandle(char *fileNamep, HANDLE * handlep)
                                  tbuffer,gle,buf);
                     }
                     errno = saveerrno;
+                    SetLastError(gle);
                 }
             }
         }
     }
 
   try_lsa_principal:
-    if (fh == INVALID_HANDLE_VALUE) {
+    if (fh == INVALID_HANDLE_VALUE &&
+        GetLastError() != ERROR_SHARING_VIOLATION) {
         int  gonext = 0;
 
         dwSize = sizeof(szUser);
@@ -725,9 +859,17 @@ GetIoctlHandle(char *fileNamep, HANDLE * handlep)
             if (gonext)
                 goto try_sam_compat;
 
-            fh = CreateFile(tbuffer, GENERIC_READ | GENERIC_WRITE,
-                             FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
-                             FILE_FLAG_WRITE_THROUGH, NULL);
+            sharingViolation = 0;
+            do {
+                if (sharingViolation)
+                    Sleep(100);
+                fh = CreateFile(tbuffer, FILE_READ_DATA | FILE_WRITE_DATA,
+                                FILE_SHARE_READ, NULL, OPEN_EXISTING,
+                                FILE_FLAG_WRITE_THROUGH, NULL);
+                sharingViolation++;
+            } while (fh == INVALID_HANDLE_VALUE &&
+                     GetLastError() == ERROR_SHARING_VIOLATION &&
+                     sharingViolation < 100);
             fflush(stdout);
             if (fh == INVALID_HANDLE_VALUE) {
                 gle = GetLastError();
@@ -748,14 +890,15 @@ GetIoctlHandle(char *fileNamep, HANDLE * handlep)
                                  tbuffer,gle,buf);
                     }
                     errno = saveerrno;
-
+                    SetLastError(gle);
                 }
             }
         }
     }
 
   try_sam_compat:
-    if ( fh == INVALID_HANDLE_VALUE ) {
+    if (fh == INVALID_HANDLE_VALUE &&
+        GetLastError() != ERROR_SHARING_VIOLATION) {
         dwSize = sizeof(szUser);
         if (GetUserNameEx(NameSamCompatible, szUser, &dwSize)) {
             if ( ioctlDebug ) {
@@ -790,9 +933,17 @@ GetIoctlHandle(char *fileNamep, HANDLE * handlep)
                 return -1;
             }
 
-            fh = CreateFile(tbuffer, GENERIC_READ | GENERIC_WRITE,
-                             FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
-                             FILE_FLAG_WRITE_THROUGH, NULL);
+            sharingViolation = 0;
+            do {
+                if (sharingViolation)
+                    Sleep(100);
+                fh = CreateFile(tbuffer, FILE_READ_DATA | FILE_WRITE_DATA,
+                                FILE_SHARE_READ, NULL, OPEN_EXISTING,
+                                FILE_FLAG_WRITE_THROUGH, NULL);
+                sharingViolation++;
+            } while (fh == INVALID_HANDLE_VALUE &&
+                     GetLastError() == ERROR_SHARING_VIOLATION &&
+                     sharingViolation < 100);
             fflush(stdout);
             if (fh == INVALID_HANDLE_VALUE) {
                 gle = GetLastError();
@@ -822,6 +973,9 @@ GetIoctlHandle(char *fileNamep, HANDLE * handlep)
         }
     }
 
+    if (fh == INVALID_HANDLE_VALUE)
+        return -1;
+
     /* return fh and success code */
     *handlep = fh;
     return 0;
@@ -973,7 +1127,7 @@ fs_GetFullPath(char *pathp, char *outPathp, long outSize)
        pathHasDrive = 0;
     }
 
-    if ( firstp[0] == '\\' && firstp[1] == '\\' || 
+    if ( firstp[0] == '\\' && firstp[1] == '\\' ||
         firstp[0] == '/' && firstp[1] == '/') {
         /* UNC path - strip off the server and sharename */
         int i, count;
@@ -1053,7 +1207,7 @@ fs_GetFullPath(char *pathp, char *outPathp, long outSize)
     /* if there is a non-null name after the drive, append it */
     if (*firstp != 0) {
         int len = (int)strlen(outPathp);
-        if (outPathp[len-1] != '\\' && outPathp[len-1] != '/') 
+        if (outPathp[len-1] != '\\' && outPathp[len-1] != '/')
             strcat(outPathp, "\\");
         strcat(outPathp, firstp);
     }
@@ -1070,15 +1224,104 @@ fs_GetFullPath(char *pathp, char *outPathp, long outSize)
     return 0;
 }
 
-static long
-pioctl_int(char *pathp, long opcode, struct ViceIoctl *blobp, int follow, int is_utf8)
+static int
+pioctl_int(char *pathp, afs_int32 opcode, struct ViceIoctl *blobp, afs_int32 follow, afs_int32 is_utf8)
 {
     fs_ioctlRequest_t preq;
     long code;
     long temp;
     char fullPath[1000];
+    char altPath[1024];
     HANDLE reqHandle;
     int save;
+    int i,j,count,all;
+
+    /*
+     * The pioctl operations for creating a mount point and a symlink are broken.
+     * Instead of 'pathp' referring to the directory object in which the symlink
+     * or mount point within which the new object is to be created, 'pathp' refers
+     * to the object itself.  This results in a problem when the object being created
+     * is located within the Freelance root.afs volume.  \\afs\foo will not be a
+     * valid share name since the 'foo' object does not yet exist.  Therefore,
+     * \\afs\foo\_._.afs_ioctl_._ cannot be opened.  Instead in these two cases
+     * we must force the use of the \\afs\all\foo form of the path.
+     *
+     * We cannot use this form in all cases because of smb submounts which are
+     * not located within the Freelance local root.
+     */
+    switch ( opcode ) {
+    case VIOC_AFS_CREATE_MT_PT:
+    case VIOC_SYMLINK:
+        if (pathp &&
+             (pathp[0] == '\\' && pathp[1] == '\\' ||
+              pathp[0] == '/' && pathp[1] == '/')) {
+            for (all = count = j = 0; pathp[j]; j++) {
+                if (pathp[j] == '\\' || pathp[j] == '/')
+                    count++;
+
+                /* Test to see if the second component is 'all' */
+                if (count == 3) {
+                    all = 1;
+                    for (i=0; pathp[i+j]; i++) {
+                        switch(i) {
+                        case 0:
+                            if (pathp[i+j] != 'a' &&
+                                pathp[i+j] != 'A') {
+                                all = 0;
+                                goto notall;
+                            }
+                            break;
+                        case 1:
+                        case 2:
+                            if (pathp[i+j] != 'l' &&
+                                 pathp[i+j] != 'L') {
+                                all = 0;
+                                goto notall;
+                            }
+                            break;
+                        default:
+                            all = 0;
+                            goto notall;
+                        }
+                    }
+                    if (i != 3)
+                        all = 0;
+                }
+
+              notall:
+                if (all)
+                    break;
+            }
+
+            /*
+             * if count is three and the second component is not 'all',
+             * then we are attempting to create an object in the
+             * Freelance root.afs volume.  Substitute the path.
+             */
+
+            if (count == 3 && !all) {
+                /* Normalize the name to use \\afs\all as the root */
+                for (count = i = j = 0; pathp[j] && i < sizeof(altPath); j++) {
+                    if (pathp[j] == '\\' || pathp[j] == '/') {
+                        altPath[i++] = '\\';
+                        count++;
+
+                        if (count == 3) {
+                            altPath[i++] = 'a';
+                            altPath[i++] = 'l';
+                            altPath[i++] = 'l';
+                            altPath[i++] = '\\';
+                            count++;
+                        }
+                    } else {
+                        altPath[i++] = pathp[j];
+                    }
+                }
+                altPath[i] = '\0';
+                pathp = altPath;
+            }
+        }
+    }
 
     code = GetIoctlHandle(pathp, &reqHandle);
     if (code) {
@@ -1158,14 +1401,14 @@ pioctl_int(char *pathp, long opcode, struct ViceIoctl *blobp, int follow, int is
     return 0;
 }
 
-long
-pioctl_utf8(char * pathp, long opcode, struct ViceIoctl * blobp, int follow)
+int
+pioctl_utf8(char * pathp, afs_int32 opcode, struct ViceIoctl * blobp, afs_int32 follow)
 {
     return pioctl_int(pathp, opcode, blobp, follow, TRUE);
 }
 
-long
-pioctl(char * pathp, long opcode, struct ViceIoctl * blobp, int follow)
+int
+pioctl(char * pathp, afs_int32 opcode, struct ViceIoctl * blobp, afs_int32 follow)
 {
     return pioctl_int(pathp, opcode, blobp, follow, FALSE);
 }