windows-cell-name-length-consistency-20080806
[openafs.git] / src / WINNT / afsd / symlink.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 <afs/param.h>
11 #include <afs/stds.h>
12
13 #include <windows.h>
14 #include <stdlib.h>
15 #include <malloc.h>
16 #include <string.h>
17 #include <stdio.h>
18 #include <time.h>
19 #include <winsock2.h>
20 #include <errno.h>
21 #include <assert.h>
22
23 #include <osi.h>
24 #include <afsint.h>
25 #include <WINNT\afsreg.h>
26
27 #include "fs_utils.h"
28 #include "cmd.h"
29
30 #define MAXNAME 100
31 #define MAXSIZE 2048
32 #define MAXINSIZE 1300    /* pioctl complains if data is larger than this */
33
34 static char space[MAXSIZE];
35 static char tspace[1024];
36
37 #ifndef WIN32
38 static struct ubik_client *uclient;
39 #endif /* not WIN32 */
40
41
42 static char pn[] = "symlink";
43 static int rxInitDone = 0;
44
45 void Die();
46
47 #if 0
48 foldcmp (a, b)
49     register char *a;
50     register char *b; {
51     register char t, u;
52     while (1) {
53         t = *a++;
54         u = *b++;
55         if (t >= 'A' && t <= 'Z') t += 0x20;
56         if (u >= 'A' && u <= 'Z') u += 0x20;
57         if (t != u) return 1;
58         if (t == 0) return 0;
59     }
60 }
61 #endif
62
63 /* this function returns TRUE (1) if the file is in AFS, otherwise false (0) */
64 static int InAFS(register char *apath)
65 {
66     struct ViceIoctl blob;
67     register afs_int32 code;
68
69     blob.in_size = 0;
70     blob.out_size = MAXSIZE;
71     blob.out = space;
72
73     code = pioctl_utf8(apath, VIOC_FILE_CELL_NAME, &blob, 1);
74     if (code) {
75         if ((errno == EINVAL) || (errno == ENOENT)) 
76             return 0;
77     }
78     return 1;
79 }
80
81 static int 
82 IsFreelanceRoot(char *apath)
83 {
84     struct ViceIoctl blob;
85     afs_int32 code;
86
87     blob.in_size = 0;
88     blob.out_size = MAXSIZE;
89     blob.out = space;
90
91     code = pioctl_utf8(apath, VIOC_FILE_CELL_NAME, &blob, 1);
92     if (code == 0)
93         return !stricmp("Freelance.Local.Root",space);
94     return 1;   /* assume it is because it is more restrictive that way */
95 }
96
97 static const char * NetbiosName(void)
98 {
99     static char buffer[1024] = "AFS";
100     HKEY  parmKey;
101     DWORD code;
102     DWORD dummyLen;
103     DWORD enabled = 0;
104
105     code = RegOpenKeyEx(HKEY_LOCAL_MACHINE, AFSREG_CLT_SVC_PARAM_SUBKEY,
106                          0, (IsWow64()?KEY_WOW64_64KEY:0)|KEY_QUERY_VALUE, &parmKey);
107     if (code == ERROR_SUCCESS) {
108         dummyLen = sizeof(buffer);
109         code = RegQueryValueEx(parmKey, "NetbiosName", NULL, NULL,
110                                buffer, &dummyLen);
111         RegCloseKey (parmKey);
112     } else {
113         strcpy(buffer, "AFS");
114     }
115     return buffer;
116 }
117
118 #define AFSCLIENT_ADMIN_GROUPNAME "AFS Client Admins"
119
120 static BOOL IsAdmin (void)
121 {
122     static BOOL fAdmin = FALSE;
123     static BOOL fTested = FALSE;
124
125     if (!fTested)
126     {
127         /* Obtain the SID for the AFS client admin group.  If the group does
128          * not exist, then assume we have AFS client admin privileges.
129          */
130         PSID psidAdmin = NULL;
131         DWORD dwSize, dwSize2;
132         char pszAdminGroup[ MAX_COMPUTERNAME_LENGTH + sizeof(AFSCLIENT_ADMIN_GROUPNAME) + 2 ];
133         char *pszRefDomain = NULL;
134         SID_NAME_USE snu = SidTypeGroup;
135
136         dwSize = sizeof(pszAdminGroup);
137
138         if (!GetComputerName(pszAdminGroup, &dwSize)) {
139             /* Can't get computer name.  We return false in this case.
140                Retain fAdmin and fTested. This shouldn't happen.*/
141             return FALSE;
142         }
143
144         dwSize = 0;
145         dwSize2 = 0;
146
147         strcat(pszAdminGroup,"\\");
148         strcat(pszAdminGroup, AFSCLIENT_ADMIN_GROUPNAME);
149
150         LookupAccountName(NULL, pszAdminGroup, NULL, &dwSize, NULL, &dwSize2, &snu);
151         /* that should always fail. */
152
153         if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
154             /* if we can't find the group, then we allow the operation */
155             fAdmin = TRUE;
156             return TRUE;
157         }
158
159         if (dwSize == 0 || dwSize2 == 0) {
160             /* Paranoia */
161             fAdmin = TRUE;
162             return TRUE;
163         }
164
165         psidAdmin = (PSID)malloc(dwSize); memset(psidAdmin,0,dwSize);
166         assert(psidAdmin);
167         pszRefDomain = (char *)malloc(dwSize2);
168         assert(pszRefDomain);
169
170         if (!LookupAccountName(NULL, pszAdminGroup, psidAdmin, &dwSize, pszRefDomain, &dwSize2, &snu)) {
171             /* We can't lookup the group now even though we looked it up earlier.  
172                Could this happen? */
173             fAdmin = TRUE;
174         } else {
175             /* Then open our current ProcessToken */
176             HANDLE hToken;
177
178             if (OpenProcessToken (GetCurrentProcess(), TOKEN_QUERY, &hToken))
179             {
180
181                 if (!CheckTokenMembership(hToken, psidAdmin, &fAdmin)) {
182                     /* We'll have to allocate a chunk of memory to store the list of
183                      * groups to which this user belongs; find out how much memory
184                      * we'll need.
185                      */
186                     DWORD dwSize = 0;
187                     PTOKEN_GROUPS pGroups;
188
189                     GetTokenInformation (hToken, TokenGroups, NULL, dwSize, &dwSize);
190
191                     pGroups = (PTOKEN_GROUPS)malloc(dwSize);
192                     assert(pGroups);
193
194                     /* Allocate that buffer, and read in the list of groups. */
195                     if (GetTokenInformation (hToken, TokenGroups, pGroups, dwSize, &dwSize))
196                     {
197                         /* Look through the list of group SIDs and see if any of them
198                          * matches the AFS Client Admin group SID.
199                          */
200                         size_t iGroup = 0;
201                         for (; (!fAdmin) && (iGroup < pGroups->GroupCount); ++iGroup)
202                         {
203                             if (EqualSid (psidAdmin, pGroups->Groups[ iGroup ].Sid)) {
204                                 fAdmin = TRUE;
205                             }
206                         }
207                     }
208
209                     if (pGroups)
210                         free(pGroups);
211                 }
212
213                 /* if do not have permission because we were not explicitly listed
214                  * in the Admin Client Group let's see if we are the SYSTEM account
215                  */
216                 if (!fAdmin) {
217                     PTOKEN_USER pTokenUser;
218                     SID_IDENTIFIER_AUTHORITY SIDAuth = SECURITY_NT_AUTHORITY;
219                     PSID pSidLocalSystem = 0;
220                     DWORD gle;
221
222                     GetTokenInformation(hToken, TokenUser, NULL, 0, &dwSize);
223
224                     pTokenUser = (PTOKEN_USER)malloc(dwSize);
225                     assert(pTokenUser);
226
227                     if (!GetTokenInformation(hToken, TokenUser, pTokenUser, dwSize, &dwSize))
228                         gle = GetLastError();
229
230                     if (AllocateAndInitializeSid( &SIDAuth, 1,
231                                                   SECURITY_LOCAL_SYSTEM_RID,
232                                                   0, 0, 0, 0, 0, 0, 0,
233                                                   &pSidLocalSystem))
234                     {
235                         if (EqualSid(pTokenUser->User.Sid, pSidLocalSystem)) {
236                             fAdmin = TRUE;
237                         }
238
239                         FreeSid(pSidLocalSystem);
240                     }
241
242                     if ( pTokenUser )
243                         free(pTokenUser);
244                 }
245             }
246         }
247
248         free(psidAdmin);
249         free(pszRefDomain);
250
251         fTested = TRUE;
252     }
253
254     return fAdmin;
255 }
256
257 /* return a static pointer to a buffer */
258 static char *Parent(apath)
259 char *apath; {
260     register char *tp;
261     strcpy(tspace, apath);
262     tp = strrchr(tspace, '\\');
263     if (tp) {
264         *(tp+1) = 0;    /* lv trailing slash so Parent("k:\foo") is "k:\" not "k:" */
265     }
266     else {
267         fs_ExtractDriveLetter(apath, tspace);
268         strcat(tspace, ".");
269     }
270     return tspace;
271 }
272
273
274 static ListLinkCmd(register struct cmd_syndesc *as, void *arock)
275 {
276     register afs_int32 code;
277     struct ViceIoctl blob;
278     int error;
279     register struct cmd_item *ti;
280     char orig_name[1024];               /*Original name, may be modified*/
281     char true_name[1024];               /*``True'' dirname (e.g., symlink target)*/
282     char parent_dir[1024];              /*Parent directory of true name*/
283     register char *last_component;      /*Last component of true name*/
284 #ifndef WIN32
285     struct stat statbuff;               /*Buffer for status info*/
286 #endif /* not WIN32 */
287 #ifndef WIN32
288     int link_chars_read;                /*Num chars read in readlink()*/
289 #endif /* not WIN32 */
290     int thru_symlink;                   /*Did we get to a mount point via a symlink?*/
291     
292     error = 0;
293     for(ti=as->parms[0].items; ti; ti=ti->next) {
294         /* once per file */
295         thru_symlink = 0;
296 #ifdef WIN32
297         strcpy(orig_name, ti->data);
298 #else /* not WIN32 */
299         sprintf(orig_name, "%s%s",
300                 (ti->data[0] == '/') ? "" : "./",
301                 ti->data);
302 #endif /* not WIN32 */
303
304 #ifndef WIN32
305         if (lstat(orig_name, &statbuff) < 0) {
306             /* if lstat fails, we should still try the pioctl, since it
307                 may work (for example, lstat will fail, but pioctl will
308                     work if the volume of offline (returning ENODEV). */
309             statbuff.st_mode = S_IFDIR; /* lie like pros */
310         }
311
312         /*
313          * The lstat succeeded.  If the given file is a symlink, substitute
314          * the file name with the link name.
315          */
316         if ((statbuff.st_mode & S_IFMT) == S_IFLNK) {
317             thru_symlink = 1;
318             /*
319              * Read name of resolved file.
320              */
321             link_chars_read = readlink(orig_name, true_name, 1024);
322             if (link_chars_read <= 0) {
323                 fprintf(stderr,"%s: Can't read target name for '%s' symbolic link!\n",
324                        pn, orig_name);
325                 exit(1);
326             }
327
328             /*
329              * Add a trailing null to what was read, bump the length.
330              */
331             true_name[link_chars_read++] = 0;
332
333             /*
334              * If the symlink is an absolute pathname, we're fine.  Otherwise, we
335              * have to create a full pathname using the original name and the
336              * relative symlink name.  Find the rightmost slash in the original
337              * name (we know there is one) and splice in the symlink value.
338              */
339             if (true_name[0] != '\\') {
340                 last_component = (char *) strrchr(orig_name, '\\');
341                 strcpy(++last_component, true_name);
342                 strcpy(true_name, orig_name);
343             }
344         }
345         else
346             strcpy(true_name, orig_name);
347 #else   /* WIN32 */
348         strcpy(true_name, orig_name);
349 #endif /* WIN32 */
350
351         /*
352          * Find rightmost slash, if any.
353          */
354         last_component = (char *) strrchr(true_name, '\\');
355         if (!last_component)
356             last_component = (char *) strrchr(true_name, '/');
357         if (last_component) {
358             /*
359              * Found it.  Designate everything before it as the parent directory,
360              * everything after it as the final component.
361              */
362             strncpy(parent_dir, true_name, last_component - true_name + 1);
363             parent_dir[last_component - true_name + 1] = 0;
364             last_component++;   /*Skip the slash*/
365
366 #ifdef WIN32
367             if (!InAFS(parent_dir)) {
368                 const char * nbname = NetbiosName();
369                 int len = (int)strlen(nbname);
370
371                 if (parent_dir[0] == '\\' && parent_dir[1] == '\\' &&
372                     parent_dir[len+2] == '\\' &&
373                     parent_dir[len+3] == '\0' &&
374                     !strnicmp(nbname,&parent_dir[2],len))
375                 {
376                     sprintf(parent_dir,"\\\\%s\\all\\", nbname);
377                 }
378             }
379 #endif
380         }
381         else {
382             /*
383              * No slash appears in the given file name.  Set parent_dir to the current
384              * directory, and the last component as the given name.
385              */
386             fs_ExtractDriveLetter(true_name, parent_dir);
387             strcat(parent_dir, ".");
388             last_component = true_name;
389             fs_StripDriveLetter(true_name, true_name, sizeof(true_name));
390         }
391
392         if (strcmp(last_component, ".") == 0 || strcmp(last_component, "..") == 0) {
393             fprintf(stderr,"%s: you may not use '.' or '..' as the last component\n", pn);
394             fprintf(stderr,"%s: of a name in the 'symlink list' command.\n", pn);
395             error = 1;
396             continue;
397         }
398         blob.in = last_component;
399         blob.in_size = (long)strlen(last_component)+1;
400         blob.out_size = MAXSIZE;
401         blob.out = space;
402         memset(space, 0, MAXSIZE);
403
404         code = pioctl_utf8(parent_dir, VIOC_LISTSYMLINK, &blob, 1);
405
406         if (code == 0)
407             printf("'%s' is a %ssymlink to '%s'\n",
408                    ti->data,
409                    (thru_symlink ? "symbolic link, leading to a " : ""),
410                    space);
411
412         else {
413             error = 1;
414             if (errno == EINVAL)
415                 fprintf(stderr,"'%s' is not a symlink.\n",
416                        ti->data);
417             else {
418                 Die(errno, (ti->data ? ti->data : parent_dir));
419             }
420         }
421     }
422     return error;
423 }
424
425 static MakeLinkCmd(register struct cmd_syndesc *as, void *arock)
426 {
427     register afs_int32 code;
428     struct ViceIoctl blob;
429     char * parent;
430     char path[1024] = "";
431
432     strcpy(path, as->parms[0].items->data);
433     parent = Parent(path);
434
435     if (!InAFS(parent)) {
436 #ifdef WIN32
437         const char * nbname = NetbiosName();
438         int len = (int)strlen(nbname);
439
440         if (parent[0] == '\\' && parent[1] == '\\' &&
441             parent[len+2] == '\\' &&
442             parent[len+3] == '\0' &&
443             !strnicmp(nbname,&parent[2],len))
444         {
445             sprintf(path,"%sall\\%s", parent, &as->parms[0].items->data[strlen(parent)]);
446             parent = Parent(path);
447             if (!InAFS(parent)) {
448                 fprintf(stderr,"%s: symlinks must be created within the AFS file system\n", pn);
449                 return 1;
450             }
451         } else 
452 #endif
453         {
454             fprintf(stderr,"%s: symlinks must be created within the AFS file system\n", pn);
455             return 1;
456         }
457     }
458
459 #ifdef WIN32
460     if ( IsFreelanceRoot(parent) && !IsAdmin() ) {
461         fprintf(stderr,"%s: Only AFS Client Administrators may alter the root.afs volume\n", pn);
462         return 1;
463     }
464
465     fprintf(stderr, "Creating symlink [%s] to [%s]\n", path, as->parms[1].items->data);
466
467     /* create symlink with a special pioctl for Windows NT, since it doesn't
468      * have a symlink system call.
469      */
470     blob.out_size = 0;
471     blob.in_size = 1 + (long)strlen(as->parms[1].items->data);
472     blob.in = as->parms[1].items->data;
473     blob.out = NULL;
474     code = pioctl_utf8(path, VIOC_SYMLINK, &blob, 0);
475 #else /* not WIN32 */
476     code = symlink(as->parms[1].items->data, path);
477 #endif /* not WIN32 */
478     if (code) {
479         Die(errno, as->parms[0].items->data);
480         return 1;
481     }
482     return 0;
483 }
484
485 /*
486  * Delete AFS symlinks.  Variables are used as follows:
487  *       tbuffer: Set to point to the null-terminated directory name of the
488  *          symlink (or ``.'' if none is provided)
489  *      tp: Set to point to the actual name of the symlink to nuke.
490  */
491 static RemoveLinkCmd(register struct cmd_syndesc *as, void *arock)
492 {
493     register afs_int32 code=0;
494     struct ViceIoctl blob;
495     register struct cmd_item *ti;
496     char tbuffer[1024];
497     char lsbuffer[1024];
498     register char *tp;
499     
500     for(ti=as->parms[0].items; ti; ti=ti->next) {
501         /* once per file */
502         tp = (char *) strrchr(ti->data, '\\');
503         if (!tp)
504             tp = (char *) strrchr(ti->data, '/');
505         if (tp) {
506             strncpy(tbuffer, ti->data, code=(afs_int32)(tp-ti->data+1));  /* the dir name */
507             tbuffer[code] = 0;
508             tp++;   /* skip the slash */
509
510 #ifdef WIN32
511             if (!InAFS(tbuffer)) {
512                 const char * nbname = NetbiosName();
513                 int len = (int)strlen(nbname);
514
515                 if (tbuffer[0] == '\\' && tbuffer[1] == '\\' &&
516                      tbuffer[len+2] == '\\' &&
517                      tbuffer[len+3] == '\0' &&
518                      !strnicmp(nbname,&tbuffer[2],len))
519                 {
520                     sprintf(tbuffer,"\\\\%s\\all\\", nbname);
521                 }
522             }
523 #endif
524         }
525         else {
526             fs_ExtractDriveLetter(ti->data, tbuffer);
527             strcat(tbuffer, ".");
528             tp = ti->data;
529             fs_StripDriveLetter(tp, tp, 0);
530         }
531         blob.in = tp;
532         blob.in_size = (int)strlen(tp)+1;
533         blob.out = lsbuffer;
534         blob.out_size = sizeof(lsbuffer);
535         code = pioctl_utf8(tbuffer, VIOC_LISTSYMLINK, &blob, 0);
536         if (code) {
537             if (errno == EINVAL)
538                 fprintf(stderr,"symlink: '%s' is not a symlink.\n", ti->data);
539             else {
540                 Die(errno, ti->data);
541             }
542             continue;   /* don't bother trying */
543         }
544
545         if ( IsFreelanceRoot(tbuffer) && !IsAdmin() ) {
546             fprintf(stderr,"symlink: Only AFS Client Administrators may alter the root.afs volume\n");
547             code = 1;
548             continue;   /* skip */
549         }
550
551         blob.out_size = 0;
552         blob.in = tp;
553         blob.in_size = (long)strlen(tp)+1;
554         code = pioctl_utf8(tbuffer, VIOC_DELSYMLINK, &blob, 0);
555         if (code) {
556             Die(errno, ti->data);
557         }
558     }
559     return code;
560 }
561
562 static void
563 FreeUtf8CmdLine(int argc, char ** argv)
564 {
565     int i;
566     for (i=0; i < argc; i++) {
567         if (argv[i])
568             free(argv[i]);
569     }
570     free(argv);
571 }
572
573 static char **
574 MakeUtf8Cmdline(int argc, const wchar_t **wargv)
575 {
576     char ** argv;
577     int i;
578
579     argv = calloc(argc, sizeof(argv[0]));
580     if (argv == NULL)
581         return NULL;
582
583     for (i=0; i < argc; i++) {
584         int s;
585
586         s = WideCharToMultiByte(CP_UTF8, 0, wargv[i], -1, NULL, 0, NULL, FALSE);
587         if (s == 0 ||
588             (argv[i] = calloc(s+1, sizeof(char))) == NULL) {
589             break;
590         }
591
592         s = WideCharToMultiByte(CP_UTF8, 0, wargv[i], -1, argv[i], s+1, NULL, FALSE);
593         if (s == 0) {
594             break;
595         }
596     }
597
598     if (i < argc) {
599         FreeUtf8CmdLine(argc, argv);
600         return NULL;
601     }
602
603     return argv;
604 }
605
606 static    struct ViceIoctl gblob;
607 static int debug = 0;
608
609 int wmain(int argc, wchar_t **wargv)
610 {
611     register afs_int32 code;
612     register struct cmd_syndesc *ts;
613     char ** argv;
614     
615 #ifdef  AFS_AIX32_ENV
616     /*
617      * The following signal action for AIX is necessary so that in case of a 
618      * crash (i.e. core is generated) we can include the user's data section 
619      * in the core dump. Unfortunately, by default, only a partial core is
620      * generated which, in many cases, isn't too useful.
621      */
622     struct sigaction nsa;
623     
624     sigemptyset(&nsa.sa_mask);
625     nsa.sa_handler = SIG_DFL;
626     nsa.sa_flags = SA_FULLDUMP;
627     sigaction(SIGSEGV, &nsa, NULL);
628 #endif
629
630 #ifdef WIN32
631     WSADATA WSAjunk;
632     WSAStartup(0x0101, &WSAjunk);
633 #endif /* WIN32 */
634
635     /* try to find volume location information */
636     
637     argv = MakeUtf8Cmdline(argc, wargv);
638
639     osi_Init();
640
641     ts = cmd_CreateSyntax("list", ListLinkCmd, NULL, "list symlink");    
642     cmd_AddParm(ts, "-name", CMD_LIST, 0, "name");
643     
644     ts = cmd_CreateSyntax("make", MakeLinkCmd, NULL, "make symlink");
645     cmd_AddParm(ts, "-name", CMD_SINGLE, 0, "name");
646     cmd_AddParm(ts, "-to", CMD_SINGLE, 0, "target");
647
648     ts = cmd_CreateSyntax("remove", RemoveLinkCmd, NULL, "remove symlink");
649     cmd_AddParm(ts, "-name", CMD_LIST, 0, "name");
650     cmd_CreateAlias(ts, "rm");
651
652     code = cmd_Dispatch(argc, argv);
653
654 #ifndef WIN32
655     if (rxInitDone) rx_Finalize();
656 #endif /* not WIN32 */
657     
658     FreeUtf8CmdLine(argc, argv);
659     
660     return code;
661 }
662
663 void Die(code, filename)
664     int   code;
665     char *filename;
666 { /*Die*/
667
668     if (code == EINVAL) {
669         if (filename)
670             fprintf(stderr,"%s: Invalid argument; it is possible that %s is not in AFS.\n", pn, filename);
671         else fprintf(stderr,"%s: Invalid argument.\n", pn);
672     }
673     else if (code == ENOENT) {
674         if (filename) fprintf(stderr,"%s: File '%s' doesn't exist.\n", pn, filename);
675         else fprintf(stderr,"%s: no such file returned.\n", pn);
676     }
677     else if (code == EEXIST) {
678         if (filename) fprintf(stderr,"%s: File '%s' already exists.\n", pn, filename);
679         else fprintf(stderr,"%s: the specified file already exists.\n", pn);
680     }
681     else if (code == EROFS)  fprintf(stderr,"%s: You can not change a backup or readonly volume\n", pn);
682     else if (code == EACCES || code == EPERM) {
683         if (filename) fprintf(stderr,"%s: You don't have the required access rights on '%s'\n", pn, filename);
684         else fprintf(stderr,"%s: You do not have the required rights to do this operation\n", pn);
685     }
686     else if (code == ENODEV) {
687         fprintf(stderr,"%s: AFS service may not have started.\n", pn);
688     }
689     else if (code == ESRCH) {
690         fprintf(stderr,"%s: Cell name not recognized.\n", pn);
691     }
692     else if (code == EPIPE) {
693         fprintf(stderr,"%s: Volume name or ID not recognized.\n", pn);
694     }
695     else if (code == EFBIG) {
696         fprintf(stderr,"%s: Cache size too large.\n", pn);
697     }
698     else if (code == ETIMEDOUT) {
699         if (filename)
700             fprintf(stderr,"%s:'%s': Connection timed out", pn, filename);
701         else
702             fprintf(stderr,"%s: Connection timed out", pn);
703     }
704     else {
705         if (filename) fprintf(stderr,"%s:'%s'", pn, filename);
706         else fprintf(stderr,"%s", pn);
707 #ifdef WIN32
708         fprintf(stderr, ": code 0x%x\n", code);
709 #else /* not WIN32 */
710         fprintf(stderr,": %s\n", afs_error_message(code));
711 #endif /* not WIN32 */
712     }
713 } /*Die*/