diff --git a/Makefile.in b/Makefile.in index 123429414..c8a8aa761 100644 --- a/Makefile.in +++ b/Makefile.in @@ -83,6 +83,7 @@ INSTALLZENMAP=@INSTALLZENMAP@ INSTALLNDIFF=@INSTALLNDIFF@ INSTALLNPING=@INSTALLNPING@ UNINSTALLZENMAP=@UNINSTALLZENMAP@ +UNINSTALLNDIFF=@UNINSTALLNDIFF@ UNINSTALLNPING=@UNINSTALLNPING@ ifneq (@LIBLUA_LIBS@,) @@ -365,7 +366,7 @@ install-nping: $(NPINGDIR)/nping install: install-nmap $(INSTALLNSE) $(INSTALLZENMAP) @NCAT_INSTALL@ @NMAP_UPDATE_INSTALL@ $(INSTALLNDIFF) $(INSTALLNPING) @echo "NMAP SUCCESSFULLY INSTALLED" -uninstall: uninstall-nmap $(UNINSTALLZENMAP) @NCAT_UNINSTALL@ @NMAP_UPDATE_UNINSTALL@ $(UNINSTALLNPING) +uninstall: uninstall-nmap $(UNINSTALLZENMAP) @NCAT_UNINSTALL@ @NMAP_UPDATE_UNINSTALL@ $(UNINSTALLNDIFF) $(UNINSTALLNPING) uninstall-nmap: rm -f $(DESTDIR)$(bindir)/$(TARGET) @@ -382,6 +383,9 @@ uninstall-zenmap: fi rm -f $(DESTDIR)$(bindir)/xnmap +uninstall-ndiff: + cd $(NDIFFDIR) && $(PYTHON) setup.py uninstall + uninstall-ncat: @cd $(NCATDIR) && $(MAKE) uninstall diff --git a/configure b/configure index b0d7b5c6b..4ed7d9341 100755 --- a/configure +++ b/configure @@ -690,6 +690,7 @@ ZENMAPDIR NDIFF_DIST_CLEAN NDIFF_CLEAN NDIFF_CHECK +UNINSTALLNDIFF INSTALLNDIFF BUILDNDIFF NDIFFDIR @@ -5711,12 +5712,14 @@ fi if test "$with_ndiff" = "no"; then BUILDNDIFF="" INSTALLNDIFF="" + UNINSTALLNDIFF="" NDIFF_CHECK="" NDIFF_CLEAN="" NDIFF_DIST_CLEAN="" else BUILDNDIFF=build-ndiff INSTALLNDIFF=install-ndiff + UNINSTALLNDIFF=uninstall-ndiff NDIFF_CHECK="ndiff_check" NDIFF_CLEAN=ndiff_clean NDIFF_DIST_CLEAN=ndiff_dist_clean @@ -5728,6 +5731,7 @@ fi + ZENMAPDIR=zenmap # Do they want Zenmap? diff --git a/configure.ac b/configure.ac index 721a88f49..1a75c0659 100644 --- a/configure.ac +++ b/configure.ac @@ -232,12 +232,14 @@ fi if test "$with_ndiff" = "no"; then BUILDNDIFF="" INSTALLNDIFF="" + UNINSTALLNDIFF="" NDIFF_CHECK="" NDIFF_CLEAN="" NDIFF_DIST_CLEAN="" else BUILDNDIFF=build-ndiff INSTALLNDIFF=install-ndiff + UNINSTALLNDIFF=uninstall-ndiff NDIFF_CHECK="ndiff_check" NDIFF_CLEAN=ndiff_clean NDIFF_DIST_CLEAN=ndiff_dist_clean @@ -245,6 +247,7 @@ fi AC_SUBST(NDIFFDIR) AC_SUBST(BUILDNDIFF) AC_SUBST(INSTALLNDIFF) +AC_SUBST(UNINSTALLNDIFF) AC_SUBST(NDIFF_CHECK) AC_SUBST(NDIFF_CLEAN) AC_SUBST(NDIFF_DIST_CLEAN) diff --git a/ndiff/setup.py b/ndiff/setup.py index 58b97c970..2c47a726b 100644 --- a/ndiff/setup.py +++ b/ndiff/setup.py @@ -6,14 +6,32 @@ import os import os.path import re +from stat import * + import distutils.command import distutils.command.install import distutils.core import distutils.cmd import distutils.errors +from distutils import log from distutils.command.install import install APP_NAME = "ndiff" +# The name of the file used to record the list of installed files, so that the +# uninstall command can remove them. +INSTALLED_FILES_NAME = "INSTALLED_FILES" + + +# path_startswith and path_strip_prefix are used to deal with the installation +# root (--root option, also known as DESTDIR). +def path_startswith(path, prefix): + """Returns True if path starts with prefix. It's a little more intelligent + than str.startswith because it normalizes the paths to remove multiple + directory separators and down-up traversals.""" + path = os.path.normpath(path) + prefix = os.path.normpath(prefix) + return path.startswith(prefix) + def path_strip_prefix(path, prefix): """Return path stripped of its directory prefix if it starts with prefix, @@ -42,10 +60,9 @@ def path_strip_prefix(path, prefix): return path - ############################################################################### # Distutils subclasses - + class null_command(distutils.cmd.Command): """This is a dummy distutils command that does nothing. We use it to replace the install_egg_info and avoid installing a .egg-info file, because @@ -56,6 +73,9 @@ class null_command(distutils.cmd.Command): def finalize_options(self): pass + def get_outputs(self): + return () + def run(self): pass @@ -67,6 +87,11 @@ class checked_install(distutils.command.install.install): """ def finalize_options(self): + # Ubuntu's python2.6-2.6.4-0ubuntu3 package changes sys.prefix in + # install.finalize_options when sys.prefix is "/usr/local" (our + # default). Because we need the unchanged value later, remember it + # here. + self.saved_prefix = sys.prefix try: distutils.command.install.install.finalize_options(self) except distutils.errors.DistutilsPlatformError, e: @@ -85,32 +110,233 @@ Installing your distribution's python-dev package may solve this problem.""") app_file = open(app_file_name, "r") lines = app_file.readlines() app_file.close() - + for i in range(len(lines)): if re.match(r'^INSTALL_LIB =', lines[i]): lines[i] = "INSTALL_LIB = %s\n" % repr(modules_dir) break else: raise ValueError( - "INSTALL_LIB replacement not found in %s" % app_file_name) - + "INSTALL_LIB replacement not found in %s" % app_file_name) + app_file = open(app_file_name, "w") app_file.writelines(lines) app_file.close() - def run(self): install.run(self) -# These below are from Zenmap. We're only using set_modules_path right now, but we might consider whether the others would be useful (or, if not, whether we should remove them from Zenmap). +# These below are from Zenmap. We're only using set_modules_path right now, but +# we might consider whether the others would be useful (or, if not, whether we +# should remove them from Zenmap). # self.set_perms() self.set_modules_path() # self.fix_paths() -# self.create_uninstaller() -# self.write_installed_files() + self.create_uninstaller() + self.write_installed_files() + + def get_installed_files(self): + """Return a list of installed files and directories, each prefixed with + the installation root if given. The list of installed directories + doesn't come from distutils so it may be incomplete.""" + installed_files = self.get_outputs() + for package in self.distribution.py_modules: + dir = package.replace(".", "/") + installed_files.append(os.path.join(self.install_lib, dir)) + installed_files.append( + os.path.join(self.install_scripts, "uninstall_" + APP_NAME)) + return installed_files + + def create_uninstaller(self): + uninstaller_filename = os.path.join( + self.install_scripts, "uninstall_" + APP_NAME) + + uninstaller = """\ +#!/usr/bin/env python +import errno, os, os.path, sys + +print 'Uninstall %(name)s' + +answer = raw_input('Are you sure that you want to uninstall ' + '%(name)s (yes/no) ') + +if answer != 'yes' and answer != 'y': + print 'Not uninstalling.' + sys.exit(0) + +""" % {'name': APP_NAME} + + installed_files = [] + for output in self.get_installed_files(): + if self.root is not None: + # If we have a root (DESTDIR), we need to strip it off the + # front of paths so the uninstaller runs on the target host. + # The path manipulations are tricky, but made easier because + # the uninstaller only has to run on Unix. + if not path_startswith(output, self.root): + # This should never happen (everything gets installed + # inside the root), but if it does, be safe and don't + # delete anything. + uninstaller += ("print '%s was not installed inside " + "the root %s; skipping.'\n" % (output, self.root)) + continue + output = path_strip_prefix(output, self.root) + assert os.path.isabs(output) + installed_files.append(output) + + uninstaller += """\ +INSTALLED_FILES = ( +""" + for file in installed_files: + uninstaller += " %s,\n" % repr(file) + uninstaller += """\ +) + +# Split the list into lists of files and directories. +files = [] +dirs = [] +for path in INSTALLED_FILES: + if os.path.isfile(path) or os.path.islink(path): + files.append(path) + elif os.path.isdir(path): + dirs.append(path) +# Delete the files. +for file in files: + print "Removing '%s'." % file + try: + os.remove(file) + except OSError, e: + print >> sys.stderr, ' Error: %s.' % str(e) +# Delete the directories. First reverse-sort the normalized paths by +# length so that child directories are deleted before their parents. +dirs = [os.path.normpath(dir) for dir in dirs] +dirs.sort(key = len, reverse = True) +for dir in dirs: + try: + print "Removing the directory '%s'." % dir + os.rmdir(dir) + except OSError, e: + if e.errno == errno.ENOTEMPTY: + print "Directory '%s' not empty; not removing." % dir + else: + print >> sys.stderr, str(e) +""" + + uninstaller_file = open(uninstaller_filename, 'w') + uninstaller_file.write(uninstaller) + uninstaller_file.close() + + # Set exec bit for uninstaller + mode = ((os.stat(uninstaller_filename)[ST_MODE]) | 0555) & 07777 + os.chmod(uninstaller_filename, mode) + + def set_modules_path(self): + app_file_name = os.path.join(self.install_scripts, APP_NAME) + # Find where the modules are installed. distutils will put them in + # self.install_lib, but that path can contain the root (DESTDIR), so we + # must strip it off if necessary. + modules_dir = self.install_lib + if self.root is not None: + modules_dir = path_strip_prefix(modules_dir, self.root) + + app_file = open(app_file_name, "r") + lines = app_file.readlines() + app_file.close() + + for i in range(len(lines)): + if re.match(r'^INSTALL_LIB =', lines[i]): + lines[i] = "INSTALL_LIB = %s\n" % repr(modules_dir) + break + else: + raise ValueError( + "INSTALL_LIB replacement not found in %s" % app_file_name) + + app_file = open(app_file_name, "w") + app_file.writelines(lines) + app_file.close() + + def write_installed_files(self): + """Write a list of installed files for use by the uninstall command. + This is similar to what happens with the --record option except that it + doesn't strip off the installation root, if any. File names containing + newline characters are not handled.""" + if INSTALLED_FILES_NAME == self.record: + distutils.log.warn("warning: installation record is overwriting " + "--record file '%s'." % self.record) + f = open(INSTALLED_FILES_NAME, "w") + try: + for output in self.get_installed_files(): + assert "\n" not in output + print >> f, output + finally: + f.close() + + +class my_uninstall(distutils.cmd.Command): + """A distutils command that performs uninstallation. It reads the list of + installed files written by the install command.""" + + command_name = "uninstall" + description = "uninstall installed files recorded in '%s'" % ( + INSTALLED_FILES_NAME) + user_options = [] + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + def run(self): + # Read the list of installed files. + try: + f = open(INSTALLED_FILES_NAME, "r") + except IOError, e: + if e.errno == errno.ENOENT: + log.error("Couldn't open the installation record '%s'. " + "Have you installed yet?" % INSTALLED_FILES_NAME) + return + installed_files = [file.rstrip("\n") for file in f.readlines()] + f.close() + # Delete the installation record too. + installed_files.append(INSTALLED_FILES_NAME) + # Split the list into lists of files and directories. + files = [] + dirs = [] + for path in installed_files: + if os.path.isfile(path) or os.path.islink(path): + files.append(path) + elif os.path.isdir(path): + dirs.append(path) + # Delete the files. + for file in files: + log.info("Removing '%s'." % file) + try: + if not self.dry_run: + os.remove(file) + except OSError, e: + log.error(str(e)) + # Delete the directories. First reverse-sort the normalized paths by + # length so that child directories are deleted before their parents. + dirs = [os.path.normpath(dir) for dir in dirs] + dirs.sort(key=len, reverse=True) + for dir in dirs: + try: + log.info("Removing the directory '%s'." % dir) + if not self.dry_run: + os.rmdir(dir) + except OSError, e: + if e.errno == errno.ENOTEMPTY: + log.info("Directory '%s' not empty; not removing." % dir) + else: + log.error(str(e)) distutils.core.setup(name=u"ndiff", scripts=[u"scripts/ndiff"], py_modules=[u"ndiff"], data_files=[(u"share/man/man1", [u"docs/ndiff.1"])], - cmdclass={"install_egg_info": null_command, "install": checked_install}) + cmdclass={ + "install_egg_info": null_command, + "install": checked_install, + "uninstall": my_uninstall + })