diff options
author | Sergey Poznyakoff <gray@gnu.org.ua> | 2006-02-09 16:37:31 +0000 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org.ua> | 2006-02-09 16:37:31 +0000 |
commit | c055a4be5a74b9fb3dc4ab17f921c545988b84e8 (patch) | |
tree | 4c58d4e08ed821a701d1117f0f09f3035a457bb3 /cvs/sv_sync_www.c | |
parent | fbb6f17b55d7c0babb528a75c747f4280d75e264 (diff) | |
download | gsc-c055a4be5a74b9fb3dc4ab17f921c545988b84e8.tar.gz gsc-c055a4be5a74b9fb3dc4ab17f921c545988b84e8.tar.bz2 |
Rewritten to incorporate the functionality of
sv_sync_www_flush and to make updates more safe.
git-svn-id: file:///svnroot/gsc/trunk@181 d2de0444-eb31-0410-8365-af798a554d48
Diffstat (limited to 'cvs/sv_sync_www.c')
-rw-r--r-- | cvs/sv_sync_www.c | 567 |
1 files changed, 447 insertions, 120 deletions
diff --git a/cvs/sv_sync_www.c b/cvs/sv_sync_www.c index e61aab1..3db8150 100644 --- a/cvs/sv_sync_www.c +++ b/cvs/sv_sync_www.c @@ -2,7 +2,7 @@ Copyright (C) Gordon Matzigkeit <gord@fig.org>, Loic Dachary <loic@gnu.org>, 2001 - Copyright (C) 2005, Sergey Poznyakoff + Copyright (C) 2005, 2006 Sergey Poznyakoff This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License @@ -21,24 +21,24 @@ */ -/* sf_sync_www.c - Allow unprivileged users to do a web update on www.gnu.org +/* sv_sync_www.c - Allow unprivileged users to do a web update on www.gnu.org.ua In CVSROOT/loginfo file ALL /bin/sv_sync_www_schedule ${USER} ${CVSROOT} %s In crontab (remove the backslash before /): - 0 *\/1 * * * /bin/sv_sync_www_flush + 0 *\/1 * * * /bin/sv_sync_www -s /var/spool/savane 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 + ALL (date; cat; (sleep 2; /bin/sv_sync_www %{s} ) &\ ) >> /var/log/sv_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' + ./sv_sync_www -n ' bla bla' + ./sv_sync_www -n 'foo/bar bla bla' + ./sv_sync_www -n 'f'\''oo bla bla' + ./sv_sync_www -n 'f'\''oo - New directory' + ./sv_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 @@ -87,6 +87,9 @@ Introduce command line options, make program configurable and implement .symlinks functionality Sergey Poznyakoff <gray@gnu.org.ua>, 2005-07-31 + + Implement 'scan directory' mode + Sergey Poznyakoff <gray@gnu.org.ua>, 2005-02-09 */ @@ -95,19 +98,24 @@ #endif #include <sys/types.h> +#include <sys/wait.h> +#include <sys/stat.h> #include <ctype.h> -#include <string.h> +#include <unistd.h> +#include <stdlib.h> #include <stdio.h> -#include <sys/wait.h> +#include <string.h> +#include <dirent.h> #include <error.h> #include <errno.h> -#include <sys/stat.h> -#include <unistd.h> -#include <stdlib.h> #include <pwd.h> +#define obstack_chunk_alloc malloc +#define obstack_chunk_free free +#include <obstack.h> + /* * Return a malloc'ed copy of the string argument with all ' * substituted by '\'' and a trailing and leading '. @@ -115,7 +123,7 @@ * fib'ou -> 'fib'\''ou' */ char * -quote (char *string) +quote (const char *string) { int i; int length; @@ -135,7 +143,7 @@ quote (char *string) out = (char *) malloc (length + 1 + count * 3 + 2); if (count > 0) { - char *from_p = string; + const char *from_p = string; char *to_p = out; *to_p++ = '\''; while (*from_p) @@ -157,37 +165,45 @@ quote (char *string) return out; } -char *user; char *rsh_program = "/usr/bin/ssh"; char *hostname; -char *repository = "/webcvs"; +char *default_repository = "/webcvs"; +char *default_spooldir = "/var/spool/savane"; char *access_method = NULL; char *www_directory = "/home/puszcza/software"; char *cvs_command = "CVS_RSH=ssh cvs -q -z3"; +size_t min_uid = 100; +size_t min_gid = 100; int dry_run = 0; int debug = 0; void usage () { - printf ("sv_sync_www [OPTIONS] directory\n\n"); + printf ("sv_sync_www [OPTIONS] DIRECTORY\n\n"); printf ("OPTIONS are:\n"); + printf ("\nMode selection:\n"); + printf (" -h Display this help summary\n"); + printf (" -s Scan mode: process each file in DIRECTORY\n"); + printf ("\nMode modification:\n"); + printf (" -n Dry run: do nothing, only print what would have been done\n"); + printf (" -x Enable debugging output\n"); + printf ("\nConfiguration:\n"); printf (" -c COMMAND Set cvs command to use (default %s)\n", cvs_command); 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 (" -h Display this help summary\n"); - printf (" -n Dry run: do nothing, only print what would have been done\n"); printf (" -m METHOD Set default access method. METHOD will be prepended to\n"); printf (" REPOSITORY (see -R option below) before accessing the\n"); printf (" repository\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 (" (default: %s)\n", default_repository); printf (" -w WWW_DIRECTORY Set full name of the WWW directory\n"); printf (" (default: %s)\n", www_directory); - printf (" -x Enable debugging output"); + printf (" -U UID Set minimum allowable UID value\n"); + printf (" -G UID Set minimum allowable GID value\n"); } static int @@ -220,7 +236,7 @@ run_cmd (char *cmd) else if (WIFSIGNALED (status)) { error (0, 0, "subprocess terminated on signal %d", - WTERMSIG (status)); + WTERMSIG (status)); return -1; } else @@ -266,24 +282,377 @@ run_cmd (char *cmd) } int +has_file_list (const char *arg) +{ + /* + * If this is a new directory creation or an import command, + * there are no files to collect in the argument list. + * Otherwise, there are files to collect. + */ + return (strcmp (arg, "- New directory") + && strcmp (arg, "- Imported sources")); +} + +static int +split_arg (const char *arg, char **pdir, char **pfiles) +{ + char *tmp; + /* + * First comes the relative directory name or empty string if current. + * directory = 0 if no subdirectory + * directory = relative path if subdirectory + */ + if ((tmp = strchr (arg, ' '))) + { + size_t len = tmp - arg; + if (len > 0) + { + *pdir = malloc (len + 1); + if (!*pdir) + return -1; + memcpy (*pdir, arg, len); + (*pdir)[len] = 0; + } + else + *pdir = NULL; + *pfiles = strdup (tmp + 1); + if (!*pfiles) + { + free (*pdir); + return -1; + } + return 0; + } + return 1; +} + + +int +sync_www (const char *repository, const char *unquoted_directory, int files) +{ + int rc; + char *directory = quote (unquoted_directory); + char *cmd; + + /* Check that the local directory exists. */ + if (!dry_run && files && unquoted_directory) + { + struct stat stbuf; + char *localdir; + size_t len = strlen (repository); + + localdir = malloc (len + 1 + strlen (unquoted_directory) + 1); + if (!localdir) + { + error (0, 0, "not enough memory"); + return 1; + } + + strcpy (localdir, repository); + if (localdir[len - 1] != '/') + localdir[len++] = '/'; + strcpy (localdir + len, unquoted_directory); + + /* Check to see that the local unquoted_directory actually exists. */ + if (stat (localdir, &stbuf) || + (!S_ISDIR (stbuf.st_mode) && (errno = ENOTDIR))) + { + error (0, errno, "Cannot find `%s'", localdir); + return 1; + } + } + + /* Update from CVS */ + if (directory && !files) + asprintf (&cmd, "cd %s && ( %s -d %s%s checkout %s )", + www_directory, cvs_command, access_method ? access_method : "", + repository, 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 (0, 0, "Unexpected null directory and files"); + return 1; + } + + 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); + + rc = run_cmd (cmd); + free (cmd); + return rc; +} + +struct sync_entry +{ + char *repo; + char *dir; + int files; +}; + +static int +dir_cmp (const char *a, const char *b) +{ + if (!a) + { + if (b) + return -1; + else + return 0; + } + else if (!b) + return 1; + return strcmp (a, b); +} + +static int +ent_cmp (const void *a, const void *b) +{ + const struct sync_entry *sa = a; + const struct sync_entry *sb = b; + int rc; + + rc = strcmp (sa->repo, sb->repo); + if (rc) + return rc; + rc = dir_cmp (sa->dir, sb->dir); + if (rc) + return rc; + return sa->files - sb->files; +} + +int +sync_from_file (uid_t uid, const char *spooldir, const char *file) +{ + char *buf = NULL; + size_t size = 0; + size_t line = 0; + char *user, *dir, *filelist; + int err = 0; + struct obstack stk; + struct sync_entry *sp; + struct sync_entry ent; + struct sync_entry *sync_array; + FILE *fp = fopen (file, "r"); + + if (!fp) + { + error (0, errno, "Cannot open file `%s/%s'", spooldir, file); + return 1; + } + + obstack_init (&stk); + while (getline (&buf, &size, fp) > 0) + { + char *p; + struct passwd *pw; + + + p = buf + strlen (buf) - 1; + if (*p == '\n') + *p = 0; + + line++; + + p = strtok (buf, " \t"); + if (!p) + { + error (0, 0, "%s/%s:%lu: Invalid line", + spooldir, file, + (unsigned long) line); + err = 1; + break; + } + + pw = getpwnam (p); + if (!pw) + { + error (0, 0, "%s/%s:%lu: cannot get password entry for user %s", + spooldir, file, + (unsigned long) line, p); + err = 1; + break; + } + if (uid != pw->pw_uid) + { + error (0, 0, "%s/%s:%lu: invalid uid", + spooldir, file, + (unsigned long) line); + err = 1; + break; + } + + p = strtok (NULL, " \t"); + if (!p) + { + error (0, 0, "%s/%s:%lu: Invalid line, second field is missing", + spooldir, file, + (unsigned long) line); + err = 1; + break; + } + ent.repo = strdup (p); + if (!ent.repo) + { + error (0, ENOMEM, "%s/%s:%lu", + spooldir, file, + (unsigned long) line); + err = 1; + break; + } + + p = strtok (NULL, ""); + if (!p) + { + error (0, 0, "%s/%s:%lu: Invalid line, only two fields", + spooldir, file, + (unsigned long) line); + err = 1; + break; + } + err = split_arg (p, &ent.dir, &p); + if (err) + { + error (0, err < 0 ? errno : 0, "%s/%s:%lu: Cannot split argument", + spooldir, file, + (unsigned long) line); + break; + } + ent.files = has_file_list (p); + free (p); + + obstack_grow (&stk, &ent, sizeof (ent)); + } + fclose (fp); + + sync_array = obstack_finish (&stk); + + if (err == 0) + { + struct sync_entry *sp, *lastp = NULL; + + /* Sort the array */ + qsort (sync_array, line, sizeof ent, ent_cmp); + + for (sp = sync_array; sp < sync_array + line; sp++) + { + if (lastp) + { + if (strcmp (lastp->repo, sp->repo) == 0 + && dir_cmp (lastp->dir, sp->dir) == 0) + continue; + } + sync_www (sp->repo, sp->dir, sp->files); + lastp = sp; + } + } + + for (sp = sync_array; sp < sync_array + line; sp++) + { + free (sp->repo); + free (sp->dir); + } + + obstack_free (&stk, NULL); + + return err; +} + +int +scan_spool (const char *spooldir) +{ + DIR *dir; + struct dirent *ent; + size_t total_count = 0; /* Total number of jobs seen */ + size_t success_count = 0; /* Number of successfully processed jobs */ + + if (chdir (spooldir)) + { + error (0, errno, "Cannot change to `%s'", spooldir); + return 1; + } + + dir = opendir ("."); + if (!dir) + { + error (0, errno, "Cannot open directory `%s'", spooldir); + return 1; + } + + while ((ent = readdir (dir))) + { + struct stat st; + + if (ent->d_name[0] == '.') + continue; + + total_count++; + if (stat (ent->d_name, &st)) + { + error (0, errno, "Cannot stat file `%s/%s'", spooldir, ent->d_name); + continue; + } + + if (S_ISREG (st.st_mode)) + { + if (st.st_uid < min_uid || st.st_gid < min_gid) + { + error (0, 0, "Ignoring file `%s/%s': UID or GID out of range", + spooldir, ent->d_name); + continue; + } + + if (sync_from_file (st.st_uid, spooldir, ent->d_name) == 0) + { + if (!dry_run && unlink (ent->d_name)) + error (0, errno, "Cannot unlink file `%s/%s'", + spooldir, ent->d_name); + + success_count++; + } + } + } + + printf ("Total files: %lu; Jobs processed: %lu\n", + (unsigned long) total_count, + (unsigned long) success_count); + + closedir (dir); + return 0; +} + +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; + char *p; + int spooldir_mode; + char *user = NULL; + char *repository = default_repository; - while ((i = getopt (argc, argv, "c:hH:m:nr:R:u:w:x")) != EOF) + while ((i = getopt (argc, argv, "c:hH:m:nr:R:su:U:G:w:x")) != EOF) switch (i) { case 'c': cvs_command = optarg; break; - + case 'H': hostname = optarg; break; @@ -292,6 +661,12 @@ main (int argc, char **argv) usage (); exit (0); + case 'G': + min_gid = strtoul (optarg, &p, 0); + if (p) + error (1, 0, "invalid numeric value (near %s)", p); + break; + case 'm': access_method = optarg; break; @@ -308,6 +683,16 @@ main (int argc, char **argv) repository = optarg; break; + case 's': + spooldir_mode = 1; + break; + + case 'U': + min_uid = strtoul (optarg, &p, 0); + if (p) + error (1, 0, "invalid numeric value (near %s)", p); + break; + case 'u': user = optarg; break; @@ -355,101 +740,43 @@ main (int argc, char **argv) if (setreuid (uid, uid)) error (1, errno, "cannot assume effective user id"); - if (argc == 1) + if (spooldir_mode) { - 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++; - } + char *spooldir = NULL; + + if (argc > 1) + error (1, 0, "Extra arguments in scan mode"); + else if (argc == 1) + spooldir = argv[0]; 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"); + spooldir = default_spooldir; - 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. - */ + return scan_spool (spooldir); } else { - /* - * There are files to collect in the argument list. - */ - files = 1; - } - - /* Check that the local directory exists. */ - if (!dry_run && files && 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); + char *unquoted_directory = NULL; + char *tmp; + + if (argc == 1) + { + int rc = split_arg (argv[0], &unquoted_directory, &tmp); + if (rc < 0) + error (1, errno, "Cannot split argument"); + else if (rc > 0) + 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"); - /* 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); + return sync_www (repository, unquoted_directory, has_file_list (tmp)); } - - /* Update from CVS */ - if (directory && !files) - asprintf (&cmd, "cd %s && ( %s -d %s%s checkout %s )", - www_directory, cvs_command, access_method ? access_method : "", - repository, 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); + /*NOTREACHED*/ + return 0; } |