1 /* Implementation for file attribute munging features.
2
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation; either version 2, or (at your option)
6 any later version.
7
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details. */
12
13 #include "cvs.h"
14 #include "getline.h"
15 #include "fileattr.h"
16
17 __RCSID("$MirOS: src/gnu/usr.bin/cvs/src/fileattr.c,v 1.4 2010/09/19 19:43:03 tg Exp $");
18
19 static void fileattr_read (void);
20 static int writeattr_proc (Node *, void *);
21
22 /* Where to look for CVSREP_FILEATTR. */
23 static char *fileattr_stored_repos;
24
25 /* The in-memory attributes. */
26 static List *attrlist;
27 static char *fileattr_default_attrs;
28 /* We have already tried to read attributes and failed in this directory
29 (for example, there is no CVSREP_FILEATTR file). */
30 static int attr_read_attempted;
31
32 /* Have the in-memory attributes been modified since we read them? */
33 static int attrs_modified;
34
35 /* More in-memory attributes: linked list of unrecognized
36 fileattr lines. We pass these on unchanged. */
37 struct unrecog {
38 char *line;
39 struct unrecog *next;
40 };
41 static struct unrecog *unrecog_head;
42
43
44
45 /* Note that if noone calls fileattr_get, this is very cheap. No stat(),
46 no open(), no nothing. */
47 void
fileattr_startdir(const char * repos)48 fileattr_startdir (const char *repos)
49 {
50 assert (fileattr_stored_repos == NULL);
51 fileattr_stored_repos = xstrdup (repos);
52 assert (attrlist == NULL);
53 attr_read_attempted = 0;
54 assert (unrecog_head == NULL);
55 }
56
57
58
59 static void
fileattr_delproc(Node * node)60 fileattr_delproc (Node *node)
61 {
62 assert (node->data != NULL);
63 free (node->data);
64 node->data = NULL;
65 }
66
67 /* Read all the attributes for the current directory into memory. */
68 static void
fileattr_read(void)69 fileattr_read (void)
70 {
71 char *fname;
72 FILE *fp;
73 char *line = NULL;
74 size_t line_len = 0;
75
76 /* If there are no attributes, don't waste time repeatedly looking
77 for the CVSREP_FILEATTR file. */
78 if (attr_read_attempted)
79 return;
80
81 /* If NULL was passed to fileattr_startdir, then it isn't kosher to look
82 at attributes. */
83 assert (fileattr_stored_repos != NULL);
84
85 fname = Xasprintf ("%s/%s", fileattr_stored_repos, CVSREP_FILEATTR);
86
87 attr_read_attempted = 1;
88 fp = CVS_FOPEN (fname, FOPEN_BINARY_READ);
89 if (fp == NULL)
90 {
91 if (!existence_error (errno))
92 error (0, errno, "cannot read %s", fname);
93 free (fname);
94 return;
95 }
96 attrlist = getlist ();
97 while (1) {
98 int nread;
99 nread = getline (&line, &line_len, fp);
100 if (nread < 0)
101 break;
102 /* Remove trailing newline.
103 * It is okay to reference line[nread - 1] here, since getline must
104 * always return 1 character or EOF, but we need to verify that the
105 * character we eat is the newline, since getline can return a line
106 * w/o a newline just before returning EOF.
107 */
108 if (line[nread - 1] == '\n') line[nread - 1] = '\0';
109 if (line[0] == 'F')
110 {
111 char *p;
112 Node *newnode;
113
114 p = strchr (line, '\t');
115 if (p == NULL)
116 error (1, 0,
117 "file attribute database corruption: tab missing in %s",
118 primary_root_inverse_translate (fname));
119 *p++ = '\0';
120 newnode = getnode ();
121 newnode->type = FILEATTR;
122 newnode->delproc = fileattr_delproc;
123 newnode->key = xstrdup (line + 1);
124 newnode->data = xstrdup (p);
125 if (addnode (attrlist, newnode) != 0)
126 /* If the same filename appears twice in the file, discard
127 any line other than the first for that filename. This
128 is the way that CVS has behaved since file attributes
129 were first introduced. */
130 freenode (newnode);
131 }
132 else if (line[0] == 'D')
133 {
134 char *p;
135 /* Currently nothing to skip here, but for future expansion,
136 ignore anything located here. */
137 p = strchr (line, '\t');
138 if (p == NULL)
139 error (1, 0,
140 "file attribute database corruption: tab missing in %s",
141 fname);
142 ++p;
143 if (fileattr_default_attrs) free (fileattr_default_attrs);
144 fileattr_default_attrs = xstrdup (p);
145 }
146 else
147 {
148 /* Unrecognized type, we want to just preserve the line without
149 changing it, for future expansion. */
150 struct unrecog *new;
151
152 new = xmalloc (sizeof (struct unrecog));
153 new->line = xstrdup (line);
154 new->next = unrecog_head;
155 unrecog_head = new;
156 }
157 }
158 if (ferror (fp))
159 error (0, errno, "cannot read %s", fname);
160 if (line != NULL)
161 free (line);
162 if (fclose (fp) < 0)
163 error (0, errno, "cannot close %s", fname);
164 attrs_modified = 0;
165 free (fname);
166 }
167
168
169
170 char *
fileattr_get(const char * filename,const char * attrname)171 fileattr_get (const char *filename, const char *attrname)
172 {
173 Node *node;
174 size_t attrname_len = strlen (attrname);
175 char *p;
176
177 if (attrlist == NULL)
178 fileattr_read ();
179 if (attrlist == NULL)
180 /* Either nothing has any attributes, or fileattr_read already printed
181 an error message. */
182 return NULL;
183
184 if (filename == NULL)
185 p = fileattr_default_attrs;
186 else
187 {
188 node = findnode (attrlist, filename);
189 if (node == NULL)
190 /* A file not mentioned has no attributes. */
191 return NULL;
192 p = node->data;
193 }
194 while (p)
195 {
196 if (strncmp (attrname, p, attrname_len) == 0
197 && p[attrname_len] == '=')
198 {
199 /* Found it. */
200 return p + attrname_len + 1;
201 }
202 p = strchr (p, ';');
203 if (p == NULL)
204 break;
205 ++p;
206 }
207 /* The file doesn't have this attribute. */
208 return NULL;
209 }
210
211
212
213 char *
fileattr_get0(const char * filename,const char * attrname)214 fileattr_get0 (const char *filename, const char *attrname)
215 {
216 char *cp;
217 char *cpend;
218 char *retval;
219
220 cp = fileattr_get (filename, attrname);
221 if (cp == NULL)
222 return NULL;
223 cpend = strchr (cp, ';');
224 if (cpend == NULL)
225 cpend = cp + strlen (cp);
226 retval = xmalloc (cpend - cp + 1);
227 strncpy (retval, cp, cpend - cp);
228 retval[cpend - cp] = '\0';
229 return retval;
230 }
231
232
233
234 char *
fileattr_modify(char * list,const char * attrname,const char * attrval,int namevalsep,int entsep)235 fileattr_modify (char *list, const char *attrname, const char *attrval, int namevalsep, int entsep)
236 {
237 char *retval;
238 char *rp;
239 size_t attrname_len = strlen (attrname);
240
241 /* Portion of list before the attribute to be replaced. */
242 char *pre;
243 char *preend;
244 /* Portion of list after the attribute to be replaced. */
245 char *post;
246
247 char *p;
248 char *p2;
249
250 p = list;
251 pre = list;
252 preend = NULL;
253 /* post is NULL unless set otherwise. */
254 post = NULL;
255 p2 = NULL;
256 if (list != NULL)
257 {
258 while (1) {
259 p2 = strchr (p, entsep);
260 if (p2 == NULL)
261 {
262 p2 = p + strlen (p);
263 if (preend == NULL)
264 preend = p2;
265 }
266 else
267 ++p2;
268 if (strncmp (attrname, p, attrname_len) == 0
269 && p[attrname_len] == namevalsep)
270 {
271 /* Found it. */
272 preend = p;
273 if (preend > list)
274 /* Don't include the preceding entsep. */
275 --preend;
276
277 post = p2;
278 }
279 if (p2[0] == '\0')
280 break;
281 p = p2;
282 }
283 }
284 if (post == NULL)
285 post = p2;
286
287 if (preend == pre && attrval == NULL && post == p2)
288 return NULL;
289
290 retval = xmalloc ((preend - pre)
291 + 1
292 + (attrval == NULL ? 0 : (attrname_len + 1
293 + strlen (attrval)))
294 + 1
295 + (p2 - post)
296 + 1);
297 if (preend != pre)
298 {
299 strncpy (retval, pre, preend - pre);
300 rp = retval + (preend - pre);
301 if (attrval != NULL)
302 *rp++ = entsep;
303 *rp = '\0';
304 }
305 else
306 retval[0] = '\0';
307 if (attrval != NULL)
308 {
309 strcat (retval, attrname);
310 rp = retval + strlen (retval);
311 *rp++ = namevalsep;
312 strcpy (rp, attrval);
313 }
314 if (post != p2)
315 {
316 rp = retval + strlen (retval);
317 if (preend != pre || attrval != NULL)
318 *rp++ = entsep;
319 strncpy (rp, post, p2 - post);
320 rp += p2 - post;
321 *rp = '\0';
322 }
323 return retval;
324 }
325
326 void
fileattr_set(const char * filename,const char * attrname,const char * attrval)327 fileattr_set (const char *filename, const char *attrname, const char *attrval)
328 {
329 Node *node;
330 char *p;
331
332 if (filename == NULL)
333 {
334 p = fileattr_modify (fileattr_default_attrs, attrname, attrval,
335 '=', ';');
336 if (fileattr_default_attrs != NULL)
337 free (fileattr_default_attrs);
338 fileattr_default_attrs = p;
339 attrs_modified = 1;
340 return;
341 }
342 if (attrlist == NULL)
343 fileattr_read ();
344 if (attrlist == NULL)
345 {
346 /* Not sure this is a graceful way to handle things
347 in the case where fileattr_read was unable to read the file. */
348 /* No attributes existed previously. */
349 attrlist = getlist ();
350 }
351
352 node = findnode (attrlist, filename);
353 if (node == NULL)
354 {
355 if (attrval == NULL)
356 /* Attempt to remove an attribute which wasn't there. */
357 return;
358
359 /* First attribute for this file. */
360 node = getnode ();
361 node->type = FILEATTR;
362 node->delproc = fileattr_delproc;
363 node->key = xstrdup (filename);
364 node->data = Xasprintf ("%s=%s", attrname, attrval);
365 addnode (attrlist, node);
366 }
367
368 p = fileattr_modify (node->data, attrname, attrval, '=', ';');
369 if (p == NULL)
370 delnode (node);
371 else
372 {
373 free (node->data);
374 node->data = p;
375 }
376
377 attrs_modified = 1;
378 }
379
380
381
382 char *
fileattr_getall(const char * filename)383 fileattr_getall (const char *filename)
384 {
385 Node *node;
386 char *p;
387
388 if (attrlist == NULL)
389 fileattr_read ();
390 if (attrlist == NULL)
391 /* Either nothing has any attributes, or fileattr_read already printed
392 an error message. */
393 return NULL;
394
395 if (filename == NULL)
396 p = fileattr_default_attrs;
397 else
398 {
399 node = findnode (attrlist, filename);
400 if (node == NULL)
401 /* A file not mentioned has no attributes. */
402 return NULL;
403 p = node->data;
404 }
405 return xstrdup (p);
406 }
407
408
409
410 void
fileattr_setall(const char * filename,const char * attrs)411 fileattr_setall (const char *filename, const char *attrs)
412 {
413 Node *node;
414
415 if (filename == NULL)
416 {
417 if (fileattr_default_attrs != NULL)
418 free (fileattr_default_attrs);
419 fileattr_default_attrs = xstrdup (attrs);
420 attrs_modified = 1;
421 return;
422 }
423 if (attrlist == NULL)
424 fileattr_read ();
425 if (attrlist == NULL)
426 {
427 /* Not sure this is a graceful way to handle things
428 in the case where fileattr_read was unable to read the file. */
429 /* No attributes existed previously. */
430 attrlist = getlist ();
431 }
432
433 node = findnode (attrlist, filename);
434 if (node == NULL)
435 {
436 /* The file had no attributes. Add them if we have any to add. */
437 if (attrs != NULL)
438 {
439 node = getnode ();
440 node->type = FILEATTR;
441 node->delproc = fileattr_delproc;
442 node->key = xstrdup (filename);
443 node->data = xstrdup (attrs);
444 addnode (attrlist, node);
445 }
446 }
447 else
448 {
449 if (attrs == NULL)
450 delnode (node);
451 else
452 {
453 free (node->data);
454 node->data = xstrdup (attrs);
455 }
456 }
457
458 attrs_modified = 1;
459 }
460
461
462
463 void
fileattr_newfile(const char * filename)464 fileattr_newfile (const char *filename)
465 {
466 Node *node;
467
468 if (attrlist == NULL)
469 fileattr_read ();
470
471 if (fileattr_default_attrs == NULL)
472 return;
473
474 if (attrlist == NULL)
475 {
476 /* Not sure this is a graceful way to handle things
477 in the case where fileattr_read was unable to read the file. */
478 /* No attributes existed previously. */
479 attrlist = getlist ();
480 }
481
482 node = getnode ();
483 node->type = FILEATTR;
484 node->delproc = fileattr_delproc;
485 node->key = xstrdup (filename);
486 node->data = xstrdup (fileattr_default_attrs);
487 addnode (attrlist, node);
488 attrs_modified = 1;
489 }
490
491
492
493 static int
writeattr_proc(Node * node,void * data)494 writeattr_proc (Node *node, void *data)
495 {
496 FILE *fp = (FILE *)data;
497 fputs ("F", fp);
498 fputs (node->key, fp);
499 fputs ("\t", fp);
500 fputs (node->data, fp);
501 fputs ("\012", fp);
502 return 0;
503 }
504
505
506
507 /*
508 * callback proc to run a script when fileattrs are updated.
509 */
510 static int
postwatch_proc(const char * repository,const char * filter,void * closure)511 postwatch_proc (const char *repository, const char *filter, void *closure)
512 {
513 char *cmdline;
514 const char *srepos = Short_Repository (repository);
515
516 TRACE (TRACE_FUNCTION, "postwatch_proc (%s, %s)", repository, filter);
517
518 /* %c = command name
519 * %I = commit ID
520 * %p = shortrepos
521 * %r = repository
522 */
523 /*
524 * Cast any NULL arguments as appropriate pointers as this is an
525 * stdarg function and we need to be certain the caller gets what
526 * is expected.
527 */
528 cmdline = format_cmdline (
529 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
530 false, srepos,
531 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
532 filter,
533 "c", "s", cvs_cmd_name,
534 "I", "s", global_session_id,
535 #ifdef SERVER_SUPPORT
536 "R", "s", referrer ? referrer->original : "NONE",
537 #endif /* SERVER_SUPPORT */
538 "p", "s", srepos,
539 "r", "s", current_parsed_root->directory,
540 (char *) NULL);
541
542 if (!cmdline || !strlen (cmdline))
543 {
544 if (cmdline) free (cmdline);
545 error (0, 0, "postwatch proc resolved to the empty string!");
546 return 1;
547 }
548
549 run_setup (cmdline);
550
551 free (cmdline);
552
553 /* FIXME - read the comment in verifymsg_proc() about why we use abs()
554 * below() and shouldn't.
555 */
556 return abs (run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
557 RUN_NORMAL | RUN_SIGIGNORE));
558 }
559
560
561
562 void
fileattr_write(void)563 fileattr_write (void)
564 {
565 FILE *fp;
566 char *fname;
567 mode_t omask;
568 struct unrecog *p;
569
570 if (!attrs_modified)
571 return;
572
573 if (noexec)
574 return;
575
576 /* If NULL was passed to fileattr_startdir, then it isn't kosher to set
577 attributes. */
578 assert (fileattr_stored_repos != NULL);
579
580 fname = Xasprintf ("%s/%s", fileattr_stored_repos, CVSREP_FILEATTR);
581
582 if (list_isempty (attrlist)
583 && fileattr_default_attrs == NULL
584 && unrecog_head == NULL)
585 {
586 /* There are no attributes. */
587 if (unlink_file (fname) < 0)
588 {
589 if (!existence_error (errno))
590 {
591 error (0, errno, "cannot remove %s", fname);
592 }
593 }
594
595 /* Now remove CVSREP directory, if empty. The main reason we bother
596 is that CVS 1.6 and earlier will choke if a CVSREP directory
597 exists, so provide the user a graceful way to remove it. */
598 strcpy (fname, fileattr_stored_repos);
599 strcat (fname, "/");
600 strcat (fname, CVSREP);
601 if (CVS_RMDIR (fname) < 0)
602 {
603 if (errno != ENOTEMPTY
604
605 /* Don't know why we would be here if there is no CVSREP
606 directory, but it seemed to be happening anyway, so
607 check for it. */
608 && !existence_error (errno))
609 error (0, errno, "cannot remove %s", fname);
610 }
611
612 free (fname);
613 return;
614 }
615
616 omask = umask (cvsumask);
617 fp = CVS_FOPEN (fname, FOPEN_BINARY_WRITE);
618 if (fp == NULL)
619 {
620 if (existence_error (errno))
621 {
622 /* Maybe the CVSREP directory doesn't exist. Try creating it. */
623 char *repname;
624
625 repname = Xasprintf ("%s/%s", fileattr_stored_repos, CVSREP);
626
627 if (CVS_MKDIR (repname, 0777) < 0 && errno != EEXIST)
628 {
629 error (0, errno, "cannot make directory %s", repname);
630 (void) umask (omask);
631 free (fname);
632 free (repname);
633 return;
634 }
635 free (repname);
636
637 fp = CVS_FOPEN (fname, FOPEN_BINARY_WRITE);
638 }
639 if (fp == NULL)
640 {
641 error (0, errno, "cannot write %s", fname);
642 (void) umask (omask);
643 free (fname);
644 return;
645 }
646 }
647 (void) umask (omask);
648
649 /* First write the "F" attributes. */
650 walklist (attrlist, writeattr_proc, fp);
651
652 /* Then the "D" attribute. */
653 if (fileattr_default_attrs != NULL)
654 {
655 fputs ("D\t", fp);
656 fputs (fileattr_default_attrs, fp);
657 fputs ("\012", fp);
658 }
659
660 /* Then any other attributes. */
661 for (p = unrecog_head; p != NULL; p = p->next)
662 {
663 fputs (p->line, fp);
664 fputs ("\012", fp);
665 }
666
667 if (fclose (fp) < 0)
668 error (0, errno, "cannot close %s", fname);
669 attrs_modified = 0;
670 free (fname);
671
672 Parse_Info (CVSROOTADM_POSTWATCH, fileattr_stored_repos, postwatch_proc,
673 PIOPT_ALL, NULL);
674 }
675
676
677
678 void
fileattr_free(void)679 fileattr_free (void)
680 {
681 /* Note that attrs_modified will ordinarily be zero, but there are
682 a few cases in which fileattr_write will fail to zero it (if
683 noexec is set, or error conditions). This probably is the way
684 it should be. */
685 dellist (&attrlist);
686 if (fileattr_stored_repos != NULL)
687 free (fileattr_stored_repos);
688 fileattr_stored_repos = NULL;
689 if (fileattr_default_attrs != NULL)
690 free (fileattr_default_attrs);
691 fileattr_default_attrs = NULL;
692 while (unrecog_head)
693 {
694 struct unrecog *p = unrecog_head;
695 unrecog_head = p->next;
696 free (p->line);
697 free (p);
698 }
699 }
700