OLD | NEW |
1 # Copyright 2014 The LUCI Authors. All rights reserved. | 1 # Copyright 2014 The LUCI Authors. All rights reserved. |
2 # Use of this source code is governed under the Apache License, Version 2.0 | 2 # Use of this source code is governed under the Apache License, Version 2.0 |
3 # that can be found in the LICENSE file. | 3 # that can be found in the LICENSE file. |
4 | 4 |
5 """Understands .isolated files and can do local operations on them.""" | 5 """Understands .isolated files and can do local operations on them.""" |
6 | 6 |
7 import hashlib | 7 import hashlib |
8 import json | 8 import json |
9 import logging | 9 import logging |
10 import os | 10 import os |
(...skipping 291 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
302 expand_directory_and_symlink( | 302 expand_directory_and_symlink( |
303 indir, relfile, blacklist, follow_symlinks)) | 303 indir, relfile, blacklist, follow_symlinks)) |
304 except MappingError as e: | 304 except MappingError as e: |
305 if not ignore_broken_items: | 305 if not ignore_broken_items: |
306 raise | 306 raise |
307 logging.info('warning: %s', e) | 307 logging.info('warning: %s', e) |
308 return outfiles | 308 return outfiles |
309 | 309 |
310 | 310 |
311 @tools.profile | 311 @tools.profile |
312 def file_to_metadata(filepath, prevdict, read_only, algo): | 312 def file_to_metadata(filepath, prevdict, read_only, algo, collapse_symlinks): |
313 """Processes an input file, a dependency, and return meta data about it. | 313 """Processes an input file, a dependency, and return meta data about it. |
314 | 314 |
315 Behaviors: | 315 Behaviors: |
316 - Retrieves the file mode, file size, file timestamp, file link | 316 - Retrieves the file mode, file size, file timestamp, file link |
317 destination if it is a file link and calcultate the SHA-1 of the file's | 317 destination if it is a file link and calcultate the SHA-1 of the file's |
318 content if the path points to a file and not a symlink. | 318 content if the path points to a file and not a symlink. |
319 | 319 |
320 Arguments: | 320 Arguments: |
321 filepath: File to act on. | 321 filepath: File to act on. |
322 prevdict: the previous dictionary. It is used to retrieve the cached sha-1 | 322 prevdict: the previous dictionary. It is used to retrieve the cached sha-1 |
323 to skip recalculating the hash. Optional. | 323 to skip recalculating the hash. Optional. |
324 read_only: If 1 or 2, the file mode is manipulated. In practice, only save | 324 read_only: If 1 or 2, the file mode is manipulated. In practice, only save |
325 one of 4 modes: 0755 (rwx), 0644 (rw), 0555 (rx), 0444 (r). On | 325 one of 4 modes: 0755 (rwx), 0644 (rw), 0555 (rx), 0444 (r). On |
326 windows, mode is not set since all files are 'executable' by | 326 windows, mode is not set since all files are 'executable' by |
327 default. | 327 default. |
328 algo: Hashing algorithm used. | 328 algo: Hashing algorithm used. |
| 329 collapse_symlinks: True if symlinked files should be treated like they were |
| 330 the normal underlying file. |
329 | 331 |
330 Returns: | 332 Returns: |
331 The necessary dict to create a entry in the 'files' section of an .isolated | 333 The necessary dict to create a entry in the 'files' section of an .isolated |
332 file. | 334 file. |
333 """ | 335 """ |
334 # TODO(maruel): None is not a valid value. | 336 # TODO(maruel): None is not a valid value. |
335 assert read_only in (None, 0, 1, 2), read_only | 337 assert read_only in (None, 0, 1, 2), read_only |
336 out = {} | 338 out = {} |
337 # Always check the file stat and check if it is a link. The timestamp is used | 339 # Always check the file stat and check if it is a link. The timestamp is used |
338 # to know if the file's content/symlink destination should be looked into. | 340 # to know if the file's content/symlink destination should be looked into. |
339 # E.g. only reuse from prevdict if the timestamp hasn't changed. | 341 # E.g. only reuse from prevdict if the timestamp hasn't changed. |
340 # There is the risk of the file's timestamp being reset to its last value | 342 # There is the risk of the file's timestamp being reset to its last value |
341 # manually while its content changed. We don't protect against that use case. | 343 # manually while its content changed. We don't protect against that use case. |
342 try: | 344 try: |
343 filestats = os.lstat(filepath) | 345 if collapse_symlinks: |
| 346 # os.stat follows symbolic links |
| 347 filestats = os.stat(filepath) |
| 348 else: |
| 349 # os.lstat does not follow symbolic links, and thus preserves them. |
| 350 filestats = os.lstat(filepath) |
344 except OSError: | 351 except OSError: |
345 # The file is not present. | 352 # The file is not present. |
346 raise MappingError('%s is missing' % filepath) | 353 raise MappingError('%s is missing' % filepath) |
347 is_link = stat.S_ISLNK(filestats.st_mode) | 354 is_link = stat.S_ISLNK(filestats.st_mode) |
348 | 355 |
349 if sys.platform != 'win32': | 356 if sys.platform != 'win32': |
350 # Ignore file mode on Windows since it's not really useful there. | 357 # Ignore file mode on Windows since it's not really useful there. |
351 filemode = stat.S_IMODE(filestats.st_mode) | 358 filemode = stat.S_IMODE(filestats.st_mode) |
352 # Remove write access for group and all access to 'others'. | 359 # Remove write access for group and all access to 'others'. |
353 filemode &= ~(stat.S_IWGRP | stat.S_IRWXO) | 360 filemode &= ~(stat.S_IWGRP | stat.S_IRWXO) |
(...skipping 213 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
567 data['files'] = dict( | 574 data['files'] = dict( |
568 (k.replace(wrong_path_sep, os.path.sep), v) | 575 (k.replace(wrong_path_sep, os.path.sep), v) |
569 for k, v in data['files'].iteritems()) | 576 for k, v in data['files'].iteritems()) |
570 for v in data['files'].itervalues(): | 577 for v in data['files'].itervalues(): |
571 if 'l' in v: | 578 if 'l' in v: |
572 v['l'] = v['l'].replace(wrong_path_sep, os.path.sep) | 579 v['l'] = v['l'].replace(wrong_path_sep, os.path.sep) |
573 if 'relative_cwd' in data: | 580 if 'relative_cwd' in data: |
574 data['relative_cwd'] = data['relative_cwd'].replace( | 581 data['relative_cwd'] = data['relative_cwd'].replace( |
575 wrong_path_sep, os.path.sep) | 582 wrong_path_sep, os.path.sep) |
576 return data | 583 return data |
OLD | NEW |