edit · history · print

An example patch submission

The community I submitted a patch to was cfengine, a system-administratration tool. I showed the page that shows their mailing lists and developer page.

Patch overview

The patch I wrote adds Gentoo Portage package support to cfengine, so that I could use the native package management support in cfengine on Gentoo.

I showed how you can use the diff command compare two files or folders. Use diff -ru directory1 directory2 to generate a `unified' (with context), recursive diff for two folders. For two files, drop the `r'.

Patch diff

diff -ru cfengine-2.1.20.orig/src/cf.defs.h cfengine-2.1.20/src/cf.defs.h
--- cfengine-2.1.20.orig/src/cf.defs.h	2006-03-29 13:11:29.000000000 +0000
+++ cfengine-2.1.20/src/cf.defs.h	2006-08-31 22:38:55.000000000 +0000
@@ -1191,6 +1191,7 @@
    pkgmgr_rpm,
    pkgmgr_dpkg,
    pkgmgr_sun,
+   pkgmgr_portage,
    pkgmgr_none
    };

diff -ru cfengine-2.1.20.orig/src/do.c cfengine-2.1.20/src/do.c
--- cfengine-2.1.20.orig/src/do.c	2006-03-02 07:33:45.000000000 +0000
+++ cfengine-2.1.20/src/do.c	2006-08-31 22:39:30.000000000 +0000
@@ -2721,6 +2721,9 @@
      case pkgmgr_sun:
        match = SUNPackageCheck(ptr->name, ptr->ver, ptr->cmp);
        break;
+     case pkgmgr_portage:
+       match = PortagePackageCheck(ptr->name, ptr->ver, ptr->cmp);
+       break;
      default:
        /* UGH!  This should *never* happen.  GetPkgMgr() and
         * InstallPackagesItem() should have caught this before it
diff -ru cfengine-2.1.20.orig/src/globals.c cfengine-2.1.20/src/globals.c
--- cfengine-2.1.20.orig/src/globals.c	2006-03-29 13:11:29.000000000 +0000
+++ cfengine-2.1.20/src/globals.c	2006-08-31 22:39:55.000000000 +0000
@@ -656,6 +656,7 @@
       "rpm",
       "dpkg",   /* aptget ? */
       "sun",    /* pkginfo/pkgadd/pkgrm */
+      "portage",
       NULL
       };

diff -ru cfengine-2.1.20.orig/src/package.c cfengine-2.1.20/src/package.c
--- cfengine-2.1.20.orig/src/package.c	2006-03-29 13:11:29.000000000 +0000
+++ cfengine-2.1.20/src/package.c	2006-08-31 22:41:11.000000000 +0000
@@ -327,6 +327,18 @@
                CF_BUFSIZE);
        break;

+       /* Portage */
+   case pkgmgr_portage:
+
+       if (!GetMacroValue(CONTEXTID,"PortageInstallCommand"))
+          {
+          Verbose("PortageInstallCommand NOT Set.  Package Installation Not Possible!\n");
+          return 0;
+          }
+       strncpy(rawinstcmd, GetMacroValue(CONTEXTID,"PortageInstallCommand"),
+               CF_BUFSIZE);
+       break;
+
        /* Default */
    default:
        Verbose("InstallPackage(): Unknown package manager %d\n",pkgmgr);
@@ -816,6 +828,197 @@
   free(tmpcpy);  
 }

+/*********************************************************************/
+/* Gentoo Portage                                                    */
+/*********************************************************************/
+
+int PortagePackageCheck(char *package,char *version,enum cmpsense cmp)
+
+{ FILE *pp;
+  struct Item *ebuildlist = NULL;
+  struct Item *ebuild;
+  enum cmpsense result;
+  int match = 0;
+
+  /* Yes, it's an ugly python one-liner that does something beautiful */
+  snprintf(VBUFF,CF_BUFSIZE,"/usr/bin/python -c 'import portage, re, sys; "
+    "[sys.stdout.write(re.sub(\"\\w*?\\-\\w*?\\/([a-zA-Z0-9_+]|\\-(?![0-9]))*\\-\", \"\", package, 1) + "
+    "\"\\n\") for package in portage.db[\"/\"][\"vartree\"].dbapi.match("
+    "\"%s\")]'", package);
+
+  if ((pp = cfpopen(VBUFF, "r")) == NULL)
+  {
+    Verbose("Could not execute the equery command.  Assuming package not installed.\n");
+    return 0;
+  }
+
+  while(!feof(pp))
+  {
+    *VBUFF = '\0';
+
+    ReadLine(VBUFF,CF_BUFSIZE,pp);
+
+    if (*VBUFF != '\0')
+    {
+      AppendItem(&ebuildlist,VBUFF,"");
+    }
+  }
+ 
+  /* Non-zero exit status means that we could not find the package, so
+   * zero the list and bail. */
+
+  if (cfpclose(pp) != 0)
+  {
+    DeleteItemList(ebuildlist);
+    ebuildlist = NULL;
+  }
+ 
+  if (ebuildlist == NULL)
+  {
+    Verbose("Package %s not installed.\n", package);
+    return 0;
+  }
+ 
+
+  Verbose("PortageCheckPackage(): Requested %s %s %s\n", package, CMPSENSETEXT[cmp],(version[0] ? version : "ANY"));
+
+  /* If no version was specified, just return 1, because if we got this far
+   * some package by that name exists. */
+
+  if (!version[0])
+  {
+    DeleteItemList(ebuildlist);
+    return 1;
+  }
+
+  /* The rule here will be: if any package in the list matches, then the
+   * first one to match wins, and we bail out. */
+
+  char *comparehead = NULL;
+  char *comparetail = NULL;
+  char *installedhead = NULL;
+  char *installedtail = NULL;
+
+  for (ebuild = ebuildlist; ebuild != NULL; ebuild=ebuild->next)
+  {
+    char *ebuildver;
+    ebuildver = ebuild->name;
+
+    Verbose("PortageCheckPackage(): Trying installed version %s\n", ebuildver);
+
+    /* Start out assuming the comparison will be equal. */
+    result = cmpsense_eq;
+
+    comparehead = version;
+    comparetail = NULL;
+    installedhead = ebuildver;
+    installedtail = NULL;
+
+    /* Iterate over version portions delimited by `-' */
+    while (result == cmpsense_eq)
+    {
+      if (*comparehead == '\0' && *installedhead == '\0')
+      {
+        /* No substrings remain, break from while */
+        break;
+      }
+      else if (*comparehead == '\0')
+      {
+        /* Installed version has more version substrings than given */
+        result = cmpsense_gt;
+      }
+      else if (*installedhead == '\0')
+      {
+        /* Installed version has less version substrings than given */
+        result = cmpsense_lt;
+      }
+      else
+      {
+        /* New substring in both to test */
+        comparetail = strchr(comparehead, '-');
+        installedtail = strchr(installedhead, '-');
+
+        /* Throw a \0 over the `-' so just the substring is tested.
+         * If the tail is less than the head, we must be at last substring,
+         * as no `-'s were found (so the `\0' is already there) */
+        if (comparetail > comparehead) *comparetail = '\0';
+        if (installedtail > installedhead) *installedtail = '\0';
+
+        switch (rpmvercmp(installedhead, comparehead))
+        {
+        case 1:
+          result = cmpsense_gt;
+          break;
+        case -1:
+          result = cmpsense_lt;
+          break;
+        }
+
+        if (comparetail > comparehead)
+        {
+          /* Restore `-' at tail and move head just past it */
+          *comparetail = '-';
+          comparehead = comparetail + 1;
+        }
+        else
+        {
+          /* Move head to the end of the line (`\0') */
+          comparehead = comparehead + strlen(comparehead);
+        }
+        if (installedtail > installedhead)
+        {
+          /* Restore `-' at tail and move head just past it */
+          *installedtail = '-';
+          installedhead = installedtail + 1;
+        }
+        else
+        {
+          /* Move head to the end of the line (`\0') */
+          installedhead = installedhead + strlen(installedhead);
+        }
+      }
+    }
+
+    Verbose("Comparison result: %s\n",CMPSENSETEXT[result]);
+   
+    switch(cmp)
+    {
+    case cmpsense_gt:
+      match = (result == cmpsense_gt);
+      break;
+    case cmpsense_ge:
+      match = (result == cmpsense_gt || result == cmpsense_eq);
+      break;
+    case cmpsense_lt:
+      match = (result == cmpsense_lt);
+      break;
+    case cmpsense_le:
+      match = (result == cmpsense_lt || result == cmpsense_eq);
+      break;
+    case cmpsense_eq:
+      match = (result == cmpsense_eq);
+      break;
+    case cmpsense_ne:
+      match = (result != cmpsense_eq);
+      break;
+    }
+   
+    /* If we find a match, just return it now, and don't bother checking
+     * other ebuilds */
+   
+    if (match)
+    {
+      DeleteItemList(ebuildlist);
+      return 1;
+    }
+  }
+ 
+  /* if we manage to make it out of the loop, we did not find a match. */
+
+  DeleteItemList(ebuildlist);
+  return 0;
+
+}

 /*********************************************************************/
 /* RPM Version string comparison logic
diff -ru cfengine-2.1.20.orig/src/prototypes.h cfengine-2.1.20/src/prototypes.h
--- cfengine-2.1.20.orig/src/prototypes.h	2006-03-11 13:53:03.000000000 +0000
+++ cfengine-2.1.20/src/prototypes.h	2006-08-31 22:41:37.000000000 +0000
@@ -847,6 +847,7 @@
 int RPMPackageCheck (char *package, char *version, enum cmpsense cmp);
 int DPKGPackageCheck (char *package, char *version, enum cmpsense cmp);
 int SUNPackageCheck (char *package, char *version, enum cmpsense cmp);
+int PortagePackageCheck (char *package, char *version, enum cmpsense cmp);
 int InstallPackage  (char *name, enum pkgmgrs pkgmgr);
 int RemovePackage  (char *name, enum pkgmgrs pkgmgr);

Email submission

I sent the patch to the developers' mailing list:

To: bug-cfengine@cfengine.org
From: Eric Searcy <emsearcy@osuosl.org>
Subject: packages patch for Gentoo Portage support
Date: Tue, 16 Jan 2007 19:47:48 -0800

Hello,

I've written a patch (attached) for cfengine that adds support to the  
`packages:' action for the Gentoo Linux [1] Portage [2] package  
management system.  I have been using cfengine 2.1.20 with this patch  
for the last several months on a network of several dozen production  
hosts.  The same unified diff also correctly applies to the 2.2.21  
release. (I would test against the latest SVN revision, but I can't  
seem to connect at the moment, and neither can viewcvs on the site.   
It is probably broken by the recently-added AIX support.)  I based it  
very closely off the RPMPackageCheck code, and it reuses the  
rpmvercmp function.

To use, the DefaultPkgMgr/pkgmgr should be given the value `portage',  
and the function expects (and checks for) the macro  
PortageInstallCommand, which should be set to ``/usr/bin/emerge -- 
nocolor'' or similar.  Setting cmp and version, as well as setting  
neither (existence check only) is supported.

Please comment on whether this patch could be incorporated into the  
trunk.  If you would like any clarifications on the design of the  
PortagePackageCheck function or any other changes I made, please  
ask.  One quick note is that it makes an exec call to a python one- 
liner to grab package information.  Portage is a python-based system  
with a python API, and since python is required by portage, there  
shouldn't be any dependancy concerns.

[1] http://www.gentoo.org/
[2] http://www.gentoo.org/doc/en/handbook/handbook-x86.xml?part=2&chap=1

-- 
Eric Searcy
OSU Open Source Lab

I received the reply:

Date: Wed, 17 Jan 2007 10:06:29 +0100
From: Mark Burgess <Mark.Burgess@iu.hio.no>
To: Eric Searcy <emsearcy@osuosl.org>
CC: bug-cfengine@cfengine.org
Subject: Re: packages patch for Gentoo Portage support

THanks very much Eric. I would certainly like to include this, once I
have looked over the code briefly. The svn server is very flakey and
the database needs to be recovered at regular intervals...sigh. It
should be working again now.

Eric Searcy wrote:

<trim>

and:

Date: Tue, 23 Jan 2007 16:36:09 +0100
From: Mark Burgess <Mark.Burgess@iu.hio.no>
To: Eric Searcy <emsearcy@osuosl.org>
Subject: Re: packages patch for Gentoo Portage support

Thank you -- patches applied.

<trim>

Overview

Submitting a patch is not a very difficult process--depending on the community. The packages portion of cfengine is all contributer-based; the developer Mark Burgess doesn't hold the same ownership, so it is easier to contribute to that portion of the code. The key is having the patch to submit (this can be anything from spelling corrections, to feature additions, to bug-fixes), and to know which channel the open-source community expects you to submit your patch in (developer mailing list or bug tracker ticket).

edit · history · print
Page last modified on January 25, 2007, at 11:33 AM