Skip to content

Instantly share code, notes, and snippets.

@mpg
Created August 12, 2011 20:49
Show Gist options
  • Save mpg/1142965 to your computer and use it in GitHub Desktop.
Save mpg/1142965 to your computer and use it in GitHub Desktop.
list files in /etc not belonging to an installed Debian package
#!/usr/bin/perl
# etcorphan - list files in /etc not belonging to an installed Debian package
#
# Usage: etcorphan [ exclude_file... ]
#
# A lot of files don't formally belong to any package (eg because they are
# generated by an installation script) but are legitimate. You may want to
# ignore those files. The exclude files indicated on the command line use
# basically the same format as gitignore: one pattern per linei (supported
# metacharacters: * ? [], patterns starting with a / are anchored at the
# root), lines starting with a # are comments. Negated patterns are not
# supported. See example after the END mark, covering files from a basic
# Debian system.
#
# Manuel Pégourié-Gonnard, 2011. WTFPL v2.
use warnings;
use strict;
use IPC::Open3;
use File::Find;
exit(main());
sub main {
get_excludes();
get_files();
check_files();
exit;
}
# populate the @excludes list from files on the command line
my @excludes;
sub get_excludes {
while (my $line = <>) {
# ignore comments and empty lines
next if $line =~ /^\s*$/;
next if $line =~ /^#/;
chomp $line;
# store the correspondig regex
push @excludes, glob2pat($line);
}
}
# turn a shell-style pattern (as used in ignore files) into a regex
# arg: a shell pattern
# ret: a regex
sub glob2pat {
my ($globstr) = @_;
# remember if glob is anchored before changing it
my $anchored = ($globstr =~ s!^/!!);
# turn glob into a regex
# adapted from http://docstore.mik.ua/orelly/perl/cookbook/ch06_10.htm
my %patmap = ( '*' => '[^/]*', '?' => '.', '[' => '[', ']' => ']', );
$globstr =~ s[ (.) ][ $patmap{$1} || "\Q$1" ]gex;
# fix anchoring in the resulting regex
my $regex = $anchored
? qr!^$globstr(?:$|/)!
: qr!(?:^|/)$globstr(?:$|/)!;
return $regex;
}
# tell if a file is accepted according to the @excludes list
# arg: relative pathname, ie without the initial '/etc/' part
# ret: boolean
sub accepted {
my $name = shift;
for my $pat (@excludes) {
return 0 if $name =~ /$pat/;
}
return 1;
}
# populate the @files list with acceptable files from /etc
# items are stored as pathnames relative to /etc
my @files;
sub get_files {
my $wanted = sub {
my $abspath = $File::Find::name;
(my $relpath = $abspath) =~ s!^/etc/!!;
if (accepted($relpath)) {
push @files, $relpath unless -d $abspath
} else {
$File::Find::prune = 1;
}
};
find($wanted, '/etc/');
}
# check files in the @files list and print unkown files
sub check_files {
# dpkg-query needs absolute pathanames
my @paths = map { "/etc/$_" } @files;
# prepare filehandles for dpkg-query, we need only its output
open my $in, '<', '/dev/null';
open my $err, '>', '/dev/null';
my $out;
# actually check the files, getting a cluttered list of know files
my $pid = open3($in, $out, $err, 'dpkg-query', '-S', @paths);
my @outlines = <$out>;
waitpid($pid, 0);
# turn the cluttered list of know files into a hash whose keys
# are pathnames relative to /etc
my %found;
for my $line (@outlines) {
chomp $line;
# dpkg-query's ouput looks like
# <package-name>: /etc/relpath
$line =~ s!^[^ ]+: /etc/!!;
$found{$line}++;
}
# report unknown files
for my $file (@files) {
print "$file\n" unless $found{$file};
}
}
__END__
# example exclude file
# generated links
/rc?.d
/ssl/certs/*.[01]
/ssl/certs/*.pem
/alternatives
# private
/ssl/private
# misc
adduser.conf
aliases
apt/apt.conf.d/00CDMountPoint
apt/apt.conf.d/00trustcdrom
apt/listchanges.conf
apt/secring.gpg
apt/sources.list
apt/trustdb.gpg
apt/trusted.gpg
ca-certificates.conf
console-setup/cached.kmap.gz
console-setup/Lat15-Fixed16.psf.gz
default/console-setup
default/exim4
default/grub
default/keyboard
default/locale
default/nfs-common
default/portmap
default/rcS
dictionaries-common/default.aff
dictionaries-common/default.hash
dictionaries-common/ispell-default
dictionaries-common/words
dpkg/origins/default
environment
exim4/update-exim4.conf.conf
fstab
group
group-
gshadow
gshadow-
hostname
hosts
hosts.allow
hosts.deny
idmapd.conf
initramfs-tools/conf.d/resume
initramfs-tools/modules
inittab
inputrc
kernel-img.conf
ld.so.conf
locale.gen
localtime
mailcap
mailname
modules
motd
motd.tail
network/interfaces
networks
nsswitch.conf
pam.d/common-account
pam.d/common-auth
pam.d/common-password
pam.d/common-session
pam.d/common-session-noninteractive
passwd
passwd-
profile
python/debian_config
rc.local
security/opasswd
sgml/catalog
sgml/xml-core.cat
shadow
shadow-
shells
ssh/sshd_config
ssh/ssh_host_dsa_key
ssh/ssh_host_dsa_key.pub
ssh/ssh_host_rsa_key
ssh/ssh_host_rsa_key.pub
ssl/certs/ca-certificates.crt
timezone
udev/rules.d/70-persistent-cd.rules
udev/rules.d/70-persistent-net.rules
X11/X
X11/Xwrapper.config
xml/catalog
xml/xml-core.xml
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment