Current File : //lib/svc/share/migrate_shared_files.py
#!/usr/bin/python2.7

#
# Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved.
#

# This program relocates any files (and copies any directories) found
# under the first argument to the second argument, using the third
# argument as a tag as part of the user messages explaining what is
# happening.
#
# If a file by the same path already exists, the event is logged to stderr
# but the command still exits with a zero exit code.  Such files have
# ".NOT-MIGRATED-EXISTS-" prepended to their name. The following will cause
# the command to exit with a non-zero code:
#
#  Attempts to change an objects type (file > dir, dir > file)
#
# Only files, directories and symlinks are moved, attempts to move different
# filetypes are logged, but will not cause a non-zero exit code.
# All actions are logged to stderr.  Overall progress is logged to stdout.

import curses
import os
from stat import *
from errno import *
import sys

def _meg(number):
        return int(number / 1024 / 1024)

class MigrationProgress(object):
        """A class to keep the user informed of our progress while
        migrating directory contents.  We only output progress
        information if we're moving more than a certain amount of data."""

        def __init__(self, source, dest, svc):
                """Initialise our progress meter, with 'source' as the directory
                we intend to migrate, 'dest' as the location to migrate to, and
                'svc' as the name of the service performing the migration."""

                self.sizes = {}
                self.total = 0
                self.completed = 0
                self.print_progress = False

                # How many megabytes of data we must be moving before
                # choosing to output progress
                self.threshold = 200

                self.clear_eol = ""
                self.cr = "\n"

                self.banner = "%(svc)s moving data to %(dest)s:" % locals()
                total = 0

                for dirpath, dirnames, filenames in os.walk(source):
                        for name in filenames:
                                path = os.path.join(dirpath, name)
                                size = 0
                                if os.path.isfile(path) and not \
                                    os.path.islink(path):
                                        size = os.path.getsize(path)
                                        self.sizes[path] = size
                                self.total += size
                                # if we're migrating a lot of data, merely
                                # working out how much data is involved can
                                # take some time - once we go over a certain
                                # threshold, we tell the user what we're doing.
                                if not self.print_progress and \
                                    _meg(self.total) > self.threshold:
                                            os.environ.setdefault("TERM",
                                                "sun-color")
                                            self.setup_term()
                                            print self.banner,
                                            sys.stdout.flush()
                                            self.print_progress = True

        def setup_term(self):
                if not os.isatty(sys.stdout.fileno()):
                        return
                curses.setupterm()
                self.clear_eol = curses.tigetstr("el")
                self.cr = curses.tigetstr("cr")
                if not self.clear_eol:
                        self.clear_eol = ""
                if not self.cr:
                        self.cr = "\n"

        def update(self, file_name):
                """update our progress meter."""

                if not self.print_progress:
                        return

                print self.cr,

                last_prog = _meg(self.completed)
                self.completed += self.sizes[file_name]
                cur_prog = _meg(self.completed)
                if cur_prog > last_prog:
                        s = (self.banner + " %d/%d MB" %
                            (_meg(self.completed), _meg(self.total)))
                        sys.stdout.write(s + self.clear_eol)
                        sys.stdout.flush()

        def done(self):
                s = self.cr + self.banner + " Complete."
                sys.stdout.write(s + self.clear_eol + "\n")
                sys.stdout.flush()


progress = None
# the top level dir being migrated, to use when resolving symlinks
source_root = None

def main_func():
        global progress
        global source_root
        if len(sys.argv) != 4:
                logit("Error: must be called with source, dest and FMRI "
                    "arguments")
                return 2

        src, dest, fmri = sys.argv[1:4]
        source_root = os.path.abspath(src)
        src = source_root
        dest = os.path.abspath(dest)
	for path in src, dest:
                no_dir = False
                if not os.path.isdir(path):
                        no_dir = True
		        logit("Error: %s is not a directory" % path)
                if no_dir:
                        return 1

        progress = MigrationProgress(src, dest, fmri)
        ret = migrate_dir(src, dest)
        if progress.print_progress:
                progress.done()
        if ret > 0:
                logit("Error: Unable to move some contents of %s to %s." %
                    (src, dest))
                return 1
        return 0

def logit(string):
        """Show the string on the stderr"""
        print >> sys.stderr, "%s: %s" % (sys.argv[0], string)

def migrate_symlink(source, dest):
        """Tries to migrate a symlink from source to dest_path,
        returns False if the source is not a symlink, or there were problems
        trying to move the file. We assume that source exists. If the
        destination exists and is a symlink, we rename the source and return.
        """
        try:
                target = os.readlink(source)
                reltgt = target
                try:
                        d_st = os.lstat(dest)
                except OSError, e:
                        d_st = None

                # an existing symlink at the destination causes
                # the source to be renamed and we return immediately
                if d_st and S_ISLNK(d_st.st_mode):
                        source_dir = os.path.dirname(source)
                        source_base = os.path.basename(source)
                        new_name = os.path.join(source_dir,
                            ".NOT-MIGRATED-EXISTS-%s" % source_base)
                        os.rename(source, new_name)
                        logit("Info: %s exists already; moved to %s" %
                            (dest, new_name))
                        return True

                # we can deal with absolute links, or links whose targets are
                # going to be moved too, so we'll try to resolve the target
                if not os.path.isabs(target):
                        reltgt = os.path.join(os.path.dirname(source), target)
                        reltgt = os.path.normpath(reltgt)

                if reltgt.startswith(source_root) or os.path.isabs(target):
                        os.symlink(target, dest)
                        os.unlink(source)
                        logit("Info: migrated link: %s" % (dest))
                        return True

                # we've got a relative link that's linked to somewhere above
                # the source_root. This is impossible to fix, since we don't
                # know where this link was migrated from, and so don't know
                # what it should be relative to.
                logit("Error: Unable to move relative link from %s to %s." %
                    (source, dest))
                return False
        except Exception, e:
                logit("Error: Problem moving symlink for %s: %s" % (source, e))
                return False

def migrate_dir(source_path, dest_path, files=[]):
        """ handle migration"""
        exit_code = 0
        for f in os.listdir(source_path):
                if f.startswith(".NOT-MIGRATED-EXISTS-"):
                        continue
                source = os.path.join(source_path, f)
                dest = os.path.join(dest_path, f)
                try:
                        s_st = os.lstat(source)
                except OSError, e:
                        if e.errno == os.errno.ENOENT:
                                logit("Error: unable to migrate %s: "
                                    "no such file or directory" % source)
                        else:
                               logit("Error: cannot migrate %s unable to stat"
                                   "existing file at %s: %s" % (source, dest,e))
                        exit_code += 1
                        continue

                # Deal with possible symlinks first.
                if S_ISLNK(s_st.st_mode):
                        if not migrate_symlink(source, dest):
                                exit_code += 1
                        continue
                else:
                        s_st = os.stat(source)

                if S_ISDIR(s_st.st_mode):
                        try:
                                d_st = os.stat(dest)
                                # ensure mode/ownership changes are preserved
                                if d_st.st_uid != s_st.st_uid or \
                                    d_st.st_gid != s_st.st_gid:
                                        os.chown(dest, s_st.st_uid, s_st.st_gid)
                                        logit("Info: changed ownership on %s" %
                                            dest)
                                if d_st.st_mode != s_st.st_mode:
                                        os.chmod(dest, s_st.st_mode)
                                        logit("Info: changed mode on %s" % dest)
                        except OSError, e:
                                # if the dest doesn't exist, create it
                                if e.errno == os.errno.ENOENT:
                                        os.mkdir(dest)
                                        os.chown(dest, s_st.st_uid, s_st.st_gid)
                                        os.chmod(dest, s_st.st_mode)
                                        logit("Info: migrated directory %s" %
                                            dest)
                                        exit_code += migrate_dir(source, dest)
                                        continue
                                # otherwise, something else is wrong
                                logit("Error: unable to migrate %s: %s" %
                                     (source, e))
                                exit_code += 1
                                continue

                        if S_ISDIR(d_st.st_mode):
                                exit_code += migrate_dir(source, dest)
                        else:
                                # destination exists and is not a directory
                                logit("Error: %s is dir; %s is not" % (source,
                                    dest))
                                return 1
                elif S_ISREG(s_st.st_mode):
                        try:
                                d_st = os.stat(dest)
                        except OSError, e:
                                if e.errno == os.errno.ENOENT:
                                        # time to copy the file
                                        d_fd = None
                                        s_fd = None
                                        try:
                                                s_fd = open(source, "rb")
                                                d_fd = open(dest, "wb")
                                                while True:
                                                        buff = s_fd.read(
                                                            128*1024)
                                                        if buff == "":
                                                                break
                                                        d_fd.write(buff)
                                                s_fd.close()
                                                d_fd.close()
                                                os.chown(dest, s_st.st_uid,
                                                    s_st.st_gid)
                                                os.utime(dest, (s_st.st_atime,
                                                    s_st.st_mtime))
                                                os.chmod(dest, s_st.st_mode)
                                                os.unlink(source)
                                        except Exception, e:
                                                logit("Error: cannot migrate "
                                                    "file %s:%s" % (source, e))
                                                exit_code += 1
                                                continue
                                        finally:
                                                if s_fd:
                                                        s_fd.close()
                                                if d_fd:
                                                        d_fd.close()
                                        logit("Info: migrated file: %s" %
                                            (dest))
                                        progress.update(source)
                                        continue
                                else:
                                        logit("Error: cannot migrate %s unable "
                                            "to stat existing file at %s: %s" %
                                            (source, dest,e))
                                        exit_code += 1
                                        continue

                        # file already exists; rename file so we ignore from now
                        # on.
                        try:
                                d_st = os.stat(dest)
                        except Exception, e:
                                logit("Error: cannot migrate %s unable to stat"
                                    "existing file at %s: %s" %
                                    (source, dest,e))
                                exit_code += 1
                                continue
                        if S_ISREG(d_st.st_mode):
                                new_name = os.path.join(source_path,
                                    ".NOT-MIGRATED-EXISTS-%s" % f)
                                os.rename(source, new_name)
                                logit("Info: %s exists already; moved to %s" %
                                    (dest, new_name))
                        else:
                                exit_code += 1
                                logit("Error: %s is file; %s is not" % (source,
                                    dest))
                else:
                        logit("Warning: %s is not file, directory or symlink; "
                            "not migrated" % source)


        return exit_code

if __name__ == "__main__":
        sys.exit(main_func())