death to register
[openafs.git] / src / update / client.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
13
14 #include <afs/stds.h>
15 #ifdef  AFS_AIX32_ENV
16 #include <signal.h>
17 #endif
18 #include <sys/types.h>
19 #include <sys/stat.h>
20 #ifdef AFS_NT40_ENV
21 #include <fcntl.h>
22 #include <winsock2.h>
23 #include <WINNT/afsevent.h>
24 #include <sys/utime.h>
25 #include <afs/dirent.h>
26 #include <direct.h>
27 #include <process.h>
28 #include <io.h>
29 #include <afs/procmgmt.h>
30 #else
31 #include <sys/file.h>
32 #include <netdb.h>
33 #include <netinet/in.h>
34 #include <sys/time.h>
35 #include <dirent.h>
36 #endif
37 #include <string.h>
38 #ifdef HAVE_UNISTD_H
39 #include <unistd.h>
40 #endif
41 #include <stdio.h>
42 #include <errno.h>
43 #include <rx/xdr.h>
44 #include <rx/rx.h>
45 #include <rx/rxkad.h>
46 #include <afs/com_err.h>
47 #include <afs/cellconfig.h>
48 #include <afs/afsutil.h>
49 #include <afs/fileutil.h>
50 #ifdef  AFS_AIX_ENV
51 #include <sys/statfs.h>
52 #endif
53 #include "update.h"
54 #include "global.h"
55 #include "update_internal.h"
56
57 char *whoami;
58 static int verbose;
59
60 /* prototypes */
61 static int GetFileFromUpServer(struct rx_connection *conn, char *filename,
62                                short uid, short gid, afs_uint32 mode,
63                                afs_int32 atime, afs_int32 mtime);
64 static int RenameNewFiles(struct filestr *modFiles);
65 static int PathsAreEquivalent(char *path1, char *path2);
66 int FetchFile(struct rx_call *, char *, char *, int);
67 int IsCompatible(char *, afs_int32, afs_int32);
68 int NotOnHost(char *, struct filestr *);
69 int update_ReceiveFile(int, struct rx_call *, struct stat *);
70
71 afs_int32
72 GetServer(char *aname)
73 {
74     struct hostent *th;
75     afs_int32 addr;
76
77     th = gethostbyname(aname);
78     if (!th) {
79         printf("host %s not found \n", aname);
80         exit(1);
81     }
82     memcpy(&addr, th->h_addr, sizeof(addr));
83     return addr;
84 }
85
86
87 int
88 osi_audit(void)
89 {
90 /* this sucks but it works for now.
91 */
92     return 0;
93 }
94
95 #ifndef AFS_NT40_ENV
96 #include "AFS_component_version_number.c"
97 #endif
98
99 int
100 main(int argc, char **argv)
101 {
102     struct rx_connection *conn;
103     struct rx_call *call;
104     struct afsconf_dir *cdir;
105     afs_int32 scIndex;
106     struct rx_securityClass *sc;
107
108     short uid, gid;
109     afs_uint32 u_uid, u_gid;    /*Unsigned long versions of the above */
110     struct stat tstat;
111
112     afs_uint32 mode;
113     int error;
114     char hostname[MAXFNSIZE];
115     FILE *stream;
116     afs_int32 time, length, atime;
117     struct filestr *df;
118     afs_int32 errcode;
119     int retrytime;
120     unsigned int interval;
121     afs_int32 host;
122     int a, cnt;
123     rxkad_level level;
124
125     char dirbuf[MAXFNSIZE], filename[MAXFNSIZE];
126     struct filestr *dirname, *ModFiles, *okhostfiles;
127 #ifdef  AFS_AIX32_ENV
128     /*
129      * The following signal action for AIX is necessary so that in case of a 
130      * crash (i.e. core is generated) we can include the user's data section 
131      * in the core dump. Unfortunately, by default, only a partial core is
132      * generated which, in many cases, isn't too useful.
133      */
134     struct sigaction nsa;
135
136     sigemptyset(&nsa.sa_mask);
137     nsa.sa_handler = SIG_DFL;
138     nsa.sa_flags = SA_FULLDUMP;
139     sigaction(SIGABRT, &nsa, NULL);
140     sigaction(SIGSEGV, &nsa, NULL);
141 #endif
142     whoami = argv[0];
143 #ifdef AFS_NT40_ENV
144     /* dummy signal call to force afsprocmgmt.dll to load on NT */
145     signal(SIGUSR1, SIG_DFL);
146
147     /* initialize winsock */
148     if (afs_winsockInit() < 0) {
149         ReportErrorEventAlt(AFSEVT_SVR_WINSOCK_INIT_FAILED, 0, argv[0], 0);
150         fprintf(stderr, "%s: Couldn't initialize winsock.\n", whoami);
151         exit(1);
152     }
153 #endif
154
155     /* Initialize dirpaths */
156     if (!(initAFSDirPath() & AFSDIR_SERVER_PATHS_OK)) {
157 #ifdef AFS_NT40_ENV
158         ReportErrorEventAlt(AFSEVT_SVR_NO_INSTALL_DIR, 0, argv[0], 0);
159 #endif
160         fprintf(stderr, "%s: Unable to obtain AFS server directory.\n",
161                 argv[0]);
162         exit(2);
163     }
164     retrytime = 60;
165     dirname = NULL;
166     ModFiles = NULL;
167     okhostfiles = NULL;
168
169     verbose = 0;
170     interval = TIMEOUT;
171     level = rxkad_crypt;        /* safest default */
172     strcpy(hostname, "");
173
174     /* Note that IsArg only checks as many bytes as specified in the command line arg,
175      * so that, for instance, -t still matches -time.
176      */
177     for (a = 1; a < argc; a++) {
178         if (argv[a][0] == '-') {        /* parse options */
179             int arglen = strlen(argv[a]);
180             char arg[256];
181             lcstring(arg, argv[a], sizeof(arg));
182 #define IsArg(a) (strncmp (arg,a, arglen) == 0)
183             if (IsArg("-time"))
184                 interval = atol(argv[++a]);
185             else if (IsArg("-crypt"))
186                 level = rxkad_crypt;
187             else if (IsArg("-clear"))
188                 level = rxkad_clear;
189             else if (IsArg("-verbose"))
190                 verbose++;
191             else {
192               usage:
193                 printf
194                     ("Usage: upclient <hostname> [-crypt] [-clear] [-t <retry time>] [-verbose]* <dir>+ [-help]\n");
195                 exit(1);
196             }
197         } else if (strlen(hostname) == 0)
198             strcpy(hostname, argv[a]);
199         else {
200             strcpy(filename, argv[a]);
201             FilepathNormalize(filename);
202             AddToList(&dirname, filename);
203         }
204     }
205     if (level == -1)
206         goto usage;
207     if (strlen(hostname) == 0)
208         goto usage;
209     host = GetServer(hostname);
210     if (interval < retrytime)
211         retrytime = interval;
212     if (dirname == 0)
213         goto usage;
214
215     errcode = rx_Init(0);
216     if (errcode) {
217         printf("Rx initialize failed \n");
218         afs_com_err(whoami, errcode, "calling Rx init");
219         exit(1);
220     }
221
222     cdir = afsconf_Open(AFSDIR_SERVER_ETC_DIRPATH);
223     if (cdir == 0) {
224         fprintf(stderr, "Can't get server configuration info (%s)\n",
225                 AFSDIR_SERVER_ETC_DIRPATH);
226         exit(1);
227     }
228
229     if (level == rxkad_crypt)
230         errcode = afsconf_ClientAuthSecure(cdir, &sc, &scIndex);
231     else if (level == rxkad_clear)
232         errcode = afsconf_ClientAuth(cdir, &sc, &scIndex);
233     else {
234         printf("Unsupported security level %d\n", level);
235         exit(1);
236     }
237     if (errcode) {
238         afs_com_err(whoami, errcode, "Couldn't get security obect for localAuth");
239         exit(1);
240     }
241
242   again:
243     conn =
244         rx_NewConnection(host, htons(AFSCONF_UPDATEPORT), UPDATE_SERVICEID,
245                          sc, scIndex);
246     cnt = 0;
247     while (1) {                 /*keep doing it */
248         char c, c1;
249         for (df = dirname; df; df = df->next) { /*for each directory do */
250             char *curDir;
251
252             if (verbose)
253                 printf("Checking dir %s\n", df->name);
254             /* initialize lists */
255             ZapList(&ModFiles);
256             ZapList(&okhostfiles);
257
258             /* construct local path from canonical (wire-format) path */
259             if ((errcode = ConstructLocalPath(df->name, "/", &curDir))) {
260                 afs_com_err(whoami, errcode, "Unable to construct local path");
261                 return errcode;
262             }
263
264             if (stat(curDir, &tstat) < 0) {
265                 /* try to make the dir */
266 #ifdef AFS_NT40_ENV
267                 if (mkdir(curDir) < 0) {
268 #else
269                 if (mkdir(curDir, 0700) < 0) {
270 #endif
271                     afs_com_err(whoami, errno, "can't create dir");
272                     printf("upclient: Can't update dir %s\n", curDir);
273                     return -1;
274                 }
275             } else {
276                 if ((tstat.st_mode & S_IFMT) != S_IFDIR) {
277                     printf(" file %s is not a directory; aborting\n", curDir);
278                     return -1;
279                 }
280             }
281             call = rx_NewCall(conn);
282
283             /* scratch pad file */
284             sprintf(dirbuf, "%s/upclient.%d", gettmpdir(), getpid());
285
286             errcode = FetchFile(call, df->name, dirbuf, 1);     /* get the names and relevant info about all the files in the directory df->name into file dirbuf */
287             error = rx_EndCall(call, 0);
288             if (error && !errcode) {
289                 printf("could not end rx call \n");
290                 afs_com_err(whoami, error, "calling EndCall");
291                 goto fail;
292             }
293             if (errcode) {
294                 printf
295                     ("warning: could not fetch the contents of directory %s \n",
296                      df->name);
297                 afs_com_err(whoami, errcode, "calling FetchFile");
298                 cnt++;
299                 goto fail;
300             }
301
302             stream = fopen(dirbuf, "r");
303             if (stream == NULL) {
304                 printf("fopen failed on %s \n", dirbuf);
305                 afs_com_err(whoami, errno, "fopen");
306                 goto fail;
307             }
308             umask(00);
309
310             /* while there is more info about files in file dirbuf */
311             while (fscanf
312                    (stream, "%c%[^\"]%c %u %u %u %u %u %u\n", &c, filename,
313                     &c1, &time, &length, &mode, &u_uid, &u_gid,
314                     &atime) != EOF) {
315                 uid = (short)u_uid;
316                 gid = (short)u_gid;
317                 AddToList(&okhostfiles, filename);
318                 /*record all the file names which exist on the remote
319                  * sync site, to enable purging of redundant files */
320                 if (verbose >= 3)
321                     printf("    checking %s\n", filename);
322                 if (!IsCompatible(filename, time, length)) {
323                     /* if the file info has changed , record all the 
324                      *changed files in the ModFiles array*/
325                     if (verbose >= 2)
326                         printf("  getting %s\n", filename);
327                     AddToList(&ModFiles, filename);
328
329                     /* now get the file from the server. The received 
330                      * file is created under the name filename.NEW */
331                     errcode =
332                         GetFileFromUpServer(conn, filename, uid, gid, mode,
333                                             atime, time);
334                     if (errcode == 1)   /* this file failed, but keep trying */
335                         goto fail_dirbuf;
336                     if (errcode == -1) {        /* time to quit */
337                         fclose(stream);
338                         unlink(dirbuf);
339                         return -1;
340                     }
341                 }
342
343             }
344             fclose(stream);
345             unlink(dirbuf);
346
347             {                   /*delete all the redundant files on the client */
348                 DIR *dirp;
349                 struct dirent *dp;
350                 char filename[MAXFNSIZE];
351
352                 dirp = opendir(curDir);
353                 if (dirp == 0) {
354                     afs_com_err(whoami, errno, "Can't open local dir %s", curDir);
355                     goto fail;
356                 }
357
358                 while ((dp = readdir(dirp))) {
359                     /* for all the files in the directory df->name do */
360                     strcpy(filename, curDir);
361                     strcat(filename, "/");
362                     strcat(filename, dp->d_name);
363                     /* if the file filename is redundant, delete it */
364                     errcode = NotOnHost(filename, okhostfiles);
365                     if (errcode == -1)
366                         return -1;
367                     if (errcode == 1) {
368                         if (verbose >= 2)
369                             printf("  flushing %s\n", filename);
370                         errcode = unlink(filename);
371                         if (errcode) {
372                             printf("could not delete file %s \n", filename);
373                             afs_com_err(whoami, errno, "could not delete file %s",
374                                     filename);
375                         }
376                     }
377                 }
378                 closedir(dirp);
379             }
380             /* Now, rename the .NEW files created by FetchFile */
381             if (RenameNewFiles(ModFiles))
382                 return -1;
383
384             free(curDir);
385         }                       /* end for each dir loop */
386         /*delete the file with info on files in directory df->name */
387         IOMGR_Sleep(interval);
388         continue;
389
390       fail_dirbuf:
391         fclose(stream);
392         unlink(dirbuf);
393       fail:
394         IOMGR_Sleep(retrytime);
395         if (cnt > 10) {
396             rx_DestroyConnection(conn);
397             goto again;
398         }
399         /* start the cycle again */
400
401     }
402 }
403
404 /* returns 1 if the file is upto date else returns 0*/
405 /*check the dir case more carefully */
406 int
407 IsCompatible(char *filename, afs_int32 time, afs_int32 length)  
408 {
409     struct stat status;
410     afs_int32 error;
411     char *localname;
412
413     /* construct a local path from canonical (wire-format) path */
414     if ((error = ConstructLocalPath(filename, "/", &localname))) {
415         afs_com_err(whoami, error, "Unable to construct local path");
416         return error;
417     }
418
419     error = stat(localname, &status);
420
421     free(localname);
422
423     if (error == -1)
424         return 0;       /*a non-existent file, has to be fetched fresh */
425     if ((status.st_mode & S_IFMT) == S_IFDIR
426         || ((status.st_mtime == time) && (status.st_size == length)))
427         return (1);
428     else
429         return 0;
430 }
431
432 int
433 FetchFile(struct rx_call *call, char *remoteFile, char *localFile, int dirFlag)
434 {
435     int fd = -1, error = 0;
436     struct stat status;
437
438     if (dirFlag) {
439         if (StartUPDATE_FetchInfo(call, remoteFile))
440             return UPDATE_ERROR;
441     } else {
442         if (StartUPDATE_FetchFile(call, remoteFile))
443             return UPDATE_ERROR;
444     }
445     fd = open(localFile, O_CREAT | O_TRUNC | O_WRONLY, 0666);
446     if (fd < 0) {
447         printf("Could not create %s\n", localFile);
448         afs_com_err(whoami, errno, "Could not create %s", localFile);
449         error = UPDATE_ERROR;
450         return error;
451     }
452     if (fstat(fd, &status) < 0) {
453         afs_com_err(whoami, errno, "Could not stat %s", localFile);
454         close(fd);
455         printf("could not stat %s\n", localFile);
456         return UPDATE_ERROR;
457     }
458     if (update_ReceiveFile(fd, call, &status))
459         error = UPDATE_ERROR;
460
461     close(fd);
462
463     return error;
464 }
465
466
467
468 int
469 update_ReceiveFile(int fd, struct rx_call *call, struct stat *status)
470 {
471     char *buffer = (char *)0;
472     afs_int32 length;
473     int blockSize;
474     afs_int32 error = 0, len;
475 #ifdef  AFS_AIX_ENV
476     struct statfs tstatfs;
477 #endif
478
479     len = rx_Read(call, (char *)&length, sizeof(afs_int32));
480     length = ntohl(length);
481     if (len != sizeof(afs_int32))
482         return UPDATE_ERROR;
483 #ifdef  AFS_AIX_ENV
484     /* Unfortunately in AIX valuable fields such as st_blksize are gone from the stat structure!! */
485     fstatfs(fd, &tstatfs);
486     blockSize = tstatfs.f_bsize;
487 #elif AFS_NT40_ENV
488     blockSize = 4096;
489 #else
490     blockSize = status->st_blksize;
491 #endif
492     buffer = (char *)malloc(blockSize);
493     if (!buffer) {
494         printf("malloc failed\n");
495         return UPDATE_ERROR;
496     }
497     while (!error && length) {
498         int nbytes = (length > blockSize ? blockSize : length);
499         nbytes = rx_Read(call, buffer, nbytes);
500         if (!nbytes)
501             error = UPDATE_ERROR;
502         if (write(fd, buffer, nbytes) != nbytes) {
503             afs_com_err(whoami, errno, "File system write failed!");
504             printf("File system write failed!\n");
505             error = UPDATE_ERROR;
506         }
507         length -= nbytes;
508     }
509     if (buffer)
510         free(buffer);
511     if (!error)
512         fstat(fd, status);
513     return error;
514 }
515
516
517 /*
518  * PathsAreEquivalent() -- determine if paths are equivalent
519  */
520 static int
521 PathsAreEquivalent(char *path1, char *path2)
522 {
523     int areEq = 0;
524     char pathNorm1[AFSDIR_PATH_MAX], pathNorm2[AFSDIR_PATH_MAX];
525
526 #ifdef AFS_NT40_ENV
527     /* case-insensitive comparison of normalized, same-flavor (short) paths */
528     DWORD status;
529
530     status = GetShortPathName(path1, pathNorm1, AFSDIR_PATH_MAX);
531     if (status == 0 || status > AFSDIR_PATH_MAX) {
532         /* can't convert path to short version; just use long version */
533         strcpy(pathNorm1, path1);
534     }
535     FilepathNormalize(pathNorm1);
536
537     status = GetShortPathName(path2, pathNorm2, AFSDIR_PATH_MAX);
538     if (status == 0 || status > AFSDIR_PATH_MAX) {
539         /* can't convert path to short version; just use long version */
540         strcpy(pathNorm2, path2);
541     }
542     FilepathNormalize(pathNorm2);
543
544     if (_stricmp(pathNorm1, pathNorm2) == 0) {
545         areEq = 1;
546     }
547 #else
548     /* case-sensitive comparison of normalized paths */
549     strcpy(pathNorm1, path1);
550     FilepathNormalize(pathNorm1);
551
552     strcpy(pathNorm2, path2);
553     FilepathNormalize(pathNorm2);
554
555     if (strcmp(pathNorm1, pathNorm2) == 0) {
556         areEq = 1;
557     }
558 #endif /* AFS_NT40_ENV */
559     return areEq;
560 }
561
562
563
564 /* returns 1 if filename does not exist on the host site (=> it should be
565  * deleted on client site) else it returns 0 */
566
567 int
568 NotOnHost(char *filename, struct filestr *okhostfiles)
569 {
570     int i, rc;
571     struct stat status;
572     struct filestr *tf;
573     char *hostfile;
574
575     stat(filename, &status);
576
577     if ((status.st_mode & S_IFMT) == S_IFDIR)
578         return 0;
579     i = strlen(filename);
580     if (!strcmp(&filename[i - 4], ".NEW"))
581         return 0;
582
583     for (tf = okhostfiles; tf; tf = tf->next) {
584         /* construct local path from canonical (wire-format) path */
585         if ((rc = ConstructLocalPath(tf->name, "/", &hostfile))) {
586             afs_com_err(whoami, rc, "Unable to construct local path");
587             return -1;
588         }
589         if (PathsAreEquivalent(hostfile, filename)) {
590             free(hostfile);
591             return 0;
592         }
593         free(hostfile);
594     }
595     return 1;
596 }
597
598
599 /* RenameNewFiles() - renames all the newly copied files from the
600  * server. Looks for files with .NEW extension and renames them
601  */
602 static int
603 RenameNewFiles(struct filestr *modFiles)
604 {
605     char newname[MAXFNSIZE];
606     char *fname;
607     int errcode = 0;
608     struct filestr *tf;
609
610     for (tf = modFiles; tf; tf = tf->next) {
611         /* construct local path from canonical (wire-format) path */
612         if ((errcode = ConstructLocalPath(tf->name, "/", &fname))) {
613             afs_com_err(whoami, errcode, "Unable to construct local path");
614             return errcode;
615         }
616         strcpy(newname, fname);
617         strcat(newname, ".NEW");
618         if (verbose >= 2)
619             printf("  renaming %s\n", newname);
620         errcode = renamefile(newname, fname);
621         if (errcode) {
622             printf("could not rename %s to %s\n", newname, fname);
623             afs_com_err(whoami, errno, "could not rename %s to %s", newname,
624                     fname);
625         }
626         free(fname);
627     }
628     return errcode;
629 }
630
631
632
633 /* GetFileFromUpServer() - Makes the FetchFile() call and gets the
634  * file from the upserver. 
635  * Return Values:
636  *   0 -  Alls well
637  *   -1 - Serious error. Quit right away.
638  *   1  - Error, but keep trying for the other files 
639  * 
640  * The file obtained is written to the localized version of the filename.NEW
641  * and the uid, gid, file mode, access and modification times will be set to
642  * the passed in values.
643  */
644 static int
645 GetFileFromUpServer(struct rx_connection *conn, /* handle for upserver */
646                     char *filename,     /* name of file to be fetched */
647                     short uid, short gid,       /* uid/gid for fetched file */
648                     afs_uint32 mode,    /* file mode */
649                     afs_int32 atime, afs_int32 mtime)
650 {                               /* access/modification times */
651     struct rx_call *call;
652     afs_int32 errcode;
653     char *lfile;
654 #ifdef AFS_NT40_ENV
655     struct _utimbuf utbuf;
656 #else
657     struct timeval tvp[2];
658 #endif
659     char newfile[MAXFNSIZE];
660
661     /* construct local path from canonical (wire-format) path */
662     errcode = ConstructLocalPath(filename, "/", &lfile);
663     if (errcode) {
664         afs_com_err(whoami, errcode, "Unable to construct local path");
665         return -1;
666     }
667     strcpy(newfile, lfile);
668     free(lfile);
669
670     strcat(newfile, ".NEW");
671
672     /* fetch filename into newfile from the host, since the current file
673      * is outdated. the new versions of changed files is stored as
674      * oldname.new */
675     call = rx_NewCall(conn);
676     errcode = FetchFile(call, filename, newfile, 0);
677     errcode = rx_EndCall(call, errcode);
678
679     if (errcode) {
680         printf("failed to fetch file %s \n", filename);
681         afs_com_err(whoami, errcode, "fetching file");
682         unlink(newfile);
683         return 1;
684     }
685
686     /* now set the rest of the file status */
687     errcode = chmod(newfile, mode);
688     if (errcode) {
689         printf("could not change protection on %s to %u\n", newfile,
690                (unsigned int)mode);
691         afs_com_err(whoami, errno, "could not change protection on %s to %u",
692                 newfile, mode);
693         unlink(newfile);
694         return 1;
695     }
696 #ifdef AFS_NT40_ENV
697     utbuf.actime = atime;
698     utbuf.modtime = mtime;
699     errcode = _utime(newfile, &utbuf);
700 #else
701     errcode = chown(newfile, uid, gid);
702     if (errcode) {
703         printf("warning: could not change uid and gid on %s to %u and %u \n",
704                newfile, gid, uid);
705         afs_com_err(whoami, errno,
706                 "warning: could not change uid and gid on %s to %u and %u",
707                 newfile, gid, uid);
708     }
709     tvp[0].tv_sec = atime;
710     tvp[0].tv_usec = 0;
711     tvp[1].tv_sec = mtime;
712     tvp[1].tv_usec = 0;
713     errcode = utimes(newfile, tvp);
714 #endif /* NT40 */
715     if (errcode) {
716         printf("could not change access and modify times on %s to %u %u\n",
717                newfile, (unsigned int)atime, (unsigned int)mtime);
718         afs_com_err(whoami, errno,
719                 "could not change access and modify times on %s to %u %u",
720                 newfile, atime, mtime);
721         unlink(newfile);
722         return 1;
723     }
724
725     return 0;
726 }