home *** CD-ROM | disk | FTP | other *** search
/ Linux Cubed Series 4: GNU Archives / Linux Cubed Series 4 - GNU Archives.iso / gnu / cvs-1.8 / cvs-1 / cvs-1.8.1 / src / edit.c < prev    next >
Encoding:
C/C++ Source or Header  |  1996-05-06  |  22.3 KB  |  1,021 lines

  1. /* Implementation for "cvs edit", "cvs watch on", and related commands
  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.    You should have received a copy of the GNU General Public License
  14.    along with this program; if not, write to the Free Software
  15.    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
  16.  
  17. #include "cvs.h"
  18. #include "getline.h"
  19. #include "watch.h"
  20. #include "edit.h"
  21. #include "fileattr.h"
  22.  
  23. static int watch_onoff PROTO ((int, char **));
  24.  
  25. static int setting_default;
  26. static int turning_on;
  27.  
  28. static int setting_tedit;
  29. static int setting_tunedit;
  30. static int setting_tcommit;
  31.  
  32. static int onoff_fileproc PROTO ((struct file_info *finfo));
  33.  
  34. static int
  35. onoff_fileproc (finfo)
  36.     struct file_info *finfo;
  37. {
  38.     fileattr_set (finfo->file, "_watched", turning_on ? "" : NULL);
  39.     return 0;
  40. }
  41.  
  42. static int onoff_filesdoneproc PROTO ((int, char *, char *));
  43.  
  44. static int
  45. onoff_filesdoneproc (err, repository, update_dir)
  46.     int err;
  47.     char *repository;
  48.     char *update_dir;
  49. {
  50.     if (setting_default)
  51.     fileattr_set (NULL, "_watched", turning_on ? "" : NULL);
  52.     return err;
  53. }
  54.  
  55. static int
  56. watch_onoff (argc, argv)
  57.     int argc;
  58.     char **argv;
  59. {
  60.     int c;
  61.     int local = 0;
  62.     int err;
  63.  
  64.     optind = 1;
  65.     while ((c = getopt (argc, argv, "l")) != -1)
  66.     {
  67.     switch (c)
  68.     {
  69.         case 'l':
  70.         local = 1;
  71.         break;
  72.         case '?':
  73.         default:
  74.         usage (watch_usage);
  75.         break;
  76.     }
  77.     }
  78.     argc -= optind;
  79.     argv += optind;
  80.  
  81. #ifdef CLIENT_SUPPORT
  82.     if (client_active)
  83.     {
  84.     start_server ();
  85.  
  86.     ign_setup ();
  87.  
  88.     if (local)
  89.         send_arg ("-l");
  90.     send_file_names (argc, argv, SEND_EXPAND_WILD);
  91.     /* FIXME:  We shouldn't have to send current files, but I'm not sure
  92.        whether it works.  So send the files --
  93.        it's slower but it works.  */
  94.     send_files (argc, argv, local, 0);
  95.     send_to_server (turning_on ? "watch-on\012" : "watch-off\012", 0);
  96.     return get_responses_and_close ();
  97.     }
  98. #endif /* CLIENT_SUPPORT */
  99.  
  100.     setting_default = (argc <= 0);
  101.  
  102.     lock_tree_for_write (argc, argv, local, 0);
  103.  
  104.     err = start_recursion (onoff_fileproc, onoff_filesdoneproc,
  105.                (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL,
  106.                argc, argv, local, W_LOCAL, 0, 0, (char *)NULL,
  107.                0, 0);
  108.  
  109.     lock_tree_cleanup ();
  110.     return err;
  111. }
  112.  
  113. int
  114. watch_on (argc, argv)
  115.     int argc;
  116.     char **argv;
  117. {
  118.     turning_on = 1;
  119.     return watch_onoff (argc, argv);
  120. }
  121.  
  122. int
  123. watch_off (argc, argv)
  124.     int argc;
  125.     char **argv;
  126. {
  127.     turning_on = 0;
  128.     return watch_onoff (argc, argv);
  129. }
  130.  
  131. static int dummy_fileproc PROTO ((struct file_info *finfo));
  132.  
  133. static int
  134. dummy_fileproc (finfo)
  135.     struct file_info *finfo;
  136. {
  137.     /* This is a pretty hideous hack, but the gist of it is that recurse.c
  138.        won't call notify_check unless there is a fileproc, so we can't just
  139.        pass NULL for fileproc.  */
  140.     return 0;
  141. }
  142.  
  143. static int ncheck_fileproc PROTO ((struct file_info *finfo));
  144.  
  145. /* Check for and process notifications.  Local only.  I think that doing
  146.    this as a fileproc is the only way to catch all the
  147.    cases (e.g. foo/bar.c), even though that means checking over and over
  148.    for the same CVSADM_NOTIFY file which we removed the first time we
  149.    processed the directory.  */
  150.  
  151. static int
  152. ncheck_fileproc (finfo)
  153.     struct file_info *finfo;
  154. {
  155.     int notif_type;
  156.     char *filename;
  157.     char *val;
  158.     char *cp;
  159.     char *watches;
  160.  
  161.     FILE *fp;
  162.     char *line = NULL;
  163.     size_t line_len = 0;
  164.  
  165.     /* We send notifications even if noexec.  I'm not sure which behavior
  166.        is most sensible.  */
  167.  
  168.     fp = fopen (CVSADM_NOTIFY, "r");
  169.     if (fp == NULL)
  170.     {
  171.     if (!existence_error (errno))
  172.         error (0, errno, "cannot open %s", CVSADM_NOTIFY);
  173.     return 0;
  174.     }
  175.  
  176.     while (getline (&line, &line_len, fp) > 0)
  177.     {
  178.     notif_type = line[0];
  179.     if (notif_type == '\0')
  180.         continue;
  181.     filename = line + 1;
  182.     cp = strchr (filename, '\t');
  183.     if (cp == NULL)
  184.         continue;
  185.     *cp++ = '\0';
  186.     val = cp;
  187.     cp = strchr (val, '\t');
  188.     if (cp == NULL)
  189.         continue;
  190.     *cp++ = '+';
  191.     cp = strchr (cp, '\t');
  192.     if (cp == NULL)
  193.         continue;
  194.     *cp++ = '+';
  195.     cp = strchr (cp, '\t');
  196.     if (cp == NULL)
  197.         continue;
  198.     *cp++ = '\0';
  199.     watches = cp;
  200.     cp = strchr (cp, '\n');
  201.     if (cp == NULL)
  202.         continue;
  203.     *cp = '\0';
  204.  
  205.     notify_do (notif_type, filename, getcaller (), val, watches,
  206.            finfo->repository);
  207.     }
  208.     free (line);
  209.  
  210.     if (ferror (fp))
  211.     error (0, errno, "cannot read %s", CVSADM_NOTIFY);
  212.     if (fclose (fp) < 0)
  213.     error (0, errno, "cannot close %s", CVSADM_NOTIFY);
  214.  
  215.     if (unlink (CVSADM_NOTIFY) < 0)
  216.     error (0, errno, "cannot remove %s", CVSADM_NOTIFY);
  217.  
  218.     return 0;
  219. }
  220.  
  221. static int send_notifications PROTO ((int, char **, int));
  222.  
  223. /* Look through the CVSADM_NOTIFY file and process each item there
  224.    accordingly.  */
  225. static int
  226. send_notifications (argc, argv, local)
  227.     int argc;
  228.     char **argv;
  229.     int local;
  230. {
  231.     int err = 0;
  232.  
  233. #ifdef CLIENT_SUPPORT
  234.     /* OK, we've done everything which needs to happen on the client side.
  235.        Now we can try to contact the server; if we fail, then the
  236.        notifications stay in CVSADM_NOTIFY to be sent next time.  */
  237.     if (client_active)
  238.     {
  239.     if (strcmp (command_name, "release") != 0)
  240.     {
  241.         start_server ();
  242.         ign_setup ();
  243.     }
  244.  
  245.     err += start_recursion (dummy_fileproc, (FILESDONEPROC) NULL,
  246.                 (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL,
  247.                 argc, argv, local, W_LOCAL, 0, 0, (char *)NULL,
  248.                 0, 0);
  249.  
  250.     send_to_server ("noop\012", 0);
  251.     if (strcmp (command_name, "release") == 0)
  252.         err += get_server_responses ();
  253.     else
  254.         err += get_responses_and_close ();
  255.     }
  256.     else
  257. #endif
  258.     {
  259.     /* Local.  */
  260.  
  261.     lock_tree_for_write (argc, argv, local, 0);
  262.     err += start_recursion (ncheck_fileproc, (FILESDONEPROC) NULL,
  263.                 (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL,
  264.                 argc, argv, local, W_LOCAL, 0, 0, (char *)NULL,
  265.                 0, 0);
  266.     lock_tree_cleanup ();
  267.     }
  268.     return err;
  269. }
  270.  
  271. static int edit_fileproc PROTO ((struct file_info *finfo));
  272.  
  273. static int
  274. edit_fileproc (finfo)
  275.     struct file_info *finfo;
  276. {
  277.     FILE *fp;
  278.     time_t now;
  279.     char *ascnow;
  280.     char *basefilename;
  281.  
  282.     if (noexec)
  283.     return 0;
  284.  
  285.     fp = open_file (CVSADM_NOTIFY, "a");
  286.  
  287.     (void) time (&now);
  288.     ascnow = asctime (gmtime (&now));
  289.     ascnow[24] = '\0';
  290.     fprintf (fp, "E%s\t%s GMT\t%s\t%s\t", finfo->file,
  291.          ascnow, hostname, CurDir);
  292.     if (setting_tedit)
  293.     fprintf (fp, "E");
  294.     if (setting_tunedit)
  295.     fprintf (fp, "U");
  296.     if (setting_tcommit)
  297.     fprintf (fp, "C");
  298.     fprintf (fp, "\n");
  299.  
  300.     if (fclose (fp) < 0)
  301.     {
  302.     if (finfo->update_dir[0] == '\0')
  303.         error (0, errno, "cannot close %s", CVSADM_NOTIFY);
  304.     else
  305.         error (0, errno, "cannot close %s/%s", finfo->update_dir,
  306.            CVSADM_NOTIFY);
  307.     }
  308.  
  309.     xchmod (finfo->file, 1);
  310.  
  311.     /* Now stash the file away in CVSADM so that unedit can revert even if
  312.        it can't communicate with the server.  We stash away a writable
  313.        copy so that if the user removes the working file, then restores it
  314.        with "cvs update" (which clears _editors but does not update
  315.        CVSADM_BASE), then a future "cvs edit" can still win.  */
  316.     /* Could save a system call by only calling mkdir if trying to create
  317.        the output file fails.  But copy_file isn't set up to facilitate
  318.        that.  */
  319.     if (CVS_MKDIR (CVSADM_BASE, 0777) < 0)
  320.     {
  321.         if (errno != EEXIST
  322. #ifdef EACCESS
  323.             /* OS/2; see longer comment in client.c.  */
  324.             && errno != EACCESS
  325. #endif
  326.             )
  327.         error (1, errno, "cannot mkdir %s", CVSADM_BASE);
  328.     }
  329.     basefilename = xmalloc (10 + sizeof CVSADM_BASE + strlen (finfo->file));
  330.     strcpy (basefilename, CVSADM_BASE);
  331.     strcat (basefilename, "/");
  332.     strcat (basefilename, finfo->file);
  333.     copy_file (finfo->file, basefilename);
  334.     free (basefilename);
  335.  
  336.     return 0;
  337. }
  338.  
  339. static const char *const edit_usage[] =
  340. {
  341.     "Usage: %s %s [-l] [files...]\n",
  342.     "-l: Local directory only, not recursive\n",
  343.     "-a: Specify what actions for temporary watch, one of\n",
  344.     "    edit,unedit,commit.all,none\n",
  345.     NULL
  346. };
  347.  
  348. int
  349. edit (argc, argv)
  350.     int argc;
  351.     char **argv;
  352. {
  353.     int local = 0;
  354.     int c;
  355.     int err;
  356.     int a_omitted;
  357.  
  358.     if (argc == -1)
  359.     usage (edit_usage);
  360.  
  361.     a_omitted = 1;
  362.     setting_tedit = 0;
  363.     setting_tunedit = 0;
  364.     setting_tcommit = 0;
  365.     optind = 1;
  366.     while ((c = getopt (argc, argv, "la:")) != -1)
  367.     {
  368.     switch (c)
  369.     {
  370.         case 'l':
  371.         local = 1;
  372.         break;
  373.         case 'a':
  374.         a_omitted = 0;
  375.         if (strcmp (optarg, "edit") == 0)
  376.             setting_tedit = 1;
  377.         else if (strcmp (optarg, "unedit") == 0)
  378.             setting_tunedit = 1;
  379.         else if (strcmp (optarg, "commit") == 0)
  380.             setting_tcommit = 1;
  381.         else if (strcmp (optarg, "all") == 0)
  382.         {
  383.             setting_tedit = 1;
  384.             setting_tunedit = 1;
  385.             setting_tcommit = 1;
  386.         }
  387.         else if (strcmp (optarg, "none") == 0)
  388.         {
  389.             setting_tedit = 0;
  390.             setting_tunedit = 0;
  391.             setting_tcommit = 0;
  392.         }
  393.         else
  394.             usage (edit_usage);
  395.         break;
  396.         case '?':
  397.         default:
  398.         usage (edit_usage);
  399.         break;
  400.     }
  401.     }
  402.     argc -= optind;
  403.     argv += optind;
  404.  
  405.     if (a_omitted)
  406.     {
  407.     setting_tedit = 1;
  408.     setting_tunedit = 1;
  409.     setting_tcommit = 1;
  410.     }
  411.  
  412.     /* No need to readlock since we aren't doing anything to the
  413.        repository.  */
  414.     err = start_recursion (edit_fileproc, (FILESDONEPROC) NULL,
  415.                (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL,
  416.                argc, argv, local, W_LOCAL, 0, 0, (char *)NULL,
  417.                0, 0);
  418.  
  419.     err += send_notifications (argc, argv, local);
  420.  
  421.     return err;
  422. }
  423.  
  424. static int unedit_fileproc PROTO ((struct file_info *finfo));
  425.  
  426. static int
  427. unedit_fileproc (finfo)
  428.     struct file_info *finfo;
  429. {
  430.     FILE *fp;
  431.     time_t now;
  432.     char *ascnow;
  433.     char *basefilename;
  434.  
  435.     if (noexec)
  436.     return 0;
  437.  
  438.     basefilename = xmalloc (10 + sizeof CVSADM_BASE + strlen (finfo->file));
  439.     strcpy (basefilename, CVSADM_BASE);
  440.     strcat (basefilename, "/");
  441.     strcat (basefilename, finfo->file);
  442.     if (!isfile (basefilename))
  443.     {
  444.     /* This file apparently was never cvs edit'd (e.g. we are uneditting
  445.        a directory where only some of the files were cvs edit'd.  */
  446.     free (basefilename);
  447.     return 0;
  448.     }
  449.  
  450.     if (xcmp (finfo->file, basefilename) != 0)
  451.     {
  452.     printf ("%s has been modified; revert changes? ", finfo->fullname);
  453.     if (!yesno ())
  454.     {
  455.         /* "no".  */
  456.         free (basefilename);
  457.         return 0;
  458.     }
  459.     }
  460.     rename_file (basefilename, finfo->file);
  461.     free (basefilename);
  462.  
  463.     fp = open_file (CVSADM_NOTIFY, "a");
  464.  
  465.     (void) time (&now);
  466.     ascnow = asctime (gmtime (&now));
  467.     ascnow[24] = '\0';
  468.     fprintf (fp, "U%s\t%s GMT\t%s\t%s\t\n", finfo->file,
  469.          ascnow, hostname, CurDir);
  470.  
  471.     if (fclose (fp) < 0)
  472.     {
  473.     if (finfo->update_dir[0] == '\0')
  474.         error (0, errno, "cannot close %s", CVSADM_NOTIFY);
  475.     else
  476.         error (0, errno, "cannot close %s/%s", finfo->update_dir,
  477.            CVSADM_NOTIFY);
  478.     }
  479.  
  480.     xchmod (finfo->file, 0);
  481.     return 0;
  482. }
  483.  
  484. int
  485. unedit (argc, argv)
  486.     int argc;
  487.     char **argv;
  488. {
  489.     int local = 0;
  490.     int c;
  491.     int err;
  492.  
  493.     if (argc == -1)
  494.     usage (edit_usage);
  495.  
  496.     optind = 1;
  497.     while ((c = getopt (argc, argv, "l")) != -1)
  498.     {
  499.     switch (c)
  500.     {
  501.         case 'l':
  502.         local = 1;
  503.         break;
  504.         case '?':
  505.         default:
  506.         usage (edit_usage);
  507.         break;
  508.     }
  509.     }
  510.     argc -= optind;
  511.     argv += optind;
  512.  
  513.     /* No need to readlock since we aren't doing anything to the
  514.        repository.  */
  515.     err = start_recursion (unedit_fileproc, (FILESDONEPROC) NULL,
  516.                (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL,
  517.                argc, argv, local, W_LOCAL, 0, 0, (char *)NULL,
  518.                0, 0);
  519.  
  520.     err += send_notifications (argc, argv, local);
  521.  
  522.     return err;
  523. }
  524.  
  525. void
  526. mark_up_to_date (file)
  527.     char *file;
  528. {
  529.     char *base;
  530.  
  531.     /* The file is up to date, so we better get rid of an out of
  532.        date file in CVSADM_BASE.  */
  533.     base = xmalloc (strlen (file) + 80);
  534.     strcpy (base, CVSADM_BASE);
  535.     strcat (base, "/");
  536.     strcat (base, file);
  537.     if (unlink_file (base) < 0 && ! existence_error (errno))
  538.     error (0, errno, "cannot remove %s", file);
  539.     free (base);
  540. }
  541.  
  542.  
  543. void
  544. editor_set (filename, editor, val)
  545.     char *filename;
  546.     char *editor;
  547.     char *val;
  548. {
  549.     char *edlist;
  550.     char *newlist;
  551.  
  552.     edlist = fileattr_get0 (filename, "_editors");
  553.     newlist = fileattr_modify (edlist, editor, val, '>', ',');
  554.     if (edlist != NULL)
  555.     free (edlist);
  556.     /* If the attributes is unchanged, don't rewrite the attribute file.  */
  557.     if (!((edlist == NULL && newlist == NULL)
  558.       || (edlist != NULL
  559.           && newlist != NULL
  560.           && strcmp (edlist, newlist) == 0)))
  561.     fileattr_set (filename, "_editors", newlist);
  562.     if (newlist != NULL)
  563.     free (newlist);
  564. }
  565.  
  566. struct notify_proc_args {
  567.     /* What kind of notification, "edit", "tedit", etc.  */
  568.     char *type;
  569.     /* User who is running the command which causes notification.  */
  570.     char *who;
  571.     /* User to be notified.  */
  572.     char *notifyee;
  573.     /* File.  */
  574.     char *file;
  575. };
  576.  
  577. /* Pass as a static until we get around to fixing Parse_Info to pass along
  578.    a void * where we can stash it.  */
  579. static struct notify_proc_args *notify_args;
  580.  
  581. static int notify_proc PROTO ((char *repository, char *filter));
  582.  
  583. static int
  584. notify_proc (repository, filter)
  585.     char *repository;
  586.     char *filter;
  587. {
  588.     FILE *pipefp;
  589.     char *prog;
  590.     char *expanded_prog;
  591.     char *p;
  592.     char *q;
  593.     char *srepos;
  594.     struct notify_proc_args *args = notify_args;
  595.  
  596.     srepos = Short_Repository (repository);
  597.     prog = xmalloc (strlen (filter) + strlen (args->notifyee) + 1);
  598.     /* Copy FILTER to PROG, replacing the first occurrence of %s with
  599.        the notifyee.  We only allocated enough memory for one %s, and I doubt
  600.        there is a need for more.  */
  601.     for (p = filter, q = prog; *p != '\0'; ++p)
  602.     {
  603.     if (p[0] == '%')
  604.     {
  605.         if (p[1] == 's')
  606.         {
  607.         strcpy (q, args->notifyee);
  608.         q += strlen (q);
  609.         strcpy (q, p + 2);
  610.         q += strlen (q);
  611.         break;
  612.         }
  613.         else
  614.         continue;
  615.     }
  616.     *q++ = *p;
  617.     }
  618.     *q = '\0';
  619.  
  620.     /* FIXME: why are we calling expand_proc?  Didn't we already
  621.        expand it in Parse_Info, before passing it to notify_proc?  */
  622.     expanded_prog = expand_path (prog, "notify", 0);
  623.     if (!expanded_prog)
  624.     {
  625.     free (prog);
  626.     return 1;
  627.     }
  628.  
  629.     pipefp = run_popen (expanded_prog, "w");
  630.     if (pipefp == NULL)
  631.     {
  632.     error (0, errno, "cannot write entry to notify filter: %s", prog);
  633.     free (prog);
  634.     free (expanded_prog);
  635.     return 1;
  636.     }
  637.  
  638.     fprintf (pipefp, "%s %s\n---\n", srepos, args->file);
  639.     fprintf (pipefp, "Triggered %s watch on %s\n", args->type, repository);
  640.     fprintf (pipefp, "By %s\n", args->who);
  641.  
  642.     /* Lots more potentially useful information we could add here; see
  643.        logfile_write for inspiration.  */
  644.  
  645.     free (prog);
  646.     free (expanded_prog);
  647.     return (pclose (pipefp));
  648. }
  649.  
  650. void
  651. notify_do (type, filename, who, val, watches, repository)
  652.     int type;
  653.     char *filename;
  654.     char *who;
  655.     char *val;
  656.     char *watches;
  657.     char *repository;
  658. {
  659.     static struct addremove_args blank;
  660.     struct addremove_args args;
  661.     char *watchers;
  662.     char *p;
  663.     char *endp;
  664.     char *nextp;
  665.  
  666.     /* Initialize fields to 0, NULL, or 0.0.  */
  667.     args = blank;
  668.     switch (type)
  669.     {
  670.     case 'E':
  671.         editor_set (filename, who, val);
  672.         break;
  673.     case 'U':
  674.     case 'C':
  675.         editor_set (filename, who, NULL);
  676.         break;
  677.     default:
  678.         return;
  679.     }
  680.  
  681.     watchers = fileattr_get0 (filename, "_watchers");
  682.     p = watchers;
  683.     while (p != NULL)
  684.     {
  685.     char *q;
  686.     char *endq;
  687.     char *nextq;
  688.     char *notif;
  689.  
  690.     endp = strchr (p, '>');
  691.     if (endp == NULL)
  692.         break;
  693.     nextp = strchr (p, ',');
  694.  
  695.     if ((size_t)(endp - p) == strlen (who) && strncmp (who, p, endp - p) == 0)
  696.     {
  697.         /* Don't notify user of their own changes.  Would perhaps
  698.            be better to check whether it is the same working
  699.            directory, not the same user, but that is hairy.  */
  700.         p = nextp == NULL ? nextp : nextp + 1;
  701.         continue;
  702.     }
  703.  
  704.     /* Now we point q at a string which looks like
  705.        "edit+unedit+commit,"... and walk down it.  */
  706.     q = endp + 1;
  707.     notif = NULL;
  708.     while (q != NULL)
  709.     {
  710.         endq = strchr (q, '+');
  711.         if (endq == NULL || (nextp != NULL && endq > nextp))
  712.         {
  713.         if (nextp == NULL)
  714.             endq = q + strlen (q);
  715.         else
  716.             endq = nextp;
  717.         nextq = NULL;
  718.         }
  719.         else
  720.         nextq = endq + 1;
  721.  
  722.         /* If there is a temporary and a regular watch, send a single
  723.            notification, for the regular watch.  */
  724.         if (type == 'E' && endq - q == 4 && strncmp ("edit", q, 4) == 0)
  725.         {
  726.         notif = "edit";
  727.         }
  728.         else if (type == 'U'
  729.              && endq - q == 6 && strncmp ("unedit", q, 6) == 0)
  730.         {
  731.         notif = "unedit";
  732.         }
  733.         else if (type == 'C'
  734.              && endq - q == 6 && strncmp ("commit", q, 6) == 0)
  735.         {
  736.         notif = "commit";
  737.         }
  738.         else if (type == 'E'
  739.              && endq - q == 5 && strncmp ("tedit", q, 5) == 0)
  740.         {
  741.         if (notif == NULL)
  742.             notif = "temporary edit";
  743.         }
  744.         else if (type == 'U'
  745.              && endq - q == 7 && strncmp ("tunedit", q, 7) == 0)
  746.         {
  747.         if (notif == NULL)
  748.             notif = "temporary unedit";
  749.         }
  750.         else if (type == 'C'
  751.              && endq - q == 7 && strncmp ("tcommit", q, 7) == 0)
  752.         {
  753.         if (notif == NULL)
  754.             notif = "temporary commit";
  755.         }
  756.         q = nextq;
  757.     }
  758.     if (nextp != NULL)
  759.         ++nextp;
  760.  
  761.     if (notif != NULL)
  762.     {
  763.         struct notify_proc_args args;
  764.         size_t len = endp - p;
  765.         FILE *fp;
  766.         char *usersname;
  767.         char *line = NULL;
  768.         size_t line_len = 0;
  769.  
  770.         args.notifyee = NULL;
  771.         usersname = xmalloc (strlen (CVSroot)
  772.                  + sizeof CVSROOTADM
  773.                  + sizeof CVSROOTADM_USERS
  774.                  + 20);
  775.         strcpy (usersname, CVSroot);
  776.         strcat (usersname, "/");
  777.         strcat (usersname, CVSROOTADM);
  778.         strcat (usersname, "/");
  779.         strcat (usersname, CVSROOTADM_USERS);
  780.         fp = fopen (usersname, "r");
  781.         if (fp == NULL && !existence_error (errno))
  782.         error (0, errno, "cannot read %s", usersname);
  783.         if (fp != NULL)
  784.         {
  785.         while (getline (&line, &line_len, fp) >= 0)
  786.         {
  787.             if (strncmp (line, p, len) == 0
  788.             && line[len] == ':')
  789.             {
  790.             char *cp;
  791.             args.notifyee = xstrdup (line + len + 1);
  792.             cp = strchr (args.notifyee, ':');
  793.             if (cp != NULL)
  794.                 *cp = '\0';
  795.             break;
  796.             }
  797.         }
  798.         if (ferror (fp))
  799.             error (0, errno, "cannot read %s", usersname);
  800.         if (fclose (fp) < 0)
  801.             error (0, errno, "cannot close %s", usersname);
  802.         }
  803.         free (usersname);
  804.         free (line);
  805.  
  806.         if (args.notifyee == NULL)
  807.         {
  808.         args.notifyee = xmalloc (endp - p + 1);
  809.         strncpy (args.notifyee, p, endp - p);
  810.         args.notifyee[endp - p] = '\0';
  811.         }
  812.  
  813.         notify_args = &args;
  814.         args.type = notif;
  815.         args.who = who;
  816.         args.file = filename;
  817.  
  818.         (void) Parse_Info (CVSROOTADM_NOTIFY, repository, notify_proc, 1);
  819.         free (args.notifyee);
  820.     }
  821.  
  822.     p = nextp;
  823.     }
  824.     if (watchers != NULL)
  825.     free (watchers);
  826.  
  827.     switch (type)
  828.     {
  829.     case 'E':
  830.         if (*watches == 'E')
  831.         {
  832.         args.add_tedit = 1;
  833.         ++watches;
  834.         }
  835.         if (*watches == 'U')
  836.         {
  837.         args.add_tunedit = 1;
  838.         ++watches;
  839.         }
  840.         if (*watches == 'C')
  841.         {
  842.         args.add_tcommit = 1;
  843.         }
  844.         watch_modify_watchers (filename, &args);
  845.         break;
  846.     case 'U':
  847.     case 'C':
  848.         args.remove_temp = 1;
  849.         watch_modify_watchers (filename, &args);
  850.         break;
  851.     }
  852. }
  853.  
  854. #ifdef CLIENT_SUPPORT
  855. /* Check and send notifications.  This is only for the client.  */
  856. void
  857. notify_check (repository, update_dir)
  858.     char *repository;
  859.     char *update_dir;
  860. {
  861.     FILE *fp;
  862.     char *line = NULL;
  863.     size_t line_len = 0;
  864.  
  865.     if (! server_started)
  866.     /* We are in the midst of a command which is not to talk to
  867.        the server (e.g. the first phase of a cvs edit).  Just chill
  868.        out, we'll catch the notifications on the flip side.  */
  869.     return;
  870.  
  871.     /* We send notifications even if noexec.  I'm not sure which behavior
  872.        is most sensible.  */
  873.  
  874.     fp = fopen (CVSADM_NOTIFY, "r");
  875.     if (fp == NULL)
  876.     {
  877.     if (!existence_error (errno))
  878.         error (0, errno, "cannot open %s", CVSADM_NOTIFY);
  879.     return;
  880.     }
  881.     while (getline (&line, &line_len, fp) > 0)
  882.     {
  883.     int notif_type;
  884.     char *filename;
  885.     char *val;
  886.     char *cp;
  887.  
  888.     notif_type = line[0];
  889.     if (notif_type == '\0')
  890.         continue;
  891.     filename = line + 1;
  892.     cp = strchr (filename, '\t');
  893.     if (cp == NULL)
  894.         continue;
  895.     *cp++ = '\0';
  896.     val = cp;
  897.  
  898.     client_notify (repository, update_dir, filename, notif_type, val);
  899.     }
  900.  
  901.     if (ferror (fp))
  902.     error (0, errno, "cannot read %s", CVSADM_NOTIFY);
  903.     if (fclose (fp) < 0)
  904.     error (0, errno, "cannot close %s", CVSADM_NOTIFY);
  905.  
  906.     /* Leave the CVSADM_NOTIFY file there, until the server tells us it
  907.        has dealt with it.  */
  908. }
  909. #endif /* CLIENT_SUPPORT */
  910.  
  911.  
  912. static const char *const editors_usage[] =
  913. {
  914.     "Usage: %s %s [files...]\n",
  915.     NULL
  916. };
  917.  
  918. static int editors_fileproc PROTO ((struct file_info *finfo));
  919.  
  920. static int
  921. editors_fileproc (finfo)
  922.     struct file_info *finfo;
  923. {
  924.     char *them;
  925.     char *p;
  926.  
  927.     them = fileattr_get0 (finfo->file, "_editors");
  928.     if (them == NULL)
  929.     return 0;
  930.  
  931.     fputs (finfo->fullname, stdout);
  932.  
  933.     p = them;
  934.     while (1)
  935.     {
  936.     putc ('\t', stdout);
  937.     while (*p != '>' && *p != '\0')
  938.         putc (*p++, stdout);
  939.     if (*p == '\0')
  940.     {
  941.         /* Only happens if attribute is misformed.  */
  942.         putc ('\n', stdout);
  943.         break;
  944.     }
  945.     ++p;
  946.     putc ('\t', stdout);
  947.     while (1)
  948.     {
  949.         while (*p != '+' && *p != ',' && *p != '\0')
  950.         putc (*p++, stdout);
  951.         if (*p == '\0')
  952.         {
  953.         putc ('\n', stdout);
  954.         goto out;
  955.         }
  956.         if (*p == ',')
  957.         {
  958.         ++p;
  959.         break;
  960.         }
  961.         ++p;
  962.         putc ('\t', stdout);
  963.     }
  964.     putc ('\n', stdout);
  965.     }
  966.   out:;
  967.     return 0;
  968. }
  969.  
  970. int
  971. editors (argc, argv)
  972.     int argc;
  973.     char **argv;
  974. {
  975.     int local = 0;
  976.     int c;
  977.  
  978.     if (argc == -1)
  979.     usage (editors_usage);
  980.  
  981.     optind = 1;
  982.     while ((c = getopt (argc, argv, "l")) != -1)
  983.     {
  984.     switch (c)
  985.     {
  986.         case 'l':
  987.         local = 1;
  988.         break;
  989.         case '?':
  990.         default:
  991.         usage (editors_usage);
  992.         break;
  993.     }
  994.     }
  995.     argc -= optind;
  996.     argv += optind;
  997.  
  998. #ifdef CLIENT_SUPPORT
  999.     if (client_active)
  1000.     {
  1001.     start_server ();
  1002.     ign_setup ();
  1003.  
  1004.     if (local)
  1005.         send_arg ("-l");
  1006.     send_file_names (argc, argv, SEND_EXPAND_WILD);
  1007.     /* FIXME:  We shouldn't have to send current files, but I'm not sure
  1008.        whether it works.  So send the files --
  1009.        it's slower but it works.  */
  1010.     send_files (argc, argv, local, 0);
  1011.     send_to_server ("editors\012", 0);
  1012.     return get_responses_and_close ();
  1013.     }
  1014. #endif /* CLIENT_SUPPORT */
  1015.  
  1016.     return start_recursion (editors_fileproc, (FILESDONEPROC) NULL,
  1017.                 (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL,
  1018.                 argc, argv, local, W_LOCAL, 0, 1, (char *)NULL,
  1019.                 0, 0);
  1020. }
  1021.