Explorar o código

dpkg-statoverride: Rewrite in C

Guillem Jover %!s(int64=17) %!d(string=hai) anos
pai
achega
12a61afc46
Modificáronse 7 ficheiros con 455 adicións e 251 borrados
  1. 1 0
      debian/changelog
  2. 1 1
      po/POTFILES.in
  3. 0 2
      scripts/Makefile.am
  4. 0 247
      scripts/dpkg-statoverride.pl
  5. 1 0
      src/.gitignore
  6. 15 1
      src/Makefile.am
  7. 437 0
      src/statcmd.c

+ 1 - 0
debian/changelog

@@ -19,6 +19,7 @@ dpkg (1.15.5) UNRELEASED; urgency=low
   * Add C coding style document.
   * Make dpkg as strict as dpkg-statoverride on input when validating the
     parsed data from the statdb.
+  * Rewrite dpkg-statoverride in C.
 
   [ Raphaël Hertzog ]
   * Add versioned dependency on base-files (>= 5.0.0) to dpkg-dev to ensure

+ 1 - 1
po/POTFILES.in

@@ -42,6 +42,7 @@ src/processarc.c
 src/query.c
 src/remove.c
 src/select.c
+src/statcmd.c
 src/statdb.c
 src/trigcmd.c
 src/trigproc.c
@@ -61,5 +62,4 @@ dpkg-split/split.c
 utils/start-stop-daemon.c
 
 scripts/dpkg-divert.pl
-scripts/dpkg-statoverride.pl
 scripts/update-alternatives.pl

+ 0 - 2
scripts/Makefile.am

@@ -16,7 +16,6 @@ bin_SCRIPTS = \
 	dpkg-scanpackages \
 	dpkg-scansources \
 	dpkg-shlibdeps \
-	dpkg-statoverride \
 	dpkg-source \
 	dpkg-vendor \
 	update-alternatives
@@ -41,7 +40,6 @@ EXTRA_DIST = \
 	dpkg-shlibdeps.pl \
 	dpkg-source.pl \
 	dpkg-divert.pl \
-	dpkg-statoverride.pl \
 	dpkg-vendor.pl \
 	update-alternatives.pl \
 	changelog/debian.pl \

+ 0 - 247
scripts/dpkg-statoverride.pl

@@ -1,247 +0,0 @@
-#! /usr/bin/perl
-
-BEGIN { # Work-around for bug #479711 in perl
-    $ENV{PERL_DL_NONLAZY} = 1;
-}
-
-use strict;
-use warnings;
-
-use POSIX;
-use POSIX qw(:errno_h :signal_h);
-use Dpkg;
-use Dpkg::Gettext;
-
-textdomain("dpkg");
-
-my $verbose = 1;
-my $doforce = 0;
-my $doupdate = 0;
-my $mode = "";
-
-my %owner;
-my %group;
-my %mode;
-
-sub version {
-	printf _g("Debian %s version %s.\n"), $progname, $version;
-
-	printf _g("
-Copyright (C) 2000 Wichert Akkerman.");
-
-	printf _g("
-This is free software; see the GNU General Public Licence version 2 or
-later for copying conditions. There is NO warranty.
-");
-}
-
-sub usage {
-	printf _g(
-"Usage: %s [<option> ...] <command>
-
-Commands:
-  --add <owner> <group> <mode> <file>
-                           add a new entry into the database.
-  --remove <file>          remove file from the database.
-  --list [<glob-pattern>]  list current overrides in the database.
-
-Options:
-  --admindir <directory>   set the directory with the statoverride file.
-  --update                 immediately update file permissions.
-  --force                  force an action even if a sanity check fails.
-  --quiet                  quiet operation, minimal output.
-  --help                   show this help message.
-  --version                show the version.
-"), $progname;
-}
-
-sub CheckModeConflict {
-	return unless $mode;
-	badusage(sprintf(_g("two commands specified: %s and --%s"), $_, $mode));
-}
-
-while (@ARGV) {
-	$_=shift(@ARGV);
-	last if m/^--$/;
-	if (!m/^-/) {
-		unshift(@ARGV,$_); last;
-	} elsif (m/^--help$/) {
-		usage();
-		exit(0);
-	} elsif (m/^--version$/) {
-		version();
-		exit(0);
-	} elsif (m/^--update$/) {
-		$doupdate=1;
-	} elsif (m/^--quiet$/) {
-		$verbose=0;
-	} elsif (m/^--force$/) {
-		$doforce=1;
-	} elsif (m/^--admindir$/) {
-		@ARGV || badusage(sprintf(_g("--%s needs a <directory> argument"), "admindir"));
-		$admindir= shift(@ARGV);
-	} elsif (m/^--add$/) {
-		CheckModeConflict();
-		$mode= 'add';
-	} elsif (m/^--remove$/) {
-		CheckModeConflict();
-		$mode= 'remove';
-	} elsif (m/^--list$/) {
-		CheckModeConflict();
-		$mode= 'list';
-	} else {
-		badusage(sprintf(_g("unknown option \`%s'"), $_));
-	}
-}
-
-my $dowrite = 0;
-my $exitcode = 0;
-
-badusage(_g("no mode specified")) unless $mode;
-ReadOverrides();
-
-if ($mode eq "add") {
-	@ARGV == 4 || badusage(_g("--add needs four arguments"));
-
-	my $user = $ARGV[0];
-	my $uid = 0;
-	my $gid = 0;
-
-	if ($user =~ m/^#([0-9]+)$/) {
-	    $uid=$1;
-	    badusage(sprintf(_g("illegal user %s"), $user)) if ($uid < 0);
-	} else {
-	    my ($name, $pw);
-	    (($name, $pw, $uid) = getpwnam($user)) ||
-	        badusage(sprintf(_g("non-existing user %s"), $user));
-	}
-
-	my $group = $ARGV[1];
-	if ($group =~ m/^#([0-9]+)$/) {
-	    $gid=$1;
-	    badusage(sprintf(_g("illegal group %s"), $group)) if ($gid < 0);
-	} else {
-	    my ($name, $pw);
-	    (($name, $pw, $gid) = getgrnam($group)) ||
-	        badusage(sprintf(_g("non-existing group %s"), $group));
-	}
-
-	my $mode = $ARGV[2];
-	(($mode < 0) or (oct($mode) > 07777) or ($mode !~ m/\d+/)) &&
-	    badusage(sprintf(_g("illegal mode %s"), $mode));
-	my $file = $ARGV[3];
-	$file =~ m/\n/ && badusage(_g("file may not contain newlines"));
-	$file =~ s,/+$,, && print STDERR _g("stripping trailing /")."\n";
-
-	if (defined $owner{$file}) {
-		printf STDERR _g("An override for \"%s\" already exists, "), $file;
-		if ($doforce) {
-			print STDERR _g("but --force specified so will be ignored.")."\n";
-		} else {
-			print STDERR _g("aborting")."\n";
-			exit(3);
-		}
-	}
-	$owner{$file}=$user;
-	$group{$file}=$group;
-	$mode{$file}=$mode;
-	$dowrite=1;
-
-	if ($doupdate) {
-	    if (not -e $file) {
-		printf STDERR _g("warning: --update given but %s does not exist")."\n", $file;
-	    } else {
-		chown ($uid,$gid,$file) || warn sprintf(_g("failed to chown %s: %s"), $file, $!)."\n";
-		chmod (oct($mode),$file) || warn sprintf(_g("failed to chmod %s: %s"), $file, $!)."\n";
-	    }
-	}
-} elsif ($mode eq "remove") {
-	@ARGV == 1 || badusage(sprintf(_g("--%s needs a single argument"), "remove"));
-	my $file = $ARGV[0];
-	$file =~ s,/+$,, && print STDERR _g("stripping trailing /")."\n";
-	if (not defined $owner{$file}) {
-		print STDERR _g("No override present.")."\n";
-		exit(0) if ($doforce); 
-		exit(2);
-	}
-	delete $owner{$file};
-	delete $group{$file};
-	delete $mode{$file};
-	$dowrite=1;
-	print(STDERR _g("warning: --update is useless for --remove")."\n") if ($doupdate);
-} elsif ($mode eq "list") {
-	my (@list, @ilist);
-
-	@ilist= @ARGV ? @ARGV : ('*');
-	while (defined($_=shift(@ilist))) {
-		s/\W/\\$&/g;
-		s/\\\?/./g;
-		s/\\\*/.*/g;
-		s,/+$,, && print STDERR _g("stripping trailing /")."\n";
-		push(@list,"^$_\$");
-	}
-
-	my $pattern = join('|', @list);
-	$exitcode=1;
-	for my $file (keys %owner) {
-		next unless ($file =~ m/$pattern/o);
-		$exitcode=0;
-		print "$owner{$file} $group{$file} $mode{$file} $file\n";
-	}
-}
-
-WriteOverrides() if ($dowrite);
-
-exit($exitcode);
-
-sub ReadOverrides {
-	open(SO, "$admindir/statoverride") ||
-	    quit(sprintf(_g("cannot open statoverride: %s"), $!));
-	while (<SO>) {
-		my ($owner,$group,$mode,$file);
-		chomp;
-
-		($owner,$group,$mode,$file)=split(' ', $_, 4);
-		die sprintf(_g("Multiple overrides for \"%s\", aborting"), $file)
-			if defined $owner{$file};
-		$owner{$file}=$owner;
-		$group{$file}=$group;
-		$mode{$file}=$mode;
-	}
-	close(SO);
-}
-
-
-sub WriteOverrides {
-	my ($file);
-
-	open(SO, ">$admindir/statoverride-new") ||
-	    quit(sprintf(_g("cannot open new statoverride file: %s"), $!));
-	foreach $file (keys %owner) {
-		print SO "$owner{$file} $group{$file} $mode{$file} $file\n";
-	}
-	close(SO);
-	chmod(0644, "$admindir/statoverride-new");
-	unlink("$admindir/statoverride-old") ||
-		$! == ENOENT || quit(sprintf(_g("error removing statoverride-old: %s"), $!));
-	link("$admindir/statoverride","$admindir/statoverride-old") ||
-		$! == ENOENT || quit(sprintf(_g("error creating new statoverride-old: %s"), $!));
-	rename("$admindir/statoverride-new","$admindir/statoverride")
-		|| quit(sprintf(_g("error installing new statoverride: %s"), $!));
-}
-
-
-sub quit
-{
-	printf STDERR "%s: %s\n", $0, "@_";
-	exit(2);
-}
-
-sub badusage
-{
-	printf STDERR "%s: %s\n\n", $0, "@_";
-	usage();
-	exit(2);
-}
-
-# vi: ts=8 sw=8 ai si cindent

+ 1 - 0
src/.gitignore

@@ -1,3 +1,4 @@
 dpkg
 dpkg-query
+dpkg-statoverride
 dpkg-trigger

+ 15 - 1
src/Makefile.am

@@ -11,7 +11,11 @@ AM_CPPFLAGS = \
 	-I$(top_srcdir)/lib
 
 
-bin_PROGRAMS = dpkg dpkg-query dpkg-trigger
+bin_PROGRAMS = \
+	dpkg \
+	dpkg-query \
+	dpkg-statoverride \
+	dpkg-trigger
 
 dpkg_SOURCES = \
 	archives.c archives.h \
@@ -52,6 +56,16 @@ dpkg_query_LDADD = \
 	../lib/compat/libcompat.a \
 	$(LIBINTL)
 
+dpkg_statoverride_SOURCES = \
+	filesdb.c filesdb.h \
+	statdb.c \
+	statcmd.c
+
+dpkg_statoverride_LDADD = \
+	../lib/dpkg/libdpkg.a \
+	../lib/compat/libcompat.a \
+	$(LIBINTL)
+
 dpkg_trigger_SOURCES = \
 	trigcmd.c
 

+ 437 - 0
src/statcmd.c

@@ -0,0 +1,437 @@
+/*
+ * dpkg-statoverrides - override ownership and mode of files
+ *
+ * Copyright © 2000, 2001 Wichert Akkerman <wakkerma@debian.org>
+ * Copyright © 2006-2009 Guillem Jover <guillem@debian.org>
+ *
+ * This 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,
+ * or (at your option) any later version.
+ *
+ * This 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 dpkg; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <dpkg/i18n.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <signal.h>
+#include <grp.h>
+#include <pwd.h>
+#include <fnmatch.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#if HAVE_LOCALE_H
+#include <locale.h>
+#endif
+
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/path.h>
+#include <dpkg/myopt.h>
+
+#include "main.h"
+#include "filesdb.h"
+
+const char thisname[] = "dpkg-statoverrides";
+const char printforhelp[] = N_(
+"Use --help for help about querying packages;\n"
+"Use --license for copyright license and lack of warranty (GNU GPL).");
+
+static void
+printversion(const struct cmdinfo *cip, const char *value)
+{
+	printf(_("Debian %s version %s.\n"), thisname, DPKG_VERSION_ARCH);
+
+	printf(_(
+"Copyright (C) 2000, 2001 Wichert Akkerman.\n"
+"Copyright (C) 2006-2009 Guillem Jover."));
+
+	printf(_(
+"This is free software; see the GNU General Public Licence version 2 or\n"
+"later for copying conditions. There is NO warranty.\n"));
+
+	m_output(stdout, _("<standard output"));
+
+	exit(0);
+}
+
+static void
+usage(const struct cmdinfo *cip, const char *value)
+{
+	printf(_(
+"Usage: %s [<option> ...] <command>\n"
+"\n"), thisname);
+
+	printf(_(
+"Commands:\n"
+"  --add <owner> <group> <mode> <file>\n"
+"                           add a new entry into the database.\n"
+"  --remove <file>          remove file from the database.\n"
+"  --list [<glob-pattern>]  list current overrides in the database.\n"
+"\n"));
+
+	printf(_(
+"Options:\n"
+"  --admindir <directory>   set the directory with the statoverride file.\n"
+"  --update                 immediately update file permissions.\n"
+"  --force                  force an action even if a sanity check fails.\n"
+"  --quiet                  quiet operation, minimal output.\n"
+"  --help                   show this help message.\n"
+"  --version                show the version.\n"
+"\n"));
+
+	m_output(stdout, _("<standard output"));
+
+	exit(0);
+}
+
+const struct cmdinfo *cipaction = NULL;
+const char *admindir = ADMINDIR;
+
+static int opt_verbose = 1;
+static int opt_force = 0;
+static int opt_update = 0;
+
+static void
+setaction(const struct cmdinfo *cip, const char *value)
+{
+	if (cipaction)
+		badusage(_("conflicting actions -%c (--%s) and -%c (--%s)"),
+		         cip->oshort, cip->olong,
+		         cipaction->oshort, cipaction->olong);
+	cipaction = cip;
+}
+
+static char *
+path_cleanup(const char *path)
+{
+	char *new_path = m_strdup(path);
+
+	path_rtrim_slash_slashdot(new_path);
+	if (strcmp(path, new_path) != 0)
+		warning(_("stripping trailing /"));
+
+	return new_path;
+}
+
+static struct filestatoverride *
+statdb_node_new(const char *user, const char *group, const char *mode)
+{
+	struct filestatoverride *filestat;
+
+	filestat = nfmalloc(sizeof(*filestat));
+
+	filestat->uid = statdb_parse_uid(user);
+	filestat->gid = statdb_parse_gid(group);
+	filestat->mode = statdb_parse_mode(mode);
+
+	return filestat;
+}
+
+static struct filestatoverride **
+statdb_node_find(const char *filename)
+{
+	struct filenamenode *file;
+
+	file = findnamenode(filename, 0);
+
+	return &file->statoverride;
+}
+
+static int
+statdb_node_remove(const char *filename)
+{
+	struct filenamenode *file;
+
+	file = findnamenode(filename, fnn_nonew);
+	if (!file || (file && !file->statoverride))
+		return 0;
+
+	file->statoverride = NULL;
+
+	return 1;
+}
+
+static void
+statdb_node_apply(const char *filename, struct filestatoverride *filestat)
+{
+	if (access(filename, F_OK) != 0) {
+		warning(_("--update given but %s does not exist"), filename);
+	} else {
+		if (chown(filename, filestat->uid, filestat->gid) < 0)
+			warning(_("failed to chown %s: %s"), filename,
+			        strerror(errno));
+		if (chmod(filename, filestat->mode))
+			warning(_("failed to chmod %s: %s"), filename,
+			        strerror(errno));
+	}
+}
+
+static void
+statdb_node_print(FILE *out, struct filenamenode *file)
+{
+	struct filestatoverride *filestat = file->statoverride;
+	struct passwd *pw;
+	struct group *gr;
+
+	if (!filestat)
+		return;
+
+	pw = getpwuid(filestat->uid);
+	if (pw == NULL)
+		ohshite(_("error getting user name for uid %d"), filestat->uid);
+	gr = getgrgid(filestat->gid);
+	if (gr == NULL)
+		ohshite(_("error getting group name for gid %d"), filestat->gid);
+
+	fprintf(out, "%s %s %o %s\n", pw->pw_name, gr->gr_name, filestat->mode,
+	        file->name);
+}
+
+static void
+statdb_write(void)
+{
+	FILE *dbfile;
+	struct fileiterator *i;
+	struct filenamenode *file;
+	struct varbuf dbname = VARBUF_INIT;
+	struct varbuf dbname_new = VARBUF_INIT;
+	struct varbuf dbname_old = VARBUF_INIT;
+
+	varbufaddstr(&dbname, admindir);
+	varbufaddstr(&dbname, "/" STATOVERRIDEFILE);
+	varbufaddc(&dbname, '\0');
+
+	varbufaddstr(&dbname_new, dbname.buf);
+	varbufaddstr(&dbname_new, NEWDBEXT);
+	varbufaddc(&dbname_new, '\0');
+
+	varbufaddstr(&dbname_old, dbname.buf);
+	varbufaddstr(&dbname_old, OLDDBEXT);
+	varbufaddc(&dbname_old, '\0');
+
+	dbfile = fopen(dbname_new.buf, "w");
+	if (!dbfile)
+		ohshite(_("cannot open new statoverride file"));
+
+	i = iterfilestart();
+	while ((file = iterfilenext(i)))
+		statdb_node_print(dbfile, file);
+	iterfileend(i);
+
+	fclose(dbfile);
+
+	chmod(dbname_new.buf, 0644);
+	if (unlink(dbname_old.buf) && errno != ENOENT)
+		ohshite(_("error removing statoverride-old"));
+	if (link(dbname.buf, dbname_old.buf) && errno != ENOENT)
+		ohshite(_("error creating new statoverride-old"));
+	if (rename(dbname_new.buf, dbname.buf))
+		ohshite(_("error installing new statoverride"));
+
+	varbuffree(&dbname);
+	varbuffree(&dbname_new);
+	varbuffree(&dbname_old);
+}
+
+static int
+statoverride_add(const char *const *argv)
+{
+	const char *user = argv[0];
+	const char *group = argv[1];
+	const char *mode = argv[2];
+	const char *path = argv[3];
+	char *filename;
+	struct filestatoverride **filestat;
+
+	if (!user || !group || !mode || !path || argv[4])
+		badusage(_("--add needs four arguments"));
+
+	if (strchr(path, '\n'))
+		badusage(_("file may not contain newlines"));
+
+	filename = path_cleanup(path);
+
+	filestat = statdb_node_find(filename);
+	if (*filestat == NULL) {
+		if (opt_force)
+			warning(_("An override for '%s' already exists, "
+			          "but --force specified so will be ignored."),
+			        filename);
+		else
+			ohshit(_("An override for '%s' already exists, "
+			         "aborting."), filename);
+	}
+
+	*filestat = statdb_node_new(user, group, mode);
+
+	if (opt_update)
+		statdb_node_apply(filename, *filestat);
+
+	statdb_write();
+
+	free(filename);
+
+	return 0;
+}
+
+static int
+statoverride_remove(const char *const *argv)
+{
+	const char *path = argv[0];
+	char *filename;
+
+	if (!path || argv[1])
+		badusage(_("--%s needs a single argument"), "remove");
+
+	filename = path_cleanup(path);
+
+	if (!statdb_node_remove(filename)) {
+		warning(_("No override present."));
+		if (opt_force)
+			exit(0);
+		else
+			exit(2);
+	}
+
+	if (opt_update)
+		warning(_("--update is useless for --remove"));
+
+	statdb_write();
+
+	free(filename);
+
+	return 0;
+}
+
+struct glob_node {
+	struct glob_node *next;
+	char *pattern;
+};
+
+static void
+glob_list_prepend(struct glob_node **list, char *pattern)
+{
+	struct glob_node *node;
+
+	node = m_malloc(sizeof(*node));
+	node->pattern = pattern;
+	node->next = *list;
+	*list = node;
+}
+
+static void
+glob_list_free(struct glob_node *head)
+{
+	while (head) {
+		struct glob_node *node = head;
+
+		head = head->next;
+		free(node->pattern);
+		free(node);
+	}
+}
+
+static int
+statoverride_list(const char *const *argv)
+{
+	struct fileiterator *i;
+	struct filenamenode *file;
+	const char *thisarg;
+	struct glob_node *glob_list = NULL;
+	int ret = 1;
+
+	while ((thisarg = *argv++)) {
+		char *pattern = path_cleanup(thisarg);
+
+		glob_list_prepend(&glob_list, pattern);
+	}
+	if (glob_list == NULL)
+		glob_list_prepend(&glob_list, m_strdup("*"));
+
+	i = iterfilestart();
+	while ((file = iterfilenext(i))) {
+		struct glob_node *g;
+
+		for (g = glob_list; g; g = g->next) {
+			if (fnmatch(g->pattern, file->name, 0) == 0) {
+				statdb_node_print(stdout, file);
+				ret = 0;
+				break;
+			}
+		}
+	}
+	iterfileend(i);
+
+	glob_list_free(glob_list);
+
+	return ret;
+}
+
+#define ACTION(longopt, shortopt, code, function) \
+ { longopt, shortopt, 0, 0, 0, setaction, code, 0, (voidfnp)function }
+
+static const struct cmdinfo cmdinfos[] = {
+	ACTION("add",    'L', act_listfiles,  statoverride_add),
+	ACTION("remove", 's', act_status,     statoverride_remove),
+	ACTION("list",   'p', act_printavail, statoverride_list),
+
+	{ "admindir",   0,   1,  NULL,         &admindir, NULL          },
+	{ "quiet",      0,   0,  &opt_verbose, NULL,      NULL          },
+	{ "force",      0,   0,  &opt_force,   NULL,      NULL          },
+	{ "update",     0,   0,  &opt_update,  NULL,      NULL          },
+	{ "help",       'h', 0,  NULL,         NULL,      usage         },
+	{ "version",    0,   0,  NULL,         NULL,      printversion  },
+	/* UK spelling */
+	{ "licence",    0,   0,  NULL,         NULL,      showcopyright },
+	/* US spelling */
+	{ "license",    0,   0,  NULL,         NULL,      showcopyright },
+	{  NULL,        0                                               }
+};
+
+int
+main(int argc, const char *const *argv)
+{
+	jmp_buf ejbuf;
+	static int (*actionfunction)(const char *const *argv);
+	int ret;
+
+	setlocale(LC_ALL, "");
+	bindtextdomain(PACKAGE, LOCALEDIR);
+	textdomain(PACKAGE);
+
+	standard_startup(&ejbuf);
+	myopt(&argv, cmdinfos);
+
+	if (!cipaction)
+		badusage(_("need an action option"));
+
+	setvbuf(stdout, NULL, _IONBF, 0);
+
+	filesdbinit();
+	ensure_statoverrides();
+
+	actionfunction = (int (*)(const char *const *))cipaction->farg;
+	ret = actionfunction(argv);
+
+	standard_shutdown();
+
+	return ret;
+}
+