HAMMER Utilities: Sync with 59A
[dragonfly.git] / contrib / cvs-1.12 / src / fileattr.c
blob26794bbd31d479b3d01c05e2c9ba4ec250ad0960
1 /* Implementation for file attribute munging features.
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.
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. */
13 #include "cvs.h"
14 #include "getline.h"
15 #include "fileattr.h"
17 static void fileattr_read (void);
18 static int writeattr_proc (Node *, void *);
20 /* Where to look for CVSREP_FILEATTR. */
21 static char *fileattr_stored_repos;
23 /* The in-memory attributes. */
24 static List *attrlist;
25 static char *fileattr_default_attrs;
26 /* We have already tried to read attributes and failed in this directory
27 (for example, there is no CVSREP_FILEATTR file). */
28 static int attr_read_attempted;
30 /* Have the in-memory attributes been modified since we read them? */
31 static int attrs_modified;
33 /* More in-memory attributes: linked list of unrecognized
34 fileattr lines. We pass these on unchanged. */
35 struct unrecog {
36 char *line;
37 struct unrecog *next;
39 static struct unrecog *unrecog_head;
43 /* Note that if noone calls fileattr_get, this is very cheap. No stat(),
44 no open(), no nothing. */
45 void
46 fileattr_startdir (const char *repos)
48 assert (fileattr_stored_repos == NULL);
49 fileattr_stored_repos = xstrdup (repos);
50 assert (attrlist == NULL);
51 attr_read_attempted = 0;
52 assert (unrecog_head == NULL);
57 static void
58 fileattr_delproc (Node *node)
60 assert (node->data != NULL);
61 free (node->data);
62 node->data = NULL;
65 /* Read all the attributes for the current directory into memory. */
66 static void
67 fileattr_read (void)
69 char *fname;
70 FILE *fp;
71 char *line = NULL;
72 size_t line_len = 0;
74 /* If there are no attributes, don't waste time repeatedly looking
75 for the CVSREP_FILEATTR file. */
76 if (attr_read_attempted)
77 return;
79 /* If NULL was passed to fileattr_startdir, then it isn't kosher to look
80 at attributes. */
81 assert (fileattr_stored_repos != NULL);
83 fname = Xasprintf ("%s/%s", fileattr_stored_repos, CVSREP_FILEATTR);
85 attr_read_attempted = 1;
86 fp = CVS_FOPEN (fname, FOPEN_BINARY_READ);
87 if (fp == NULL)
89 if (!existence_error (errno))
90 error (0, errno, "cannot read %s", fname);
91 free (fname);
92 return;
94 attrlist = getlist ();
95 while (1) {
96 int nread;
97 nread = getline (&line, &line_len, fp);
98 if (nread < 0)
99 break;
100 /* Remove trailing newline.
101 * It is okay to reference line[nread - 1] here, since getline must
102 * always return 1 character or EOF, but we need to verify that the
103 * character we eat is the newline, since getline can return a line
104 * w/o a newline just before returning EOF.
106 if (line[nread - 1] == '\n') line[nread - 1] = '\0';
107 if (line[0] == 'F')
109 char *p;
110 Node *newnode;
112 p = strchr (line, '\t');
113 if (p == NULL)
114 error (1, 0,
115 "file attribute database corruption: tab missing in %s",
116 primary_root_inverse_translate (fname));
117 *p++ = '\0';
118 newnode = getnode ();
119 newnode->type = FILEATTR;
120 newnode->delproc = fileattr_delproc;
121 newnode->key = xstrdup (line + 1);
122 newnode->data = xstrdup (p);
123 if (addnode (attrlist, newnode) != 0)
124 /* If the same filename appears twice in the file, discard
125 any line other than the first for that filename. This
126 is the way that CVS has behaved since file attributes
127 were first introduced. */
128 freenode (newnode);
130 else if (line[0] == 'D')
132 char *p;
133 /* Currently nothing to skip here, but for future expansion,
134 ignore anything located here. */
135 p = strchr (line, '\t');
136 if (p == NULL)
137 error (1, 0,
138 "file attribute database corruption: tab missing in %s",
139 fname);
140 ++p;
141 if (fileattr_default_attrs) free (fileattr_default_attrs);
142 fileattr_default_attrs = xstrdup (p);
144 else
146 /* Unrecognized type, we want to just preserve the line without
147 changing it, for future expansion. */
148 struct unrecog *new;
150 new = xmalloc (sizeof (struct unrecog));
151 new->line = xstrdup (line);
152 new->next = unrecog_head;
153 unrecog_head = new;
156 if (ferror (fp))
157 error (0, errno, "cannot read %s", fname);
158 if (line != NULL)
159 free (line);
160 if (fclose (fp) < 0)
161 error (0, errno, "cannot close %s", fname);
162 attrs_modified = 0;
163 free (fname);
168 char *
169 fileattr_get (const char *filename, const char *attrname)
171 Node *node;
172 size_t attrname_len = strlen (attrname);
173 char *p;
175 if (attrlist == NULL)
176 fileattr_read ();
177 if (attrlist == NULL)
178 /* Either nothing has any attributes, or fileattr_read already printed
179 an error message. */
180 return NULL;
182 if (filename == NULL)
183 p = fileattr_default_attrs;
184 else
186 node = findnode (attrlist, filename);
187 if (node == NULL)
188 /* A file not mentioned has no attributes. */
189 return NULL;
190 p = node->data;
192 while (p)
194 if (strncmp (attrname, p, attrname_len) == 0
195 && p[attrname_len] == '=')
197 /* Found it. */
198 return p + attrname_len + 1;
200 p = strchr (p, ';');
201 if (p == NULL)
202 break;
203 ++p;
205 /* The file doesn't have this attribute. */
206 return NULL;
211 char *
212 fileattr_get0 (const char *filename, const char *attrname)
214 char *cp;
215 char *cpend;
216 char *retval;
218 cp = fileattr_get (filename, attrname);
219 if (cp == NULL)
220 return NULL;
221 cpend = strchr (cp, ';');
222 if (cpend == NULL)
223 cpend = cp + strlen (cp);
224 retval = xmalloc (cpend - cp + 1);
225 strncpy (retval, cp, cpend - cp);
226 retval[cpend - cp] = '\0';
227 return retval;
232 char *
233 fileattr_modify (char *list, const char *attrname, const char *attrval, int namevalsep, int entsep)
235 char *retval;
236 char *rp;
237 size_t attrname_len = strlen (attrname);
239 /* Portion of list before the attribute to be replaced. */
240 char *pre;
241 char *preend;
242 /* Portion of list after the attribute to be replaced. */
243 char *post;
245 char *p;
246 char *p2;
248 p = list;
249 pre = list;
250 preend = NULL;
251 /* post is NULL unless set otherwise. */
252 post = NULL;
253 p2 = NULL;
254 if (list != NULL)
256 while (1) {
257 p2 = strchr (p, entsep);
258 if (p2 == NULL)
260 p2 = p + strlen (p);
261 if (preend == NULL)
262 preend = p2;
264 else
265 ++p2;
266 if (strncmp (attrname, p, attrname_len) == 0
267 && p[attrname_len] == namevalsep)
269 /* Found it. */
270 preend = p;
271 if (preend > list)
272 /* Don't include the preceding entsep. */
273 --preend;
275 post = p2;
277 if (p2[0] == '\0')
278 break;
279 p = p2;
282 if (post == NULL)
283 post = p2;
285 if (preend == pre && attrval == NULL && post == p2)
286 return NULL;
288 retval = xmalloc ((preend - pre)
290 + (attrval == NULL ? 0 : (attrname_len + 1
291 + strlen (attrval)))
293 + (p2 - post)
294 + 1);
295 if (preend != pre)
297 strncpy (retval, pre, preend - pre);
298 rp = retval + (preend - pre);
299 if (attrval != NULL)
300 *rp++ = entsep;
301 *rp = '\0';
303 else
304 retval[0] = '\0';
305 if (attrval != NULL)
307 strcat (retval, attrname);
308 rp = retval + strlen (retval);
309 *rp++ = namevalsep;
310 strcpy (rp, attrval);
312 if (post != p2)
314 rp = retval + strlen (retval);
315 if (preend != pre || attrval != NULL)
316 *rp++ = entsep;
317 strncpy (rp, post, p2 - post);
318 rp += p2 - post;
319 *rp = '\0';
321 return retval;
324 void
325 fileattr_set (const char *filename, const char *attrname, const char *attrval)
327 Node *node;
328 char *p;
330 if (filename == NULL)
332 p = fileattr_modify (fileattr_default_attrs, attrname, attrval,
333 '=', ';');
334 if (fileattr_default_attrs != NULL)
335 free (fileattr_default_attrs);
336 fileattr_default_attrs = p;
337 attrs_modified = 1;
338 return;
340 if (attrlist == NULL)
341 fileattr_read ();
342 if (attrlist == NULL)
344 /* Not sure this is a graceful way to handle things
345 in the case where fileattr_read was unable to read the file. */
346 /* No attributes existed previously. */
347 attrlist = getlist ();
350 node = findnode (attrlist, filename);
351 if (node == NULL)
353 if (attrval == NULL)
354 /* Attempt to remove an attribute which wasn't there. */
355 return;
357 /* First attribute for this file. */
358 node = getnode ();
359 node->type = FILEATTR;
360 node->delproc = fileattr_delproc;
361 node->key = xstrdup (filename);
362 node->data = Xasprintf ("%s=%s", attrname, attrval);
363 addnode (attrlist, node);
366 p = fileattr_modify (node->data, attrname, attrval, '=', ';');
367 if (p == NULL)
368 delnode (node);
369 else
371 free (node->data);
372 node->data = p;
375 attrs_modified = 1;
380 char *
381 fileattr_getall (const char *filename)
383 Node *node;
384 char *p;
386 if (attrlist == NULL)
387 fileattr_read ();
388 if (attrlist == NULL)
389 /* Either nothing has any attributes, or fileattr_read already printed
390 an error message. */
391 return NULL;
393 if (filename == NULL)
394 p = fileattr_default_attrs;
395 else
397 node = findnode (attrlist, filename);
398 if (node == NULL)
399 /* A file not mentioned has no attributes. */
400 return NULL;
401 p = node->data;
403 return xstrdup (p);
408 void
409 fileattr_setall (const char *filename, const char *attrs)
411 Node *node;
413 if (filename == NULL)
415 if (fileattr_default_attrs != NULL)
416 free (fileattr_default_attrs);
417 fileattr_default_attrs = xstrdup (attrs);
418 attrs_modified = 1;
419 return;
421 if (attrlist == NULL)
422 fileattr_read ();
423 if (attrlist == NULL)
425 /* Not sure this is a graceful way to handle things
426 in the case where fileattr_read was unable to read the file. */
427 /* No attributes existed previously. */
428 attrlist = getlist ();
431 node = findnode (attrlist, filename);
432 if (node == NULL)
434 /* The file had no attributes. Add them if we have any to add. */
435 if (attrs != NULL)
437 node = getnode ();
438 node->type = FILEATTR;
439 node->delproc = fileattr_delproc;
440 node->key = xstrdup (filename);
441 node->data = xstrdup (attrs);
442 addnode (attrlist, node);
445 else
447 if (attrs == NULL)
448 delnode (node);
449 else
451 free (node->data);
452 node->data = xstrdup (attrs);
456 attrs_modified = 1;
461 void
462 fileattr_newfile (const char *filename)
464 Node *node;
466 if (attrlist == NULL)
467 fileattr_read ();
469 if (fileattr_default_attrs == NULL)
470 return;
472 if (attrlist == NULL)
474 /* Not sure this is a graceful way to handle things
475 in the case where fileattr_read was unable to read the file. */
476 /* No attributes existed previously. */
477 attrlist = getlist ();
480 node = getnode ();
481 node->type = FILEATTR;
482 node->delproc = fileattr_delproc;
483 node->key = xstrdup (filename);
484 node->data = xstrdup (fileattr_default_attrs);
485 addnode (attrlist, node);
486 attrs_modified = 1;
491 static int
492 writeattr_proc (Node *node, void *data)
494 FILE *fp = (FILE *)data;
495 fputs ("F", fp);
496 fputs (node->key, fp);
497 fputs ("\t", fp);
498 fputs (node->data, fp);
499 fputs ("\012", fp);
500 return 0;
506 * callback proc to run a script when fileattrs are updated.
508 static int
509 postwatch_proc (const char *repository, const char *filter, void *closure)
511 char *cmdline;
512 const char *srepos = Short_Repository (repository);
514 TRACE (TRACE_FUNCTION, "postwatch_proc (%s, %s)", repository, filter);
516 /* %c = command name
517 * %p = shortrepos
518 * %r = repository
521 * Cast any NULL arguments as appropriate pointers as this is an
522 * stdarg function and we need to be certain the caller gets what
523 * is expected.
525 cmdline = format_cmdline (
526 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
527 false, srepos,
528 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
529 filter,
530 "c", "s", cvs_cmd_name,
531 #ifdef SERVER_SUPPORT
532 "R", "s", referrer ? referrer->original : "NONE",
533 #endif /* SERVER_SUPPORT */
534 "p", "s", srepos,
535 "r", "s", current_parsed_root->directory,
536 (char *) NULL);
538 if (!cmdline || !strlen (cmdline))
540 if (cmdline) free (cmdline);
541 error (0, 0, "postwatch proc resolved to the empty string!");
542 return 1;
545 run_setup (cmdline);
547 free (cmdline);
549 /* FIXME - read the comment in verifymsg_proc() about why we use abs()
550 * below() and shouldn't.
552 return abs (run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
553 RUN_NORMAL | RUN_SIGIGNORE));
558 void
559 fileattr_write (void)
561 FILE *fp;
562 char *fname;
563 mode_t omask;
564 struct unrecog *p;
566 if (!attrs_modified)
567 return;
569 if (noexec)
570 return;
572 /* If NULL was passed to fileattr_startdir, then it isn't kosher to set
573 attributes. */
574 assert (fileattr_stored_repos != NULL);
576 fname = Xasprintf ("%s/%s", fileattr_stored_repos, CVSREP_FILEATTR);
578 if (list_isempty (attrlist)
579 && fileattr_default_attrs == NULL
580 && unrecog_head == NULL)
582 /* There are no attributes. */
583 if (unlink_file (fname) < 0)
585 if (!existence_error (errno))
587 error (0, errno, "cannot remove %s", fname);
591 /* Now remove CVSREP directory, if empty. The main reason we bother
592 is that CVS 1.6 and earlier will choke if a CVSREP directory
593 exists, so provide the user a graceful way to remove it. */
594 strcpy (fname, fileattr_stored_repos);
595 strcat (fname, "/");
596 strcat (fname, CVSREP);
597 if (CVS_RMDIR (fname) < 0)
599 if (errno != ENOTEMPTY
601 /* Don't know why we would be here if there is no CVSREP
602 directory, but it seemed to be happening anyway, so
603 check for it. */
604 && !existence_error (errno))
605 error (0, errno, "cannot remove %s", fname);
608 free (fname);
609 return;
612 omask = umask (cvsumask);
613 fp = CVS_FOPEN (fname, FOPEN_BINARY_WRITE);
614 if (fp == NULL)
616 if (existence_error (errno))
618 /* Maybe the CVSREP directory doesn't exist. Try creating it. */
619 char *repname;
621 repname = Xasprintf ("%s/%s", fileattr_stored_repos, CVSREP);
623 if (CVS_MKDIR (repname, 0777) < 0 && errno != EEXIST)
625 error (0, errno, "cannot make directory %s", repname);
626 (void) umask (omask);
627 free (fname);
628 free (repname);
629 return;
631 free (repname);
633 fp = CVS_FOPEN (fname, FOPEN_BINARY_WRITE);
635 if (fp == NULL)
637 error (0, errno, "cannot write %s", fname);
638 (void) umask (omask);
639 free (fname);
640 return;
643 (void) umask (omask);
645 /* First write the "F" attributes. */
646 walklist (attrlist, writeattr_proc, fp);
648 /* Then the "D" attribute. */
649 if (fileattr_default_attrs != NULL)
651 fputs ("D\t", fp);
652 fputs (fileattr_default_attrs, fp);
653 fputs ("\012", fp);
656 /* Then any other attributes. */
657 for (p = unrecog_head; p != NULL; p = p->next)
659 fputs (p->line, fp);
660 fputs ("\012", fp);
663 if (fclose (fp) < 0)
664 error (0, errno, "cannot close %s", fname);
665 attrs_modified = 0;
666 free (fname);
668 Parse_Info (CVSROOTADM_POSTWATCH, fileattr_stored_repos, postwatch_proc,
669 PIOPT_ALL, NULL);
674 void
675 fileattr_free (void)
677 /* Note that attrs_modified will ordinarily be zero, but there are
678 a few cases in which fileattr_write will fail to zero it (if
679 noexec is set, or error conditions). This probably is the way
680 it should be. */
681 dellist (&attrlist);
682 if (fileattr_stored_repos != NULL)
683 free (fileattr_stored_repos);
684 fileattr_stored_repos = NULL;
685 if (fileattr_default_attrs != NULL)
686 free (fileattr_default_attrs);
687 fileattr_default_attrs = NULL;
688 while (unrecog_head)
690 struct unrecog *p = unrecog_head;
691 unrecog_head = p->next;
692 free (p->line);
693 free (p);