source: trunk/athena/etc/synctree/synctree.c @ 12218

Revision 12218, 17.4 KB checked in by rbasch, 26 years ago (diff)
Portability fixes for IRIX 6.5.
Line 
1/* Copyright (C) 1988  Tim Shepard   All rights reserved. */
2
3#include <stdio.h>
4#include <sys/types.h>
5#include <sys/param.h>
6#include <stdlib.h>
7#include <unistd.h>
8#include <dirent.h>
9#include <sys/stat.h>
10
11#include <sys/time.h>
12#include <sys/resource.h>
13#include <string.h>
14#ifdef OLD
15#include <ndbm.h>
16#endif
17#include <errno.h>
18
19#ifdef SOLARIS
20#define NO_LINEBUF
21#endif
22#include "synctree.h"
23
24#ifndef DEBUG
25char *srcdir = "/server";
26char *dstdir = "";            /* effectively "/" */
27char *src_rule_file = ".rconf";
28char *dst_rule_file = ".rconf.local";
29char *date_db_name  = ".reconcile_dates";
30#else /* DEBUG */
31char *srcdir = "s";
32char *dstdir = "d";
33char *src_rule_file = ".rconf";
34char *dst_rule_file = ".rconf.local";
35#ifdef DATE
36char *date_db_name  = ".reconcile_dates";
37#endif
38#endif /* DEBUG */
39
40rule rules[MAXNRULES];
41unsigned int lastrule;
42unsigned int rflag = 0;
43
44int dodir();
45char *destination_pathname();
46
47int  verbosef = 1;
48int  nflag = 0;
49int  aflag = 0;
50char *afile;
51
52bool nosrcrules = FALSE;
53bool nodstrules = FALSE;
54uid_t uid, euid;
55#ifndef SOLARIS
56uid_t getuid(), geteuid();
57#endif
58void usage(arg0)
59     char *arg0;
60{
61  fprintf(stderr,"Usage: %s [-v] [-n] [-q] [-nosrcrules] [-nodstrules] [-s srcdir] [-d dstdir] [-a rules]\n",arg0);
62  exit(1);
63}
64
65main(argc, argv)
66     int argc;
67     char *argv[];
68{
69  int i;
70#ifndef NO_RLIMIT
71  struct rlimit rl;
72#endif
73
74#ifndef NO_LINEBUF
75  setlinebuf(stdout);
76#else
77  setvbuf(stdout,NULL,_IOLBF,BUFSIZ);
78#endif
79
80  uid = getuid();
81  euid = geteuid();
82
83#ifndef NO_RLIMIT
84  getrlimit(RLIMIT_DATA,&rl);
85  rl.rlim_cur = rl.rlim_max;
86  setrlimit(RLIMIT_DATA,&rl);
87
88  getrlimit(RLIMIT_STACK,&rl);
89  rl.rlim_cur = rl.rlim_max;
90  setrlimit(RLIMIT_STACK,&rl);
91#endif
92
93  i = 0;
94 
95  while (++i < argc)
96    {
97      if (argv[i][0] != '-') usage(argv[0]);
98
99      switch (argv[i][1]) {
100      case 's':
101        if (argv[i][2])
102          { srcdir = (char *) malloc(strlen(&argv[i][2])+1);
103            (void) strcpy(srcdir, &argv[i][2]);
104          }
105        else if (++i >= argc) usage(argv[0]);
106        else
107          { srcdir = (char *) malloc(strlen(&argv[i][0])+1);
108            (void) strcpy(srcdir, argv[i]);
109          }
110        break;
111
112      case 'd':
113        if (argv[i][2])
114          { dstdir = (char *) malloc(strlen(&argv[i][2])+1);
115            (void) strcpy(dstdir, &argv[i][2]);
116          }
117        else if (++i >= argc) usage(argv[0]);
118        else
119          { dstdir = (char *) malloc(strlen(&argv[i][0])+1);
120            (void) strcpy(dstdir, argv[i]);
121          }
122        if (strcmp(dstdir,"/") == 0)
123          dstdir[0] = '\0';
124        break;
125
126      case 'a':
127        if (argv[i][2])
128          { afile = (char *) malloc(strlen(&argv[i][2])+1);
129            (void) strcpy(afile, &argv[i][2]);
130          }
131        else if (++i >= argc) usage(argv[0]);
132        else
133          { afile = (char *) malloc(strlen(&argv[i][0])+1);
134            (void) strcpy(afile, argv[i]);
135          }
136        if (strcmp(afile,"/") == 0)
137          afile[0] = '\0';
138        aflag++;
139        break;
140
141      case 'v':
142        verbosef++;
143        break;
144      case 'q':
145        verbosef = 0;
146        break;
147      case 'n':
148        if (strcmp(&argv[i][1],"nosrcrules") == 0)
149          { nosrcrules = TRUE;
150            break;
151          }         
152        if (strcmp(&argv[i][1],"nodstrules") == 0)
153          { nodstrules = TRUE;
154            break;
155          }         
156        if (strcmp(&argv[i][1],"n") == 0)
157          { nflag = 1;
158            break;
159          }         
160        /* fall through to default */
161      default:
162        usage(argv[0]);
163      }
164    }
165     
166  (void) umask(0);
167
168  if (srcdir[0] != '/')
169    { char *p = srcdir;
170      srcdir = (char *) malloc(MAXPATHLEN);
171      if ((srcdir = getcwd(srcdir,MAXPATHLEN )) == NULL)
172        { perror("getcwd() failed");
173          fprintf(stderr,"exiting\n");
174          exit(1);
175        }
176      if ((strlen(srcdir) + strlen("/") + strlen(p) + 1) > MAXPATHLEN)
177        { fprintf(stderr,"full pathname is too long, exiting");
178          exit(1);
179        }
180      (void) strcat(srcdir,"/");
181      (void) strcat(srcdir,p);
182    }
183
184  if ((dstdir[0] != '/') && (dstdir[0] != '\0'))
185    { char *p = dstdir;
186      dstdir = (char *) malloc(MAXPATHLEN);
187      if ((dstdir = getcwd(dstdir, MAXPATHLEN)) == NULL)
188        { perror("getcwd() failed");
189          fprintf(stderr,"exiting\n");
190          exit(1);
191        }
192      if ((strlen(dstdir) + strlen("/") + strlen(p) + 1) > MAXPATHLEN)
193        { fprintf(stderr,"full pathname is too long, exiting");
194          exit(1);
195        }
196
197      (void) strcat(dstdir,"/");
198      (void) strcat(dstdir,p);
199    }
200
201
202  lastrule = 0;
203
204  /* The hardwired rules (number zero and number one):
205            map * $
206            ignore *
207
208            */
209
210  {
211      static char *mapdests[] = { "$", (char *)0 };
212
213      lstrule.type = R_MAP;
214      lstrule.u.u_map.globexp    = "*";
215      lstrule.u.u_map.file_types = TYPE_ALL;
216      lstrule.u.u_map.dests      = mapdests;
217  }
218  newrule();
219  lstrule.type = R_ACTION;
220  lstrule.u.u_action.type        = ACTION_IGNORE;
221  lstrule.u.u_action.globexp     = "*";
222  lstrule.u.u_action.file_types  = TYPE_ALL;
223  lstrule.u.u_action.options     = 0;
224
225  exit(dodir(srcdir,dstdir,""));
226}
227
228struct src {
229  char *name;
230  char *pathname;
231  struct stat stat;
232  unsigned int type;
233  int map_ruleno;
234  bool chased;
235  struct src *next;
236};
237
238struct targ {
239  char *pathname;
240  struct src *sp;
241  int action_ruleno;
242  bool updated; /* for when */
243  struct targ *next;
244};
245
246int dodir(src,dst,part)
247     char *src;
248     char *dst;
249     char *part;
250{
251    struct src srclist,*sp,*spp,*sppp;
252    struct targ targlist,*tp,*tpp,*tppp;
253    int i,j;
254    void *getmemt;
255    bool sorted;
256    DIR *dirp;
257    struct dirent *dp;
258    char *testpath;
259    struct stat teststat;
260
261    if (verbosef)
262        printf("processing %s\n", dst);
263 
264    srclist.next = 0;
265    targlist.next = 0;
266
267#define getmem(amt) (((getmemt = malloc(amt)) == NULL)? panic("dodir: bad return from malloc"), (void *) NULL : getmemt)
268#define newsrc(ptr)  (ptr = (struct src *)  getmem(sizeof(struct src)),  ptr->next=srclist.next,  srclist.next=ptr)
269#define newtarg(ptr) (ptr = (struct targ *) getmem(sizeof(struct targ)), ptr->next=targlist.next, targlist.next=ptr)
270#define allsrcs(ptr)  for (ptr=srclist.next; ptr!=NULL; ptr=ptr->next)
271#define alltargs(ptr) for (ptr=targlist.next; ptr!=NULL; ptr=ptr->next)
272
273    getrules(src,dst);
274 
275    /* read in source directory */
276    if ((dirp = opendir(src)) == NULL) {
277        fprintf(stderr, "Can't read directory %s: %s\n", src, strerror(errno));
278        return 1;
279    }
280    while ((dp = readdir(dirp)) != NULL) {
281        if (!strcmp(dp->d_name,".") || !strcmp(dp->d_name,".."))
282            continue;
283        newsrc(sp);
284        sp->pathname = (char *)0;
285        sp->name = (char *) getmem(strlen(dp->d_name) + 1);
286        strcpy(sp->name, dp->d_name);
287    }
288    closedir(dirp);
289
290    /* XXX */
291    /* read in target directory */
292    if ((rflag & RFLAG_SCAN_TARGET) && (dirp = opendir(dst))) {
293        testpath = (char *) getmem(strlen(src) + 66);
294        while ((dp = readdir(dirp)) != NULL) {
295            if (!strcmp(dp->d_name,".") || !strcmp(dp->d_name,".."))
296                continue;
297            strcpy(testpath,src);
298            strcat(testpath,"/");
299            strcat(testpath,dp->d_name);
300            if (lstat(testpath, &teststat)) {
301                newsrc(sp);
302                sp->name  = (char *) getmem(strlen(dp->d_name) + 1);
303                strcpy(sp->name, dp->d_name);
304
305                sp->pathname =
306                    (char *) getmem( strlen(src) + strlen(sp->name) + 2 );
307                strcpy(sp->pathname, testpath);
308            }
309        }
310        closedir(dirp);
311    }
312    /* XXX */
313
314    /* process each src */
315    allsrcs(sp) {
316
317        /*
318         * If sp->pathname is already set, it must be a virtual file,
319         * which indicates that the file only exists in the target
320         * directory.  We still wish to get the stats for such files,
321         * but we want to mark it as a virtual file, so we frob with
322         * the sp->type field.
323         */
324        if (!sp->pathname) {
325            sp->pathname =
326                (char *) getmem( strlen(src) + strlen(sp->name) + 2 );
327            (void) strcpy(sp->pathname,src);
328            (void) strcat(sp->pathname,"/");
329            (void) strcat(sp->pathname,sp->name);
330            /* get information about file */
331            if (lstat(sp->pathname,&(sp->stat)) < 0) {
332#define perror(problem, whatnext) printf("%s: %s: %s. %s\n", sp->pathname, problem, strerror(errno), whatnext)
333                perror("lstat() failed", "ignoring");
334                sp->map_ruleno = 0;
335                continue;
336            }
337
338            sp->type = 0;
339
340        } else {
341            char path[MAXPATHLEN];
342
343            (void) strcpy(path, dst);
344            (void) strcat(path, "/");
345            (void) strcat(path, sp->name);
346
347            if (lstat(path, &(sp->stat)) < 0) {
348#define perror(problem, whatnext) printf("%s: %s: %s. %s\n", sp->pathname, problem, strerror(errno), whatnext)
349                perror("lstat() failed", "ignoring");
350                sp->map_ruleno = 0;
351                continue;
352            }
353             
354            sp->type = TYPE_V;
355        }
356   
357        {
358            int mode = sp->stat.st_mode & S_IFMT;
359            switch(mode) {
360#define X(a,b) case a: \
361                if (verbosef > 1) printf("Found %s, mode %7.7o\n",sp->pathname,mode); \
362                    sp->type |= b; break
363                        X(S_IFDIR,TYPE_D);
364                X(S_IFCHR,TYPE_C);
365                X(S_IFBLK,TYPE_B);
366                X(S_IFREG,TYPE_R);
367                X(S_IFLNK,TYPE_L);
368                X(S_IFSOCK,TYPE_S);
369#undef X
370            default:
371                printf("%s: unknown type of src file: %7.7o, ignoring.\n",
372                       sp->pathname, mode);
373                sp->map_ruleno = 0;
374                continue;
375            }
376        }
377   
378        if ((sp->type == TYPE_L) &&
379            (findrule(sp->pathname,R_CHASE,sp->type,sp->pathname) > 0)) {
380            sp->chased = TRUE;
381            if (stat(sp->pathname,&(sp->stat)) < 0) {
382                perror("stat() after chase failed", "ignoring.");
383#undef perror
384                sp->map_ruleno = 0;
385                continue;
386            }
387            {
388                int mode = sp->stat.st_mode & S_IFMT;
389                switch (mode) {
390#define X(a,b) case a: sp->type = b ; break
391                    X(S_IFDIR,TYPE_D);
392                    X(S_IFCHR,TYPE_C);
393                    X(S_IFBLK,TYPE_B);
394                    X(S_IFREG,TYPE_R);
395                    X(S_IFLNK,TYPE_L);
396                    X(S_IFSOCK,TYPE_S);
397#undef X
398                default:
399                    printf("%s: unknown type of chased file: %7.7o, ignoring.\n",
400                           sp->pathname, mode);
401                    sp->map_ruleno = 0;
402                    continue;
403                }
404            }
405        } else {
406            sp->chased = FALSE;
407        }
408 
409        sp->map_ruleno = findrule(sp->pathname,R_MAP,sp->type,sp->pathname);
410
411        for (i = 0; rules[sp->map_ruleno].u.u_map.dests[i] != 0; i++) {
412            char *r;
413            newtarg(tp);
414            r = destination_pathname(dst,
415                                     sp->name,
416                                     rules[sp->map_ruleno].u.u_map.dests[i]);
417            tp->pathname = (char *) getmem(strlen(r)+1);
418            strcpy(tp->pathname,r);
419            tp->sp = sp;
420            if (verbosef > 2) printf("new targ: %s (from %s)\n",
421                                     tp->pathname, sp->pathname);
422
423        }
424    }
425    alltargs(tp) {
426        tp->action_ruleno =
427            findrule(tp->pathname,R_ACTION,tp->sp->type,tp->sp->pathname);
428        tp->updated = FALSE;
429    }
430
431    alltargs(tp) {
432        bool exists = FALSE;
433        bool chased = FALSE;
434        struct stat targstat;
435        unsigned int targtype;
436        time_t dbdate;
437        if (lstat(tp->pathname,&targstat) == 0) {
438            int mode = targstat.st_mode & S_IFMT;
439       
440            exists = TRUE;     
441            switch (mode) {
442#define X(a,b) case a: targtype = b ; break
443                X(S_IFDIR,TYPE_D);
444                X(S_IFCHR,TYPE_C);
445                X(S_IFBLK,TYPE_B);
446                X(S_IFREG,TYPE_R);
447                X(S_IFLNK,TYPE_L);
448                X(S_IFSOCK,TYPE_S);
449#undef X
450            }
451            if ((targtype == TYPE_L) &&
452                option_on(tp->action_ruleno, 'l')) {
453                if (stat(tp->pathname,&targstat) != 0) {
454                    /*** the link exists but its target does not */
455                    /*** hopefully, we can make it exist ..... */
456                    chased = TRUE;
457                    exists = FALSE;
458                } else {
459                    chased = TRUE;
460                    switch (targstat.st_mode & S_IFMT) {
461#define X(a,b) case a: targtype = b ; break
462                        X(S_IFDIR,TYPE_D);
463                        X(S_IFCHR,TYPE_C);
464                        X(S_IFBLK,TYPE_B);
465                        X(S_IFREG,TYPE_R);
466                        X(S_IFLNK,TYPE_L);
467                        X(S_IFSOCK,TYPE_S);
468#undef X
469                    }
470                }
471            }
472        }
473
474#define srcstat (tp->sp->stat)
475        /*      targstat */
476
477        /* "switch.c" knows only about what is defined or mentioned between here and where it is included */
478
479#define typeofaction (rules[tp->action_ruleno].u.u_action.type)
480#define pflag   (option_on(tp->action_ruleno,'p'))
481#define fflag   (option_on(tp->action_ruleno,'f'))
482#define dflag   (option_on(tp->action_ruleno,'d'))
483
484        /* modemask is not known to switch.c */
485#define modemask (pflag ? 07777 : 00777)
486
487#define srcname (tp->sp->pathname)
488#define srcdev  (int) (srcstat.st_rdev)
489#define srctype (tp->sp->type)
490#define srcuid  ((int) (srcstat.st_uid))
491
492#define targname (tp->pathname)
493#define targdev  (int) (targstat.st_rdev)
494#define targuid  ((int) (targstat.st_uid))
495#define targmode (int) (targstat.st_mode & modemask)
496#define tmark()  (tp->updated = TRUE)
497
498#define srcgid  ((gid_t) (srcstat.st_gid))
499#define targgid  ((gid_t) (targstat.st_gid))
500
501#if defined(_AIX) && defined(_IBMR2)
502#define srcmode (int)(srcstat.st_mode & ((srctype==TYPE_D) ? 07777 : modemask))
503#else
504#define srcmode (int)(srcstat.st_mode & modemask)
505#endif
506
507#define ERROR -1
508#define NODBDATE 0
509#define NODBDATE_P LOCAL                /* (forceupdatef? OUTOFDATE : LOCAL) */
510#define UPTODATE 1
511#define OUTOFDATE 2
512#define NEWERDATE 3
513#define LOCAL 4
514#define LOCAL_P LOCAL                   /* (forceupdatef? OUTOFDATE : LOCAL) */
515#define LOCALBUTOUTOFDATE 6
516#define LOCALBUTOUTOFDATE_P LOCAL       /* (forceupdatef? OUTOFDATE : LOCAL) */
517#define FIXMODE 6
518#define FIXOWNANDMODE 7
519
520#define filecheck3() \
521        ((srcstat.st_mtime > targstat.st_mtime) ? \
522         OUTOFDATE : \
523         ((srcstat.st_mtime < targstat.st_mtime) || \
524          (srcstat.st_size != targstat.st_size)) ? \
525         NEWERDATE : \
526         (pflag && ((srcuid != targuid) || (srcgid != targgid))) ? \
527         FIXOWNANDMODE : \
528         ((srcmode&modemask) != (targmode&modemask)) ? \
529         FIXMODE : \
530         UPTODATE)
531       
532#define dircheck() \
533        ((pflag && ((srcuid != targuid) || \
534                    ((srcgid >= 0) && (srcgid != targgid)))) \
535         ? FIXOWNANDMODE : \
536         (pflag && ((srcmode&modemask) != (targmode&modemask))) \
537         ? FIXMODE : UPTODATE)
538
539#define setdates() \
540        { time_t date = srcstat.st_mtime; \
541          struct timeval tv[2]; \
542          tv[0].tv_sec = tv[1].tv_sec = date; \
543          tv[0].tv_usec = tv[1].tv_usec = 0; \
544          (void) utimes(targname,tv); \
545          tmark(); \
546        }
547
548    if (verbosef > 2) {
549      printf("entering switch.c:  %s ----> %s\n", srcname, targname);
550      printf("              typeofaction   is %d\n", typeofaction);
551      printf("              pflag              is %d\n", pflag);
552      printf("              modemask       is %4.4o\n", modemask);
553      printf("              srcname            is %s\n", srcname);
554      printf("              srcmode            is %7.7o\n", srcstat.st_mode);
555      printf("              srcdev             is %4.4x\n", srcdev);
556      printf("              srctype            is %d\n", srctype);
557      printf("              srcuid             is %d\n", srcuid);
558      printf("              srcgid             is %d\n", srcgid);
559      printf("              targname       is %s\n", targname);
560      printf("              exists             is %d\n", exists);
561      if (exists) {
562        printf("              targmode   is %7.7o\n", targstat.st_mode);
563        printf("              targdev    is %4.4x\n", targdev);
564        printf("              targtype   is %d\n", targtype);
565        printf("              targuid    is %d\n", targuid);
566        printf("              targgid    is %d\n", targgid);
567      }
568      printf("\n");
569    }     
570
571#include "switch.c"
572  }
573 
574  /* do when rules */
575  alltargs(tp) {
576    if (tp->updated) {
577      void dowhenrule();
578      int whenruleno;
579      whenruleno = findrule(tp->pathname, R_WHEN, tp->sp->type, tp->sp->pathname);
580      if (whenruleno != -1)
581        dowhenrule(whenruleno, tp->pathname);
582    }
583  }
584
585    {
586        char * ptr = (char *)NULL;
587       
588        allsrcs(sp) {
589            free(ptr);
590            ptr = (char *) sp;
591            free(sp->pathname);
592            free(sp->name);
593        }
594        alltargs(tp) {
595            free(ptr);
596            ptr = (char *) tp;
597            free(tp->pathname);
598        }
599        free(ptr);
600    }
601   
602  poprules();
603  return 0;
604}
605
606char * destination_pathname(dstdir, sname, mapstring)
607     char *dstdir, *sname, *mapstring;
608{
609  static char buf[MAXPATHLEN];
610  register char *p,*bp,*sp;
611  register char *lastdot;
612  bp = buf;
613
614  if (verbosef>2) printf("destination_pathname(%s,%s,%s)\n",
615                         dstdir, sname, mapstring);
616
617  for (p = dstdir; *p != '\0'; p++) *bp++ = *p;
618
619  *bp++ = '/';
620
621  for (p = mapstring; *p != '\0'; p++) {
622    switch (*p) {
623    case '$':
624      lastdot = NULL;
625      for (sp = sname; *sp != '\0'; sp++)
626        if ((*bp++ = *sp) == '.')
627          lastdot = bp - 1;
628      p++;
629      switch(*p) {
630      default:
631        p--; continue;
632      case ':':
633        p++;
634        switch(*p) {
635        case '\0':
636          /***** maybe this should be an error */
637          p--; p--; continue;
638        case 'r':
639          if (lastdot)
640            bp = lastdot;
641          else
642            { ; /***** should be an error of some sort */ }
643          continue;
644        default:
645          /***** should be an error of some sort */
646          continue;
647        }
648      }
649      /*NOTREACHED*/
650    default:
651      *bp++ = *p;
652      continue;
653    }
654  }
655
656  *bp = '\0';
657 
658  if (verbosef>1) printf("destination_pathname returning %s\n",buf);
659
660  return buf;
661}
662
663
664void dowhenrule(ruleno, tname)
665     int ruleno;
666     char *tname;
667{
668    char *cp;
669    int pid;
670    char *shell, *arg1;
671    int pipefds[2];
672
673    if (rules[ruleno].type != R_WHEN)
674        panic("dowhenrule:  rule type is not R_WHEN");
675
676    switch(rules[ruleno].u.u_when.type) {
677    case WHEN_SH:
678        shell = "/bin/sh";
679        arg1  = "-s";
680        break;
681    case WHEN_CSH:
682        shell = "/bin/csh";
683        arg1  = "-fs";
684        break;
685    }
686   
687    if (pipe(pipefds) != 0)
688        epanic("dowhenrule:  pipe");
689
690    if (nflag == 0) {
691        pid = fork();
692   
693        if (pid == 0) {
694
695            cp = tname;
696            while (*cp != '\0') cp++;
697            while (*cp != '/') cp--;
698            *cp = '\0';
699            if (cp == tname)
700                chdir("/");
701            else
702                chdir(tname);
703            *cp = '/';
704
705            dup2(pipefds[0], 0);
706            execl(shell, shell, arg1, 0);
707            epanic("dowhenrule:  execl");
708        } else if (pid > 0) {
709            char **c;
710            FILE *f;
711
712            f = fdopen(pipefds[1],"w");
713            c = rules[ruleno].u.u_when.cmds;
714
715            while (*c) {
716                if (verbosef) printf("%c %s",
717                                     (rules[ruleno].u.u_when.type == WHEN_SH)
718                                     ? '$' : '%', *c);
719                fprintf(f,"%s\n", *(c++));
720            }
721            /* kludge killing the sh because I cannot figure out how to
722             * make it go away on end of file */
723            switch(rules[ruleno].u.u_when.type) {
724            case WHEN_SH:
725                fprintf(f,"kill $$\n");
726                break;
727            default:
728                break;
729            }
730            fclose(f);
731        } else if (pid < 0) {
732            epanic("dowhenrule: fork");
733        }
734    } else                              /* If noop */
735        {
736            char **c;
737            c = rules[ruleno].u.u_when.cmds;
738
739            while (*c) {
740                if (verbosef) printf("%c %s",
741                                     (rules[ruleno].u.u_when.type == WHEN_SH)? '$': '%',
742                                     *c);
743            }   
744        }
745}
746
Note: See TracBrowser for help on using the repository browser.