117721Speter/* Implementation for "cvs edit", "cvs watch on", and related commands 217721Speter 317721Speter This program is free software; you can redistribute it and/or modify 417721Speter it under the terms of the GNU General Public License as published by 517721Speter the Free Software Foundation; either version 2, or (at your option) 617721Speter any later version. 717721Speter 817721Speter This program is distributed in the hope that it will be useful, 917721Speter but WITHOUT ANY WARRANTY; without even the implied warranty of 1017721Speter MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 1125839Speter GNU General Public License for more details. */ 1217721Speter 1317721Speter#include "cvs.h" 1417721Speter#include "getline.h" 1517721Speter#include "watch.h" 1617721Speter#include "edit.h" 1717721Speter#include "fileattr.h" 1817721Speter 1917721Speterstatic int watch_onoff PROTO ((int, char **)); 2017721Speter 2117721Speterstatic int setting_default; 2217721Speterstatic int turning_on; 2317721Speter 2417721Speterstatic int setting_tedit; 2517721Speterstatic int setting_tunedit; 2617721Speterstatic int setting_tcommit; 2717721Speter 2825839Speterstatic int onoff_fileproc PROTO ((void *callerdat, struct file_info *finfo)); 2917721Speter 3017721Speterstatic int 3125839Speteronoff_fileproc (callerdat, finfo) 3225839Speter void *callerdat; 3317721Speter struct file_info *finfo; 3417721Speter{ 35175261Sobrien char *watched = fileattr_get0 (finfo->file, "_watched"); 3617721Speter fileattr_set (finfo->file, "_watched", turning_on ? "" : NULL); 37175261Sobrien if (watched != NULL) 38175261Sobrien free (watched); 3917721Speter return 0; 4017721Speter} 4117721Speter 4217721Speter 43128266Speter 44128266Speterstatic int onoff_filesdoneproc PROTO ((void *, int, const char *, const char *, 45128266Speter List *)); 46128266Speter 4717721Speterstatic int 4825839Speteronoff_filesdoneproc (callerdat, err, repository, update_dir, entries) 4925839Speter void *callerdat; 5017721Speter int err; 51128266Speter const char *repository; 52128266Speter const char *update_dir; 5325839Speter List *entries; 5417721Speter{ 5517721Speter if (setting_default) 56175261Sobrien { 57175261Sobrien char *watched = fileattr_get0 (NULL, "_watched"); 5817721Speter fileattr_set (NULL, "_watched", turning_on ? "" : NULL); 59175261Sobrien if (watched != NULL) 60175261Sobrien free (watched); 61175261Sobrien } 6217721Speter return err; 6317721Speter} 6417721Speter 6517721Speterstatic int 6617721Speterwatch_onoff (argc, argv) 6717721Speter int argc; 6817721Speter char **argv; 6917721Speter{ 7017721Speter int c; 7117721Speter int local = 0; 7217721Speter int err; 7317721Speter 7426065Speter optind = 0; 7525839Speter while ((c = getopt (argc, argv, "+lR")) != -1) 7617721Speter { 7717721Speter switch (c) 7817721Speter { 7917721Speter case 'l': 8017721Speter local = 1; 8117721Speter break; 8225839Speter case 'R': 8325839Speter local = 0; 8425839Speter break; 8517721Speter case '?': 8617721Speter default: 8717721Speter usage (watch_usage); 8817721Speter break; 8917721Speter } 9017721Speter } 9117721Speter argc -= optind; 9217721Speter argv += optind; 9317721Speter 9417721Speter#ifdef CLIENT_SUPPORT 9581404Speter if (current_parsed_root->isremote) 9617721Speter { 9717721Speter start_server (); 9817721Speter 9917721Speter ign_setup (); 10017721Speter 10117721Speter if (local) 10217721Speter send_arg ("-l"); 103107484Speter send_arg ("--"); 10454427Speter send_files (argc, argv, local, 0, SEND_NO_CONTENTS); 10517721Speter send_file_names (argc, argv, SEND_EXPAND_WILD); 10617721Speter send_to_server (turning_on ? "watch-on\012" : "watch-off\012", 0); 10717721Speter return get_responses_and_close (); 10817721Speter } 10917721Speter#endif /* CLIENT_SUPPORT */ 11017721Speter 11117721Speter setting_default = (argc <= 0); 11217721Speter 11381404Speter lock_tree_for_write (argc, argv, local, W_LOCAL, 0); 11417721Speter 11517721Speter err = start_recursion (onoff_fileproc, onoff_filesdoneproc, 11625839Speter (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL, 117109655Speter argc, argv, local, W_LOCAL, 0, CVS_LOCK_NONE, 118128266Speter (char *) NULL, 0, (char *) NULL); 11917721Speter 12025839Speter Lock_Cleanup (); 12117721Speter return err; 12217721Speter} 12317721Speter 12417721Speterint 12517721Speterwatch_on (argc, argv) 12617721Speter int argc; 12717721Speter char **argv; 12817721Speter{ 12917721Speter turning_on = 1; 13017721Speter return watch_onoff (argc, argv); 13117721Speter} 13217721Speter 13317721Speterint 13417721Speterwatch_off (argc, argv) 13517721Speter int argc; 13617721Speter char **argv; 13717721Speter{ 13817721Speter turning_on = 0; 13917721Speter return watch_onoff (argc, argv); 14017721Speter} 14117721Speter 14225839Speterstatic int dummy_fileproc PROTO ((void *callerdat, struct file_info *finfo)); 14317721Speter 14417721Speterstatic int 14525839Speterdummy_fileproc (callerdat, finfo) 14625839Speter void *callerdat; 14717721Speter struct file_info *finfo; 14817721Speter{ 14917721Speter /* This is a pretty hideous hack, but the gist of it is that recurse.c 150177391Sobrien won't call cvs_notify_check unless there is a fileproc, so we 151177391Sobrien can't just pass NULL for fileproc. */ 15217721Speter return 0; 15317721Speter} 15417721Speter 15525839Speterstatic int ncheck_fileproc PROTO ((void *callerdat, struct file_info *finfo)); 15617721Speter 15717721Speter/* Check for and process notifications. Local only. I think that doing 15817721Speter this as a fileproc is the only way to catch all the 15917721Speter cases (e.g. foo/bar.c), even though that means checking over and over 16017721Speter for the same CVSADM_NOTIFY file which we removed the first time we 16117721Speter processed the directory. */ 16217721Speter 16317721Speterstatic int 16425839Speterncheck_fileproc (callerdat, finfo) 16525839Speter void *callerdat; 16617721Speter struct file_info *finfo; 16717721Speter{ 16817721Speter int notif_type; 16917721Speter char *filename; 17017721Speter char *val; 17117721Speter char *cp; 17217721Speter char *watches; 17317721Speter 17417721Speter FILE *fp; 17517721Speter char *line = NULL; 17617721Speter size_t line_len = 0; 17717721Speter 17817721Speter /* We send notifications even if noexec. I'm not sure which behavior 17917721Speter is most sensible. */ 18017721Speter 18125839Speter fp = CVS_FOPEN (CVSADM_NOTIFY, "r"); 18217721Speter if (fp == NULL) 18317721Speter { 18417721Speter if (!existence_error (errno)) 18517721Speter error (0, errno, "cannot open %s", CVSADM_NOTIFY); 18617721Speter return 0; 18717721Speter } 18817721Speter 18917721Speter while (getline (&line, &line_len, fp) > 0) 19017721Speter { 19117721Speter notif_type = line[0]; 19217721Speter if (notif_type == '\0') 19317721Speter continue; 19417721Speter filename = line + 1; 19517721Speter cp = strchr (filename, '\t'); 19617721Speter if (cp == NULL) 19717721Speter continue; 19817721Speter *cp++ = '\0'; 19917721Speter val = cp; 20017721Speter cp = strchr (val, '\t'); 20117721Speter if (cp == NULL) 20217721Speter continue; 20317721Speter *cp++ = '+'; 20417721Speter cp = strchr (cp, '\t'); 20517721Speter if (cp == NULL) 20617721Speter continue; 20717721Speter *cp++ = '+'; 20817721Speter cp = strchr (cp, '\t'); 20917721Speter if (cp == NULL) 21017721Speter continue; 21117721Speter *cp++ = '\0'; 21217721Speter watches = cp; 21317721Speter cp = strchr (cp, '\n'); 21417721Speter if (cp == NULL) 21517721Speter continue; 21617721Speter *cp = '\0'; 21717721Speter 21817721Speter notify_do (notif_type, filename, getcaller (), val, watches, 21917721Speter finfo->repository); 22017721Speter } 22117721Speter free (line); 22217721Speter 22317721Speter if (ferror (fp)) 22417721Speter error (0, errno, "cannot read %s", CVSADM_NOTIFY); 22517721Speter if (fclose (fp) < 0) 22617721Speter error (0, errno, "cannot close %s", CVSADM_NOTIFY); 22717721Speter 22825839Speter if ( CVS_UNLINK (CVSADM_NOTIFY) < 0) 22917721Speter error (0, errno, "cannot remove %s", CVSADM_NOTIFY); 23017721Speter 23117721Speter return 0; 23217721Speter} 23317721Speter 23417721Speterstatic int send_notifications PROTO ((int, char **, int)); 23517721Speter 23617721Speter/* Look through the CVSADM_NOTIFY file and process each item there 23717721Speter accordingly. */ 23817721Speterstatic int 23917721Spetersend_notifications (argc, argv, local) 24017721Speter int argc; 24117721Speter char **argv; 24217721Speter int local; 24317721Speter{ 24417721Speter int err = 0; 24517721Speter 24617721Speter#ifdef CLIENT_SUPPORT 24717721Speter /* OK, we've done everything which needs to happen on the client side. 24817721Speter Now we can try to contact the server; if we fail, then the 24917721Speter notifications stay in CVSADM_NOTIFY to be sent next time. */ 25081404Speter if (current_parsed_root->isremote) 25117721Speter { 252128266Speter if (strcmp (cvs_cmd_name, "release") != 0) 25317721Speter { 25417721Speter start_server (); 25517721Speter ign_setup (); 25617721Speter } 25717721Speter 25817721Speter err += start_recursion (dummy_fileproc, (FILESDONEPROC) NULL, 25925839Speter (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL, 26017721Speter argc, argv, local, W_LOCAL, 0, 0, (char *)NULL, 261128266Speter 0, (char *) NULL); 26217721Speter 26317721Speter send_to_server ("noop\012", 0); 264128266Speter if (strcmp (cvs_cmd_name, "release") == 0) 26517721Speter err += get_server_responses (); 26617721Speter else 26717721Speter err += get_responses_and_close (); 26817721Speter } 26917721Speter else 27017721Speter#endif 27117721Speter { 27217721Speter /* Local. */ 27317721Speter 27481404Speter lock_tree_for_write (argc, argv, local, W_LOCAL, 0); 27517721Speter err += start_recursion (ncheck_fileproc, (FILESDONEPROC) NULL, 27625839Speter (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL, 27717721Speter argc, argv, local, W_LOCAL, 0, 0, (char *)NULL, 278128266Speter 0, (char *) NULL); 27925839Speter Lock_Cleanup (); 28017721Speter } 28117721Speter return err; 28217721Speter} 28317721Speter 28425839Speterstatic int edit_fileproc PROTO ((void *callerdat, struct file_info *finfo)); 28517721Speter 28617721Speterstatic int 28725839Speteredit_fileproc (callerdat, finfo) 28825839Speter void *callerdat; 28917721Speter struct file_info *finfo; 29017721Speter{ 29117721Speter FILE *fp; 29217721Speter time_t now; 29317721Speter char *ascnow; 29417721Speter char *basefilename; 29517721Speter 29617721Speter if (noexec) 29717721Speter return 0; 29817721Speter 29932785Speter /* This is a somewhat screwy way to check for this, because it 30032785Speter doesn't help errors other than the nonexistence of the file 30132785Speter (e.g. permissions problems). It might be better to rearrange 30232785Speter the code so that CVSADM_NOTIFY gets written only after the 30332785Speter various actions succeed (but what if only some of them 30432785Speter succeed). */ 30532785Speter if (!isfile (finfo->file)) 30632785Speter { 30732785Speter error (0, 0, "no such file %s; ignored", finfo->fullname); 30832785Speter return 0; 30932785Speter } 31032785Speter 31117721Speter fp = open_file (CVSADM_NOTIFY, "a"); 31217721Speter 31317721Speter (void) time (&now); 31417721Speter ascnow = asctime (gmtime (&now)); 31517721Speter ascnow[24] = '\0'; 31681404Speter /* Fix non-standard format. */ 31781404Speter if (ascnow[8] == '0') ascnow[8] = ' '; 31817721Speter fprintf (fp, "E%s\t%s GMT\t%s\t%s\t", finfo->file, 31917721Speter ascnow, hostname, CurDir); 32017721Speter if (setting_tedit) 32117721Speter fprintf (fp, "E"); 32217721Speter if (setting_tunedit) 32317721Speter fprintf (fp, "U"); 32417721Speter if (setting_tcommit) 32517721Speter fprintf (fp, "C"); 32617721Speter fprintf (fp, "\n"); 32717721Speter 32817721Speter if (fclose (fp) < 0) 32917721Speter { 33017721Speter if (finfo->update_dir[0] == '\0') 33117721Speter error (0, errno, "cannot close %s", CVSADM_NOTIFY); 33217721Speter else 33317721Speter error (0, errno, "cannot close %s/%s", finfo->update_dir, 33417721Speter CVSADM_NOTIFY); 33517721Speter } 33617721Speter 33717721Speter xchmod (finfo->file, 1); 33817721Speter 33917721Speter /* Now stash the file away in CVSADM so that unedit can revert even if 34017721Speter it can't communicate with the server. We stash away a writable 34117721Speter copy so that if the user removes the working file, then restores it 34217721Speter with "cvs update" (which clears _editors but does not update 34317721Speter CVSADM_BASE), then a future "cvs edit" can still win. */ 34425839Speter /* Could save a system call by only calling mkdir_if_needed if 34525839Speter trying to create the output file fails. But copy_file isn't 34625839Speter set up to facilitate that. */ 34725839Speter mkdir_if_needed (CVSADM_BASE); 34817721Speter basefilename = xmalloc (10 + sizeof CVSADM_BASE + strlen (finfo->file)); 34917721Speter strcpy (basefilename, CVSADM_BASE); 35017721Speter strcat (basefilename, "/"); 35117721Speter strcat (basefilename, finfo->file); 35217721Speter copy_file (finfo->file, basefilename); 35317721Speter free (basefilename); 35417721Speter 35532785Speter { 35632785Speter Node *node; 35732785Speter 35832785Speter node = findnode_fn (finfo->entries, finfo->file); 35932785Speter if (node != NULL) 36032785Speter base_register (finfo, ((Entnode *) node->data)->version); 36132785Speter } 36232785Speter 36317721Speter return 0; 36417721Speter} 36517721Speter 36617721Speterstatic const char *const edit_usage[] = 36717721Speter{ 368175261Sobrien "Usage: %s %s [-lR] [-a <action>]... [<file>]...\n", 369175261Sobrien "-l\tLocal directory only, not recursive.\n", 370175261Sobrien "-R\tProcess directories recursively (default).\n", 371175261Sobrien "-a\tSpecify action to register for temporary watch, one of:\n", 372175261Sobrien " \t`edit', `unedit', `commit', `all', or `none' (defaults to `all').\n", 373175261Sobrien "(Specify the --help global option for a list of other help options.)\n", 37417721Speter NULL 37517721Speter}; 37617721Speter 37717721Speterint 37817721Speteredit (argc, argv) 37917721Speter int argc; 38017721Speter char **argv; 38117721Speter{ 38217721Speter int local = 0; 38317721Speter int c; 38417721Speter int err; 38517721Speter int a_omitted; 38617721Speter 38717721Speter if (argc == -1) 38817721Speter usage (edit_usage); 38917721Speter 39017721Speter a_omitted = 1; 39117721Speter setting_tedit = 0; 39217721Speter setting_tunedit = 0; 39317721Speter setting_tcommit = 0; 39426065Speter optind = 0; 39525839Speter while ((c = getopt (argc, argv, "+lRa:")) != -1) 39617721Speter { 39717721Speter switch (c) 39817721Speter { 39917721Speter case 'l': 40017721Speter local = 1; 40117721Speter break; 40225839Speter case 'R': 40325839Speter local = 0; 40425839Speter break; 40517721Speter case 'a': 40617721Speter a_omitted = 0; 40717721Speter if (strcmp (optarg, "edit") == 0) 40817721Speter setting_tedit = 1; 40917721Speter else if (strcmp (optarg, "unedit") == 0) 41017721Speter setting_tunedit = 1; 41117721Speter else if (strcmp (optarg, "commit") == 0) 41217721Speter setting_tcommit = 1; 41317721Speter else if (strcmp (optarg, "all") == 0) 41417721Speter { 41517721Speter setting_tedit = 1; 41617721Speter setting_tunedit = 1; 41717721Speter setting_tcommit = 1; 41817721Speter } 41917721Speter else if (strcmp (optarg, "none") == 0) 42017721Speter { 42117721Speter setting_tedit = 0; 42217721Speter setting_tunedit = 0; 42317721Speter setting_tcommit = 0; 42417721Speter } 42517721Speter else 42617721Speter usage (edit_usage); 42717721Speter break; 42817721Speter case '?': 42917721Speter default: 43017721Speter usage (edit_usage); 43117721Speter break; 43217721Speter } 43317721Speter } 43417721Speter argc -= optind; 43517721Speter argv += optind; 43617721Speter 43717721Speter if (a_omitted) 43817721Speter { 43917721Speter setting_tedit = 1; 44017721Speter setting_tunedit = 1; 44117721Speter setting_tcommit = 1; 44217721Speter } 44317721Speter 44466525Speter if (strpbrk (hostname, "+,>;=\t\n") != NULL) 44566525Speter error (1, 0, 44666525Speter "host name (%s) contains an invalid character (+,>;=\\t\\n)", 44766525Speter hostname); 44866525Speter if (strpbrk (CurDir, "+,>;=\t\n") != NULL) 44966525Speter error (1, 0, 45066525Speter"current directory (%s) contains an invalid character (+,>;=\\t\\n)", 45166525Speter CurDir); 45266525Speter 45317721Speter /* No need to readlock since we aren't doing anything to the 45417721Speter repository. */ 45517721Speter err = start_recursion (edit_fileproc, (FILESDONEPROC) NULL, 45625839Speter (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL, 457128266Speter argc, argv, local, W_LOCAL, 0, 0, (char *) NULL, 458128266Speter 0, (char *) NULL); 45917721Speter 46017721Speter err += send_notifications (argc, argv, local); 46117721Speter 46217721Speter return err; 46317721Speter} 46417721Speter 46525839Speterstatic int unedit_fileproc PROTO ((void *callerdat, struct file_info *finfo)); 46617721Speter 46717721Speterstatic int 46825839Speterunedit_fileproc (callerdat, finfo) 46925839Speter void *callerdat; 47017721Speter struct file_info *finfo; 47117721Speter{ 47217721Speter FILE *fp; 47317721Speter time_t now; 47417721Speter char *ascnow; 47517721Speter char *basefilename; 47617721Speter 47717721Speter if (noexec) 47817721Speter return 0; 47917721Speter 48017721Speter basefilename = xmalloc (10 + sizeof CVSADM_BASE + strlen (finfo->file)); 48117721Speter strcpy (basefilename, CVSADM_BASE); 48217721Speter strcat (basefilename, "/"); 48317721Speter strcat (basefilename, finfo->file); 48417721Speter if (!isfile (basefilename)) 48517721Speter { 48617721Speter /* This file apparently was never cvs edit'd (e.g. we are uneditting 48717721Speter a directory where only some of the files were cvs edit'd. */ 48817721Speter free (basefilename); 48917721Speter return 0; 49017721Speter } 49117721Speter 49217721Speter if (xcmp (finfo->file, basefilename) != 0) 49317721Speter { 49417721Speter printf ("%s has been modified; revert changes? ", finfo->fullname); 49517721Speter if (!yesno ()) 49617721Speter { 49717721Speter /* "no". */ 49817721Speter free (basefilename); 49917721Speter return 0; 50017721Speter } 50117721Speter } 50217721Speter rename_file (basefilename, finfo->file); 50317721Speter free (basefilename); 50417721Speter 50517721Speter fp = open_file (CVSADM_NOTIFY, "a"); 50617721Speter 50717721Speter (void) time (&now); 50817721Speter ascnow = asctime (gmtime (&now)); 50917721Speter ascnow[24] = '\0'; 51081404Speter /* Fix non-standard format. */ 51181404Speter if (ascnow[8] == '0') ascnow[8] = ' '; 51217721Speter fprintf (fp, "U%s\t%s GMT\t%s\t%s\t\n", finfo->file, 51317721Speter ascnow, hostname, CurDir); 51417721Speter 51517721Speter if (fclose (fp) < 0) 51617721Speter { 51717721Speter if (finfo->update_dir[0] == '\0') 51817721Speter error (0, errno, "cannot close %s", CVSADM_NOTIFY); 51917721Speter else 52017721Speter error (0, errno, "cannot close %s/%s", finfo->update_dir, 52117721Speter CVSADM_NOTIFY); 52217721Speter } 52317721Speter 52432785Speter /* Now update the revision number in CVS/Entries from CVS/Baserev. 52532785Speter The basic idea here is that we are reverting to the revision 52632785Speter that the user edited. If we wanted "cvs update" to update 52732785Speter CVS/Base as we go along (so that an unedit could revert to the 52832785Speter current repository revision), we would need: 52932785Speter 53032785Speter update (or all send_files?) (client) needs to send revision in 53132785Speter new Entry-base request. update (server/local) needs to check 53232785Speter revision against repository and send new Update-base response 53332785Speter (like Update-existing in that the file already exists. While 53432785Speter we are at it, might try to clean up the syntax by having the 53532785Speter mode only in a "Mode" response, not in the Update-base itself). */ 53632785Speter { 53732785Speter char *baserev; 53832785Speter Node *node; 53932785Speter Entnode *entdata; 54032785Speter 54132785Speter baserev = base_get (finfo); 54232785Speter node = findnode_fn (finfo->entries, finfo->file); 54332785Speter /* The case where node is NULL probably should be an error or 54432785Speter something, but I don't want to think about it too hard right 54532785Speter now. */ 54632785Speter if (node != NULL) 54732785Speter { 548128266Speter entdata = node->data; 54944852Speter if (baserev == NULL) 55044852Speter { 55144852Speter /* This can only happen if the CVS/Baserev file got 55244852Speter corrupted. We suspect it might be possible if the 55344852Speter user interrupts CVS, although I haven't verified 55444852Speter that. */ 55544852Speter error (0, 0, "%s not mentioned in %s", finfo->fullname, 55644852Speter CVSADM_BASEREV); 55744852Speter 55844852Speter /* Since we don't know what revision the file derives from, 55944852Speter keeping it around would be asking for trouble. */ 56044852Speter if (unlink_file (finfo->file) < 0) 56144852Speter error (0, errno, "cannot remove %s", finfo->fullname); 56244852Speter 56344852Speter /* This is cheesy, in a sense; why shouldn't we do the 56444852Speter update for the user? However, doing that would require 56544852Speter contacting the server, so maybe this is OK. */ 56644852Speter error (0, 0, "run update to complete the unedit"); 56744852Speter return 0; 56844852Speter } 56932785Speter Register (finfo->entries, finfo->file, baserev, entdata->timestamp, 57032785Speter entdata->options, entdata->tag, entdata->date, 57132785Speter entdata->conflict); 57232785Speter } 57332785Speter free (baserev); 57432785Speter base_deregister (finfo); 57532785Speter } 57632785Speter 57717721Speter xchmod (finfo->file, 0); 57817721Speter return 0; 57917721Speter} 58017721Speter 58166525Speterstatic const char *const unedit_usage[] = 58266525Speter{ 583175261Sobrien "Usage: %s %s [-lR] [<file>]...\n", 584175261Sobrien "-l\tLocal directory only, not recursive.\n", 585175261Sobrien "-R\tProcess directories recursively (default).\n", 586175261Sobrien "(Specify the --help global option for a list of other help options.)\n", 58766525Speter NULL 58866525Speter}; 58966525Speter 59017721Speterint 59117721Speterunedit (argc, argv) 59217721Speter int argc; 59317721Speter char **argv; 59417721Speter{ 59517721Speter int local = 0; 59617721Speter int c; 59717721Speter int err; 59817721Speter 59917721Speter if (argc == -1) 60066525Speter usage (unedit_usage); 60117721Speter 60226065Speter optind = 0; 60325839Speter while ((c = getopt (argc, argv, "+lR")) != -1) 60417721Speter { 60517721Speter switch (c) 60617721Speter { 60717721Speter case 'l': 60817721Speter local = 1; 60917721Speter break; 61025839Speter case 'R': 61125839Speter local = 0; 61225839Speter break; 61317721Speter case '?': 61417721Speter default: 61566525Speter usage (unedit_usage); 61617721Speter break; 61717721Speter } 61817721Speter } 61917721Speter argc -= optind; 62017721Speter argv += optind; 62117721Speter 62217721Speter /* No need to readlock since we aren't doing anything to the 62317721Speter repository. */ 62417721Speter err = start_recursion (unedit_fileproc, (FILESDONEPROC) NULL, 62525839Speter (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL, 62617721Speter argc, argv, local, W_LOCAL, 0, 0, (char *)NULL, 627128266Speter 0, (char *) NULL); 62817721Speter 62917721Speter err += send_notifications (argc, argv, local); 63017721Speter 63117721Speter return err; 63217721Speter} 63317721Speter 63417721Spetervoid 63517721Spetermark_up_to_date (file) 636128266Speter const char *file; 63717721Speter{ 63817721Speter char *base; 63917721Speter 64017721Speter /* The file is up to date, so we better get rid of an out of 64117721Speter date file in CVSADM_BASE. */ 64217721Speter base = xmalloc (strlen (file) + 80); 64317721Speter strcpy (base, CVSADM_BASE); 64417721Speter strcat (base, "/"); 64517721Speter strcat (base, file); 64617721Speter if (unlink_file (base) < 0 && ! existence_error (errno)) 64717721Speter error (0, errno, "cannot remove %s", file); 64817721Speter free (base); 64917721Speter} 65017721Speter 651128266Speter 652128266Speter 65317721Spetervoid 65417721Spetereditor_set (filename, editor, val) 655128266Speter const char *filename; 656128266Speter const char *editor; 657128266Speter const char *val; 65817721Speter{ 65917721Speter char *edlist; 66017721Speter char *newlist; 66117721Speter 66217721Speter edlist = fileattr_get0 (filename, "_editors"); 66317721Speter newlist = fileattr_modify (edlist, editor, val, '>', ','); 66417721Speter /* If the attributes is unchanged, don't rewrite the attribute file. */ 66517721Speter if (!((edlist == NULL && newlist == NULL) 66617721Speter || (edlist != NULL 66717721Speter && newlist != NULL 66817721Speter && strcmp (edlist, newlist) == 0))) 66917721Speter fileattr_set (filename, "_editors", newlist); 67032785Speter if (edlist != NULL) 67132785Speter free (edlist); 67217721Speter if (newlist != NULL) 67317721Speter free (newlist); 67417721Speter} 67517721Speter 67617721Speterstruct notify_proc_args { 67717721Speter /* What kind of notification, "edit", "tedit", etc. */ 678128266Speter const char *type; 67917721Speter /* User who is running the command which causes notification. */ 680128266Speter const char *who; 68117721Speter /* User to be notified. */ 682128266Speter const char *notifyee; 68317721Speter /* File. */ 684128266Speter const char *file; 68517721Speter}; 68617721Speter 687128266Speter 688128266Speter 68917721Speter/* Pass as a static until we get around to fixing Parse_Info to pass along 69017721Speter a void * where we can stash it. */ 69117721Speterstatic struct notify_proc_args *notify_args; 69217721Speter 69317721Speter 694128266Speter 695128266Speterstatic int notify_proc PROTO ((const char *repository, const char *filter)); 696128266Speter 69717721Speterstatic int 69817721Speternotify_proc (repository, filter) 699128266Speter const char *repository; 700128266Speter const char *filter; 70117721Speter{ 70217721Speter FILE *pipefp; 70317721Speter char *prog; 70417721Speter char *expanded_prog; 705128266Speter const char *p; 70617721Speter char *q; 707128266Speter const char *srepos; 70817721Speter struct notify_proc_args *args = notify_args; 70917721Speter 71017721Speter srepos = Short_Repository (repository); 71117721Speter prog = xmalloc (strlen (filter) + strlen (args->notifyee) + 1); 71217721Speter /* Copy FILTER to PROG, replacing the first occurrence of %s with 71317721Speter the notifyee. We only allocated enough memory for one %s, and I doubt 71417721Speter there is a need for more. */ 71517721Speter for (p = filter, q = prog; *p != '\0'; ++p) 71617721Speter { 71717721Speter if (p[0] == '%') 71817721Speter { 71917721Speter if (p[1] == 's') 72017721Speter { 72117721Speter strcpy (q, args->notifyee); 72217721Speter q += strlen (q); 72317721Speter strcpy (q, p + 2); 72417721Speter q += strlen (q); 72517721Speter break; 72617721Speter } 72717721Speter else 72817721Speter continue; 72917721Speter } 73017721Speter *q++ = *p; 73117721Speter } 73217721Speter *q = '\0'; 73317721Speter 73417721Speter /* FIXME: why are we calling expand_proc? Didn't we already 73517721Speter expand it in Parse_Info, before passing it to notify_proc? */ 73617721Speter expanded_prog = expand_path (prog, "notify", 0); 73717721Speter if (!expanded_prog) 73817721Speter { 73917721Speter free (prog); 74017721Speter return 1; 74117721Speter } 74217721Speter 74317721Speter pipefp = run_popen (expanded_prog, "w"); 74417721Speter if (pipefp == NULL) 74517721Speter { 74617721Speter error (0, errno, "cannot write entry to notify filter: %s", prog); 74717721Speter free (prog); 74817721Speter free (expanded_prog); 74917721Speter return 1; 75017721Speter } 75117721Speter 75217721Speter fprintf (pipefp, "%s %s\n---\n", srepos, args->file); 75317721Speter fprintf (pipefp, "Triggered %s watch on %s\n", args->type, repository); 75417721Speter fprintf (pipefp, "By %s\n", args->who); 75517721Speter 75617721Speter /* Lots more potentially useful information we could add here; see 75717721Speter logfile_write for inspiration. */ 75817721Speter 75917721Speter free (prog); 76017721Speter free (expanded_prog); 76117721Speter return (pclose (pipefp)); 76217721Speter} 76317721Speter 76454427Speter/* FIXME: this function should have a way to report whether there was 76554427Speter an error so that server.c can know whether to report Notified back 76654427Speter to the client. */ 76717721Spetervoid 76817721Speternotify_do (type, filename, who, val, watches, repository) 76917721Speter int type; 770128266Speter const char *filename; 771128266Speter const char *who; 772128266Speter const char *val; 773128266Speter const char *watches; 774128266Speter const char *repository; 77517721Speter{ 77617721Speter static struct addremove_args blank; 77717721Speter struct addremove_args args; 77817721Speter char *watchers; 77917721Speter char *p; 78017721Speter char *endp; 78117721Speter char *nextp; 78217721Speter 78317721Speter /* Initialize fields to 0, NULL, or 0.0. */ 78417721Speter args = blank; 78517721Speter switch (type) 78617721Speter { 78717721Speter case 'E': 78854427Speter if (strpbrk (val, ",>;=\n") != NULL) 78954427Speter { 79054427Speter error (0, 0, "invalid character in editor value"); 79154427Speter return; 79254427Speter } 79317721Speter editor_set (filename, who, val); 79417721Speter break; 79517721Speter case 'U': 79617721Speter case 'C': 79717721Speter editor_set (filename, who, NULL); 79817721Speter break; 79917721Speter default: 80017721Speter return; 80117721Speter } 80217721Speter 80317721Speter watchers = fileattr_get0 (filename, "_watchers"); 80417721Speter p = watchers; 80517721Speter while (p != NULL) 80617721Speter { 80717721Speter char *q; 80817721Speter char *endq; 80917721Speter char *nextq; 81017721Speter char *notif; 81117721Speter 81217721Speter endp = strchr (p, '>'); 81317721Speter if (endp == NULL) 81417721Speter break; 81517721Speter nextp = strchr (p, ','); 81617721Speter 81717721Speter if ((size_t)(endp - p) == strlen (who) && strncmp (who, p, endp - p) == 0) 81817721Speter { 81917721Speter /* Don't notify user of their own changes. Would perhaps 82017721Speter be better to check whether it is the same working 82117721Speter directory, not the same user, but that is hairy. */ 82217721Speter p = nextp == NULL ? nextp : nextp + 1; 82317721Speter continue; 82417721Speter } 82517721Speter 82617721Speter /* Now we point q at a string which looks like 82717721Speter "edit+unedit+commit,"... and walk down it. */ 82817721Speter q = endp + 1; 82917721Speter notif = NULL; 83017721Speter while (q != NULL) 83117721Speter { 83217721Speter endq = strchr (q, '+'); 83317721Speter if (endq == NULL || (nextp != NULL && endq > nextp)) 83417721Speter { 83517721Speter if (nextp == NULL) 83617721Speter endq = q + strlen (q); 83717721Speter else 83817721Speter endq = nextp; 83917721Speter nextq = NULL; 84017721Speter } 84117721Speter else 84217721Speter nextq = endq + 1; 84317721Speter 84417721Speter /* If there is a temporary and a regular watch, send a single 84517721Speter notification, for the regular watch. */ 84617721Speter if (type == 'E' && endq - q == 4 && strncmp ("edit", q, 4) == 0) 84717721Speter { 84817721Speter notif = "edit"; 84917721Speter } 85017721Speter else if (type == 'U' 85117721Speter && endq - q == 6 && strncmp ("unedit", q, 6) == 0) 85217721Speter { 85317721Speter notif = "unedit"; 85417721Speter } 85517721Speter else if (type == 'C' 85617721Speter && endq - q == 6 && strncmp ("commit", q, 6) == 0) 85717721Speter { 85817721Speter notif = "commit"; 85917721Speter } 86017721Speter else if (type == 'E' 86117721Speter && endq - q == 5 && strncmp ("tedit", q, 5) == 0) 86217721Speter { 86317721Speter if (notif == NULL) 86417721Speter notif = "temporary edit"; 86517721Speter } 86617721Speter else if (type == 'U' 86717721Speter && endq - q == 7 && strncmp ("tunedit", q, 7) == 0) 86817721Speter { 86917721Speter if (notif == NULL) 87017721Speter notif = "temporary unedit"; 87117721Speter } 87217721Speter else if (type == 'C' 87317721Speter && endq - q == 7 && strncmp ("tcommit", q, 7) == 0) 87417721Speter { 87517721Speter if (notif == NULL) 87617721Speter notif = "temporary commit"; 87717721Speter } 87817721Speter q = nextq; 87917721Speter } 88017721Speter if (nextp != NULL) 88117721Speter ++nextp; 88217721Speter 88317721Speter if (notif != NULL) 88417721Speter { 88517721Speter struct notify_proc_args args; 88617721Speter size_t len = endp - p; 88717721Speter FILE *fp; 88817721Speter char *usersname; 88917721Speter char *line = NULL; 89017721Speter size_t line_len = 0; 89117721Speter 89217721Speter args.notifyee = NULL; 89381404Speter usersname = xmalloc (strlen (current_parsed_root->directory) 89417721Speter + sizeof CVSROOTADM 89517721Speter + sizeof CVSROOTADM_USERS 89617721Speter + 20); 89781404Speter strcpy (usersname, current_parsed_root->directory); 89817721Speter strcat (usersname, "/"); 89917721Speter strcat (usersname, CVSROOTADM); 90017721Speter strcat (usersname, "/"); 90117721Speter strcat (usersname, CVSROOTADM_USERS); 90225839Speter fp = CVS_FOPEN (usersname, "r"); 90317721Speter if (fp == NULL && !existence_error (errno)) 90417721Speter error (0, errno, "cannot read %s", usersname); 90517721Speter if (fp != NULL) 90617721Speter { 90717721Speter while (getline (&line, &line_len, fp) >= 0) 90817721Speter { 90917721Speter if (strncmp (line, p, len) == 0 91017721Speter && line[len] == ':') 91117721Speter { 91217721Speter char *cp; 91317721Speter args.notifyee = xstrdup (line + len + 1); 91454427Speter 91554427Speter /* There may or may not be more 91654427Speter colon-separated fields added to this in the 91754427Speter future; in any case, we ignore them right 91854427Speter now, and if there are none we make sure to 91954427Speter chop off the final newline, if any. */ 92054427Speter cp = strpbrk (args.notifyee, ":\n"); 92154427Speter 92217721Speter if (cp != NULL) 92317721Speter *cp = '\0'; 92417721Speter break; 92517721Speter } 92617721Speter } 92717721Speter if (ferror (fp)) 92817721Speter error (0, errno, "cannot read %s", usersname); 92917721Speter if (fclose (fp) < 0) 93017721Speter error (0, errno, "cannot close %s", usersname); 93117721Speter } 93217721Speter free (usersname); 93354427Speter if (line != NULL) 93454427Speter free (line); 93517721Speter 93617721Speter if (args.notifyee == NULL) 93717721Speter { 938128266Speter char *tmp; 939128266Speter tmp = xmalloc (endp - p + 1); 940128266Speter strncpy (tmp, p, endp - p); 941128266Speter tmp[endp - p] = '\0'; 942128266Speter args.notifyee = tmp; 94317721Speter } 94417721Speter 94517721Speter notify_args = &args; 94617721Speter args.type = notif; 94717721Speter args.who = who; 94817721Speter args.file = filename; 94917721Speter 95017721Speter (void) Parse_Info (CVSROOTADM_NOTIFY, repository, notify_proc, 1); 951128266Speter 952128266Speter /* It's okay to cast out the const for the free() below since we 953128266Speter * just allocated this a few lines above. The const was for 954128266Speter * everybody else. 955128266Speter */ 956128266Speter free ((char *)args.notifyee); 95717721Speter } 95817721Speter 95917721Speter p = nextp; 96017721Speter } 96117721Speter if (watchers != NULL) 96217721Speter free (watchers); 96317721Speter 96417721Speter switch (type) 96517721Speter { 96617721Speter case 'E': 96717721Speter if (*watches == 'E') 96817721Speter { 96917721Speter args.add_tedit = 1; 97017721Speter ++watches; 97117721Speter } 97217721Speter if (*watches == 'U') 97317721Speter { 97417721Speter args.add_tunedit = 1; 97517721Speter ++watches; 97617721Speter } 97717721Speter if (*watches == 'C') 97817721Speter { 97917721Speter args.add_tcommit = 1; 98017721Speter } 98117721Speter watch_modify_watchers (filename, &args); 98217721Speter break; 98317721Speter case 'U': 98417721Speter case 'C': 98517721Speter args.remove_temp = 1; 98617721Speter watch_modify_watchers (filename, &args); 98717721Speter break; 98817721Speter } 98917721Speter} 99017721Speter 99117721Speter#ifdef CLIENT_SUPPORT 99217721Speter/* Check and send notifications. This is only for the client. */ 99317721Spetervoid 994177391Sobriencvs_notify_check (repository, update_dir) 995128266Speter const char *repository; 996128266Speter const char *update_dir; 99717721Speter{ 99817721Speter FILE *fp; 99917721Speter char *line = NULL; 100017721Speter size_t line_len = 0; 100117721Speter 100217721Speter if (! server_started) 100317721Speter /* We are in the midst of a command which is not to talk to 100417721Speter the server (e.g. the first phase of a cvs edit). Just chill 100517721Speter out, we'll catch the notifications on the flip side. */ 100617721Speter return; 100717721Speter 100817721Speter /* We send notifications even if noexec. I'm not sure which behavior 100917721Speter is most sensible. */ 101017721Speter 101125839Speter fp = CVS_FOPEN (CVSADM_NOTIFY, "r"); 101217721Speter if (fp == NULL) 101317721Speter { 101417721Speter if (!existence_error (errno)) 101517721Speter error (0, errno, "cannot open %s", CVSADM_NOTIFY); 101617721Speter return; 101717721Speter } 101817721Speter while (getline (&line, &line_len, fp) > 0) 101917721Speter { 102017721Speter int notif_type; 102117721Speter char *filename; 102217721Speter char *val; 102317721Speter char *cp; 102417721Speter 102517721Speter notif_type = line[0]; 102617721Speter if (notif_type == '\0') 102717721Speter continue; 102817721Speter filename = line + 1; 102917721Speter cp = strchr (filename, '\t'); 103017721Speter if (cp == NULL) 103117721Speter continue; 103217721Speter *cp++ = '\0'; 103317721Speter val = cp; 103417721Speter 103517721Speter client_notify (repository, update_dir, filename, notif_type, val); 103617721Speter } 103725839Speter if (line) 103825839Speter free (line); 103917721Speter if (ferror (fp)) 104017721Speter error (0, errno, "cannot read %s", CVSADM_NOTIFY); 104117721Speter if (fclose (fp) < 0) 104217721Speter error (0, errno, "cannot close %s", CVSADM_NOTIFY); 104317721Speter 104417721Speter /* Leave the CVSADM_NOTIFY file there, until the server tells us it 104517721Speter has dealt with it. */ 104617721Speter} 104717721Speter#endif /* CLIENT_SUPPORT */ 104817721Speter 104917721Speter 105017721Speterstatic const char *const editors_usage[] = 105117721Speter{ 1052175261Sobrien "Usage: %s %s [-lR] [<file>]...\n", 1053175261Sobrien "-l\tProcess this directory only (not recursive).\n", 1054175261Sobrien "-R\tProcess directories recursively (default).\n", 1055175261Sobrien "(Specify the --help global option for a list of other help options.)\n", 105617721Speter NULL 105717721Speter}; 105817721Speter 105925839Speterstatic int editors_fileproc PROTO ((void *callerdat, struct file_info *finfo)); 106017721Speter 106117721Speterstatic int 106225839Spetereditors_fileproc (callerdat, finfo) 106325839Speter void *callerdat; 106417721Speter struct file_info *finfo; 106517721Speter{ 106617721Speter char *them; 106717721Speter char *p; 106817721Speter 106917721Speter them = fileattr_get0 (finfo->file, "_editors"); 107017721Speter if (them == NULL) 107117721Speter return 0; 107217721Speter 107354427Speter cvs_output (finfo->fullname, 0); 107417721Speter 107517721Speter p = them; 107617721Speter while (1) 107717721Speter { 107854427Speter cvs_output ("\t", 1); 107917721Speter while (*p != '>' && *p != '\0') 108054427Speter cvs_output (p++, 1); 108117721Speter if (*p == '\0') 108217721Speter { 108317721Speter /* Only happens if attribute is misformed. */ 108454427Speter cvs_output ("\n", 1); 108517721Speter break; 108617721Speter } 108717721Speter ++p; 108854427Speter cvs_output ("\t", 1); 108917721Speter while (1) 109017721Speter { 109117721Speter while (*p != '+' && *p != ',' && *p != '\0') 109254427Speter cvs_output (p++, 1); 109317721Speter if (*p == '\0') 109417721Speter { 109554427Speter cvs_output ("\n", 1); 109617721Speter goto out; 109717721Speter } 109817721Speter if (*p == ',') 109917721Speter { 110017721Speter ++p; 110117721Speter break; 110217721Speter } 110317721Speter ++p; 110454427Speter cvs_output ("\t", 1); 110517721Speter } 110654427Speter cvs_output ("\n", 1); 110717721Speter } 110817721Speter out:; 110966525Speter free (them); 111017721Speter return 0; 111117721Speter} 111217721Speter 111317721Speterint 111417721Spetereditors (argc, argv) 111517721Speter int argc; 111617721Speter char **argv; 111717721Speter{ 111817721Speter int local = 0; 111917721Speter int c; 112017721Speter 112117721Speter if (argc == -1) 112217721Speter usage (editors_usage); 112317721Speter 112426065Speter optind = 0; 112525839Speter while ((c = getopt (argc, argv, "+lR")) != -1) 112617721Speter { 112717721Speter switch (c) 112817721Speter { 112917721Speter case 'l': 113017721Speter local = 1; 113117721Speter break; 113225839Speter case 'R': 113325839Speter local = 0; 113425839Speter break; 113517721Speter case '?': 113617721Speter default: 113717721Speter usage (editors_usage); 113817721Speter break; 113917721Speter } 114017721Speter } 114117721Speter argc -= optind; 114217721Speter argv += optind; 114317721Speter 114417721Speter#ifdef CLIENT_SUPPORT 114581404Speter if (current_parsed_root->isremote) 114617721Speter { 114717721Speter start_server (); 114817721Speter ign_setup (); 114917721Speter 115017721Speter if (local) 115117721Speter send_arg ("-l"); 1152107484Speter send_arg ("--"); 115354427Speter send_files (argc, argv, local, 0, SEND_NO_CONTENTS); 115417721Speter send_file_names (argc, argv, SEND_EXPAND_WILD); 115517721Speter send_to_server ("editors\012", 0); 115617721Speter return get_responses_and_close (); 115717721Speter } 115817721Speter#endif /* CLIENT_SUPPORT */ 115917721Speter 116017721Speter return start_recursion (editors_fileproc, (FILESDONEPROC) NULL, 116125839Speter (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL, 1162128266Speter argc, argv, local, W_LOCAL, 0, 1, (char *) NULL, 1163128266Speter 0, (char *) NULL); 116417721Speter} 1165