#!/usr/bin/env python
# Copyright (C) 2007 Oracle.  All rights reserved.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public
# License v2 as published by the Free Software Foundation.
# 
# This program 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 this program; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 021110-1307, USA.
#
import sys, os, stat, fcntl
from optparse import OptionParser

def copylink(srcname, dst, filename, statinfo, force_name):
    dstname = os.path.join(dst, force_name or filename)
    if not os.path.exists(dstname):
        link_target = os.readlink(srcname)
        os.symlink(link_target, dstname)

def copydev(srcname, dst, filename, statinfo, force_name):
    devbits = statinfo.st_mode & (stat.S_IFBLK | stat.S_IFCHR)
    mode = stat.S_IMODE(statinfo.st_mode) | devbits
    dstname = os.path.join(dst, force_name or filename)
    if not os.path.exists(dstname):
        os.mknod(dstname, mode, statinfo.st_rdev)

def copyfile(srcname, dst, filename, statinfo, force_name):
    written = 0
    dstname = os.path.join(dst, force_name or filename)

    st_mode = statinfo.st_mode
    if stat.S_ISLNK(st_mode):
        copylink(srcname, dst, part, statinfo, None)
        return
    elif stat.S_ISBLK(st_mode) or stat.S_ISCHR(st_mode):
        copydev(srcname, dst, part, statinfo, None)
        return
    elif not stat.S_ISREG(st_mode):
        return

    try:
        os.unlink(dstname)
    except:
        pass

    if options.link:
        os.link(srcname, dstname)
        return

    dstf = file(dstname, 'w')
    srcf = file(srcname, 'r')

    ret = 1

    try:
        if not options.copy:
            ret = fcntl.ioctl(dstf.fileno(), 1074041865, srcf.fileno())
    except:
        pass

    if ret != 0:
        while True:
            buf = srcf.read(256 * 1024)
            if not buf:
                break
            written += len(buf)
            dstf.write(buf)

    os.chmod(dstname, stat.S_IMODE(statinfo.st_mode))
    os.chown(dstname, statinfo.st_uid, statinfo.st_gid)


usage = "usage: %prog [options]"
parser = OptionParser(usage=usage)
parser.add_option("-l", "--link", help="Create hard links", default=False,
                  action="store_true")
parser.add_option("-c", "--copy", help="Copy file bytes (don't cow)",
                  default=False, action="store_true")

(options,args) = parser.parse_args()

if len(args) < 2:
    sys.stderr.write("source or destination not specified\n")
    sys.exit(1)

if options.link and options.copy:
    sys.stderr.write("Both -l and -c specified, using copy mode\n")
    options.link = False


total_args = len(args)
src_args = total_args - 1
orig_dst = args[-1]

if src_args > 1:
    if not os.path.exists(orig_dst):
        os.makedirs(orig_dst)
    if not os.path.isdir(orig_dst):
        sys.stderr.write("Destination %s is not a directory\n" % orig_dst)
        exit(1)

for srci in xrange(0, src_args):
    src = args[srci]
    if os.path.isfile(src):
        statinfo = os.lstat(src)
        force_name = None
        if src_args == 1:
            if not os.path.isdir(orig_dst):
                force_name = os.path.basename(orig_dst)
                orig_dst = os.path.dirname(orig_dst) or '.'
        copyfile(src, orig_dst, os.path.basename(src), statinfo, force_name)
        continue

    if src_args > 1 or os.path.exists(orig_dst):
        dst = os.path.join(orig_dst, os.path.basename(src))
    else:
        dst = orig_dst

    if not os.path.exists(dst):
        os.makedirs(dst)
        statinfo = os.stat(src)
        os.chmod(dst, stat.S_IMODE(statinfo.st_mode))
        os.chown(dst, statinfo.st_uid, statinfo.st_gid)
        
    iter = os.walk(src, topdown=True)

    for (dirpath, dirnames, filenames) in iter:
        for x in dirnames:
            srcname = os.path.join(dirpath, x)
            statinfo = os.lstat(srcname)

            part = os.path.relpath(srcname, src)

            if stat.S_ISLNK(statinfo.st_mode):
                copylink(srcname, dst, part, statinfo, None)
                continue

            dst_dir = os.path.join(dst, part)
            if not os.path.exists(dst_dir):
                os.makedirs(dst_dir)

            os.chmod(dst_dir, stat.S_IMODE(statinfo.st_mode))
            os.chown(dst_dir, statinfo.st_uid, statinfo.st_gid)

        for f in filenames:
            srcname = os.path.join(dirpath, f)
            part = os.path.relpath(srcname, src)

            statinfo = os.lstat(srcname)
            copyfile(srcname, dst, part, statinfo, None)