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
19static void fileattr_read (void);
20static int writeattr_proc (Node *, void *);
21
22/* Where to look for CVSREP_FILEATTR.  */
23static char *fileattr_stored_repos;
24
25/* The in-memory attributes.  */
26static List *attrlist;
27static 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).  */
30static int attr_read_attempted;
31
32/* Have the in-memory attributes been modified since we read them?  */
33static int attrs_modified;
34
35/* More in-memory attributes: linked list of unrecognized
36   fileattr lines.  We pass these on unchanged.  */
37struct unrecog {
38    char *line;
39    struct unrecog *next;
40};
41static 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.  */
47void
48fileattr_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
59static void
60fileattr_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.  */
68static void
69fileattr_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
170char *
171fileattr_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
213char *
214fileattr_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
234char *
235fileattr_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
326void
327fileattr_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
382char *
383fileattr_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
410void
411fileattr_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
463void
464fileattr_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
493static int
494writeattr_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 */
510static int
511postwatch_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
560void
561fileattr_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
676void
677fileattr_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