| 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())