OLD | NEW |
(Empty) | |
| 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. |
| 4 |
| 5 """Creates a tree of hardlinks, symlinks or copy the inputs files.""" |
| 6 |
| 7 import ctypes |
| 8 import logging |
| 9 import os |
| 10 import shutil |
| 11 import sys |
| 12 |
| 13 |
| 14 # Types of action accepted by recreate_tree(). |
| 15 DRY_RUN, HARDLINK, SYMLINK, COPY = range(5)[1:] |
| 16 |
| 17 |
| 18 class MappingError(OSError): |
| 19 """Failed to recreate the tree.""" |
| 20 pass |
| 21 |
| 22 |
| 23 def os_link(source, link_name): |
| 24 """Add support for os.link() on Windows.""" |
| 25 if sys.platform == 'win32': |
| 26 if not ctypes.windll.kernel32.CreateHardLinkW( |
| 27 unicode(link_name), unicode(source), 0): |
| 28 raise OSError() |
| 29 else: |
| 30 os.link(source, link_name) |
| 31 |
| 32 |
| 33 def _process_item(outdir, indir, relfile, action, blacklist): |
| 34 """Processes an input file. |
| 35 |
| 36 Returns True if processed. |
| 37 """ |
| 38 logging.debug( |
| 39 '_process_item(%s, %s, %s, %s, %s)' % ( |
| 40 outdir, indir, relfile, action, blacklist)) |
| 41 if blacklist and blacklist(relfile): |
| 42 return False |
| 43 infile = os.path.normpath(os.path.join(indir, relfile)) |
| 44 if not os.path.isfile(infile): |
| 45 raise MappingError('%s doesn\'t exist' % infile) |
| 46 |
| 47 if action == DRY_RUN: |
| 48 logging.info('Verified input: %s' % infile) |
| 49 return True |
| 50 |
| 51 outfile = os.path.normpath(os.path.join(outdir, relfile)) |
| 52 logging.debug('Mapping %s to %s' % (infile, outfile)) |
| 53 outsubdir = os.path.dirname(outfile) |
| 54 if not os.path.isdir(outsubdir): |
| 55 os.makedirs(outsubdir) |
| 56 |
| 57 if os.path.isfile(outfile): |
| 58 raise MappingError('%s already exist' % outfile) |
| 59 |
| 60 if action == COPY: |
| 61 shutil.copy(infile, outfile) |
| 62 elif action == SYMLINK and sys.platform != 'win32': |
| 63 os.symlink(infile, outfile) |
| 64 elif action == HARDLINK: |
| 65 try: |
| 66 os_link(infile, outfile) |
| 67 except OSError: |
| 68 # Probably a different file system. |
| 69 logging.warn( |
| 70 'Failed to hardlink, failing back to copy %s to %s' % ( |
| 71 infile, outfile)) |
| 72 shutil.copy(infile, outfile) |
| 73 else: |
| 74 raise ValueError('Unknown mapping action %s' % action) |
| 75 return True |
| 76 |
| 77 |
| 78 def _recurse_dir(outdir, indir, subdir, action, blacklist): |
| 79 """Processes a directory and all its decendents.""" |
| 80 logging.debug( |
| 81 '_recurse_dir(%s, %s, %s, %s, %s)' % ( |
| 82 outdir, indir, subdir, action, blacklist)) |
| 83 root = os.path.join(indir, subdir) |
| 84 for dirpath, dirnames, filenames in os.walk(root): |
| 85 # Convert the absolute path to subdir + relative subdirectory. |
| 86 relpath = dirpath[len(indir)+1:] |
| 87 |
| 88 for filename in filenames: |
| 89 relfile = os.path.join(relpath, filename) |
| 90 _process_item(outdir, indir, relfile, action, blacklist) |
| 91 |
| 92 for index, dirname in enumerate(dirnames): |
| 93 # Do not process blacklisted directories. |
| 94 if blacklist(os.path.normpath(os.path.join(relpath, dirname))): |
| 95 del dirnames[index] |
| 96 |
| 97 |
| 98 def recreate_tree(outdir, indir, infiles, action, blacklist): |
| 99 """Creates a new tree with only the input files in it. |
| 100 |
| 101 Arguments: |
| 102 outdir: Temporary directory to create the files in. |
| 103 indir: Root directory the infiles are based in. |
| 104 infiles: List of files to map from |indir| to |outdir|. |
| 105 action: See assert below. |
| 106 blacklist: Files to unconditionally ignore. |
| 107 """ |
| 108 logging.debug( |
| 109 'recreate_tree(%s, %s, %s, %s, %s)' % ( |
| 110 outdir, indir, infiles, action, blacklist)) |
| 111 logging.info('Mapping from %s to %s' % (indir, outdir)) |
| 112 |
| 113 assert action in (DRY_RUN, HARDLINK, SYMLINK, COPY) |
| 114 # Both need to be a local path. |
| 115 indir = os.path.normpath(indir) |
| 116 if not os.path.isdir(indir): |
| 117 raise MappingError('%s is not a directory' % indir) |
| 118 |
| 119 # Do not call abspath until it was verified the directory exists. |
| 120 indir = os.path.abspath(indir) |
| 121 |
| 122 if action != DRY_RUN: |
| 123 outdir = os.path.normpath(outdir) |
| 124 if not os.path.isdir(outdir): |
| 125 logging.info ('Creating %s' % outdir) |
| 126 os.makedirs(outdir) |
| 127 # Do not call abspath until the directory exists. |
| 128 outdir = os.path.abspath(outdir) |
| 129 |
| 130 for relfile in infiles: |
| 131 if os.path.isabs(relfile): |
| 132 raise MappingError('Can\'t map absolute path %s' % relfile) |
| 133 infile = os.path.normpath(os.path.join(indir, relfile)) |
| 134 if not infile.startswith(indir): |
| 135 raise MappingError('Can\'t map file %s outside %s' % (infile, indir)) |
| 136 |
| 137 isdir = os.path.isdir(infile) |
| 138 if isdir and not relfile.endswith('/'): |
| 139 raise MappingError( |
| 140 'Input directory %s must have a trailing slash' % infile) |
| 141 if not isdir and relfile.endswith('/'): |
| 142 raise MappingError( |
| 143 'Input file %s must not have a trailing slash' % infile) |
| 144 if isdir: |
| 145 _recurse_dir(outdir, indir, relfile, action, blacklist) |
| 146 else: |
| 147 _process_item(outdir, indir, relfile, action, blacklist) |
| 148 |
| 149 |
| 150 def _set_write_bit(path, read_only): |
| 151 mode = os.stat(path).st_mode |
| 152 if read_only: |
| 153 mode = mode & 0500 |
| 154 else: |
| 155 mode = mode | 0200 |
| 156 if hasattr(os, 'lchmod'): |
| 157 os.lchmod(path, mode) # pylint: disable=E1101 |
| 158 else: |
| 159 # TODO(maruel): Implement proper DACL modification on Windows. |
| 160 os.chmod(path, mode) |
| 161 |
| 162 |
| 163 def make_writable(root, read_only): |
| 164 """Toggle the writable bit on a directory tree.""" |
| 165 root = os.path.abspath(root) |
| 166 for dirpath, dirnames, filenames in os.walk(root, topdown=True): |
| 167 for filename in filenames: |
| 168 _set_write_bit(os.path.join(dirpath, filename), read_only) |
| 169 |
| 170 for dirname in dirnames: |
| 171 _set_write_bit(os.path.join(dirpath, dirname), read_only) |
OLD | NEW |