Commit 5c44f05b authored by Bernhard Link's avatar Bernhard Link

add --outhook

parent 1fd24358
2012-12-31 Bernhard R. Link <brlink@debian.org>
* add --outhook
2012-12-20 Bernhard R. Link <brlink@debian.org>
* fix inconsistent spacing of ls command,
* fix --nothingiserror ls not treating no result as error
......
......@@ -18,14 +18,14 @@ AM_CPPFLAGS = $(ARCHIVECPP) $(DBCPPFLAGS)
reprepro_LDADD = $(ARCHIVELIBS) $(DBLIBS)
changestool_LDADD = $(ARCHIVELIBS)
reprepro_SOURCES = descriptions.c sizes.c sourcecheck.c byhandhook.c archallflood.c needbuild.c globmatch.c printlistformat.c diffindex.c rredpatch.c pool.c atoms.c uncompression.c remoterepository.c indexfile.c copypackages.c sourceextraction.c checksums.c readtextfile.c filecntl.c sha1.c sha256.c configparser.c database.c freespace.c hooks.c log.c changes.c incoming.c uploaderslist.c guesscomponent.c files.c md5.c dirs.c chunks.c reference.c binaries.c sources.c checks.c names.c dpkgversions.c release.c mprintf.c updates.c strlist.c signature_check.c signedfile.c signature.c distribution.c checkindeb.c checkindsc.c checkin.c upgradelist.c target.c aptmethod.c downloadcache.c main.c override.c terms.c termdecide.c ignore.c filterlist.c exports.c tracking.c optionsfile.c donefile.c pull.c contents.c filelist.c $(ARCHIVE_USED) $(ARCHIVE_CONTENTS)
reprepro_SOURCES = outhook.c descriptions.c sizes.c sourcecheck.c byhandhook.c archallflood.c needbuild.c globmatch.c printlistformat.c diffindex.c rredpatch.c pool.c atoms.c uncompression.c remoterepository.c indexfile.c copypackages.c sourceextraction.c checksums.c readtextfile.c filecntl.c sha1.c sha256.c configparser.c database.c freespace.c hooks.c log.c changes.c incoming.c uploaderslist.c guesscomponent.c files.c md5.c dirs.c chunks.c reference.c binaries.c sources.c checks.c names.c dpkgversions.c release.c mprintf.c updates.c strlist.c signature_check.c signedfile.c signature.c distribution.c checkindeb.c checkindsc.c checkin.c upgradelist.c target.c aptmethod.c downloadcache.c main.c override.c terms.c termdecide.c ignore.c filterlist.c exports.c tracking.c optionsfile.c donefile.c pull.c contents.c filelist.c $(ARCHIVE_USED) $(ARCHIVE_CONTENTS)
EXTRA_reprepro_SOURCE = $(ARCHIVE_UNUSED)
changestool_SOURCES = uncompression.c sourceextraction.c readtextfile.c filecntl.c tool.c chunkedit.c strlist.c checksums.c sha1.c sha256.c md5.c mprintf.c chunks.c signature.c dirs.c names.c $(ARCHIVE_USED)
rredtool_SOURCES = rredtool.c rredpatch.c mprintf.c filecntl.c sha1.c
noinst_HEADERS = descriptions.h sizes.h sourcecheck.h byhandhook.h archallflood.h needbuild.h globmatch.h printlistformat.h pool.h atoms.h uncompression.h remoterepository.h copypackages.h sourceextraction.h checksums.h readtextfile.h filecntl.h sha1.h sha256.h configparser.h database_p.h database.h freespace.h hooks.h log.h changes.h incoming.h guesscomponent.h md5.h dirs.h files.h chunks.h reference.h binaries.h sources.h checks.h names.h release.h error.h mprintf.h updates.h strlist.h signature.h signature_p.h distribution.h debfile.h checkindeb.h checkindsc.h upgradelist.h target.h aptmethod.h downloadcache.h override.h terms.h termdecide.h ignore.h filterlist.h dpkgversions.h checkin.h exports.h globals.h tracking.h trackingt.h optionsfile.h donefile.h pull.h ar.h filelist.h contents.h chunkedit.h uploaderslist.h indexfile.h rredpatch.h diffindex.h
noinst_HEADERS = outhook.h descriptions.h sizes.h sourcecheck.h byhandhook.h archallflood.h needbuild.h globmatch.h printlistformat.h pool.h atoms.h uncompression.h remoterepository.h copypackages.h sourceextraction.h checksums.h readtextfile.h filecntl.h sha1.h sha256.h configparser.h database_p.h database.h freespace.h hooks.h log.h changes.h incoming.h guesscomponent.h md5.h dirs.h files.h chunks.h reference.h binaries.h sources.h checks.h names.h release.h error.h mprintf.h updates.h strlist.h signature.h signature_p.h distribution.h debfile.h checkindeb.h checkindsc.h upgradelist.h target.h aptmethod.h downloadcache.h override.h terms.h termdecide.h ignore.h filterlist.h dpkgversions.h checkin.h exports.h globals.h tracking.h trackingt.h optionsfile.h donefile.h pull.h ar.h filelist.h contents.h chunkedit.h uploaderslist.h indexfile.h rredpatch.h diffindex.h
MAINTAINERCLEANFILES = $(srcdir)/Makefile.in $(srcdir)/configure $(srcdir)/stamp-h.in $(srcdir)/aclocal.m4 $(srcdir)/config.h.in
......
......@@ -130,10 +130,10 @@ retvalue distribution_remove_packages(struct distribution *, const struct atomli
/* like distribtion_getpart, but returns NULL if there is no such target */
/*@null@*//*@dependent@*/struct target *distribution_gettarget(const struct distribution *distribution, component_t, architecture_t, packagetype_t);
retvalue distribution_fullexport(struct distribution *distribution);
retvalue distribution_fullexport(struct distribution *);
retvalue distribution_snapshot(struct distribution *distribution, const char *name);
retvalue distribution_snapshot(struct distribution *, const char */*name*/);
/* read the configuration from all distributions */
retvalue distribution_readall(/*@out@*/struct distribution **distributions);
......@@ -146,7 +146,7 @@ struct distribution *distribution_find(struct distribution *, const char *);
retvalue distribution_freelist(/*@only@*/struct distribution *distributions);
enum exportwhen {EXPORT_NEVER, EXPORT_SILENT_NEVER, EXPORT_CHANGED, EXPORT_NORMAL, EXPORT_FORCE };
retvalue distribution_exportlist(enum exportwhen when, /*@only@*/struct distribution *distributions);
retvalue distribution_exportlist(enum exportwhen when, /*@only@*/struct distribution *);
retvalue distribution_loadalloverrides(struct distribution *);
void distribution_unloadoverrides(struct distribution *distribution);
......
EXTRA_DIST = short-howto reprepro.1 changestool.1 rredtool.1 recovery bzip.example tiffany.example di.example/README di.example/DI-filter.sh di.example/distributions di.example/updates reprepro.bash_completion reprepro.zsh_completion FAQ changelogs.example manual.html copybyhand.example
EXTRA_DIST = short-howto reprepro.1 changestool.1 rredtool.1 recovery bzip.example tiffany.example di.example/README di.example/DI-filter.sh di.example/distributions di.example/updates reprepro.bash_completion reprepro.zsh_completion FAQ changelogs.example manual.html copybyhand.example outstore.py
man_MANS = reprepro.1 changestool.1 rredtool.1
MAINTAINERCLEANFILES = $(srcdir)/Makefile.in
......@@ -1043,6 +1043,7 @@ Currently you can execute your own scripts at the following occasions:
<li><a href="#byhandhook">to process byhand files</a></li>
<li><a href="#exporthook">when creating index files (Packages.gz, Sources.gz)</a></li>
<li><a href="#signhook">when signing releases</a></li>
<li><a href="#outhook">after changing the visible files of the repository managed</a></li>
<li><a href="#endhook">when reprepro finished</a></li>
</ul>
<h3><a name="addhook">Scripts to be run when adding or removing packages</a></h3>
......@@ -1196,6 +1197,52 @@ a exclamation mark followed by a space and the name of a hook script to call.
The script gets three arguments: The filename to sign, the filename of the InRelease
file to create and the filename of the Release.gpg to create (a Release.gpg does not
need to be created. reprepro will assume you do not care about that legacy file if it
<h3><a name="outhook">Scripts to be run after changing the visible files of the repository managed</a></h3>
When using the <tt class="option">--outhook</tt> command line option (or the corresponding
<tt class="constant">outhook</tt> in the <tt class="filename">options</tt> file),
reprepro will create a <tt class="suffix">.outlog</tt> file in the log directory describing
any changes done to the out dir and calls the hook script given as argument with this
file as argument.
The <tt class="suffix">.outlog</tt> file consists of lines each starting with a keyword
and then some arguments seperated by tab characters.
The possible keywords are:
<ul>
<li><tt class="constant">POOLNEW</tt>:
One argument is the filekey of a file newly added to the pool.
<li><tt class="constant">POOLDELETE</tt>:
One argument is the filekey of a file removed from the pool.
<li><tt class="constant">START-DISTRIBUTION</tt>, <tt class="constant">END-DISTRIBUTION</tt>:
two or three arguments: the codename, the directory,
and the suite (if set).
<li><tt class="constant">START-SNAPSHOT</tt>, <tt class="constant">END-SNAPSHOT</tt>:
three arguments: the codename, the directory, and the name of the snapshot generated.
<li><tt class="constant">DISTFILE</tt>:
three arguments: the directory of the distribution (relative to out dir), the name relative to that directory, and the filename generated by reprepro.
<li><tt class="constant">DISTSYMLINK</tt>:
three arguments: the directory of the distribution (relative to out dir), the name relative to that directory, and the symlink target (relative to that directory).
<li><tt class="constant">DISTDELETE</tt>:
two arguments: the directory of the distribution (relative to out dir), the name relative to that directory of a file no longer there.
<li><tt class="constant">DISTKEEP</tt> (not yet generated):
two arguments: the directory of the distribution (relative to out dir), the name relative to that directory.
</ul>
All <tt class="constant">POOLNEW</tt> come before any distribution changes referencing them
and all <tt class="constant">POOLDELETE</tt> will be afterwards.
Each line beloging to a distribution is guaranteed to be between the corresponding
<tt class="constant">START-DISTRIBUTION</tt> and
<tt class="constant">END-DISTRIBUTION</tt> or between a
<tt class="constant">START-SNAPSHOT</tt> and
<tt class="constant">END-SNAPSHOT</tt> or between a
with the same directory
(i.e. there is some redundancy so you can choose to parse the information where it is more conventient for you).
The lines starting with <tt class="constant">DIST</tt> describe new or modified files in the distribution description exported by reprepro. No hint is given if that file was previously non-existant, a proper file or a symlink (i.e. if you copy stuff, do not make any assumptions about that).
Future versions of reprepro might create <tt class="constant">DISTKEEP</tt> lines to denote files that have not changed (i.e. just ignore those lines to be future-proof).
The directories for the distribution entries are what apt expects them (i.e. always starting with <tt class="constant">dists/</tt>, while the third argument to <tt class="constant">DISTFILE</tt> is the name reprepro generated (i.e. starts with the distdir value, which can be configured to not end with <tt class="constant">dists/</tt>).
<h3><a name="endhook">when reprepro finished</a></h3>
With the <tt class="option">--endhook</tt> command line option (or the corresponding
<tt class="constant">endhook</tt> in the <tt class="filename">options</tt> file) you
......
#!/usr/bin/python3
# Copyright (C) 2012 Bernhard R. Link
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation.
#
# 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 St, Fifth Floor, Boston, MA 02111-1301 USA
# This is an example outhook script.
# Actually it is part of the testsuite and does many things
# an actual outhook script would never do.
# But it checks so many aspects of how a outhook script is called
# that it should make quite clear what a outhookscript can expect.
import sys, os, subprocess, select, dbm
def poolfile(outdir, name):
s = os.lstat(outdir + '/' + name)
return "poolfile %d bytes" % s.st_size
def distfile(outdir, name):
s = os.lstat(outdir + '/' + name)
return "distfile %d bytes" % s.st_size
def distsymlink(distdir, target):
return "distsymlink -> %s/%s" % (distdir,target)
def collecteddistfile(outdir, name):
if os.path.islink(outdir + '/' + name):
l = os.readlink(outdir + '/' + name)
d = os.path.dirname(name)
while d and l[0:3] == '../':
d = os.path.dirname(d)
l = l[3:]
if d:
d = d + '/'
return "distsymlink -> %s%s" % (d,l)
else:
return distfile(outdir, name)
def processfile(logfile, donefile, db):
# print("Parsing '%s'" % logfile)
lf = open(logfile, 'r', encoding='utf-8')
newpoolfiles = []
distributions = []
deletepoolfiles = []
mode = 'POOLNEW'
# This parser is wasteful and unnecessarily complicated, but it's
# purpose is mainly making sure the output of reprepro is
# well-formed and no so much targeted at doing actual work.
for l in lf:
if l[-1] != '\n':
raise CriticalError("Malformed file '%s' (not a text file)" % logfile)
l = l[:-1]
fields = l.split('\t')
if fields[0] != 'POOLNEW':
break
if len(fields) != 2:
raise CriticalError("Malformed file '%s': POOLNEW with more than one argument" % logfile)
newpoolfiles.append(fields[1])
else:
fields = ['EOF']
while fields[0] == 'BEGIN-DISTRIBUTION' or fields[0] == 'BEGIN-SNAPSHOT':
beginmarker = fields[0]
endmarker = 'END-' + beginmarker[6:]
if len(fields) != 3 and len(fields) != 4:
raise CriticalError("Malformed file '%s': wrong number of arguments for %s" % (logfile,beginmarker))
distname = fields[1]
distdir = fields[2]
distfiles = []
distsymlinks = []
distdeletes = []
for l in lf:
if l[-1] != '\n':
raise CriticalError("Malformed file '%s' (not a text file)" % logfile)
l = l[:-1]
fields = l.split('\t')
if fields[0] == endmarker:
if len(fields) != 3 and len(fields) != 4:
raise CriticalError("Malformed file '%s': wrong number of arguments for %s" % (logfile, endmarker))
if fields[1] != distname or fields[2] != distdir:
raise CriticalError("Malformed file '%s': %s not matching previous %s" % (logfile, endmarker, beginmarker))
break
elif fields[0] == 'DISTKEEP':
continue
elif not fields[0] in ['DISTFILE', 'DISTSYMLINK', 'DISTDELETE']:
raise CriticalError("Malformed file '%s': Unexpected '%s'" % (logfile, fields[0]))
if len(fields) < 3:
raise CriticalError("Malformed file '%s': wrong number of arguments for %s" % (logfile, fields[0]))
if fields[1] != distdir:
raise CriticalError("Malformed file '%s': wrong distdir '%s' in '%s'" %(logfile, fields[1], fields[0]))
if fields[0] == 'DISTFILE':
if len(fields) != 4:
raise CriticalError("Malformed file '%s': wrong number of arguments for %s" % (logfile, fields[0]))
distfiles.append((fields[2], fields[3]))
elif fields[0] == 'DISTDELETE':
if len(fields) != 3:
raise CriticalError("Malformed file '%s': wrong number of arguments for %s" % (logfile, fields[0]))
distdeletes.append(fields[2])
elif fields[0] == 'DISTSYMLINK':
if len(fields) != 4:
raise CriticalError("Malformed file '%s': wrong number of arguments for %s" % (logfile, fields[0]))
distsymlinks.append((fields[2], fields[3]))
else:
raise CriticalError("Malformed file '%s': unexpected end of file (%s missing)" % (logfile, endmarker))
distributions.append((distname, distdir, distfiles, distsymlinks, distdeletes))
l = next(lf, 'EOF\n')
if l[-1] != '\n':
raise CriticalError("Malformed file '%s' (not a text file)" % logfile)
l = l[:-1]
fields = l.split('\t')
while fields[0] == 'POOLDELETE':
if len(fields) != 2:
raise CriticalError("Malformed file '%s': wrong number of arguments for POOLDELETE" % logfile)
deletepoolfiles.append(fields[1])
l = next(lf, 'EOF\n')
if l[-1] != '\n':
raise CriticalError("Malformed file '%s' (not a text file)" % logfile)
l = l[:-1]
fields = l.split('\t')
if fields[0] != 'EOF' or next(lf, None) != None:
raise CriticalError("Malformed file '%s': Unexpected command '%s'" % (logfile, fields[0]))
# print("Processing '%s'" % logfile)
# Checked input to death, no actualy do something
outdir = os.environ['REPREPRO_OUT_DIR']
for p in newpoolfiles:
if p in db:
raise Exception("duplicate pool file %s" % p)
db[p] = poolfile(outdir, p)
for distname, distdir, distfiles, distsymlinks, distdeletes in distributions:
for name, orig in distfiles:
db[distdir + '/' + name] = distfile(outdir, orig)
for name, target in distsymlinks:
db[distdir + '/' + name] = distsymlink(distdir, target)
for name in distdeletes:
del db[distdir + '/' + name]
for p in deletepoolfiles:
if not p in db:
raise Exception("deleting non-existant pool file %s" % p)
del db[p]
def collectfiles(dir, name):
for l in os.listdir(dir + '/' + name):
n = name + '/' + l
if os.path.isdir(dir + '/' + n):
for x in collectfiles(dir, n):
yield x
else:
yield n
def collectpool(outdir):
if os.path.isdir(outdir + '/pool'):
return ["%s: %s" % (filename, poolfile(outdir, filename)) for filename in collectfiles(outdir, 'pool')]
else:
return []
def collectdists(outdir):
if os.path.isdir(outdir + '/dists'):
return ["%s: %s" % (filename, collecteddistfile(outdir, filename)) for filename in collectfiles(outdir, 'dists')]
else:
return []
def showdiff(i1, i2):
clean = True
l1 = next(i1, None)
l2 = next(i2, None)
while l1 or l2:
if l1 == l2:
l1 = next(i1, None)
l2 = next(i2, None)
elif l1 != None and (l2 == None or l1 < l2):
print("+ %s" % l1)
clean = False
l1 = next(i1, None)
elif l2 != None and (l1 == None or l1 > l2):
print("- %s" % l2)
clean = False
l2 = next(i2, None)
else:
raise("unexpected")
return clean
def check(db):
outdir = os.environ['REPREPRO_OUT_DIR']
actualfiles = collectpool(outdir)
actualfiles.extend(collectdists(outdir))
expectedfiles = []
for k in db.keys():
expectedfiles.append("%s: %s" % (k.decode(encoding='utf-8'), db[k].decode(encoding='utf-8')))
expectedfiles.sort()
actualfiles.sort()
if not showdiff(iter(expectedfiles), iter(actualfiles)):
raise CriticalError("outdir does not match expected state")
class CriticalError(Exception):
pass
def main(args):
if len(args) <= 0:
raise CriticalError("No .outlog files given at command line!")
if len(args) == 1 and args[0] == '--print':
db = dbm.open(os.environ['REPREPRO_OUT_DB'], 'r')
for k in sort(db.keys()):
print("%s: %s" % (k, db[k]))
return
if len(args) == 1 and args[0] == '--check':
db = dbm.open(os.environ['REPREPRO_OUT_DB'], 'r')
check(db)
return
for f in args:
if len(f) < 8 or f[-7:] != ".outlog":
raise CriticalError("command line argument '%s' does not look like a .outlog file!" % f)
db = dbm.open(os.environ['REPREPRO_OUT_DB'], 'c')
for f in args:
donefile = f[:-7] + ".outlogdone"
if os.path.exists(donefile):
print("Ignoring '%s' as '%s' already exists!" % (f,donefile), file=sys.stderr)
continue
processfile(f, donefile, db)
try:
main(sys.argv[1:])
except CriticalError as e:
print(str(e), file=sys.stderr)
raise SystemExit(1)
.TH REPREPRO 1 "2012-12-02" "reprepro" REPREPRO
.TH REPREPRO 1 "2012-12-31" "reprepro" REPREPRO
.SH NAME
reprepro \- produce, manage and sync a local repository of Debian packages
.mso www.tmac
......@@ -515,6 +515,17 @@ An example script looks like: \fB
exit 0
\fP
.TP
.B \-\-outhook \fIhookscript\fP
\fIhookscript\fP is called with a \fB.outlog\fP file as argument (located
in \fIlogdir\fP) containing a description of all changes made to \fIoutdir\fP.
The script is supposed to be located relative to \fIconfdir\fP, unless its
name starts with \fB/\fP, \fB./\fP, \fB+b/\fP, \fB+o/\fP, or \fB+c/\fP
and the name may not start (except in the cases given before) with a \fB+\fP.
For a format of the \fB.outlog\fP files generated for this script see the
\fBmanual.html\fP shiped with reprepro.
.SH COMMANDS
.TP
.BR export " [ " \fIcodenames\fP " ]"
......
......@@ -124,7 +124,8 @@ _reprepro()
--section -S --priority -P --component -C\
--architecture -A --type -T --export --waitforlock \
--spacecheck --safetymargin --dbsafetymargin\
--gunzip --bunzip2 --unlzma --unxz --lunzip --gnupghome --list-format --list-skip --list-max'
--gunzip --bunzip2 --unlzma --unxz --lunzip --gnupghome --list-format --list-skip --list-max\
--outhook --endhook'
i=1
prev=""
......
......@@ -73,6 +73,7 @@
#include "sizes.h"
#include "filterlist.h"
#include "descriptions.h"
#include "outhook.h"
#ifndef STD_BASE_DIR
#define STD_BASE_DIR "."
......@@ -107,6 +108,7 @@ static char /*@only@*/ /*@null@*/
*x_packagetype = NULL;
static char /*@only@*/ /*@null@*/ *listformat = NULL;
static char /*@only@*/ /*@null@*/ *endhook = NULL;
static char /*@only@*/ /*@null@*/ *outhook = NULL;
static char /*@only@*/
*gunzip = NULL,
*bunzip2 = NULL,
......@@ -139,7 +141,7 @@ static off_t reservedotherspace = 1024*1024;
* to change something owned by lower owners. */
enum config_option_owner config_state,
#define O(x) owner_ ## x = CONFIG_OWNER_DEFAULT
O(fast), O(x_morguedir), O(x_outdir), O(x_basedir), O(x_distdir), O(x_dbdir), O(x_listdir), O(x_confdir), O(x_logdir), O(x_methoddir), O(x_section), O(x_priority), O(x_component), O(x_architecture), O(x_packagetype), O(nothingiserror), O(nolistsdownload), O(keepunusednew), O(keepunreferenced), O(keeptemporaries), O(keepdirectories), O(askforpassphrase), O(skipold), O(export), O(waitforlock), O(spacecheckmode), O(reserveddbspace), O(reservedotherspace), O(guessgpgtty), O(verbosedatabase), O(gunzip), O(bunzip2), O(unlzma), O(unxz), O(lunzip), O(gnupghome), O(listformat), O(listmax), O(listskip), O(onlysmalldeletes), O(endhook);
O(fast), O(x_morguedir), O(x_outdir), O(x_basedir), O(x_distdir), O(x_dbdir), O(x_listdir), O(x_confdir), O(x_logdir), O(x_methoddir), O(x_section), O(x_priority), O(x_component), O(x_architecture), O(x_packagetype), O(nothingiserror), O(nolistsdownload), O(keepunusednew), O(keepunreferenced), O(keeptemporaries), O(keepdirectories), O(askforpassphrase), O(skipold), O(export), O(waitforlock), O(spacecheckmode), O(reserveddbspace), O(reservedotherspace), O(guessgpgtty), O(verbosedatabase), O(gunzip), O(bunzip2), O(unlzma), O(unxz), O(lunzip), O(gnupghome), O(listformat), O(listmax), O(listskip), O(onlysmalldeletes), O(endhook), O(outhook);
#undef O
#define CONFIGSET(variable, value) if (owner_ ## variable <= config_state) { \
......@@ -1581,7 +1583,6 @@ ACTION_F(n, n, y, y, export) {
if (verbose > 0) {
printf("Exporting %s...\n", d->codename);
}
r = distribution_fullexport(d);
if (RET_IS_OK(r))
/* avoid being exported again */
......@@ -4193,6 +4194,13 @@ static retvalue callaction(command_t command, const struct action *action, int a
if (ISSET(needs, NEED_FILESDB))
result = database_openfiles();
if (RET_IS_OK(result)) {
if (outhook != NULL) {
r = outhook_start();
RET_UPDATE(result, r);
}
}
assert (result != RET_NOTHING);
if (RET_IS_OK(result)) {
......@@ -4207,6 +4215,13 @@ static retvalue callaction(command_t command, const struct action *action, int a
argc, argv);
/* wait for package specific loggers */
logger_wait();
/* remove files added but not used */
pool_tidyadded(deletenew);
/* tell an outhook about added files */
if (outhook != NULL)
pool_sendnewfiles();
/* export changed/lookedat distributions */
if (!RET_WAS_ERROR(result)) {
r = distribution_exportlist(export,
......@@ -4214,12 +4229,6 @@ static retvalue callaction(command_t command, const struct action *action, int a
RET_ENDUPDATE(result, r);
}
/* remove files added but not used */
pool_tidyadded(deletenew);
// TODO: tell hook script the added files
// TODO: tell hook scripts the modified distributions
/* delete files losing references, or
* tell how many lost their references */
......@@ -4239,7 +4248,13 @@ static retvalue callaction(command_t command, const struct action *action, int a
r = pool_removeunreferenced(deletederef);
RET_ENDUPDATE(result, r);
// TODO: tell hook scripts the deleted files
if (outhook != NULL) {
if (interrupted())
r = RET_ERROR_INTERRUPTED;
else
r = outhook_call(outhook);
RET_ENDUPDATE(result, r);
}
}
}
}
......@@ -4317,6 +4332,7 @@ LO_RESTRICT_SRC,
LO_RESTRICT_FILE_BIN,
LO_RESTRICT_FILE_SRC,
LO_ENDHOOK,
LO_OUTHOOK,
LO_UNIGNORE};
static int longoption = 0;
const char *programname;
......@@ -4601,6 +4617,9 @@ static void handle_option(int c, const char *argument) {
case LO_ENDHOOK:
CONFIGDUP(endhook, argument);
break;
case LO_OUTHOOK:
CONFIGDUP(outhook, argument);
break;
case LO_LISTMAX:
i = parse_number("--list-max",
argument, INT_MAX);
......@@ -4764,6 +4783,7 @@ static void myexit(int status) {
free(x_morguedir);
free(gnupghome);
free(endhook);
free(outhook);
pool_free();
exit(status);
}
......@@ -4922,6 +4942,7 @@ int main(int argc, char *argv[]) {
{"restrict-file-src", required_argument, &longoption, LO_RESTRICT_FILE_SRC},
{"restrict-file-binary", required_argument, &longoption, LO_RESTRICT_FILE_BIN},
{"endhook", required_argument, &longoption, LO_ENDHOOK},
{"outhook", required_argument, &longoption, LO_OUTHOOK},
{NULL, 0, NULL, 0}
};
const struct action *a;
......@@ -5017,6 +5038,21 @@ int main(int argc, char *argv[]) {
exit(EXIT_RET(RET_ERROR_OOM));
}
}
if (outhook != NULL) {
if (outhook[0] == '+' || outhook[0] == '/' ||
(outhook[0] == '.' && outhook[1] == '/')) {
outhook = expand_plus_prefix(outhook, "outhook", "boc",
true);
} else {
char *h;
h = calc_dirconcat(x_confdir, outhook);
free(outhook);
outhook = h;
if (outhook == NULL)
exit(EXIT_RET(RET_ERROR_OOM));
}
}
if (guessgpgtty && (getenv("GPG_TTY")==NULL) && isatty(0)) {
static char terminalname[1024];
......
/* This file is part of "reprepro"
* Copyright (C) 2012 Bernhard R. Link
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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 St, Fifth Floor, Boston, MA 02111-1301 USA
*/
#include <config.h>
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "error.h"
#include "filecntl.h"
#include "mprintf.h"
#include "strlist.h"
#include "dirs.h"
#include "hooks.h"
#include "outhook.h"
static FILE *outlogfile = NULL;
static char *outlogfilename = NULL;
static bool outlognonempty = false;
retvalue outhook_start(void) {
retvalue r;
int fd;
char *template;
assert (outlogfilename == NULL);
assert (outlogfile == NULL);
r = dirs_create(global.logdir);
if (RET_WAS_ERROR(r))
return r;
template = mprintf("%s/%010llu-XXXXXX.outlog",
global.logdir, (unsigned long long)time(NULL));
if (FAILEDTOALLOC(template))
return RET_ERROR_OOM;
fd = mkstemps(template, 7);
if (fd < 0) {
int e = errno;
fprintf(stderr, "Error %d creating new file in %s: %s\n",
e, global.logdir, strerror(e));
free(template);
return RET_ERRNO(e);
}
outlogfile = fdopen(fd, "w");
if (outlogfile == NULL) {
int e = errno;
(void)close(fd);
fprintf(stderr, "Error %d from fdopen: %s\n",
e, strerror(e));
free(template);
return RET_ERRNO(e);
}
outlogfilename = template;
return RET_OK;
}
void outhook_send(const char *command, const char *arg1, const char *arg2, const char *arg3) {
assert (command != NULL);
assert (arg1 != NULL);
assert (arg3 == NULL || arg2 != NULL);
if (outlogfile == NULL)
return;
if (arg2 == NULL)
fprintf(outlogfile, "%s\t%s\n", command, arg1);
else if (arg3 == NULL)
fprintf(outlogfile, "%s\t%s\t%s\n", command, arg1, arg2);
else
fprintf(outlogfile, "%s\t%s\t%s\t%s\n", command,
arg1, arg2, arg3);
outlognonempty = true;
}
void outhook_sendpool(component_t component, const char *sourcename, const char *name) {
assert (name != NULL);
if (outlogfile == NULL)
return;
if (sourcename == NULL || *sourcename == '\0')
fprintf(outlogfile, "POOLNEW\t%s\n", name);
else if (sourcename[0] == 'l' && sourcename[1] == 'i' &&
sourcename[2] == 'b' && sourcename[3] != '\0')
fprintf(outlogfile, "POOLNEW\tpool/%s/lib%c/%s/%s\n",
atoms_components[component],
sourcename[3], sourcename, name);
else
fprintf(outlogfile, "POOLNEW\tpool/%s/%c/%s/%s\n",
atoms_components[component],
sourcename[0], sourcename, name);
outlognonempty = true;
}
static retvalue callouthook(const char *scriptname, const char *logfilename) {
pid_t child;
child = fork();
if (child == 0) {
/* Try to close all open fd but 0,1,2 */
closefrom(3);
sethookenvironment(causingfile, NULL, NULL, NULL);
(void)execl(scriptname, scriptname, logfilename, (char*)NULL);
{
int e = errno;
fprintf(stderr, "Error %d executing '%s': %s\n",
e, scriptname, strerror(e));
}
_exit(255);
}
if (child < 0) {
int e = errno;
fprintf(stderr, "Error %d forking: %s!\n", e, strerror(e));
return RET_ERRNO(e);
}
while (true) {
int status;
pid_t pid;
pid = waitpid(child, &status, 0);
if (pid == child) {
if (WIFEXITED(status)) {
if (WEXITSTATUS(status) == 0) {
return RET_OK;
}
fprintf(stderr,
"Outhook '%s' '%s' failed with exit code %d!\n",
scriptname, logfilename,
(int)(WEXITSTATUS(status)));
} else if (WIFSIGNALED(status)) {
fprintf(stderr,
"Outhook '%s' '%s' killed by signal %d!\n",
scriptname, logfilename,
(int)(WTERMSIG(status)));
} else {
fprintf(stderr,
"Outhook '%s' '%s' failed!\n",
scriptname, logfilename);
}
return RET_ERROR;
} else if (pid == (pid_t)-1) {
int e = errno;
if (e == EINTR)
continue;
fprintf(stderr,
"Error %d calling waitpid on outhook child: %s\n",
e, strerror(e));
return RET_ERRNO(e);
}
}
/* NOT REACHED */
}
retvalue outhook_call(const char *scriptname) {
retvalue result;
assert (outlogfile != NULL);
assert (outlogfilename != NULL);
if (ferror(outlogfile) != 0) {
(void)fclose(outlogfile);
fprintf(stderr, "Errors creating '%s'!\n",
outlogfilename);
result = RET_ERROR;
} else if (fclose(outlogfile) != 0) {
fprintf(stderr, "Errors creating '%s'!\n",
outlogfilename);
result = RET_ERROR;
} else if (!outlognonempty) {
unlink(outlogfilename);
result = RET_OK;
} else {
result = callouthook(scriptname, outlogfilename);
}
outlogfile = NULL;
free(outlogfilename);
outlogfilename = NULL;
return result;
}
#ifndef REPREPRO_OUTHOOK_H
#define REPREPRO_OUTHOOK_H