Windows: cm_BPlusDirNextEnumEntry return all errors
[openafs.git] / src / WINNT / afsd / fs_utils.c
1 /*
2  * Copyright 2000, International Business Machines Corporation and others.
3  * All Rights Reserved.
4  *
5  * This software has been released under the terms of the IBM Public
6  * License.  For details, see the LICENSE file in the top-level source
7  * directory or online at http://www.openafs.org/dl/license10.html
8  */
9
10 #include <afsconfig.h>
11 #include <afs/param.h>
12 #include <roken.h>
13
14 #include <afs/opr.h>
15 #include <afs/stds.h>
16 #include <afs/afs_consts.h>
17
18 #include <windows.h>
19 #include <stdlib.h>
20 #include <stdio.h>
21 #include <winioctl.h>
22 #include <winsock2.h>
23 #include <nb30.h>
24
25 #include <errno.h>
26 #include <malloc.h>
27 #include <string.h>
28 #include <strsafe.h>
29
30 #include <osi.h>
31 #include <afsd.h>
32 #include <smb.h>
33 #include <afs/cmd.h>
34 #include <afs/opr_assert.h>
35 #include <fs_utils.h>
36 #include <WINNT\afsreg.h>
37
38 long
39 fs_ExtractDriveLetter(char *inPathp, char *outPathp)
40 {
41     if (inPathp[0] != 0 && inPathp[1] == ':') {
42         /* there is a drive letter */
43         *outPathp++ = *inPathp++;
44         *outPathp++ = *inPathp++;
45         *outPathp++ = 0;
46     }
47     else
48         *outPathp = 0;
49
50     return 0;
51 }
52
53 /* strip the drive letter from a component */
54 long
55 fs_StripDriveLetter(char *inPathp, char *outPathp, size_t outSize)
56 {
57     char tempBuffer[1000];
58
59     if( FAILED(StringCbCopy(tempBuffer, sizeof(tempBuffer), inPathp))) {
60         fprintf (stderr, "fs_StripDriveLetter - not enough space on input");
61         exit(1);
62     }
63     if (tempBuffer[0] != 0 && tempBuffer[1] == ':') {
64         /* drive letter present */
65         if( FAILED(StringCbCopy(outPathp, outSize, tempBuffer+2))) {
66             fprintf (stderr, "fs_StripDriveLetter - not enough space on output");
67             exit(1);
68         }
69     }
70     else {
71         /* no drive letter present */
72         if( FAILED(StringCbCopy(outPathp, outSize, tempBuffer))) {
73             fprintf (stderr, "fs_StripDriveLetter - not enough space on output");
74             exit(1);
75         }
76     }
77     return 0;
78 }
79
80 /* take a path with a drive letter, possibly relative, and return a full path
81  * without the drive letter.  This is the full path relative to the working
82  * dir for that drive letter.  The input and output paths can be the same.
83  */
84 long
85 fs_GetFullPath(char *pathp, char *outPathp, size_t outSize)
86 {
87     char tpath[1000];
88     char origPath[1000];
89     char *firstp;
90     long code;
91     int pathHasDrive;
92     int doSwitch;
93     char newPath[3];
94
95     if (pathp[0] != 0 && pathp[1] == ':') {
96         /* there's a drive letter there */
97         firstp = pathp+2;
98         pathHasDrive = 1;
99     } else {
100         firstp = pathp;
101         pathHasDrive = 0;
102     }
103
104     if (*firstp == '\\' || *firstp == '/') {
105         /* already an absolute pathname, just copy it back */
106         if( FAILED(StringCbCopy(outPathp, outSize, firstp))) {
107             fprintf (stderr, "fs_GetFullPath - not enough space on output");
108             exit(1);
109         }
110         return 0;
111     }
112
113     GetCurrentDirectoryA(sizeof(origPath), origPath);
114
115     doSwitch = 0;
116     if (pathHasDrive && (*pathp & ~0x20) != (origPath[0] & ~0x20)) {
117         /* a drive has been specified and it isn't our current drive.
118          * to get path, switch to it first.  Must case-fold drive letters
119          * for user convenience.
120          */
121         doSwitch = 1;
122         newPath[0] = *pathp;
123         newPath[1] = ':';
124         newPath[2] = 0;
125         if (!SetCurrentDirectoryA(newPath)) {
126             code = GetLastError();
127             return code;
128         }
129     }
130
131     /* now get the absolute path to the current wdir in this drive */
132     GetCurrentDirectoryA(sizeof(tpath), tpath);
133     if( FAILED(StringCbCopy(outPathp, outSize, tpath+2))) {
134         fprintf (stderr, "fs_GetFullPath - not enough space on output");
135         exit(1);
136     }
137     /* if there is a non-null name after the drive, append it */
138     if (*firstp != 0) {
139         if( FAILED(StringCbCat(outPathp, outSize, "\\"))) {
140             fprintf (stderr, "fs_GetFullPath - not enough space on output");
141             exit(1);
142         }
143         if( FAILED(StringCbCat(outPathp, outSize, firstp))) {
144             fprintf (stderr, "fs_GetFullPath - not enough space on output");
145             exit(1);
146         }
147     }
148
149     /* finally, if necessary, switch back to our home drive letter */
150     if (doSwitch) {
151         SetCurrentDirectoryA(origPath);
152     }
153
154     return 0;
155 }
156
157 /* is this a digit or a digit-like thing? */
158 static int ismeta(int abase, int ac) {
159 /*    if (ac == '-' || ac == 'x' || ac == 'X') return 1; */
160     if (ac >= '0' && ac <= '7') return 1;
161     if (abase <= 8) return 0;
162     if (ac >= '8' && ac <= '9') return 1;
163     if (abase <= 10) return 0;
164     if (ac >= 'a' && ac <= 'f') return 1;
165     if (ac >= 'A' && ac <= 'F') return 1;
166     return 0;
167 }
168
169 /* given that this is a digit or a digit-like thing, compute its value */
170 static int getmeta(int ac) {
171     if (ac >= '0' && ac <= '9') return ac - '0';
172     if (ac >= 'a' && ac <= 'f') return ac - 'a' + 10;
173     if (ac >= 'A' && ac <= 'F') return ac - 'A' + 10;
174     return 0;
175 }
176
177 char *cm_mount_root="afs";
178 char *cm_slash_mount_root="/afs";
179 char *cm_back_slash_mount_root="\\afs";
180
181 void
182 fs_utils_InitMountRoot()
183 {
184     HKEY parmKey;
185     char mountRoot[MAX_PATH+1];
186     char *pmount=mountRoot;
187     DWORD len=sizeof(mountRoot)-1;
188
189     if ((RegOpenKeyExA(HKEY_LOCAL_MACHINE, AFSREG_CLT_SVC_PARAM_SUBKEY, 0,
190                       (IsWow64()?KEY_WOW64_64KEY:0)|KEY_QUERY_VALUE, &parmKey)!= ERROR_SUCCESS)
191         || (RegQueryValueExA(parmKey, "Mountroot", NULL, NULL,(LPBYTE)(mountRoot), &len)!= ERROR_SUCCESS)
192          || (len==sizeof(mountRoot)-1)
193          )
194     {
195         if( FAILED(StringCbCopy(mountRoot, sizeof(mountRoot), "\\afs"))) {
196             fprintf (stderr, "fs_InitMountRoot - not enough space on output");
197             exit(1);
198         }
199     }
200     RegCloseKey(parmKey);
201     mountRoot[len]=0;       /*safety see ms-help://MS.MSDNQTR.2002OCT.1033/sysinfo/base/regqueryvalueex.htm*/
202
203     cm_mount_root=malloc(len+1);
204     cm_slash_mount_root=malloc(len+2);
205     cm_back_slash_mount_root=malloc(len+2);
206     if ((*pmount=='/') || (*pmount='\\'))
207         pmount++;
208
209     if( FAILED(StringCbCopy(cm_mount_root, len+1, pmount))) {
210         fprintf (stderr, "fs_InitMountRoot - not enough space on output");
211         exit(1);
212     }
213     cm_slash_mount_root[0]='/';
214     if( FAILED(StringCbCopy(cm_slash_mount_root+1, len+1, pmount))) {
215         fprintf (stderr, "fs_InitMountRoot - not enough space on output");
216         exit(1);
217     }
218     cm_back_slash_mount_root[0]='\\';
219     if( FAILED(StringCbCopy(cm_back_slash_mount_root+1, len+1, pmount))) {
220         fprintf (stderr, "fs_InitMountRoot - not enough space on output");
221         exit(1);
222     }
223 }
224
225 /* return a static pointer to a buffer */
226 char *
227 fs_GetParent(char *apath)
228 {
229     static char tspace[1024];
230     char *tp;
231
232     if( FAILED(StringCbCopy(tspace, sizeof(tspace), apath))) {
233         fprintf (stderr, "tspace - not enough space");
234         exit(1);
235     }
236     tp = strrchr(tspace, '\\');
237     if (tp) {
238         if (tp - tspace > 2 &&
239             tspace[1] == ':' &&
240             &tspace[2] == tp)
241             *(tp+1) = 0;        /* lv trailing slash so Parent("k:\foo") is "k:\" not "k:" */
242         else
243             *tp = 0;
244     }
245     else {
246         fs_ExtractDriveLetter(apath, tspace);
247         if( FAILED(StringCbCat(tspace, sizeof(tspace), "."))) {
248             fprintf (stderr, "tspace - not enough space");
249             exit(1);
250         }
251     }
252     return tspace;
253 }
254
255 #if (_WINNT_WIN32 < 0x0600)
256 typedef struct FILE_NAME_INFO {
257     DWORD FileNameLength;
258     WCHAR FileName[1];
259 } FILE_NAME_INFO, *PFILE_NAME_INFO;
260
261 typedef enum _FILE_INFO_BY_HANDLE_CLASS {
262     FileNameInfo = 2
263 } FILE_INFO_BY_HANDLE_CLASS, *PFILE_INFO_BY_HANDLE_CLASS;
264
265 typedef BOOL
266 (WINAPI *
267 LPFN_GetFileInformationByHandleEx)(
268     __in  HANDLE hFile,
269     __in  FILE_INFO_BY_HANDLE_CLASS FileInformationClass,
270     __out LPVOID lpFileInformation,
271     __in  DWORD dwBufferSize
272 );
273 #endif
274
275 static long
276 GetFileSystemNameInfo(const char *fileNamep, char *outNamep, size_t outSize)
277 {
278     HANDLE hf;
279     union {
280         FILE_NAME_INFO  nameInfo;
281         CHAR                buffer[2048];
282     } u;
283     DWORD rc;
284 #if (_WIN32_WINNT < 0x0600)
285     HANDLE hKernel32;
286     static LPFN_GetFileInformationByHandleEx GetFileInformationByHandleEx = NULL;
287
288     if (!GetFileInformationByHandleEx) {
289         hKernel32 = GetModuleHandle("kernel32.dll");    /* no refcount increase */
290         GetFileInformationByHandleEx = (LPFN_GetFileInformationByHandleEx)GetProcAddress(hKernel32, "GetFileInformationByHandleEx");
291     }
292 #endif
293
294     if (!GetFileInformationByHandleEx)
295         return -1;
296
297     hf = CreateFile( fileNamep,
298                      GENERIC_READ,
299                      FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
300                      NULL,
301                      OPEN_EXISTING,
302                      FILE_FLAG_BACKUP_SEMANTICS,
303                      NULL
304                      );
305     if (hf == INVALID_HANDLE_VALUE)
306         return -1;
307
308     rc = GetFileInformationByHandleEx( hf, FileNameInfo, &u, sizeof(u));
309     CloseHandle(hf);
310     if (rc == FALSE)
311         return -1;
312
313     rc = WideCharToMultiByte(CP_UTF8, 0,
314                               u.nameInfo.FileName, u.nameInfo.FileNameLength,
315                               outNamep, outSize,
316                               NULL, FALSE);
317     if (rc == 0) /* failure */
318         return -1;
319
320     if (outNamep[0] == '\\' &&
321          outNamep[u.nameInfo.FileNameLength/2 - 1] == '\\')
322                 outNamep[u.nameInfo.FileNameLength/2 - 1] = 0;
323     else
324         outNamep[min(u.nameInfo.FileNameLength/2, outSize)] = 0;        /* make sure we are nul terminated */
325
326     return 0;
327 }
328
329 /* this function returns TRUE (1) if the file is in AFS, otherwise false (0) */
330 int
331 fs_InAFS(char *apath)
332 {
333     char fsname[MAX_PATH+2]="\\";
334     struct ViceIoctl blob;
335     cm_ioctlQueryOptions_t options;
336     cm_fid_t fid;
337     afs_int32 code;
338
339     /*
340      * If the operating system supports the name information query
341      * use it to resolve the full file path.  This will take care
342      * of any mappings, ntfs junctions, etc.
343      */
344     if (GetFileSystemNameInfo(apath, &fsname[1], sizeof(fsname)-1) == 0)
345         apath = fsname;
346
347     memset(&options, 0, sizeof(options));
348     options.size = sizeof(options);
349     options.field_flags |= CM_IOCTL_QOPTS_FIELD_LITERAL;
350     options.literal = 1;
351     blob.in_size = options.size;    /* no variable length data */
352     blob.in = &options;
353     blob.out_size = sizeof(cm_fid_t);
354     blob.out = (char *) &fid;
355
356     code = pioctl_utf8(apath, VIOCGETFID, &blob, 1);
357     if (code) {
358         if ((errno == EINVAL) || (errno == ENOENT))
359             return 0;
360     }
361     return 1;
362 }
363
364 int
365 fs_IsFreelanceRoot(char *apath)
366 {
367     struct ViceIoctl blob;
368     afs_int32 code;
369     char space[AFS_PIOCTL_MAXSIZE];
370
371     blob.in_size = 0;
372     blob.out_size = AFS_PIOCTL_MAXSIZE;
373     blob.out = space;
374
375     code = pioctl_utf8(apath, VIOC_FILE_CELL_NAME, &blob, 1);
376     if (code == 0) {
377         return !cm_strnicmp_utf8N("Freelance.Local.Root",space, blob.out_size);
378     }
379     return 1;   /* assume it is because it is more restrictive that way */
380 }
381
382 const char *
383 fs_NetbiosName(void)
384 {
385     static char buffer[NETBIOSNAMESZ] = "AFS";
386     HKEY  parmKey;
387     DWORD code;
388     DWORD dummyLen;
389     DWORD enabled = 0;
390
391     code = RegOpenKeyEx(HKEY_LOCAL_MACHINE, AFSREG_CLT_SVC_PARAM_SUBKEY,
392                          0, (IsWow64()?KEY_WOW64_64KEY:0)|KEY_QUERY_VALUE, &parmKey);
393     if (code == ERROR_SUCCESS) {
394         dummyLen = sizeof(buffer);
395         code = RegQueryValueEx(parmKey, "NetbiosName", NULL, NULL,
396                                buffer, &dummyLen);
397         RegCloseKey (parmKey);
398     } else {
399         if( FAILED(StringCbCopy(buffer, sizeof(buffer), "AFS"))) {
400             fprintf (stderr, "buffer - not enough space");
401             exit(1);
402         }
403     }
404     return buffer;
405 }
406
407 #define AFSCLIENT_ADMIN_GROUPNAME "AFS Client Admins"
408
409 BOOL
410 fs_IsAdmin (void)
411 {
412     static BOOL fAdmin = FALSE;
413     static BOOL fTested = FALSE;
414
415     if (!fTested)
416     {
417         /* Obtain the SID for the AFS client admin group.  If the group does
418          * not exist, then assume we have AFS client admin privileges.
419          */
420         PSID psidAdmin = NULL;
421         DWORD dwSize, dwSize2;
422         char pszAdminGroup[ MAX_COMPUTERNAME_LENGTH + sizeof(AFSCLIENT_ADMIN_GROUPNAME) + 2 ];
423         char *pszRefDomain = NULL;
424         SID_NAME_USE snu = SidTypeGroup;
425
426         dwSize = sizeof(pszAdminGroup);
427
428         if (!GetComputerName(pszAdminGroup, &dwSize)) {
429             /* Can't get computer name.  We return false in this case.
430                Retain fAdmin and fTested. This shouldn't happen.*/
431             return FALSE;
432         }
433
434         dwSize = 0;
435         dwSize2 = 0;
436
437         if( FAILED(StringCbCat(pszAdminGroup, MAX_COMPUTERNAME_LENGTH + sizeof(AFSCLIENT_ADMIN_GROUPNAME) + 2,"\\"))) {
438             fprintf (stderr, "pszAdminGroup - not enough space");
439             exit(1);
440         }
441         if( FAILED(StringCbCat(pszAdminGroup, MAX_COMPUTERNAME_LENGTH +
442             sizeof(AFSCLIENT_ADMIN_GROUPNAME) + 2, AFSCLIENT_ADMIN_GROUPNAME))) {
443             fprintf (stderr, "pszAdminGroup - not enough space");
444             exit(1);
445         }
446
447         LookupAccountName(NULL, pszAdminGroup, NULL, &dwSize, NULL, &dwSize2, &snu);
448         /* that should always fail. */
449
450         if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
451             /* if we can't find the group, then we allow the operation */
452             fAdmin = TRUE;
453             return TRUE;
454         }
455
456         if (dwSize == 0 || dwSize2 == 0) {
457             /* Paranoia */
458             fAdmin = TRUE;
459             return TRUE;
460         }
461
462         psidAdmin = (PSID)malloc(dwSize); memset(psidAdmin,0,dwSize);
463         assert(psidAdmin);
464         pszRefDomain = (char *)malloc(dwSize2);
465         assert(pszRefDomain);
466
467         if (!LookupAccountName(NULL, pszAdminGroup, psidAdmin, &dwSize, pszRefDomain, &dwSize2, &snu)) {
468             /* We can't lookup the group now even though we looked it up earlier.
469                Could this happen? */
470             fAdmin = TRUE;
471         } else {
472             /* Then open our current ProcessToken */
473             HANDLE hToken;
474
475             if (OpenProcessToken (GetCurrentProcess(), TOKEN_QUERY, &hToken))
476             {
477
478                 if (!CheckTokenMembership(hToken, psidAdmin, &fAdmin)) {
479                     /* We'll have to allocate a chunk of memory to store the list of
480                      * groups to which this user belongs; find out how much memory
481                      * we'll need.
482                      */
483                     DWORD dwSize = 0;
484                     PTOKEN_GROUPS pGroups;
485
486                     GetTokenInformation (hToken, TokenGroups, NULL, dwSize, &dwSize);
487
488                     pGroups = (PTOKEN_GROUPS)malloc(dwSize);
489                     assert(pGroups);
490
491                     /* Allocate that buffer, and read in the list of groups. */
492                     if (GetTokenInformation (hToken, TokenGroups, pGroups, dwSize, &dwSize))
493                     {
494                         /* Look through the list of group SIDs and see if any of them
495                          * matches the AFS Client Admin group SID.
496                          */
497                         size_t iGroup = 0;
498                         for (; (!fAdmin) && (iGroup < pGroups->GroupCount); ++iGroup)
499                         {
500                             if (EqualSid (psidAdmin, pGroups->Groups[ iGroup ].Sid)) {
501                                 fAdmin = TRUE;
502                             }
503                         }
504                     }
505
506                     if (pGroups)
507                         free(pGroups);
508                 }
509
510                 /* if do not have permission because we were not explicitly listed
511                  * in the Admin Client Group let's see if we are the SYSTEM account
512                  */
513                 if (!fAdmin) {
514                     PTOKEN_USER pTokenUser;
515                     SID_IDENTIFIER_AUTHORITY SIDAuth = SECURITY_NT_AUTHORITY;
516                     PSID pSidLocalSystem = 0;
517                     DWORD gle;
518
519                     GetTokenInformation(hToken, TokenUser, NULL, 0, &dwSize);
520
521                     pTokenUser = (PTOKEN_USER)malloc(dwSize);
522                     assert(pTokenUser);
523
524                     if (!GetTokenInformation(hToken, TokenUser, pTokenUser, dwSize, &dwSize))
525                         gle = GetLastError();
526
527                     if (AllocateAndInitializeSid( &SIDAuth, 1,
528                                                   SECURITY_LOCAL_SYSTEM_RID,
529                                                   0, 0, 0, 0, 0, 0, 0,
530                                                   &pSidLocalSystem))
531                     {
532                         if (EqualSid(pTokenUser->User.Sid, pSidLocalSystem)) {
533                             fAdmin = TRUE;
534                         }
535
536                         FreeSid(pSidLocalSystem);
537                     }
538
539                     if ( pTokenUser )
540                         free(pTokenUser);
541                 }
542             }
543         }
544
545         free(psidAdmin);
546         free(pszRefDomain);
547
548         fTested = TRUE;
549     }
550
551     return fAdmin;
552 }
553
554 void
555 fs_FreeUtf8CmdLine(int argc, char ** argv)
556 {
557     int i;
558     for (i=0; i < argc; i++) {
559         if (argv[i])
560             free(argv[i]);
561     }
562     free(argv);
563 }
564
565 char **
566 fs_MakeUtf8Cmdline(int argc, const wchar_t **wargv)
567 {
568     char ** argv;
569     int i;
570
571     argv = calloc(argc, sizeof(argv[0]));
572     if (argv == NULL)
573         return NULL;
574
575     for (i=0; i < argc; i++) {
576         int s;
577
578         s = WideCharToMultiByte(CP_UTF8, 0, wargv[i], -1, NULL, 0, NULL, FALSE);
579         if (s == 0 ||
580             (argv[i] = calloc(s+1, sizeof(char))) == NULL) {
581             break;
582         }
583
584         s = WideCharToMultiByte(CP_UTF8, 0, wargv[i], -1, argv[i], s+1, NULL, FALSE);
585         if (s == 0) {
586             break;
587         }
588     }
589
590     if (i < argc) {
591         fs_FreeUtf8CmdLine(argc, argv);
592         return NULL;
593     }
594
595     return argv;
596 }
597
598 static const char *pn = "<cmd>";
599 void
600 fs_SetProcessName(const char *name)
601 {
602     pn = name;
603 }
604
605 void
606 fs_Die(int code, char *filename)
607 {
608     if (code == EINVAL) {
609         if (filename)
610             fprintf(stderr,"%s: Invalid argument; it is possible that %s is not in AFS.\n", pn, filename);
611         else
612             fprintf(stderr,"%s: Invalid argument.\n", pn);
613     }
614     else if (code == ENOENT) {
615         if (filename)
616             fprintf(stderr,"%s: File '%s' doesn't exist\n", pn, filename);
617         else
618             fprintf(stderr,"%s: no such file returned\n", pn);
619     }
620     else if (code == EEXIST) {
621         if (filename)
622             fprintf(stderr,"%s: File '%s' already exists.\n", pn, filename);
623         else
624             fprintf(stderr,"%s: the specified file already exists.\n", pn);
625     }
626     else if (code == EROFS)
627         fprintf(stderr,"%s: You can not change a backup or readonly volume\n", pn);
628     else if (code == EACCES || code == EPERM) {
629         if (filename)
630             fprintf(stderr,"%s: You don't have the required access rights on '%s'\n", pn, filename);
631         else
632             fprintf(stderr,"%s: You do not have the required rights to do this operation\n", pn);
633     }
634     else if (code == ENODEV) {
635         fprintf(stderr,"%s: AFS service may not have started.\n", pn);
636     }
637     else if (code == ESRCH) {   /* hack */
638         fprintf(stderr,"%s: Cell name not recognized.\n", pn);
639     }
640     else if (code == EPIPE) {   /* hack */
641         fprintf(stderr,"%s: Volume name or ID not recognized.\n", pn);
642     }
643     else if (code == EFBIG) {
644         fprintf(stderr,"%s: Cache size too large.\n", pn);
645     }
646     else if (code == ETIMEDOUT) {
647         if (filename)
648             fprintf(stderr,"%s:'%s': Connection timed out", pn, filename);
649         else
650             fprintf(stderr,"%s: Connection timed out", pn);
651     }
652     else if (code == EBUSY) {
653         if (filename)
654             fprintf(stderr,"%s: All servers are busy on which '%s' resides\n", pn, filename);
655         else
656             fprintf(stderr,"%s: All servers are busy\n", pn);
657     }
658     else if (code == ENXIO) {
659         if (filename)
660             fprintf(stderr,"%s: All volume instances are offline on which '%s' resides\n", pn, filename);
661         else
662             fprintf(stderr,"%s: All volume instances are offline\n", pn);
663     }
664     else if (code == ENOSYS) {
665         if (filename)
666             fprintf(stderr,"%s: All servers are down on which '%s' resides\n", pn, filename);
667         else
668             fprintf(stderr,"%s: All servers are down\n", pn);
669     }
670     else if (code == ECHILD) {  /* hack */
671         if (filename)
672             fprintf(stderr,"%s: Invalid owner specified for '%s'\n", pn, filename);
673         else
674             fprintf(stderr,"%s: Invalid owner specified\n", pn);
675     }
676     else {
677         if (filename)
678             fprintf(stderr,"%s:'%s'", pn, filename);
679         else
680             fprintf(stderr,"%s", pn);
681
682         fprintf(stderr, ": code 0x%x\n", code);
683     }
684 }
685
686 /* values match cache manager File Types */
687 const char *
688 fs_filetypestr(afs_uint32 type)
689 {
690     char * s = "Object";
691
692     switch (type) {
693     case 1:
694         s = "File";
695         break;
696     case 2:
697         s = "Directory";
698         break;
699     case 3:
700         s = "Symlink";
701         break;
702     case 4:
703         s = "Mountpoint";
704         break;
705     case 5:
706         s = "DfsLink";
707         break;
708     }
709     return s;
710 }