diff options
author | Sergey Poznyakoff <gray@gnu.org.ua> | 2005-07-31 19:01:14 +0000 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org.ua> | 2005-07-31 19:01:14 +0000 |
commit | 1be8e172dee8ab5e5e6129e31140722ea8ba0f85 (patch) | |
tree | 50b3eb353e2b5955213730223836a053a034e0d7 /cvs/sv_sync_www.c | |
parent | a7f33bff55cd5c7f995412fcb51133b06c92f669 (diff) | |
download | gsc-1be8e172dee8ab5e5e6129e31140722ea8ba0f85.tar.gz gsc-1be8e172dee8ab5e5e6129e31140722ea8ba0f85.tar.bz2 |
Added to the repository
git-svn-id: file:///svnroot/gsc/trunk@58 d2de0444-eb31-0410-8365-af798a554d48
Diffstat (limited to 'cvs/sv_sync_www.c')
-rw-r--r-- | cvs/sv_sync_www.c | 435 |
1 files changed, 435 insertions, 0 deletions
diff --git a/cvs/sv_sync_www.c b/cvs/sv_sync_www.c new file mode 100644 index 0000000..c046800 --- /dev/null +++ b/cvs/sv_sync_www.c @@ -0,0 +1,435 @@ +/* + + Copyright (C) Gordon Matzigkeit <gord@fig.org>, Loic Dachary + <loic@gnu.org>, 2001 + Copyright (C) 2005, Sergey Poznyakoff + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + MA 02110-1301, USA. + +*/ + +/* sf_sync_www.c - Allow unprivileged users to do a web update on www.gnu.org + + In CVSROOT/loginfo file + ALL /bin/sv_sync_www_schedule ${USER} %s + + In crontab (remove the backslash before /): + 0 *\/1 * * * /bin/sv_sync_www_flush + + Or, to synchronize right after commit, in CVSROOT/loginfo: + + ALL (date; cat; (sleep 2; /bin/sf_sync_www %{s} ) &\ ) >> /var/log/sf_sync_www 2>&1 + + For test purpose: + ./sf_sync_www -n ' bla bla' + ./sf_sync_www -n 'foo/bar bla bla' + ./sf_sync_www -n 'f'\''oo bla bla' + ./sf_sync_www -n 'f'\''oo - New directory' + ./sf_sync_www -n 'bla - Imported sources' + + The idea is that it runs a cvs update -l (to prevent recursion) in the + directory where the commit was done. Since the command will be called + once for each directory where a commit did some action there is no + need for recursion. In the case of an import command this does not + hold and a recursion must always be done since there is only one + call to the script for a whole imported tree (this happens when the + argument contains the Imported source string). + + After running update, the program looks for file ".symlinks" and executes + it, when present. File format is: empty lines and lines started with ';' + or '#' are ignored. Rest of lines are split in two words: first word is + the name of an existing file, second one is the link name. Both names + should be in the current directory. Care is taken to prevent linking to + upper-level directories. + + The %{s} argument in loginfo invocation is a single argument that lists + the directory and all the files involved. As a special case if the + directory was added the file list is replaced by '- New directory'. This + is lame since adding the files -, New and directory will produce the same + effect, but it's unlikely. The same applies when a whole source tree is + imported using cvs import in which case the file list is replaced by + '- Imported sources'. + + There are three cases to take in account (topdir is the absolute path + of the directory in which the CVS tree was extracted, subdirectory is + the directory given in argument): + - commit that modify the top level directory files + cd topdir ; cvs update -l + - commit that adds a new directory or that import a whole source tree + cd topdir ; cvs update 'subdirectory' + - commit that modify files in a subdirectory + cd topdir/subdirectory ; cvs update -l + + In order to prevent security compromise the directory name is quoted. + + Originaly by + Gordon Matzigkeit <gord@fig.org>, 2000-11-28 + + Update CVS_COMMANDS to reduce noise + Loic Dachary <loic@gnu.org>, 2001-02-26 + + Modify to allow generic call from loginfo file in an efficient way + Loic Dachary <loic@gnu.org>, 2001-03-10 + + Introduce command line options, make program configurable and + implement .symlinks functionality + Sergey Poznyakoff <gray@gnu.org.ua>, 2005-07-31 + +*/ + +#include <sys/types.h> +#include <ctype.h> +#include <string.h> +#include <stdio.h> +#include <sys/wait.h> + +#include <error.h> +#include <errno.h> + +#include <sys/stat.h> +#include <unistd.h> +#include <stdlib.h> +#include <pwd.h> + +/* + * Return a malloc'ed copy of the string argument with all ' + * substituted by '\'' and a trailing and leading '. + * For instance : foo -> 'foo' + * fib'ou -> 'fib'\''ou' + */ +char * +quote (char *string) +{ + int i; + int length; + int count = 0; + char *out; + + if (!string) + return NULL; + + length = strlen (string); + for (i = 0; i < length; i++) + if (string[i] == '\'') + count++; + + /* +1 for null at end, (count * 3) for '\'', +2 for leading ' and + trailing ' */ + out = (char *) malloc (length + 1 + count * 3 + 2); + if (count > 0) + { + char *from_p = string; + char *to_p = out; + *to_p++ = '\''; + while (*from_p) + { + if (*from_p == '\'') + { + strcpy (to_p, "'\\''"); + to_p += 4; + from_p++; + } + else + *to_p++ = *from_p++; + } + *to_p++ = '\''; + *to_p = '\0'; + } + else + sprintf (out, "'%s'", string); + return out; +} + +char *user; +char *rsh_program = "/usr/bin/ssh"; +char *hostname; +char *repository = "/webcvs"; +char *www_directory = "/home/puszcza/software"; +char *cvs_command = "CVS_RSH=ssh cvs -q -z3"; +int dry_run = 0; +int debug = 0; + +void +usage () +{ + printf ("sv_sync_www [OPTIONS] directory\n\n"); + printf ("OPTIONS are:\n"); + printf (" -H hostname Set hostname or IP address of the host where WWW directory\n"); + printf (" resides (default: none, WWW and CVS reside on the same host)\n"); + printf (" -r rsh_program Set full name of RSH program\n"); + printf (" (default: %s)\n", rsh_program); + printf (" -R repository Set full name of the CVS repository\n"); + printf (" (default: %s)\n", repository); + printf (" -w www_directory Set full name of the WWW directory\n"); + printf (" (default: %s)\n", www_directory); + printf (" -n Dry run: do nothing, only print what would have been done\n"); + printf (" -h Display this help summary\n"); +} + +static int +run_cmd (char *cmd) +{ + pid_t pid; + int i; + char *xargv[6]; + int xargc = 0; + char *new_envp[] = { NULL }; + char *p; + char *program; + + pid = fork (); + if (pid < 0) + error (1, errno, "cannot fork"); + + if (pid > 0) /* Master process */ + { + int status; + waitpid (pid, &status, 0); + if (WIFEXITED (status)) + { + status = WEXITSTATUS (status); + if (status) + error (0, 0, "subprocess returned exit code %d", + status); + return status; + } + else if (WIFSIGNALED (status)) + { + error (0, 0, "subprocess terminated on signal %d", + WTERMSIG (status)); + return -1; + } + else + { + error (0, 0, "subprocess terminated"); + return -1; + } + } + + if (hostname) + { + program = dry_run ? "/bin/echo" : rsh_program; + xargv[xargc++] = rsh_program; + xargv[xargc++] = hostname; + } + else + { + if (dry_run) + { + program = "/bin/echo"; + xargv[xargc++] = "echo"; + } + else + { + program = "/bin/sh"; + xargv[xargc++] = "-sh"; + xargv[xargc++] = "-c"; + if (debug) + xargv[xargc++] = "-x"; + } + } + + xargv[xargc++] = cmd; + xargv[xargc++] = NULL; + + /* Pick out the name of the program. */ + p = strrchr (xargv[0], '/'); + if (p) + xargv[0] = p; + + execve (program, xargv, new_envp); + error (1, errno, "Cannot exec `%s'", program); +} + +int +main (int argc, char **argv) +{ + int i; + char *unquoted_directory = 0; + char *directory = 0; + int files = 0; + char *cmd; + char *tmp; + uid_t uid; + gid_t gid; + + while ((i = getopt (argc, argv, "hH:nr:R:u:w:x")) != EOF) + switch (i) + { + case 'H': + hostname = optarg; + break; + + case 'n': + dry_run = 1; + break; + + case 'r': + rsh_program = optarg; + break; + + case 'R': + repository = optarg; + break; + + case 'u': + user = optarg; + break; + + case 'h': + usage (); + exit (0); + + case 'w': + www_directory = optarg; + break; + + case 'x': + debug = 1; + break; + + default: + exit (1); + } + + if (debug) + { + fprintf (stderr, "Invocation:\n"); + for (i = 0; i < argc; i++) + fprintf (stderr, "%s\n", argv[i]); + fprintf (stderr, "\n"); + } + + argc -= optind; + argv += optind; + + if (user) + { + struct passwd *pw = getpwnam (user); + if (!pw) + error (1, errno, "cannot get password entry for user %s", user); + uid = pw->pw_uid; + gid = pw->pw_gid; + } + else + { + gid = getegid (); + uid = geteuid (); + } + + if (setregid (gid, gid)) + error (1, errno, "cannot assume effective group id"); + + if (setreuid (uid, uid)) + error (1, errno, "cannot assume effective user id"); + + if (argc == 1) + { + tmp = argv[0]; + /* + * First comes the relative directory name or empty string if current. + * directory = 0 if no subdirectory + * directory = relative path if subdirectory + */ + unquoted_directory = tmp; + if ((tmp = strchr (tmp, ' '))) + { + *tmp = '\0'; + if (tmp - unquoted_directory <= 0) + unquoted_directory = 0; + tmp++; + } + else + error (1, 0, "Failed to find directory in first argument: `%s'", + argv[0]); + } + else if (argc == 2) + { + unquoted_directory = argv[0]; + tmp = argv[1]; + } + else + error (1, 0, "You must specify one or two arguments"); + + directory = quote (unquoted_directory); + + if (!strcmp (tmp, "- New directory") + || !strcmp (tmp, "- Imported sources")) + { + /* + * If this is a new directory creation or an import command, + * there are no files to collect in the argument list. + */ + } + else + { + /* + * There are files to collect in the argument list. + */ + files = 1; + } + + /* Check that the local directory exists. */ + if (!dry_run && unquoted_directory) + { + struct stat stbuf; + char *localdir; + + localdir = malloc (i + 1 + strlen (unquoted_directory) + 1); + if (!localdir) + error (1, 0, "not enough memory"); + + i = strlen (repository); + + strcpy (localdir, repository); + if (localdir[i - 1] != '/') + localdir[i++] = '/'; + strcpy (localdir + i, unquoted_directory); + + /* Check to see that the local unquoted_directory actually exists. */ + if (stat (localdir, &stbuf) || + (!S_ISDIR (stbuf.st_mode) && (errno = ENOTDIR))) + error (1, errno, "Cannot find `%s'", localdir); + } + + /* Update from CVS */ + if (directory && !files) + asprintf (&cmd, "cd %s && ( %s -d %s/%s checkout %s )", + www_directory, cvs_command, repository, directory, directory); + else if (!directory && files) + asprintf (&cmd, "cd %s && ( %s update -l )", + www_directory, cvs_command); + else if (directory && files) + asprintf (&cmd, "cd %s/%s && ( %s update -l )", + www_directory, directory, cvs_command); + else + error (1, 0, "Unexpected null directory and files"); + + if (run_cmd (cmd)) + return 1; + + free (cmd); + + /* Restore symlinks */ + asprintf (&cmd, + "cd %s/%s && test -r .symlinks && " + "(grep -v '^[ \t]*[;#]' .symlinks | " + "sed 's,^/,_,;s,\\.\\./,__/,g' |" + "while read S T; do ln -sf $S $T; done)", + www_directory, directory); + + return run_cmd (cmd); +} |