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