bug-cvs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

PROPOSAL: Pass tag information per file to commitinfo on commit


From: James E Jurach Jr.
Subject: PROPOSAL: Pass tag information per file to commitinfo on commit
Date: Tue, 02 Jul 2002 01:45:19 -0500

PURPOSE:  Branch-specific ACL implementation on server side.

PROPOSAL:  Pass tag information per file to commitinfo on commit

OVERVIEW:  We want commitinfo to determine user access control based on a
file's tag as well as its path.  We could find no natural way of passing tag
information to commitinfo which would be backwards compatible with currently
existing scripts.  Therefore, we propose to add a configure argument,
"--enable-commitinfo-tags" which would have ccvs append the associated tag
to each file passed to commitinfo on its command-line.  For instance,
commitinfo might see the following arguments passed to it.

  foo/bar bar.c:HEAD baz.c:HEAD Makefile:stable

IMPLEMENTATION:  Minor change to src/commit.c and configure/build system.

PATCH:  (below patch is an example per-branch cvs_acl script)

Index: ccvs/ChangeLog
===================================================================
RCS file: /cvs/ccvs/ChangeLog,v
retrieving revision 1.649
diff -u -3 -r1.649 ChangeLog
--- ccvs/ChangeLog      28 Jun 2002 18:52:30 -0000      1.649
+++ ccvs/ChangeLog      2 Jul 2002 05:46:18 -0000
@@ -1,3 +1,13 @@
+2002-07-02  James Jurach  <address@hidden>
+
+       * configure.in: Add --enable-commitinfo-tags option to enable passing
+       tag information to commitinfo script
+
+       * src/commit.c: When COMMITINFO_TAGS_SUPPORT is defined, append tag
+       information to each file before it is passed to commitinfo.
+
+       * NEWS (Changes since 1.11.2): description of commitinfo-tags feature
+
 2002-06-28  Derek Price  <address@hidden>
 
        * INSTALL (Building [on] other platforms): Don't reference the Mac
Index: ccvs/NEWS
===================================================================
RCS file: /cvs/ccvs/NEWS,v
retrieving revision 1.107
diff -u -3 -r1.107 NEWS
--- ccvs/NEWS   22 May 2002 15:40:23 -0000      1.107
+++ ccvs/NEWS   2 Jul 2002 05:46:19 -0000
@@ -1,5 +1,12 @@
 Changes since 1.11.2:
 
+* CVS server administrators can now consider tags in the commitinfo script
+to determine if the committer has authority to perform the commit.  To
+activate this feature, the CVS build must be configured with
+--enable-commitinfo-tags option.  When this feature is enabled, the tag
+with which a file is checked in will be appended to each file listed in the
+commitinfo command-line.
+
 * We've standardized on Automake version 1.6 and Autoconf version 2.53.
 They are cleaner, less bug prone, and will hopfully allow me to start updating
 sanity.sh to use Autotest and Autoshell.  Again, this should only really affect
Index: ccvs/config.h.in
===================================================================
RCS file: /cvs/ccvs/config.h.in,v
retrieving revision 1.59
diff -u -3 -r1.59 config.h.in
--- ccvs/config.h.in    2 May 2002 19:04:03 -0000       1.59
+++ ccvs/config.h.in    2 Jul 2002 05:46:20 -0000
@@ -9,6 +9,9 @@
 /* Define to 1 if the `closedir' function returns void instead of `int'. */
 #undef CLOSEDIR_VOID
 
+/* Define if you expect tag information passed to commitinfo. */
+#undef COMMITINFO_TAGS_SUPPORT
+
 /* Define to enable encryption support. */
 #undef ENCRYPTION
 
Index: ccvs/configure.in
===================================================================
RCS file: /cvs/ccvs/configure.in,v
retrieving revision 1.149
diff -u -3 -r1.149 configure.in
--- ccvs/configure.in   8 May 2002 17:48:46 -0000       1.149
+++ ccvs/configure.in   2 Jul 2002 05:46:27 -0000
@@ -265,6 +265,15 @@
 dnl for the buffer routine replacements
 AC_FUNC_MMAP
 
+# Check for options requesting commitinfo-tag feature. If none are
+# given and we pass file names to commitinfo without tags.
+AC_ARG_ENABLE(commitinfo-tags,
+[  --enable-commitinfo-tags include code for passing tags to commitinfo],
+[if test "$enable_commitinfo_tags" = yes; then
+  AC_DEFINE(COMMITINFO_TAGS_SUPPORT, 1,
+  [Define if you expect tag information passed to commitinfo.])
+fi])
+
 # Try to find connect and gethostbyname.
 AC_CHECK_LIB(nsl, main)
 AC_SEARCH_LIBS(connect, xnet socket inet,
Index: ccvs/src/commit.c
===================================================================
RCS file: /cvs/ccvs/src/commit.c,v
retrieving revision 1.180
diff -u -3 -r1.180 commit.c
--- ccvs/src/commit.c   17 Jun 2002 19:15:18 -0000      1.180
+++ ccvs/src/commit.c   2 Jul 2002 05:46:31 -0000
@@ -1091,7 +1091,17 @@
        || li->type == T_MODIFIED
        || li->type == T_REMOVED)
     {
+#ifdef COMMITINFO_TAGS_SUPPORT
+      char *tag = li->tag ? li->tag : "HEAD";
+      char *buf = (char*) malloc(strlen(p->key) + strlen(tag) + 2);
+      strcpy(buf,p->key);
+      strcat(buf,":");
+      strcat(buf,tag);
+      run_arg (buf);
+      free(buf);
+#else
        run_arg (p->key);
+#endif /* COMMITINFO_TAGS_SUPPORT */
     }
     return (0);
 }


EXAMPLE PER-BRANCH ACL SCRIPT:

#! /usr/bin/perl -w
use strict;
# -*-Perl-*-
#
# Access control lists for CVS.  address@hidden (David G. Grubbs)
# Branch specific controls added by address@hidden (Aaron Voisine)
# Branch controls enforced by server added by address@hidden (James  Jurach)
#
# CVS "commitinfo" for matching repository names, running the program it finds
# on the same line.  More information is available in the CVS man pages.
#
# ==== INSTALLATION:
#
# To use this program as I intended, do the following four things:
#
# 0. Install PERL.  :-)
#
# 1. Put one line, as the *only* non-comment line, in your commitinfo file:
#
#       DEFAULT         /usr/local/bin/cvs_acls.pl
#
# 2. Install this file as /usr/local/bin/cvs_acls.pl and make it executable.
#
# 3. Create a file named $CVSROOT/CVSROOT/avail.
#
# ==== FORMAT OF THE avail FILE:
#
# The avail file determines whether you may commit files.  It contains lines
# read from top to bottom, keeping track of a single "bit".  The "bit"
# defaults to "on".  It can be turned "off" by "unavail" lines and "on" by
# "avail" lines.  ==> Last one counts.
#
# Any line not beginning with "avail" or "unavail" is ignored.
#
# Lines beginning with "avail" or "unavail" are assumed to be '|'-separated
# triples: (All spaces and tabs are ignored in a line.)
#
# {avail.*,unavail.*} [|user,user,... [|repos,repos,... [|branch,branch,...]]]
#
#    1. String starting with "avail" or "unavail".
#    2. Optional, comma-separated list of usernames.
#    3. Optional, comma-separated list of repository pathnames.
#       These are pathnames relative to $CVSROOT.  They can be directories or
#       filenames.  A directory name allows access to all files and
#       directories below it.
#    4. Optional, comma-separated list of branch tags.
#       If not specified, all branches are assumed. Use HEAD to reference the
#       main branch.
#
# Example:  (Text from the ';;' rightward may not appear in the file.)
#
#       unavail                 ;; Make whole repository unavailable.
#       avail|dgg               ;; Except for user "dgg".
#       avail|fred, john|bin/ls ;; Except when "fred" or "john" commit to
#                               ;; the module whose repository is "bin/ls"
#       avail|ed|/bin/ls|stable ;; Except when "ed" commits to the "stable"
#                               ;; branch of the "bin/ls" repository 
#
# PROGRAM LOGIC:
#
#       CVS passes to @ARGV an absolute directory pathname (the repository
#       appended to your $CVSROOT variable), followed by a list of filenames
#       within that directory.
#
#       We walk through the avail file looking for a line that matches the
#       username, repository and branch.
#
#       A username match is simply the user's name appearing in the second
#       column of the avail line in a space-or-comma separate list.
#
#       A repository match is either:
#               - One element of the third column matches $ARGV[0], or some
#                 parent directory of $ARGV[0].
#               - Otherwise *all* file arguments ($ARGV[1..$#ARGV]) must be
#                 in the file list in one avail line.
#           - In other words, using directory names in the third column of
#             the avail file allows committing of any file (or group of
#             files in a single commit) in the tree below that directory.
#           - If individual file names are used in the third column of
#             the avail file, then files must be committed individually or
#             all files specified in a single commit must all appear in
#             third column of a single avail line.
#
#       A branch match is either:
#               - When no branches are listed in the fourth column.
#               - One element from the fourth column matches each of the tag
#                 names for $ARGV[1..$#ARGV] found in the CVS/Entries file.
#               - HEAD specified in the fourth column will match if there
#                 is no tag listed in the CVS/Entries file.
#

my $debug = 0;
my $cvsroot = $ENV{'CVSROOT'};
my $availfile = $cvsroot . "/CVSROOT/avail";
my $myname = $ENV{"LOGNAME"} || $ENV{"USER"};

my $die;
eval "print STDERR \$die='Unknown parameter $1\n' if !defined \$$1; \$$1=\$';"
    while ($ARGV[0] =~ /^(\w+)=/ && shift(@ARGV));
exit 255 if $die;               # process any variable=value switches

die "Must set CVSROOT\n" if !$cvsroot;
(my $repos = shift) =~ s:^$cvsroot/::;
grep($_ = $repos . '/' . $_, @ARGV);

print "$$ Repos: $repos\n","$$ ==== ",join("\n$$ ==== ",@ARGV),"\n" if $debug;

my $universal_off = 0;

my @avail;
open (AVAIL, $availfile) || exit(0);    # It is ok for avail file not to exist
while (<AVAIL>) {
  chop;
  next if /^\s*\#/;
  next if /^\s*$/;
  my ($flagstr, $u, $m, $b) = split(/[\s,]*\|[\s,]*/, $_);

  # Skip anything not starting with "avail" or "unavail" and complain.
  (print "Bad avail line: $_\n"), next
    if ($flagstr !~ /^avail/ && $flagstr !~ /^unavail/);

  # Set which bit we are playing with. ('0' is OK == Available).
  my $flag = (($& eq "avail") ? 1 : -1);

  push @avail, [$flag, $u||"", $m||"", $b||"", $_];

  # If we find a "universal off" flag (i.e. a simple "unavail") remember it
  $universal_off = 1 if (($flag<1) && !$u && !$m && !$b);
}
close AVAIL;

my (@denied,%tags);
for my $file (@ARGV) {
  $file =~ s/:([^:]+)$// or die "No tag for $file\n";
  my $tag = $1;

  my $ret = acl_func(address@hidden,$file,$tag,$repos,$myname);

  if ( ($ret == 0 and $universal_off) or ($ret < 0) ) {
    push @denied, "$file:$tag";
    $tags{$tag}++;
  }
}

if (@denied) {
  print "**** Access Denied: ACL Restriction";
  # pretty-print more information:
  if (@denied == 1) {
    print " ($myname|$denied[0])\n";
  } elsif (@denied == @ARGV) {
    print " ($myname|$repos|".join(",",sort keys %tags).")\n";
  } else {
    print " ($myname|".join(",",@denied).")\n";
  }
  exit 1;
} else {
  #print "**** Access allowed: Personal Karma exceeds Environmental Karma.\n"
  #  if $universal_off;
  exit 0;
}

##############################################################################

sub acl_func {
  my ($avail,$file,$tag,$repos,$myname) = @_;

  my $return_flag = 0;

  for my $avail_row (@$avail) {
    my ($flag, $u, $m, $b, $o) = @$avail_row;


    # $myname considered "in user list" if actually in list or is NULL
    my $in_user;
    if (!($in_user = !$u)) {
      my @tmp = split /[\s,]+/, $u;
      $in_user = grep {$_ eq $myname} @tmp;
    }
    print "$$ \$myname($myname) in user list: $o\n" if $debug && $in_user;


    # Module matches if it is a NULL module list in the avail line.  If module
    # list is not null, we check every argument combination.
    my $in_repo;
    if (!($in_repo = !$m)) {
      my @tmp = split /[\s,]+/, $m;
      # If the repos from avail is a parent(or equal) dir of $repos, OK
      $in_repo = grep {$_ eq $repos or $_ eq $file or $repos =~ /^$_\//} @tmp;
    }
    print "$$ \$repos($repos) in repository list: $o\n" if $debug && $in_repo;


    # Branch matches if it is in the branch list in the avail line, the branch
    # list is NULL, or there is no branch and HEAD is in the branch list.
    my $in_branch;
    if (!($in_branch = !$b)) {
      my @tmp = split (/[\s,]+/,$b);
      $in_branch = grep {$_ eq $tag} @tmp;
    }
    print "$$ \$branch($tag) in branch list: $o\n" if $debug && $in_branch;


    $return_flag = $flag if $in_user && $in_repo && $in_branch;
  }

  return $return_flag;
}



reply via email to

[Prev in Thread] Current Thread [Next in Thread]