/* * callback proc to run a script when admin finishes. */ static int postadmin_proc (const char *repository, const char *filter, void *closure) { char *cmdline; const char *srepos = Short_Repository (repository); TRACE (TRACE_FUNCTION, "postadmin_proc (%s, %s)", repository, filter); /* %c = cvs_cmd_name * %R = referrer * %p = shortrepos * %r = repository */ /* * Cast any NULL arguments as appropriate pointers as this is an * stdarg function and we need to be certain the caller gets what * is expected. */ cmdline = format_cmdline ( #ifdef SUPPORT_OLD_INFO_FMT_STRINGS false, srepos, #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */ filter, "c", "s", cvs_cmd_name, #ifdef SERVER_SUPPORT "R", "s", referrer ? referrer->original : "NONE", #endif /* SERVER_SUPPORT */ "p", "s", srepos, "r", "s", current_parsed_root->directory, (char *) NULL); if (!cmdline || !strlen (cmdline)) { if (cmdline) free (cmdline); error (0, 0, "postadmin proc resolved to the empty string!"); return 1; } run_setup (cmdline); free (cmdline); /* FIXME - read the comment in verifymsg_proc() about why we use abs() * below() and shouldn't. */ return abs (run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL | RUN_SIGIGNORE)); }
/* * Uses setup_tmpfile() to pass the updated message on directly to any * logfile programs that have a regular expression match for the checked in * directory in the source repository. The log information is fed into the * specified program as standard input. */ void Update_Logfile (const char *repository, const char *xmessage, FILE *xlogfp, List *xchanges, const char *xbugid) { /* nothing to do if the list is empty */ if (xchanges == NULL || xchanges->list->next == xchanges->list) return; loginfo_param_t args; args.message = xmessage; args.logfp=xlogfp; args.changes=xchanges; args.directory=Short_Repository(repository); /* call Parse_Info to do the actual logfile updates */ TRACE(3,"run loginfo trigger"); run_trigger(&args, update_logfile_proc); }
/* An empty LogHistory string in CVSROOT/config will turn logging off. */ void history_write (int type, const char *update_dir, const char *revs, const char *name, const char *repository) { const char *fname; char *workdir; char *username = getcaller (); int fd; char *line; char *slash = "", *cp; const char *cp2, *repos; int i; static char *tilde = ""; static char *PrCurDir = NULL; time_t now; if (logoff) /* History is turned off by noexec or * readonlyfs. */ return; if (!strchr (config->logHistory, type)) return; if (nolock) goto out; repos = Short_Repository (repository); if (!PrCurDir) { char *pwdir; pwdir = get_homedir (); PrCurDir = CurDir; if (pwdir != NULL) { /* Assumes neither CurDir nor pwdir ends in '/' */ i = strlen (pwdir); if (!strncmp (CurDir, pwdir, i)) { PrCurDir += i; /* Point to '/' separator */ tilde = "~"; } else { /* Try harder to find a "homedir" */ struct saved_cwd cwd; char *homedir; if (save_cwd (&cwd)) error (1, errno, "Failed to save current directory."); if (CVS_CHDIR (pwdir) < 0 || (homedir = xgetcwd ()) == NULL) homedir = pwdir; if (restore_cwd (&cwd)) error (1, errno, "Failed to restore current directory, `%s'.", cwd.name); free_cwd (&cwd); i = strlen (homedir); if (!strncmp (CurDir, homedir, i)) { PrCurDir += i; /* Point to '/' separator */ tilde = "~"; } if (homedir != pwdir) free (homedir); } } } if (type == 'T') { repos = update_dir; update_dir = ""; } else if (update_dir && *update_dir) slash = "/"; else update_dir = ""; workdir = Xasprintf ("%s%s%s%s", tilde, PrCurDir, slash, update_dir); /* * "workdir" is the directory where the file "name" is. ("^~" == $HOME) * "repos" is the Repository, relative to $CVSROOT where the RCS file is. * * "$workdir/$name" is the working file name. * "$CVSROOT/$repos/$name,v" is the RCS file in the Repository. * * First, note that the history format was intended to save space, not * to be human readable. * * The working file directory ("workdir") and the Repository ("repos") * usually end with the same one or more directory elements. To avoid * duplication (and save space), the "workdir" field ends with * an integer offset into the "repos" field. This offset indicates the * beginning of the "tail" of "repos", after which all characters are * duplicates. * * In other words, if the "workdir" field has a '*' (a very stupid thing * to put in a filename) in it, then every thing following the last '*' * is a hex offset into "repos" of the first character from "repos" to * append to "workdir" to finish the pathname. * * It might be easier to look at an example: * * M273b3463|dgg|~/work*9|usr/local/cvs/examples|1.2|loginfo * * Indicates that the workdir is really "~/work/cvs/examples", saving * 10 characters, where "~/work*d" would save 6 characters and mean that * the workdir is really "~/work/examples". It will mean more on * directories like: usr/local/gnu/emacs/dist-19.17/lisp/term * * "workdir" is always an absolute pathname (~/xxx is an absolute path) * "repos" is always a relative pathname. So we can assume that we will * never run into the top of "workdir" -- there will always be a '/' or * a '~' at the head of "workdir" that is not matched by anything in * "repos". On the other hand, we *can* run off the top of "repos". * * Only "compress" if we save characters. */ cp = workdir + strlen (workdir) - 1; cp2 = repos + strlen (repos) - 1; for (i = 0; cp2 >= repos && cp > workdir && *cp == *cp2--; cp--) i++; if (i > 2) { i = strlen (repos) - i; (void) sprintf ((cp + 1), "*%x", i); } if (!revs) revs = ""; now = time (NULL); line = Xasprintf ("%c%08lx|%s|%s|%s|%s|%s\n", type, (long) now, username, workdir, repos, revs, name); fname = get_history_log_name (now); if (!history_lock (current_parsed_root->directory)) /* history_lock() will already have printed an error on failure. */ goto out; fd = CVS_OPEN (fname, O_WRONLY | O_APPEND | O_CREAT | OPEN_BINARY, 0666); if (fd < 0) { if (!really_quiet) error (0, errno, "warning: cannot open history file `%s' for write", fname); goto out; } TRACE (TRACE_FUNCTION, "open (`%s', a)", fname); /* Lessen some race conditions on non-Posix-compliant hosts. * * FIXME: I'm guessing the following was necessary for NFS when multiple * simultaneous writes to the same file are possible, since NFS does not * natively support append mode and it must be emulated via lseek(). Now * that the history file is locked for write, the following lseek() may be * unnecessary. */ if (lseek (fd, (off_t) 0, SEEK_END) == -1) error (1, errno, "cannot seek to end of history file: %s", fname); if (write (fd, line, strlen (line)) < 0) error (1, errno, "cannot write to history file: %s", fname); free (line); if (close (fd) != 0) error (1, errno, "cannot close history file: %s", fname); free (workdir); out: clear_history_lock (); }
/* Runs the user-defined verification script as part of the commit or import process. This verification is meant to be run whether or not the user included the -m atribute. unlike the do_editor function, this is independant of the running of an editor for getting a message. */ int do_verify (char **message, const char *repository) { FILE *fp; char *fname = NULL; int retcode = 0; int len; char boughtsuite[100]; if (current_parsed_root->isremote) /* The verification will happen on the server. */ return 0; /* FIXME? Do we really want to skip this on noexec? What do we do for the other administrative files? */ if (noexec) return 0; /* If there's no message, then we have nothing to verify. Can this case happen? And if so why would we print a message? */ if (message == NULL) { cvs_output ("No message to verify\n", 0); return 0; } /* open a temporary file, write the message to the temp file, and close the file. */ if ((fp = cvs_temp_file (&fname)) == NULL) error (1, errno, "cannot create temporary file %s", fname); else { fprintf (fp, "%s", *message); if ((*message)[0] == '\0' || (*message)[strlen (*message) - 1] != '\n') fprintf (fp, "%s", "\n"); if (fclose (fp) == EOF) error (1, errno, "%s", fname); /* Run the verify message */ if (repository != NULL) { verifymsg_param_t args; args.directory = Short_Repository(repository); args.filename = fname; TRACE(3,"run verifymsg trigger"); retcode = run_trigger(&args,verifymsg_proc); if(!retcode && reread_log_after_verify) { if((fp=fopen(fname,"r"))==NULL) error(1, errno, "Couldn't reread message file"); fseek(fp,0,SEEK_END); len = ftell(fp); fseek(fp,0,SEEK_SET); *message = (char*)xmalloc(len+1); if(!*message) error(1,errno,"Out of memory rereading message"); len = fread(*message, 1, len, fp); if(len < 0) error(1, errno, "Couldn't reread message file"); (*message)[len]='\0'; if (fclose (fp) == EOF) error (1, errno, "Couldn't close %s", fname); } #ifdef _WIN32 #if (CVSNT_SPECIAL_BUILD_FLAG != 0) if (strcasecmp(CVSNT_SPECIAL_BUILD,"Suite")!=0) #endif { if (message!=NULL) { if (strstr(*message,"Committed on the Free edition of March Hare Software CVSNT")==NULL) { if(CGlobalSettings::GetGlobalValue("cvsnt","PServer","HaveBoughtSuite",boughtsuite,sizeof(boughtsuite))) strcpy(boughtsuite,"no"); if (strcasecmp(boughtsuite,"yes")) { len=strlen(*message); *message=(char *)xrealloc(*message,len+400); strcat(*message,"\nCommitted on the Free edition of March Hare Software CVSNT Server.\nUpgrade to CVS Suite for more features and support:\nhttp://march-hare.com/cvsnt/"); } } } } #endif } /* Delete the temp file */ if (unlink_file (fname) < 0) error (0, errno, "cannot remove %s", fname); xfree (fname); } return retcode; }
/* * Builds a temporary file using setup_tmpfile() and invokes the user's * editor on the file. The header garbage in the resultant file is then * stripped and the log message is stored in the "message" argument. * * If REPOSITORY is non-NULL, process rcsinfo for that repository; if it * is NULL, use the CVSADM_TEMPLATE file instead. */ void do_editor (const char *dir, char **messagep, const char *repository, List *changes) { static int reuse_log_message = 0; char *line; int line_length; size_t line_chars_allocated; char *fname; struct stat pre_stbuf, post_stbuf; int retcode = 0; if (noexec || reuse_log_message) return; /* Abort creation of temp file if no editor is defined */ if (strcmp (Editor, "") == 0) error(1, 0, "no editor defined, must use -e or -m"); /* Create a temporary file */ /* FIXME - It's possible we should be relying on cvs_temp_file to open * the file here - we get race conditions otherwise. */ fname = cvs_temp_name (); again: if ((fp = CVS_FOPEN (fname, "w+")) == NULL) error (1, 0, "cannot create temporary file %s", fname); if (*messagep) { fprintf (fp, "%s", *messagep); if ((*messagep)[0] == '\0' || (*messagep)[strlen (*messagep) - 1] != '\n') fprintf (fp, "\n"); } if (repository != NULL) { rcsinfo_param_t args; args.directory = Short_Repository(repository); args.message=NULL; /* tack templates on if necessary */ TRACE(3,"run rcsinfo trigger"); if(!run_trigger(&args,rcsinfo_proc) && args.message) { fprintf(fp,"%s",args.message); if (args.message[0] == '\0' || args.message[strlen(args.message) - 1] != '\n') fprintf (fp, "\n"); } } else { FILE *tfp; char buf[1024]; size_t n; size_t nwrite; /* Why "b"? */ tfp = CVS_FOPEN (CVSADM_TEMPLATE, "rb"); if (tfp == NULL) { if (!existence_error (errno)) error (1, errno, "cannot read %s", CVSADM_TEMPLATE); } else { while (!feof (tfp)) { char *p = buf; n = fread (buf, 1, sizeof buf, tfp); nwrite = n; while (nwrite > 0) { n = fwrite (p, 1, nwrite, fp); nwrite -= n; p += n; } if (ferror (tfp)) error (1, errno, "cannot read %s", CVSADM_TEMPLATE); } if (fclose (tfp) < 0) error (0, errno, "cannot close %s", CVSADM_TEMPLATE); } } fprintf (fp,"%s----------------------------------------------------------------------\n",CVSEDITPREFIX); #ifdef _WIN32 #if (CVSNT_SPECIAL_BUILD_FLAG != 0) if (strcasecmp(CVSNT_SPECIAL_BUILD,"Suite")!=0) #endif { fprintf (fp,"%s Committed on the Free edition of March Hare Software CVSNT Server\n",CVSEDITPREFIX); fprintf (fp,"%s Upgrade to CVS Suite for more features and support:\n",CVSEDITPREFIX); fprintf (fp,"%s http://march-hare.com/cvsnt/\n",CVSEDITPREFIX); fprintf (fp,"%s----------------------------------------------------------------------\n",CVSEDITPREFIX); } #endif fprintf (fp,"%sEnter Log. Lines beginning with `%.*s' are removed automatically\n%s\n", CVSEDITPREFIX, CVSEDITPREFIXLEN, CVSEDITPREFIX,CVSEDITPREFIX); if (dir != NULL && *dir) fprintf (fp, "%sCommitting in %s\n%s\n", CVSEDITPREFIX, dir, CVSEDITPREFIX); if (changes != NULL) setup_tmpfile (fp, CVSEDITPREFIX, changes); fprintf (fp,"%s----------------------------------------------------------------------\n", CVSEDITPREFIX); /* finish off the temp file */ if (fclose (fp) == EOF) error (1, errno, "%s", fname); if ( CVS_STAT (fname, &pre_stbuf) == -1) pre_stbuf.st_mtime = 0; /* run the editor */ run_setup (Editor); run_arg (fname); if ((retcode = run_exec (true)) != 0) error (0, retcode == -1 ? errno : 0, "warning: editor session failed"); /* put the entire message back into the *messagep variable */ fp = open_file (fname, "r"); if (*messagep) xfree (*messagep); if ( CVS_STAT (fname, &post_stbuf) != 0) error (1, errno, "cannot find size of temp file %s", fname); if (post_stbuf.st_size == 0) *messagep = NULL; else { /* On NT, we might read less than st_size bytes, but we won't read more. So this works. */ *messagep = (char *) xmalloc (post_stbuf.st_size + 1); *messagep[0] = '\0'; } line = NULL; line_chars_allocated = 0; if (*messagep) { size_t message_len = post_stbuf.st_size + 1; size_t offset = 0; while (1) { line_length = getline (&line, &line_chars_allocated, fp); if (line_length == -1) { if (ferror (fp)) error (0, errno, "warning: cannot read %s", fname); break; } if (strncmp (line, CVSEDITPREFIX, CVSEDITPREFIXLEN) == 0) continue; if (offset + line_length >= message_len) expand_string (messagep, &message_len, offset + line_length + 1); strcpy (*messagep + offset, line); offset += line_length; } } if (fclose (fp) < 0) error (0, errno, "warning: cannot close %s", fname); if (pre_stbuf.st_mtime == post_stbuf.st_mtime || *messagep == NULL || strcmp (*messagep, "\n") == 0) { for (;;) { printf ("\nLog message unchanged or not specified\n"); printf ("a)bort, c)ontinue, e)dit, !)reuse this message unchanged for remaining dirs\n"); printf ("Action: (continue) "); fflush (stderr); fflush (stdout); line_length = getline (&line, &line_chars_allocated, stdin); if (line_length < 0) { error (0, errno, "cannot read from stdin"); if (unlink_file (fname) < 0) error (0, errno, "warning: cannot remove temp file %s", fname); error (1, 0, "aborting"); } else if (line_length == 0 || *line == '\n' || *line == 'c' || *line == 'C') break; if (*line == 'a' || *line == 'A') { if (unlink_file (fname) < 0) error (0, errno, "warning: cannot remove temp file %s", fname); error (1, 0, "aborted by user"); } if (*line == 'e' || *line == 'E') goto again; if (*line == '!') { reuse_log_message = 1; break; } printf ("Unknown input\n"); } } if (line) xfree (line); if (unlink_file (fname) < 0) error (0, errno, "warning: cannot remove temp file %s", fname); xfree (fname); }