]> CyberLeo.Net >> Repos - FreeBSD/releng/8.1.git/blob - contrib/csup/misc.c
Copy stable/8 to releng/8.1 in preparation for 8.1-RC1.
[FreeBSD/releng/8.1.git] / contrib / csup / misc.c
1 /*-
2  * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  *
26  * $FreeBSD$
27  */
28
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <openssl/md5.h>
32
33 #include <assert.h>
34 #include <err.h>
35 #include <errno.h>
36 #include <fcntl.h>
37 #include <limits.h>
38 #include <pthread.h>
39 #include <stdarg.h>
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <time.h>
44 #include <unistd.h>
45
46 #include "fattr.h"
47 #include "main.h"
48 #include "misc.h"
49
50 struct pattlist {
51         char **patterns;
52         size_t size;
53         size_t in;
54 };
55
56 struct backoff_timer {
57         time_t min;
58         time_t max;
59         time_t interval;
60         float backoff;
61         float jitter;
62 };
63
64 static void     bt_update(struct backoff_timer *);
65 static void     bt_addjitter(struct backoff_timer *);
66
67 int
68 asciitoint(const char *s, int *val, int base)
69 {
70         char *end;
71         long longval;
72
73         errno = 0;
74         longval = strtol(s, &end, base);
75         if (errno || *end != '\0')
76                 return (-1);
77         if (longval > INT_MAX || longval < INT_MIN) {
78                 errno = ERANGE;
79                 return (-1);
80         }
81         *val = longval;
82         return (0);
83 }
84
85 int
86 lprintf(int level, const char *fmt, ...)
87 {
88         FILE *to;
89         va_list ap;
90         int ret;
91
92         if (level > verbose)
93                 return (0);
94         if (level == -1)
95                 to = stderr;
96         else
97                 to = stdout;
98         va_start(ap, fmt);
99         ret = vfprintf(to, fmt, ap);
100         va_end(ap);
101         fflush(to);
102         return (ret);
103 }
104
105 /*
106  * Compute the MD5 checksum of a file.  The md parameter must
107  * point to a buffer containing at least MD5_DIGEST_SIZE bytes.
108  *
109  * Do not confuse OpenSSL's MD5_DIGEST_LENGTH with our own
110  * MD5_DIGEST_SIZE macro.
111  */
112 int
113 MD5_File(char *path, char *md)
114 {
115         char buf[1024];
116         MD5_CTX ctx;
117         ssize_t n;
118         int fd;
119
120         fd = open(path, O_RDONLY);
121         if (fd == -1)
122                 return (-1);
123         MD5_Init(&ctx);
124         while ((n = read(fd, buf, sizeof(buf))) > 0)
125                 MD5_Update(&ctx, buf, n);
126         close(fd);
127         if (n == -1)
128                 return (-1);
129         MD5_End(md, &ctx);
130         return (0);
131 }
132
133 /*
134  * Wrapper around MD5_Final() that converts the 128 bits MD5 hash
135  * to an ASCII string representing this value in hexadecimal.
136  */
137 void
138 MD5_End(char *md, MD5_CTX *c)
139 {
140         unsigned char md5[MD5_DIGEST_LENGTH];
141         const char hex[] = "0123456789abcdef";
142         int i, j;
143
144         MD5_Final(md5, c);
145         j = 0;
146         for (i = 0; i < MD5_DIGEST_LENGTH; i++) {
147                 md[j++] = hex[md5[i] >> 4];
148                 md[j++] = hex[md5[i] & 0xf];
149         }
150         md[j] = '\0';
151 }
152
153 int
154 pathcmp(const char *s1, const char *s2)
155 {
156         char c1, c2;
157
158         do {
159                 c1 = *s1++;
160                 if (c1 == '/')
161                         c1 = 1;
162                 c2 = *s2++;
163                 if (c2 == '/')
164                         c2 = 1;
165         } while (c1 == c2 && c1 != '\0');
166
167         return (c1 - c2);
168 }
169
170 size_t
171 commonpathlength(const char *a, size_t alen, const char *b, size_t blen)
172 {
173         size_t i, minlen, lastslash;
174
175         minlen = min(alen, blen);
176         lastslash = 0;
177         for (i = 0; i < minlen; i++) {
178                 if (a[i] != b[i])
179                         return (lastslash);
180                 if (a[i] == '/') {
181                         if (i == 0)     /* Include the leading slash. */
182                                 lastslash = 1;
183                         else
184                                 lastslash = i;
185                 }
186         }
187
188         /* One path is a prefix of the other/ */
189         if (alen > minlen) {            /* Path "b" is a prefix of "a". */
190                 if (a[minlen] == '/')
191                         return (minlen);
192                 else
193                         return (lastslash);
194         } else if (blen > minlen) {     /* Path "a" is a prefix of "b". */
195                 if (b[minlen] == '/')
196                         return (minlen);
197                 else
198                         return (lastslash);
199         }
200
201         /* The paths are identical. */
202         return (minlen);
203 }
204
205 const char *
206 pathlast(const char *path)
207 {
208         const char *s;
209
210         s = strrchr(path, '/');
211         if (s == NULL)
212                 return (path);
213         return (++s);
214 }
215
216 int
217 rcsdatetotm(const char *revdate, struct tm *tm)
218 {
219         char *cp;
220         size_t len;
221
222         cp = strchr(revdate, '.');
223         if (cp == NULL)
224                 return (-1);
225         len = cp - revdate;
226         if (len >= 4)
227                 cp = strptime(revdate, "%Y.%m.%d.%H.%M.%S", tm);
228         else if (len == 2)
229                 cp = strptime(revdate, "%y.%m.%d.%H.%M.%S", tm);
230         else
231                 return (-1);
232         if (cp == NULL || *cp != '\0')
233                 return (-1);
234         return (0);
235 }
236
237 time_t
238 rcsdatetotime(const char *revdate)
239 {
240         struct tm tm;
241         time_t t;
242         int error;
243
244         error = rcsdatetotm(revdate, &tm);
245         if (error)
246                 return (error);
247         t = timegm(&tm);
248         return (t);
249 }
250
251 /*
252  * Checks if a file is an RCS file.
253  */
254 int
255 isrcs(const char *file, size_t *len)
256 {
257         const char *cp;
258
259         if (file[0] == '/')
260                 return (0);
261         cp = file;
262         while ((cp = strstr(cp, "..")) != NULL) {
263                 if (cp == file || cp[2] == '\0' ||
264                     (cp[-1] == '/' && cp[2] == '/'))
265                         return (0);
266                 cp += 2;
267         }
268         *len = strlen(file);
269         if (*len < 2 || file[*len - 1] != 'v' || file[*len - 2] != ',') {
270                 return (0);
271         }
272
273         return (1);
274 }
275
276 /*
277  * Returns a buffer allocated with malloc() containing the absolute
278  * pathname to the checkout file made from the prefix and the path
279  * of the corresponding RCS file relatively to the prefix.  If the
280  * filename is not an RCS filename, NULL will be returned.
281  */
282 char *
283 checkoutpath(const char *prefix, const char *file)
284 {
285         char *path;
286         size_t len;
287
288         if (!isrcs(file, &len))
289                 return (NULL);
290         xasprintf(&path, "%s/%.*s", prefix, (int)len - 2, file);
291         return (path);
292 }
293
294 /*
295  * Returns a cvs path allocated with malloc() containing absolute pathname to a
296  * file in cvs mode which can reside in the attic. XXX: filename has really no
297  * restrictions.
298  */
299 char *
300 cvspath(const char *prefix, const char *file, int attic)
301 {
302         const char *last;
303         char *path;
304
305         last = pathlast(file);
306         if (attic)
307                 xasprintf(&path, "%s/%.*sAttic/%s", prefix, (int)(last - file),
308                     file, last);
309         else
310                 xasprintf(&path, "%s/%s", prefix, file);
311
312         return (path);
313 }
314
315 /*
316  * Regular or attic path if regular fails.
317  * XXX: This should perhaps also check if the Attic file exists too, and return
318  * NULL if not.
319  */
320 char *
321 atticpath(const char *prefix, const char *file)
322 {
323         char *path;
324
325         path = cvspath(prefix, file, 0);
326         if (access(path, F_OK) != 0) {
327                 free(path);
328                 path = cvspath(prefix, file, 1);
329         }
330         return (path);
331 }
332
333 int
334 mkdirhier(char *path, mode_t mask)
335 {
336         struct fattr *fa;
337         size_t i, last, len;
338         int error, finish, rv;
339
340         finish = 0;
341         last = 0;
342         len = strlen(path);
343         for (i = len - 1; i > 0; i--) {
344                 if (path[i] == '/') {
345                         path[i] = '\0';
346                         if (access(path, F_OK) == 0) {
347                                 path[i] = '/';
348                                 break;
349                         }
350                         if (errno != ENOENT) {
351                                 path[i] = '/';
352                                 if (last == 0)
353                                         return (-1);
354                                 finish = 1;
355                                 break;
356                         }
357                         last = i;
358                 }
359         }
360         if (last == 0)
361                 return (0);
362
363         i = strlen(path);
364         fa = fattr_new(FT_DIRECTORY, -1);
365         fattr_mergedefault(fa);
366         fattr_umask(fa, mask);
367         while (i < len) {
368                 if (!finish) {
369                         rv = 0;
370                         error = fattr_makenode(fa, path);
371                         if (!error)
372                                 rv = fattr_install(fa, path, NULL);
373                         if (error || rv == -1)
374                                 finish = 1;
375                 }
376                 path[i] = '/';
377                 i += strlen(path + i);
378         }
379         assert(i == len);
380         if (finish)
381                 return (-1);
382         return (0);
383 }
384
385 /*
386  * Compute temporary pathnames.
387  * This can look a bit like overkill but we mimic CVSup's behaviour.
388  */
389 #define TEMPNAME_PREFIX         "#cvs.csup"
390
391 static pthread_mutex_t tempname_mtx = PTHREAD_MUTEX_INITIALIZER;
392 static pid_t tempname_pid = -1;
393 static int tempname_count;
394
395 char *
396 tempname(const char *path)
397 {
398         char *cp, *temp;
399         int count, error;
400
401         error = pthread_mutex_lock(&tempname_mtx);
402         assert(!error);
403         if (tempname_pid == -1) {
404                 tempname_pid = getpid();
405                 tempname_count = 0;
406         }
407         count = tempname_count++;
408         error = pthread_mutex_unlock(&tempname_mtx);
409         assert(!error);
410         cp = strrchr(path, '/');
411         if (cp == NULL)
412                 xasprintf(&temp, "%s-%ld.%d", TEMPNAME_PREFIX,
413                     (long)tempname_pid, count);
414         else
415                 xasprintf(&temp, "%.*s%s-%ld.%d", (int)(cp - path + 1), path,
416                     TEMPNAME_PREFIX, (long)tempname_pid, count);
417         return (temp);
418 }
419
420 void *
421 xmalloc(size_t size)
422 {
423         void *buf;
424
425         buf = malloc(size);
426         if (buf == NULL)
427                 err(1, "malloc");
428         return (buf);
429 }
430
431 void *
432 xrealloc(void *buf, size_t size)
433 {
434
435         buf = realloc(buf, size);
436         if (buf == NULL)
437                 err(1, "realloc");
438         return (buf);
439 }
440
441 char *
442 xstrdup(const char *str)
443 {
444         char *buf;
445
446         buf = strdup(str);
447         if (buf == NULL)
448                 err(1, "strdup");
449         return (buf);
450 }
451
452 int
453 xasprintf(char **ret, const char *format, ...)
454 {
455         va_list ap;
456         int rv;
457
458         va_start(ap, format);
459         rv = vasprintf(ret, format, ap);
460         va_end(ap);
461         if (*ret == NULL)
462                 err(1, "asprintf");
463         return (rv);
464 }
465
466 struct pattlist *
467 pattlist_new(void)
468 {
469         struct pattlist *p;
470
471         p = xmalloc(sizeof(struct pattlist));
472         p->size = 4;            /* Initial size. */
473         p->patterns = xmalloc(p->size * sizeof(char *));
474         p->in = 0;
475         return (p);
476 }
477
478 void
479 pattlist_add(struct pattlist *p, const char *pattern)
480 {
481
482         if (p->in == p->size) {
483                 p->size *= 2;
484                 p->patterns = xrealloc(p->patterns, p->size * sizeof(char *));
485         }
486         assert(p->in < p->size);
487         p->patterns[p->in++] = xstrdup(pattern);
488 }
489
490 char *
491 pattlist_get(struct pattlist *p, size_t i)
492 {
493
494         assert(i < p->in);
495         return (p->patterns[i]);
496 }
497
498 size_t
499 pattlist_size(struct pattlist *p)
500 {
501
502         return (p->in);
503 }
504
505 void
506 pattlist_free(struct pattlist *p)
507 {
508         size_t i;
509         
510         for (i = 0; i < p->in; i++)
511                 free(p->patterns[i]);
512         free(p->patterns);
513         free(p);
514 }
515
516 /* Creates a backoff timer. */
517 struct backoff_timer *
518 bt_new(time_t min, time_t max, float backoff, float jitter)
519 {
520         struct backoff_timer *bt;
521
522         bt = xmalloc(sizeof(struct backoff_timer));
523         bt->min = min;
524         bt->max = max;
525         bt->backoff = backoff;
526         bt->jitter = jitter;
527         bt->interval = min;
528         bt_addjitter(bt);
529         srandom(time(0));
530         return (bt);
531 }
532
533 /* Updates the backoff timer. */
534 static void
535 bt_update(struct backoff_timer *bt)
536 {
537
538         bt->interval = (time_t)min(bt->interval * bt->backoff, bt->max);
539         bt_addjitter(bt);
540 }
541
542 /* Adds some jitter. */
543 static void
544 bt_addjitter(struct backoff_timer *bt)
545 {
546         long mag;
547
548         mag = (long)(bt->jitter * bt->interval);
549         /* We want a random number between -mag and mag. */
550         bt->interval += (time_t)(random() % (2 * mag) - mag);
551 }
552
553 /* Returns the current timer value. */
554 time_t
555 bt_get(struct backoff_timer *bt)
556 {
557
558         return (bt->interval);
559 }
560
561 /* Times out for bt->interval seconds. */
562 void
563 bt_pause(struct backoff_timer *bt)
564 {
565
566         sleep(bt->interval);
567         bt_update(bt);
568 }
569
570 void
571 bt_free(struct backoff_timer *bt)
572 {
573
574         free(bt);
575 }
576
577 /* Compare two revisions. */
578 int
579 rcsnum_cmp(char *revision1, char *revision2)
580 {
581         char *ptr1, *ptr2, *dot1, *dot2;
582         int num1len, num2len, ret;
583
584         ptr1 = revision1;
585         ptr2 = revision2;
586         while (*ptr1 != '\0' && *ptr2 != '\0') {
587                 dot1 = strchr(ptr1, '.');
588                 dot2 = strchr(ptr2, '.');
589                 if (dot1 == NULL)
590                         dot1 = strchr(ptr1, '\0');
591                 if (dot2 == NULL)
592                         dot2 = strchr(ptr2, '\0');
593
594                 num1len = dot1 - ptr1;
595                 num2len = dot2 - ptr2;
596                 /* Check the distance between each, showing how many digits */
597                 if (num1len > num2len)
598                         return (1);
599                 else if (num1len < num2len)
600                         return (-1);
601
602                 /* Equal distance means we must check each character. */
603                 ret = strncmp(ptr1, ptr2, num1len);
604                 if (ret != 0)
605                         return (ret);
606                 ptr1 = (*dot1 == '.') ? (dot1 + 1) : dot1;
607                 ptr2 = (*dot2 == '.') ? (dot2 + 1) : dot2;
608         } 
609
610         if (*ptr1 != '\0' && *ptr2 == '\0')
611                 return (1);
612         if (*ptr1 == '\0' && *ptr2 != '\0')
613                 return (-1);
614         return (0);
615
616 }
617
618 /* Returns 0 if a rcsrev is not a trunk revision number. */
619 int
620 rcsrev_istrunk(char *revnum)
621 {
622         char *tmp;
623
624         tmp = strchr(revnum, '.');
625         tmp++;
626         if (strchr(tmp, '.') != NULL)
627                 return (0);
628         return (1);
629 }
630
631 /* Return prefix of rcsfile. */
632 char *
633 rcsrev_prefix(char *revnum)
634 {
635         char *modrev, *pos;
636
637         modrev = xstrdup(revnum);
638         pos = strrchr(modrev, '.');
639         if (pos == NULL) {
640                 free(modrev);
641                 return (NULL);
642         }
643         *pos = '\0';
644         return (modrev);
645 }