def copy_all(self): """Core copy process. This is the most important step of this stage. It clones live filesystem into a local partition in the selected hard disk.""" self.db.progress('START', 0, 100, 'ubiquity/install/title') self.db.progress('INFO', 'ubiquity/install/copying') fs_size = os.path.join(self.casper_path, 'filesystem.size') if os.path.exists(fs_size): with open(fs_size) as total_size_fp: total_size = int(total_size_fp.readline()) else: # Fallback in case an Ubuntu derivative forgets to put # /casper/filesystem.size on the CD, or to account for things # like CD->USB transformation tools that don't copy this file. # This is slower than just reading the size from a file, but # better than crashing. # # Obviously doing os.walk() twice is inefficient, but I'd rather # not suck the list into ubiquity's memory, and I'm guessing # that the kernel's dentry cache will avoid most of the slowness # anyway. total_size = 0 for dirpath, dirnames, filenames in os.walk(self.source): for name in dirnames + filenames: fqpath = os.path.join(dirpath, name) total_size += os.lstat(fqpath).st_size # Progress bar handling: # We sample progress every half-second (assuming time.time() gives # us sufficiently good granularity) and use the average of progress # over the last minute or so to decide how much time remains. We # don't bother displaying any progress for the first ten seconds in # order to allow things to settle down, and we only update the "time # remaining" indicator at most every two seconds after that. copy_progress = 0 copied_size = 0 directory_times = [] time_start = time.time() times = [(time_start, copied_size)] long_enough = False time_last_update = time_start debug = 'UBIQUITY_DEBUG' in os.environ if self.db.get('ubiquity/install/md5_check') == 'false': md5_check = False else: md5_check = True # Increase kernel flush times during bulk data copying to make it # more likely that small files are packed contiguously, which should # speed up initial boot times. dirty_writeback_centisecs = None dirty_expire_centisecs = None if os.path.exists('/proc/sys/vm/dirty_writeback_centisecs'): with open('/proc/sys/vm/dirty_writeback_centisecs') as dwc: dirty_writeback_centisecs = int(dwc.readline()) with open('/proc/sys/vm/dirty_writeback_centisecs', 'w') as dwc: print('3000\n', file=dwc) if os.path.exists('/proc/sys/vm/dirty_expire_centisecs'): with open('/proc/sys/vm/dirty_expire_centisecs') as dec: dirty_expire_centisecs = int(dec.readline()) with open('/proc/sys/vm/dirty_expire_centisecs', 'w') as dec: print('6000\n', file=dec) old_umask = os.umask(0) for dirpath, dirnames, filenames in os.walk(self.source): sp = dirpath[len(self.source) + 1:] for name in dirnames + filenames: relpath = os.path.join(sp, name) # /etc/fstab was legitimately created by partman, and # shouldn't be copied again. Similarly, /etc/crypttab may # have been legitimately created by the user-setup plugin. if relpath in ("etc/fstab", "etc/crypttab"): continue sourcepath = os.path.join(self.source, relpath) targetpath = os.path.join(self.target, relpath) st = os.lstat(sourcepath) # Is the path blacklisted? if (not stat.S_ISDIR(st.st_mode) and '/%s' % relpath in self.blacklist): if debug: syslog.syslog('Not copying %s' % relpath) continue # Remove the target if necessary and if we can. install_misc.remove_target(self.source, self.target, relpath, st) # Now actually copy source to target. mode = stat.S_IMODE(st.st_mode) if stat.S_ISLNK(st.st_mode): linkto = os.readlink(sourcepath) os.symlink(linkto, targetpath) elif stat.S_ISDIR(st.st_mode): if not os.path.isdir(targetpath): try: os.mkdir(targetpath, mode) except OSError as e: # there is a small window where update-apt-cache # can race with us since it creates # "/target/var/cache/apt/...". Hence, ignore # failure if the directory does now exist where # brief moments before it didn't. if e.errno != errno.EEXIST: raise elif stat.S_ISCHR(st.st_mode): os.mknod(targetpath, stat.S_IFCHR | mode, st.st_rdev) elif stat.S_ISBLK(st.st_mode): os.mknod(targetpath, stat.S_IFBLK | mode, st.st_rdev) elif stat.S_ISFIFO(st.st_mode): os.mknod(targetpath, stat.S_IFIFO | mode) elif stat.S_ISSOCK(st.st_mode): os.mknod(targetpath, stat.S_IFSOCK | mode) elif stat.S_ISREG(st.st_mode): install_misc.copy_file(self.db, sourcepath, targetpath, md5_check) # Copy metadata. copied_size += st.st_size os.lchown(targetpath, st.st_uid, st.st_gid) if not stat.S_ISLNK(st.st_mode): os.chmod(targetpath, mode) if stat.S_ISDIR(st.st_mode): directory_times.append( (targetpath, st.st_atime, st.st_mtime)) # os.utime() sets timestamp of target, not link elif not stat.S_ISLNK(st.st_mode): try: os.utime(targetpath, (st.st_atime, st.st_mtime)) except Exception: # We can live with timestamps being wrong. pass if (hasattr(os, "listxattr") and hasattr(os, "supports_follow_symlinks") and os.supports_follow_symlinks): try: attrnames = os.listxattr(sourcepath, follow_symlinks=False) for attrname in attrnames: attrvalue = os.getxattr(sourcepath, attrname, follow_symlinks=False) os.setxattr(targetpath, attrname, attrvalue, follow_symlinks=False) except OSError as e: if e.errno not in (errno.EPERM, errno.ENOTSUP, errno.ENODATA): raise if int((copied_size * 90) / total_size) != copy_progress: copy_progress = int((copied_size * 90) / total_size) self.db.progress('SET', 10 + copy_progress) time_now = time.time() if (time_now - times[-1][0]) >= 0.5: times.append((time_now, copied_size)) if not long_enough and time_now - times[0][0] >= 10: long_enough = True if long_enough and time_now - time_last_update >= 2: time_last_update = time_now while (time_now - times[0][0] > 60 and time_now - times[1][0] >= 60): times.pop(0) speed = ((times[-1][1] - times[0][1]) / (times[-1][0] - times[0][0])) if speed != 0: time_remaining = (int( (total_size - copied_size) / speed)) if time_remaining < 60: self.db.progress( 'INFO', 'ubiquity/install/copying_minute') # Apply timestamps to all directories now that the items within them # have been copied. for dirtime in directory_times: (directory, atime, mtime) = dirtime try: os.utime(directory, (atime, mtime)) except Exception: # I have no idea why I've been getting lots of bug reports # about this failing, but I really don't care. Ignore it. pass # Revert to previous kernel flush times. if dirty_writeback_centisecs is not None: with open('/proc/sys/vm/dirty_writeback_centisecs', 'w') as dwc: print(dirty_writeback_centisecs, file=dwc) if dirty_expire_centisecs is not None: with open('/proc/sys/vm/dirty_expire_centisecs', 'w') as dec: print(dirty_expire_centisecs, file=dec) # Try some possible locations for the kernel we used to boot. This # lets us save a couple of megabytes of CD space. bootdir = self.target_file('boot') kernel = self.find_cd_kernel() if kernel: prefix = os.path.basename(kernel).split('-', 1)[0] release = os.uname()[2] target_kernel = os.path.join(bootdir, '%s-%s' % (prefix, release)) copies = [] # ISO9660 images may have to use .efi rather than .efi.signed in # order to support being booted using isolinux, which must abide # by archaic 8.3 restrictions. for suffix in (".efi", ".efi.signed"): if os.path.exists(kernel + suffix): signed_kernel = kernel + suffix break else: signed_kernel = None if os.path.exists(kernel): copies.append((kernel, target_kernel)) elif signed_kernel is not None: # No unsigned kernel. We'll construct it using sbsigntool. copies.append((signed_kernel, target_kernel)) if signed_kernel is not None: copies.append((signed_kernel, "%s.efi.signed" % target_kernel)) for source, target in copies: osextras.unlink_force(target) install_misc.copy_file(self.db, source, target, md5_check) os.lchown(target, 0, 0) os.chmod(target, 0o644) st = os.lstat(source) try: os.utime(target, (st.st_atime, st.st_mtime)) except Exception: # We can live with timestamps being wrong. pass if not os.path.exists(kernel) and signed_kernel is not None: # Construct the unsigned kernel. subprocess.check_call(["sbattach", "--remove", target_kernel]) os.umask(old_umask) self.db.progress('SET', 100) self.db.progress('STOP')
def copy_all(self): """Core copy process. This is the most important step of this stage. It clones live filesystem into a local partition in the selected hard disk.""" self.db.progress('START', 0, 100, 'ubiquity/install/title') self.db.progress('INFO', 'ubiquity/install/copying') fs_size = os.path.join(self.casper_path, 'filesystem.size') if os.path.exists(fs_size): with open(fs_size) as total_size_fp: total_size = int(total_size_fp.readline()) else: # Fallback in case an Linux Mint derivative forgets to put # /casper/filesystem.size on the CD, or to account for things # like CD->USB transformation tools that don't copy this file. # This is slower than just reading the size from a file, but # better than crashing. # # Obviously doing os.walk() twice is inefficient, but I'd rather # not suck the list into ubiquity's memory, and I'm guessing # that the kernel's dentry cache will avoid most of the slowness # anyway. total_size = 0 for dirpath, dirnames, filenames in os.walk(self.source): for name in dirnames + filenames: fqpath = os.path.join(dirpath, name) total_size += os.lstat(fqpath).st_size # Progress bar handling: # We sample progress every half-second (assuming time.time() gives # us sufficiently good granularity) and use the average of progress # over the last minute or so to decide how much time remains. We # don't bother displaying any progress for the first ten seconds in # order to allow things to settle down, and we only update the "time # remaining" indicator at most every two seconds after that. copy_progress = 0 copied_size = 0 directory_times = [] time_start = time.time() times = [(time_start, copied_size)] long_enough = False time_last_update = time_start debug = 'UBIQUITY_DEBUG' in os.environ if self.db.get('ubiquity/install/md5_check') == 'false': md5_check = False else: md5_check = True # Increase kernel flush times during bulk data copying to make it # more likely that small files are packed contiguously, which should # speed up initial boot times. dirty_writeback_centisecs = None dirty_expire_centisecs = None if os.path.exists('/proc/sys/vm/dirty_writeback_centisecs'): with open('/proc/sys/vm/dirty_writeback_centisecs') as dwc: dirty_writeback_centisecs = int(dwc.readline()) with open('/proc/sys/vm/dirty_writeback_centisecs', 'w') as dwc: print('3000\n', file=dwc) if os.path.exists('/proc/sys/vm/dirty_expire_centisecs'): with open('/proc/sys/vm/dirty_expire_centisecs') as dec: dirty_expire_centisecs = int(dec.readline()) with open('/proc/sys/vm/dirty_expire_centisecs', 'w') as dec: print('6000\n', file=dec) old_umask = os.umask(0) for dirpath, dirnames, filenames in os.walk(self.source): sp = dirpath[len(self.source) + 1:] for name in dirnames + filenames: relpath = os.path.join(sp, name) # /etc/fstab was legitimately created by partman, and # shouldn't be copied again. Similarly, /etc/crypttab may # have been legitimately created by the user-setup plugin. if relpath in ("etc/fstab", "etc/crypttab"): continue sourcepath = os.path.join(self.source, relpath) targetpath = os.path.join(self.target, relpath) st = os.lstat(sourcepath) # Is the path blacklisted? if (not stat.S_ISDIR(st.st_mode) and '/%s' % relpath in self.blacklist): if debug: syslog.syslog('Not copying %s' % relpath) continue # Remove the target if necessary and if we can. install_misc.remove_target( self.source, self.target, relpath, st) # Now actually copy source to target. mode = stat.S_IMODE(st.st_mode) if stat.S_ISLNK(st.st_mode): linkto = os.readlink(sourcepath) os.symlink(linkto, targetpath) elif stat.S_ISDIR(st.st_mode): if not os.path.isdir(targetpath): try: os.mkdir(targetpath, mode) except OSError as e: # there is a small window where update-apt-cache # can race with us since it creates # "/target/var/cache/apt/...". Hence, ignore # failure if the directory does now exist where # brief moments before it didn't. if e.errno != errno.EEXIST: raise elif stat.S_ISCHR(st.st_mode): os.mknod(targetpath, stat.S_IFCHR | mode, st.st_rdev) elif stat.S_ISBLK(st.st_mode): os.mknod(targetpath, stat.S_IFBLK | mode, st.st_rdev) elif stat.S_ISFIFO(st.st_mode): os.mknod(targetpath, stat.S_IFIFO | mode) elif stat.S_ISSOCK(st.st_mode): os.mknod(targetpath, stat.S_IFSOCK | mode) elif stat.S_ISREG(st.st_mode): install_misc.copy_file( self.db, sourcepath, targetpath, md5_check) # Copy metadata. copied_size += st.st_size os.lchown(targetpath, st.st_uid, st.st_gid) if not stat.S_ISLNK(st.st_mode): os.chmod(targetpath, mode) if stat.S_ISDIR(st.st_mode): directory_times.append( (targetpath, st.st_atime, st.st_mtime)) # os.utime() sets timestamp of target, not link elif not stat.S_ISLNK(st.st_mode): try: os.utime(targetpath, (st.st_atime, st.st_mtime)) except Exception: # We can live with timestamps being wrong. pass if (hasattr(os, "listxattr") and hasattr(os, "supports_follow_symlinks") and os.supports_follow_symlinks): try: attrnames = os.listxattr( sourcepath, follow_symlinks=False) for attrname in attrnames: attrvalue = os.getxattr( sourcepath, attrname, follow_symlinks=False) os.setxattr( targetpath, attrname, attrvalue, follow_symlinks=False) except OSError as e: if e.errno not in ( errno.EPERM, errno.ENOTSUP, errno.ENODATA): raise if int((copied_size * 90) / total_size) != copy_progress: copy_progress = int((copied_size * 90) / total_size) self.db.progress('SET', 10 + copy_progress) time_now = time.time() if (time_now - times[-1][0]) >= 0.5: times.append((time_now, copied_size)) if not long_enough and time_now - times[0][0] >= 10: long_enough = True if long_enough and time_now - time_last_update >= 2: time_last_update = time_now while (time_now - times[0][0] > 60 and time_now - times[1][0] >= 60): times.pop(0) speed = ((times[-1][1] - times[0][1]) / (times[-1][0] - times[0][0])) if speed != 0: time_remaining = ( int((total_size - copied_size) / speed)) if time_remaining < 60: self.db.progress( 'INFO', 'ubiquity/install/copying_minute') # Apply timestamps to all directories now that the items within them # have been copied. for dirtime in directory_times: (directory, atime, mtime) = dirtime try: os.utime(directory, (atime, mtime)) except Exception: # I have no idea why I've been getting lots of bug reports # about this failing, but I really don't care. Ignore it. pass # Revert to previous kernel flush times. if dirty_writeback_centisecs is not None: with open('/proc/sys/vm/dirty_writeback_centisecs', 'w') as dwc: print(dirty_writeback_centisecs, file=dwc) if dirty_expire_centisecs is not None: with open('/proc/sys/vm/dirty_expire_centisecs', 'w') as dec: print(dirty_expire_centisecs, file=dec) # Try some possible locations for the kernel we used to boot. This # lets us save a couple of megabytes of CD space. bootdir = self.target_file('boot') kernel = self.find_cd_kernel() if kernel: prefix = os.path.basename(kernel).split('-', 1)[0] release = os.uname()[2] target_kernel = os.path.join(bootdir, '%s-%s' % (prefix, release)) copies = [] # ISO9660 images may have to use .efi rather than .efi.signed in # order to support being booted using isolinux, which must abide # by archaic 8.3 restrictions. for suffix in (".efi", ".efi.signed"): if os.path.exists(kernel + suffix): signed_kernel = kernel + suffix break else: signed_kernel = None if os.path.exists(kernel): copies.append((kernel, target_kernel)) elif signed_kernel is not None: # No unsigned kernel. We'll construct it using sbsigntool. copies.append((signed_kernel, target_kernel)) if signed_kernel is not None: copies.append((signed_kernel, "%s.efi.signed" % target_kernel)) for source, target in copies: osextras.unlink_force(target) install_misc.copy_file(self.db, source, target, md5_check) os.lchown(target, 0, 0) os.chmod(target, 0o644) st = os.lstat(source) try: os.utime(target, (st.st_atime, st.st_mtime)) except Exception: # We can live with timestamps being wrong. pass if not os.path.exists(kernel) and signed_kernel is not None: # Construct the unsigned kernel. subprocess.check_call(["sbattach", "--remove", target_kernel]) os.umask(old_umask) self.db.progress('SET', 100) self.db.progress('STOP')
def copy_all(self): """Core copy process. This is the most important step of this stage. It clones live filesystem into a local partition in the selected hard disk.""" self.db.progress('START', 0, 100, 'ubiquity/install/title') self.db.progress('INFO', 'ubiquity/install/copying') fs_size = os.path.join(self.casper_path, 'filesystem.size') assert os.path.exists(fs_size), "Missing filesystem.size." with open(fs_size) as total_size_fp: total_size = int(total_size_fp.readline()) # Progress bar handling: # We sample progress every half-second (assuming time.time() gives # us sufficiently good granularity) and use the average of progress # over the last minute or so to decide how much time remains. We # don't bother displaying any progress for the first ten seconds in # order to allow things to settle down, and we only update the "time # remaining" indicator at most every two seconds after that. copy_progress = 0 copied_size, counter = 0, 0 directory_times = [] time_start = time.time() times = [(time_start, copied_size)] long_enough = False time_last_update = time_start debug = 'UBIQUITY_DEBUG' in os.environ if self.db.get('ubiquity/install/md5_check') == 'false': md5_check = False else: md5_check = True # Increase kernel flush times during bulk data copying to make it # more likely that small files are packed contiguously, which should # speed up initial boot times. dirty_writeback_centisecs = None dirty_expire_centisecs = None if os.path.exists('/proc/sys/vm/dirty_writeback_centisecs'): with open('/proc/sys/vm/dirty_writeback_centisecs') as dwc: dirty_writeback_centisecs = int(dwc.readline()) with open('/proc/sys/vm/dirty_writeback_centisecs', 'w') as dwc: print >>dwc, '3000\n' if os.path.exists('/proc/sys/vm/dirty_expire_centisecs'): with open('/proc/sys/vm/dirty_expire_centisecs') as dec: dirty_expire_centisecs = int(dec.readline()) with open('/proc/sys/vm/dirty_expire_centisecs', 'w') as dec: print >>dec, '6000\n' old_umask = os.umask(0) for dirpath, dirnames, filenames in os.walk(self.source): sp = dirpath[len(self.source) + 1:] for name in dirnames + filenames: relpath = os.path.join(sp, name) # /etc/fstab was legitimately created by partman, and # shouldn't be copied again. if relpath == "etc/fstab": continue sourcepath = os.path.join(self.source, relpath) targetpath = os.path.join(self.target, relpath) st = os.lstat(sourcepath) mode = stat.S_IMODE(st.st_mode) if stat.S_ISLNK(st.st_mode): if os.path.lexists(targetpath): os.unlink(targetpath) linkto = os.readlink(sourcepath) os.symlink(linkto, targetpath) elif stat.S_ISDIR(st.st_mode): if not os.path.isdir(targetpath): os.mkdir(targetpath, mode) elif stat.S_ISCHR(st.st_mode): os.mknod(targetpath, stat.S_IFCHR | mode, st.st_rdev) elif stat.S_ISBLK(st.st_mode): os.mknod(targetpath, stat.S_IFBLK | mode, st.st_rdev) elif stat.S_ISFIFO(st.st_mode): os.mknod(targetpath, stat.S_IFIFO | mode) elif stat.S_ISSOCK(st.st_mode): os.mknod(targetpath, stat.S_IFSOCK | mode) elif stat.S_ISREG(st.st_mode): if '/%s' % relpath in self.blacklist: if debug: syslog.syslog('Not copying %s' % relpath) continue osextras.unlink_force(targetpath) install_misc.copy_file(self.db, sourcepath, targetpath, md5_check) copied_size += st.st_size os.lchown(targetpath, st.st_uid, st.st_gid) if not stat.S_ISLNK(st.st_mode): os.chmod(targetpath, mode) if stat.S_ISDIR(st.st_mode): directory_times.append((targetpath, st.st_atime, st.st_mtime)) # os.utime() sets timestamp of target, not link elif not stat.S_ISLNK(st.st_mode): os.utime(targetpath, (st.st_atime, st.st_mtime)) if int((copied_size * 90) / total_size) != copy_progress: copy_progress = int((copied_size * 90) / total_size) self.db.progress('SET', 10 + copy_progress) time_now = time.time() if (time_now - times[-1][0]) >= 0.5: times.append((time_now, copied_size)) if not long_enough and time_now - times[0][0] >= 10: long_enough = True if long_enough and time_now - time_last_update >= 2: time_last_update = time_now while (time_now - times[0][0] > 60 and time_now - times[1][0] >= 60): times.pop(0) speed = ((times[-1][1] - times[0][1]) / (times[-1][0] - times[0][0])) if speed != 0: time_remaining = int((total_size - copied_size) / speed) if time_remaining < 60: self.db.progress( 'INFO', 'ubiquity/install/copying_minute') # Apply timestamps to all directories now that the items within them # have been copied. for dirtime in directory_times: (directory, atime, mtime) = dirtime try: os.utime(directory, (atime, mtime)) except OSError: # I have no idea why I've been getting lots of bug reports # about this failing, but I really don't care. Ignore it. pass # Revert to previous kernel flush times. if dirty_writeback_centisecs is not None: with open('/proc/sys/vm/dirty_writeback_centisecs', 'w') as dwc: print >>dwc, dirty_writeback_centisecs if dirty_expire_centisecs is not None: with open('/proc/sys/vm/dirty_expire_centisecs', 'w') as dec: print >>dec, dirty_expire_centisecs # Try some possible locations for the kernel we used to boot. This # lets us save a couple of megabytes of CD space. bootdir = os.path.join(self.target, 'boot') kernel = self.find_cd_kernel() if kernel: prefix = os.path.basename(kernel).split('-', 1)[0] release = os.uname()[2] target_kernel = os.path.join(bootdir, '%s-%s' % (prefix, release)) osextras.unlink_force(target_kernel) install_misc.copy_file(self.db, kernel, target_kernel, md5_check) os.lchown(target_kernel, 0, 0) os.chmod(target_kernel, 0644) st = os.lstat(kernel) os.utime(target_kernel, (st.st_atime, st.st_mtime)) os.umask(old_umask) self.db.progress('SET', 100) self.db.progress('STOP')
os.mkdir(targetpath, mode) elif stat.S_ISCHR(st.st_mode): os.mknod(targetpath, stat.S_IFCHR | mode, st.st_rdev) elif stat.S_ISBLK(st.st_mode): os.mknod(targetpath, stat.S_IFBLK | mode, st.st_rdev) elif stat.S_ISFIFO(st.st_mode): os.mknod(targetpath, stat.S_IFIFO | mode) elif stat.S_ISSOCK(st.st_mode): os.mknod(targetpath, stat.S_IFSOCK | mode) elif stat.S_ISREG(st.st_mode): if '/%s' % relpath in self.blacklist: if debug: syslog.syslog('Not copying %s' % relpath) continue osextras.unlink_force(targetpath) install_misc.copy_file(self.db, sourcepath, targetpath, md5_check) copied_size += st.st_size os.lchown(targetpath, st.st_uid, st.st_gid) if not stat.S_ISLNK(st.st_mode): os.chmod(targetpath, mode) if stat.S_ISDIR(st.st_mode): directory_times.append((targetpath, st.st_atime, st.st_mtime)) # os.utime() sets timestamp of target, not link elif not stat.S_ISLNK(st.st_mode): os.utime(targetpath, (st.st_atime, st.st_mtime)) if int((copied_size * 90) / total_size) != copy_progress: copy_progress = int((copied_size * 90) / total_size) self.db.progress('SET', 10 + copy_progress)
# can race with us since it creates # "/target/var/cache/apt/...". Hence, ignore # failure if the directory does now exist where # brief moments before it didn't. if e.errno != errno.EEXIST: raise elif stat.S_ISCHR(st.st_mode): os.mknod(targetpath, stat.S_IFCHR | mode, st.st_rdev) elif stat.S_ISBLK(st.st_mode): os.mknod(targetpath, stat.S_IFBLK | mode, st.st_rdev) elif stat.S_ISFIFO(st.st_mode): os.mknod(targetpath, stat.S_IFIFO | mode) elif stat.S_ISSOCK(st.st_mode): os.mknod(targetpath, stat.S_IFSOCK | mode) elif stat.S_ISREG(st.st_mode): install_misc.copy_file(self.db, sourcepath, targetpath, md5_check) # Copy metadata. copied_size += st.st_size os.lchown(targetpath, st.st_uid, st.st_gid) if not stat.S_ISLNK(st.st_mode): os.chmod(targetpath, mode) if stat.S_ISDIR(st.st_mode): directory_times.append((targetpath, st.st_atime, st.st_mtime)) # os.utime() sets timestamp of target, not link elif not stat.S_ISLNK(st.st_mode): try: os.utime(targetpath, (st.st_atime, st.st_mtime)) except Exception: # We can live with timestamps being wrong. pass