Home | History | Annotate | Line # | Download | only in src
      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
     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
     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
     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 *
    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 *
    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 *
    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
    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 *
    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
    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
    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
    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
    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
    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
    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