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