1 /* $OpenBSD: entries.c,v 1.107 2016/10/13 20:51:25 fcambus Exp $ */
2 /*
3 * Copyright (c) 2006 Joris Vink <joris@openbsd.org>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18 #include <errno.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <time.h>
22 #include <unistd.h>
23
24 #include "cvs.h"
25 #include "remote.h"
26
27 #define CVS_ENTRIES_NFIELDS 6
28 #define CVS_ENTRIES_DELIM '/'
29
30 static struct cvs_ent_line *ent_get_line(CVSENTRIES *, const char *);
31
32 CVSENTRIES *current_list = NULL;
33
34 CVSENTRIES *
cvs_ent_open(const char * dir)35 cvs_ent_open(const char *dir)
36 {
37 FILE *fp;
38 CVSENTRIES *ep;
39 char *p, buf[PATH_MAX];
40 struct cvs_ent *ent;
41 struct cvs_ent_line *line;
42
43 cvs_log(LP_TRACE, "cvs_ent_open(%s)", dir);
44
45 (void)xsnprintf(buf, sizeof(buf), "%s/%s", dir, CVS_PATH_ENTRIES);
46
47 if (current_list != NULL && !strcmp(current_list->cef_path, buf))
48 return (current_list);
49
50 if (current_list != NULL) {
51 cvs_ent_close(current_list, ENT_SYNC);
52 current_list = NULL;
53 }
54
55 ep = xcalloc(1, sizeof(*ep));
56 ep->cef_path = xstrdup(buf);
57
58 (void)xsnprintf(buf, sizeof(buf), "%s/%s",
59 dir, CVS_PATH_BACKUPENTRIES);
60
61 ep->cef_bpath = xstrdup(buf);
62
63 (void)xsnprintf(buf, sizeof(buf), "%s/%s", dir, CVS_PATH_LOGENTRIES);
64
65 ep->cef_lpath = xstrdup(buf);
66
67 TAILQ_INIT(&(ep->cef_ent));
68
69 if ((fp = fopen(ep->cef_path, "r")) != NULL) {
70 while (fgets(buf, sizeof(buf), fp)) {
71 buf[strcspn(buf, "\n")] = '\0';
72
73 if (buf[0] == 'D' && buf[1] == '\0')
74 break;
75
76 line = xmalloc(sizeof(*line));
77 line->buf = xstrdup(buf);
78 TAILQ_INSERT_TAIL(&(ep->cef_ent), line, entries_list);
79 }
80
81 (void)fclose(fp);
82 }
83
84 if ((fp = fopen(ep->cef_lpath, "r")) != NULL) {
85 while (fgets(buf, sizeof(buf), fp)) {
86 buf[strcspn(buf, "\n")] = '\0';
87
88 if (strlen(buf) < 2)
89 fatal("cvs_ent_open: %s: malformed line %s",
90 ep->cef_lpath, buf);
91
92 p = &buf[2];
93
94 if (buf[0] == 'A') {
95 line = xmalloc(sizeof(*line));
96 line->buf = xstrdup(p);
97 TAILQ_INSERT_TAIL(&(ep->cef_ent), line,
98 entries_list);
99 } else if (buf[0] == 'R') {
100 ent = cvs_ent_parse(p);
101 line = ent_get_line(ep, ent->ce_name);
102 if (line != NULL) {
103 TAILQ_REMOVE(&(ep->cef_ent), line,
104 entries_list);
105 free(line->buf);
106 free(line);
107 }
108 cvs_ent_free(ent);
109 }
110 }
111
112 (void)fclose(fp);
113 }
114
115 current_list = ep;
116 return (ep);
117 }
118
119 struct cvs_ent *
cvs_ent_parse(const char * entry)120 cvs_ent_parse(const char *entry)
121 {
122 int i;
123 struct tm t, dt;
124 struct cvs_ent *ent;
125 char *fields[CVS_ENTRIES_NFIELDS], *buf, *sp, *dp, *p;
126
127 buf = sp = xstrdup(entry);
128 i = 0;
129 do {
130 dp = strchr(sp, CVS_ENTRIES_DELIM);
131 if (dp != NULL)
132 *(dp++) = '\0';
133 fields[i++] = sp;
134 sp = dp;
135 } while (dp != NULL && i < CVS_ENTRIES_NFIELDS);
136
137 if (i < CVS_ENTRIES_NFIELDS)
138 fatal("missing fields in entry line '%s'", entry);
139
140 ent = xmalloc(sizeof(*ent));
141 ent->ce_buf = buf;
142
143 if (*fields[0] == '\0')
144 ent->ce_type = CVS_ENT_FILE;
145 else if (*fields[0] == 'D')
146 ent->ce_type = CVS_ENT_DIR;
147 else
148 ent->ce_type = CVS_ENT_NONE;
149
150 ent->ce_status = CVS_ENT_REG;
151 ent->ce_name = fields[1];
152 ent->ce_rev = NULL;
153 ent->ce_date = -1;
154 ent->ce_tag = NULL;
155 ent->ce_time = NULL;
156
157 if (ent->ce_type == CVS_ENT_FILE) {
158 if (*fields[2] == '-') {
159 ent->ce_status = CVS_ENT_REMOVED;
160 sp = fields[2] + 1;
161 } else if (*fields[2] == CVS_SERVER_QUESTIONABLE) {
162 sp = NULL;
163 ent->ce_status = CVS_ENT_UNKNOWN;
164 } else {
165 sp = fields[2];
166 if (fields[2][0] == '0' && fields[2][1] == '\0')
167 ent->ce_status = CVS_ENT_ADDED;
168 }
169
170 if (sp != NULL) {
171 if ((ent->ce_rev = rcsnum_parse(sp)) == NULL) {
172 fatal("failed to parse entry revision '%s'",
173 entry);
174 }
175 }
176
177 if (fields[3][0] == '\0' ||
178 strncmp(fields[3], CVS_DATE_DUMMY,
179 sizeof(CVS_DATE_DUMMY) - 1) == 0 ||
180 strncmp(fields[3], "Initial ", 8) == 0 ||
181 strcmp(fields[3], "Result of merge") == 0) {
182 ent->ce_mtime = CVS_DATE_DMSEC;
183 } else if (cvs_server_active == 1 &&
184 strncmp(fields[3], CVS_SERVER_UNCHANGED,
185 strlen(CVS_SERVER_UNCHANGED)) == 0) {
186 ent->ce_mtime = CVS_SERVER_UPTODATE;
187 } else {
188 p = fields[3];
189 if (strncmp(fields[3], "Result of merge+", 16) == 0)
190 p += 16;
191
192 ent->ce_time = xstrdup(p);
193
194 /* Date field can be a '+=' with remote to indicate
195 * conflict. In this case do nothing. */
196 if (strptime(p, "%a %b %d %T %Y", &t) != NULL) {
197 t.tm_isdst = -1; /* Figure out DST. */
198 t.tm_gmtoff = 0;
199 ent->ce_mtime = mktime(&t);
200 ent->ce_mtime += t.tm_gmtoff;
201 }
202 }
203 }
204
205 ent->ce_conflict = fields[3];
206 if ((dp = strchr(ent->ce_conflict, '+')) != NULL)
207 *dp = '\0';
208 else
209 ent->ce_conflict = NULL;
210
211 if (strcmp(fields[4], ""))
212 ent->ce_opts = fields[4];
213 else
214 ent->ce_opts = NULL;
215
216 if (strcmp(fields[5], "")) {
217 switch (*fields[5]) {
218 case 'D':
219 if (sscanf(fields[5] + 1, "%d.%d.%d.%d.%d.%d",
220 &dt.tm_year, &dt.tm_mon, &dt.tm_mday,
221 &dt.tm_hour, &dt.tm_min, &dt.tm_sec) != 6)
222 fatal("wrong date specification");
223 dt.tm_year -= 1900;
224 dt.tm_mon -= 1;
225 ent->ce_date = timegm(&dt);
226 ent->ce_tag = NULL;
227 break;
228 case 'T':
229 ent->ce_tag = fields[5] + 1;
230 break;
231 default:
232 fatal("invalid sticky entry");
233 }
234 }
235
236 return (ent);
237 }
238
239 struct cvs_ent *
cvs_ent_get(CVSENTRIES * ep,const char * name)240 cvs_ent_get(CVSENTRIES *ep, const char *name)
241 {
242 struct cvs_ent *ent;
243 struct cvs_ent_line *l;
244
245 l = ent_get_line(ep, name);
246 if (l == NULL)
247 return (NULL);
248
249 ent = cvs_ent_parse(l->buf);
250 return (ent);
251 }
252
253 void
cvs_ent_close(CVSENTRIES * ep,int writefile)254 cvs_ent_close(CVSENTRIES *ep, int writefile)
255 {
256 FILE *fp;
257 struct cvs_ent_line *l;
258 int dflag;
259
260 dflag = 1;
261 cvs_log(LP_TRACE, "cvs_ent_close(%s, %d)", ep->cef_bpath, writefile);
262
263 if (cvs_cmdop == CVS_OP_EXPORT)
264 writefile = 0;
265
266 fp = NULL;
267 if (writefile)
268 fp = fopen(ep->cef_bpath, "w");
269
270 while ((l = TAILQ_FIRST(&(ep->cef_ent))) != NULL) {
271 if (fp != NULL) {
272 if (l->buf[0] == 'D')
273 dflag = 0;
274
275 fputs(l->buf, fp);
276 fputc('\n', fp);
277 }
278
279 TAILQ_REMOVE(&(ep->cef_ent), l, entries_list);
280 free(l->buf);
281 free(l);
282 }
283
284 if (fp != NULL) {
285 if (dflag) {
286 fputc('D', fp);
287 fputc('\n', fp);
288 }
289 (void)fclose(fp);
290
291 if (rename(ep->cef_bpath, ep->cef_path) == -1)
292 fatal("cvs_ent_close: rename: `%s'->`%s': %s",
293 ep->cef_bpath, ep->cef_path, strerror(errno));
294
295 (void)unlink(ep->cef_lpath);
296 }
297
298 free(ep->cef_path);
299 free(ep->cef_bpath);
300 free(ep->cef_lpath);
301 free(ep);
302 }
303
304 void
cvs_ent_add(CVSENTRIES * ep,const char * line)305 cvs_ent_add(CVSENTRIES *ep, const char *line)
306 {
307 FILE *fp;
308 struct cvs_ent_line *l;
309 struct cvs_ent *ent;
310
311 if ((ent = cvs_ent_parse(line)) == NULL)
312 fatal("cvs_ent_add: parsing failed '%s'", line);
313
314 l = ent_get_line(ep, ent->ce_name);
315 if (l != NULL)
316 cvs_ent_remove(ep, ent->ce_name);
317
318 cvs_ent_free(ent);
319
320 if (cvs_server_active == 0)
321 cvs_log(LP_TRACE, "cvs_ent_add(%s, %s)", ep->cef_path, line);
322
323 if ((fp = fopen(ep->cef_lpath, "a")) == NULL)
324 fatal("cvs_ent_add: fopen: `%s': %s",
325 ep->cef_lpath, strerror(errno));
326
327 fputs("A ", fp);
328 fputs(line, fp);
329 fputc('\n', fp);
330
331 (void)fclose(fp);
332
333 l = xmalloc(sizeof(*l));
334 l->buf = xstrdup(line);
335 TAILQ_INSERT_TAIL(&(ep->cef_ent), l, entries_list);
336 }
337
338 void
cvs_ent_remove(CVSENTRIES * ep,const char * name)339 cvs_ent_remove(CVSENTRIES *ep, const char *name)
340 {
341 FILE *fp;
342 struct cvs_ent_line *l;
343
344 if (cvs_server_active == 0)
345 cvs_log(LP_TRACE, "cvs_ent_remove(%s, %s)", ep->cef_path, name);
346
347 l = ent_get_line(ep, name);
348 if (l == NULL)
349 return;
350
351 if ((fp = fopen(ep->cef_lpath, "a")) == NULL)
352 fatal("cvs_ent_remove: fopen: `%s': %s", ep->cef_lpath,
353 strerror(errno));
354
355 fputs("R ", fp);
356 fputs(l->buf, fp);
357 fputc('\n', fp);
358
359 (void)fclose(fp);
360
361 TAILQ_REMOVE(&(ep->cef_ent), l, entries_list);
362 free(l->buf);
363 free(l);
364 }
365
366 /*
367 * cvs_ent_line_str()
368 *
369 * Build CVS/Entries line.
370 *
371 */
372 void
cvs_ent_line_str(const char * name,char * rev,char * tstamp,char * opts,char * sticky,int isdir,int isremoved,char * buf,size_t len)373 cvs_ent_line_str(const char *name, char *rev, char *tstamp, char *opts,
374 char *sticky, int isdir, int isremoved, char *buf, size_t len)
375 {
376 if (isdir == 1) {
377 (void)xsnprintf(buf, len, "D/%s////", name);
378 return;
379 }
380
381 (void)xsnprintf(buf, len, "/%s/%s%s/%s/%s/%s",
382 name, isremoved == 1 ? "-" : "", rev, tstamp, opts, sticky);
383 }
384
385 void
cvs_ent_free(struct cvs_ent * ent)386 cvs_ent_free(struct cvs_ent *ent)
387 {
388 free(ent->ce_rev);
389 free(ent->ce_time);
390 free(ent->ce_buf);
391 free(ent);
392 }
393
394 static struct cvs_ent_line *
ent_get_line(CVSENTRIES * ep,const char * name)395 ent_get_line(CVSENTRIES *ep, const char *name)
396 {
397 char *p, *s;
398 struct cvs_ent_line *l;
399
400 TAILQ_FOREACH(l, &(ep->cef_ent), entries_list) {
401 if (l->buf[0] == 'D')
402 p = &(l->buf[2]);
403 else
404 p = &(l->buf[1]);
405
406 if ((s = strchr(p, '/')) == NULL)
407 fatal("ent_get_line: bad entry line '%s'", l->buf);
408
409 *s = '\0';
410
411 if (!strcmp(p, name)) {
412 *s = '/';
413 return (l);
414 }
415
416 *s = '/';
417 }
418
419 return (NULL);
420 }
421
422 void
cvs_parse_tagfile(char * dir,char ** tagp,char ** datep,int * nbp)423 cvs_parse_tagfile(char *dir, char **tagp, char **datep, int *nbp)
424 {
425 FILE *fp;
426 int i, linenum;
427 size_t len;
428 struct tm datetm;
429 char linebuf[128], tagpath[PATH_MAX];
430
431 cvs_directory_date = -1;
432
433 if (tagp != NULL)
434 *tagp = NULL;
435
436 if (datep != NULL)
437 *datep = NULL;
438
439 if (nbp != NULL)
440 *nbp = 0;
441
442 i = snprintf(tagpath, PATH_MAX, "%s/%s", dir, CVS_PATH_TAG);
443 if (i < 0 || i >= PATH_MAX)
444 return;
445
446 if ((fp = fopen(tagpath, "r")) == NULL) {
447 if (errno != ENOENT)
448 cvs_log(LP_NOTICE, "failed to open `%s' : %s", tagpath,
449 strerror(errno));
450 return;
451 }
452
453 linenum = 0;
454
455 while (fgets(linebuf, (int)sizeof(linebuf), fp) != NULL) {
456 linenum++;
457 if ((len = strlen(linebuf)) == 0)
458 continue;
459 if (linebuf[len - 1] != '\n') {
460 cvs_log(LP_NOTICE, "line too long in `%s:%d'",
461 tagpath, linenum);
462 break;
463 }
464 linebuf[--len] = '\0';
465
466 switch (*linebuf) {
467 case 'T':
468 if (tagp != NULL)
469 *tagp = xstrdup(linebuf + 1);
470 break;
471 case 'D':
472 if (sscanf(linebuf + 1, "%d.%d.%d.%d.%d.%d",
473 &datetm.tm_year, &datetm.tm_mon, &datetm.tm_mday,
474 &datetm.tm_hour, &datetm.tm_min, &datetm.tm_sec) !=
475 6)
476 fatal("wrong date specification");
477 datetm.tm_year -= 1900;
478 datetm.tm_mon -= 1;
479
480 cvs_directory_date = timegm(&datetm);
481
482 if (datep != NULL)
483 *datep = xstrdup(linebuf + 1);
484 break;
485 case 'N':
486 if (tagp != NULL)
487 *tagp = xstrdup(linebuf + 1);
488 if (nbp != NULL)
489 *nbp = 1;
490 break;
491 default:
492 break;
493 }
494 }
495 if (ferror(fp))
496 cvs_log(LP_NOTICE, "failed to read line from `%s'", tagpath);
497
498 (void)fclose(fp);
499 }
500
501 void
cvs_write_tagfile(const char * dir,char * tag,char * date)502 cvs_write_tagfile(const char *dir, char *tag, char *date)
503 {
504 FILE *fp;
505 RCSNUM *rev;
506 char tagpath[PATH_MAX];
507 char sticky[CVS_REV_BUFSZ];
508 struct tm datetm;
509 int i;
510
511 cvs_log(LP_TRACE, "cvs_write_tagfile(%s, %s, %s)", dir,
512 tag != NULL ? tag : "", date != NULL ? date : "");
513
514 if (cvs_noexec == 1)
515 return;
516
517 i = snprintf(tagpath, PATH_MAX, "%s/%s", dir, CVS_PATH_TAG);
518 if (i < 0 || i >= PATH_MAX)
519 return;
520
521 if (tag != NULL || cvs_specified_date != -1 ||
522 cvs_directory_date != -1) {
523 if ((fp = fopen(tagpath, "w+")) == NULL) {
524 if (errno != ENOENT) {
525 cvs_log(LP_NOTICE, "failed to open `%s' : %s",
526 tagpath, strerror(errno));
527 }
528 return;
529 }
530
531 if (tag != NULL) {
532 if ((rev = rcsnum_parse(tag)) != NULL) {
533 (void)xsnprintf(sticky, sizeof(sticky),
534 "N%s", tag);
535 free(rev);
536 } else {
537 (void)xsnprintf(sticky, sizeof(sticky),
538 "T%s", tag);
539 }
540 } else {
541 if (cvs_specified_date != -1)
542 gmtime_r(&cvs_specified_date, &datetm);
543 else
544 gmtime_r(&cvs_directory_date, &datetm);
545 (void)strftime(sticky, sizeof(sticky),
546 "D"CVS_DATE_FMT, &datetm);
547 }
548
549 (void)fprintf(fp, "%s\n", sticky);
550 (void)fclose(fp);
551 }
552 }
553