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 #include <sys/cdefs.h>
13 __RCSID("$NetBSD: fileattr.c,v 1.2 2016/05/17 14:00:09 christos Exp $");
14
15 #include "cvs.h"
16 #include "getline.h"
17 #include "fileattr.h"
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 * %p = shortrepos
520 * %r = repository
521 */
522 /*
523 * Cast any NULL arguments as appropriate pointers as this is an
524 * stdarg function and we need to be certain the caller gets what
525 * is expected.
526 */
527 cmdline = format_cmdline (
528 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
529 false, srepos,
530 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
531 filter,
532 "c", "s", cvs_cmd_name,
533 #ifdef SERVER_SUPPORT
534 "R", "s", referrer ? referrer->original : "NONE",
535 #endif /* SERVER_SUPPORT */
536 "p", "s", srepos,
537 "r", "s", current_parsed_root->directory,
538 (char *) NULL);
539
540 if (!cmdline || !strlen (cmdline))
541 {
542 if (cmdline) free (cmdline);
543 error (0, 0, "postwatch proc resolved to the empty string!");
544 return 1;
545 }
546
547 run_setup (cmdline);
548
549 free (cmdline);
550
551 /* FIXME - read the comment in verifymsg_proc() about why we use abs()
552 * below() and shouldn't.
553 */
554 return abs (run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
555 RUN_NORMAL | RUN_SIGIGNORE));
556 }
557
558
559
560 void
fileattr_write(void)561 fileattr_write (void)
562 {
563 FILE *fp;
564 char *fname;
565 mode_t omask;
566 struct unrecog *p;
567
568 if (!attrs_modified)
569 return;
570
571 if (noexec)
572 return;
573
574 /* If NULL was passed to fileattr_startdir, then it isn't kosher to set
575 attributes. */
576 assert (fileattr_stored_repos != NULL);
577
578 fname = Xasprintf ("%s/%s", fileattr_stored_repos, CVSREP_FILEATTR);
579
580 if (list_isempty (attrlist)
581 && fileattr_default_attrs == NULL
582 && unrecog_head == NULL)
583 {
584 /* There are no attributes. */
585 if (unlink_file (fname) < 0)
586 {
587 if (!existence_error (errno))
588 {
589 error (0, errno, "cannot remove %s", fname);
590 }
591 }
592
593 /* Now remove CVSREP directory, if empty. The main reason we bother
594 is that CVS 1.6 and earlier will choke if a CVSREP directory
595 exists, so provide the user a graceful way to remove it. */
596 strcpy (fname, fileattr_stored_repos);
597 strcat (fname, "/");
598 strcat (fname, CVSREP);
599 if (CVS_RMDIR (fname) < 0)
600 {
601 if (errno != ENOTEMPTY
602
603 /* Don't know why we would be here if there is no CVSREP
604 directory, but it seemed to be happening anyway, so
605 check for it. */
606 && !existence_error (errno))
607 error (0, errno, "cannot remove %s", fname);
608 }
609
610 free (fname);
611 return;
612 }
613
614 omask = umask (cvsumask);
615 fp = CVS_FOPEN (fname, FOPEN_BINARY_WRITE);
616 if (fp == NULL)
617 {
618 if (existence_error (errno))
619 {
620 /* Maybe the CVSREP directory doesn't exist. Try creating it. */
621 char *repname;
622
623 repname = Xasprintf ("%s/%s", fileattr_stored_repos, CVSREP);
624
625 if (CVS_MKDIR (repname, 0777) < 0 && errno != EEXIST)
626 {
627 error (0, errno, "cannot make directory %s", repname);
628 (void) umask (omask);
629 free (fname);
630 free (repname);
631 return;
632 }
633 free (repname);
634
635 fp = CVS_FOPEN (fname, FOPEN_BINARY_WRITE);
636 }
637 if (fp == NULL)
638 {
639 error (0, errno, "cannot write %s", fname);
640 (void) umask (omask);
641 free (fname);
642 return;
643 }
644 }
645 (void) umask (omask);
646
647 /* First write the "F" attributes. */
648 walklist (attrlist, writeattr_proc, fp);
649
650 /* Then the "D" attribute. */
651 if (fileattr_default_attrs != NULL)
652 {
653 fputs ("D\t", fp);
654 fputs (fileattr_default_attrs, fp);
655 fputs ("\012", fp);
656 }
657
658 /* Then any other attributes. */
659 for (p = unrecog_head; p != NULL; p = p->next)
660 {
661 fputs (p->line, fp);
662 fputs ("\012", fp);
663 }
664
665 if (fclose (fp) < 0)
666 error (0, errno, "cannot close %s", fname);
667 attrs_modified = 0;
668 free (fname);
669
670 Parse_Info (CVSROOTADM_POSTWATCH, fileattr_stored_repos, postwatch_proc,
671 PIOPT_ALL, NULL);
672 }
673
674
675
676 void
fileattr_free(void)677 fileattr_free (void)
678 {
679 /* Note that attrs_modified will ordinarily be zero, but there are
680 a few cases in which fileattr_write will fail to zero it (if
681 noexec is set, or error conditions). This probably is the way
682 it should be. */
683 dellist (&attrlist);
684 if (fileattr_stored_repos != NULL)
685 free (fileattr_stored_repos);
686 fileattr_stored_repos = NULL;
687 if (fileattr_default_attrs != NULL)
688 free (fileattr_default_attrs);
689 fileattr_default_attrs = NULL;
690 while (unrecog_head)
691 {
692 struct unrecog *p = unrecog_head;
693 unrecog_head = p->next;
694 free (p->line);
695 free (p);
696 }
697 }
698