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