/contrib/cvs/src/edit.c

https://bitbucket.org/freebsd/freebsd-head/ · C · 1164 lines · 909 code · 136 blank · 119 comment · 253 complexity · 675fa811de507ed7af777b7e15821542 MD5 · raw file

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