shutil.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  1. """Utility functions for copying and archiving files and directory trees.
  2. XXX The functions here don't copy the resource fork or other metadata on Mac.
  3. """
  4. import os
  5. import sys
  6. import stat
  7. from os.path import abspath
  8. import fnmatch
  9. import collections
  10. import errno
  11. try:
  12. from pwd import getpwnam
  13. except ImportError:
  14. getpwnam = None
  15. try:
  16. from grp import getgrnam
  17. except ImportError:
  18. getgrnam = None
  19. __all__ = ["copyfileobj", "copyfile", "copymode", "copystat", "copy", "copy2",
  20. "copytree", "move", "rmtree", "Error", "SpecialFileError",
  21. "ExecError", "make_archive", "get_archive_formats",
  22. "register_archive_format", "unregister_archive_format",
  23. "ignore_patterns"]
  24. class Error(EnvironmentError):
  25. pass
  26. class SpecialFileError(EnvironmentError):
  27. """Raised when trying to do a kind of operation (e.g. copying) which is
  28. not supported on a special file (e.g. a named pipe)"""
  29. class ExecError(EnvironmentError):
  30. """Raised when a command could not be executed"""
  31. try:
  32. WindowsError
  33. except NameError:
  34. WindowsError = None
  35. def copyfileobj(fsrc, fdst, length=16*1024):
  36. """copy data from file-like object fsrc to file-like object fdst"""
  37. while 1:
  38. buf = fsrc.read(length)
  39. if not buf:
  40. break
  41. fdst.write(buf)
  42. def _samefile(src, dst):
  43. # Macintosh, Unix.
  44. if hasattr(os.path, 'samefile'):
  45. try:
  46. return os.path.samefile(src, dst)
  47. except OSError:
  48. return False
  49. # All other platforms: check for same pathname.
  50. return (os.path.normcase(os.path.abspath(src)) ==
  51. os.path.normcase(os.path.abspath(dst)))
  52. def copyfile(src, dst):
  53. """Copy data from src to dst"""
  54. if _samefile(src, dst):
  55. raise Error("`%s` and `%s` are the same file" % (src, dst))
  56. for fn in [src, dst]:
  57. try:
  58. st = os.stat(fn)
  59. except OSError:
  60. # File most likely does not exist
  61. pass
  62. else:
  63. # XXX What about other special files? (sockets, devices...)
  64. if stat.S_ISFIFO(st.st_mode):
  65. raise SpecialFileError("`%s` is a named pipe" % fn)
  66. with open(src, 'rb') as fsrc:
  67. with open(dst, 'wb') as fdst:
  68. copyfileobj(fsrc, fdst)
  69. def copymode(src, dst):
  70. """Copy mode bits from src to dst"""
  71. if hasattr(os, 'chmod'):
  72. st = os.stat(src)
  73. mode = stat.S_IMODE(st.st_mode)
  74. os.chmod(dst, mode)
  75. def copystat(src, dst):
  76. """Copy all stat info (mode bits, atime, mtime, flags) from src to dst"""
  77. st = os.stat(src)
  78. mode = stat.S_IMODE(st.st_mode)
  79. if hasattr(os, 'utime'):
  80. os.utime(dst, (st.st_atime, st.st_mtime))
  81. if hasattr(os, 'chmod'):
  82. os.chmod(dst, mode)
  83. if hasattr(os, 'chflags') and hasattr(st, 'st_flags'):
  84. try:
  85. os.chflags(dst, st.st_flags)
  86. except OSError, why:
  87. for err in 'EOPNOTSUPP', 'ENOTSUP':
  88. if hasattr(errno, err) and why.errno == getattr(errno, err):
  89. break
  90. else:
  91. raise
  92. def copy(src, dst):
  93. """Copy data and mode bits ("cp src dst").
  94. The destination may be a directory.
  95. """
  96. if os.path.isdir(dst):
  97. dst = os.path.join(dst, os.path.basename(src))
  98. copyfile(src, dst)
  99. copymode(src, dst)
  100. def copy2(src, dst):
  101. """Copy data and all stat info ("cp -p src dst").
  102. The destination may be a directory.
  103. """
  104. if os.path.isdir(dst):
  105. dst = os.path.join(dst, os.path.basename(src))
  106. copyfile(src, dst)
  107. copystat(src, dst)
  108. def ignore_patterns(*patterns):
  109. """Function that can be used as copytree() ignore parameter.
  110. Patterns is a sequence of glob-style patterns
  111. that are used to exclude files"""
  112. def _ignore_patterns(path, names):
  113. ignored_names = []
  114. for pattern in patterns:
  115. ignored_names.extend(fnmatch.filter(names, pattern))
  116. return set(ignored_names)
  117. return _ignore_patterns
  118. def copytree(src, dst, symlinks=False, ignore=None):
  119. """Recursively copy a directory tree using copy2().
  120. The destination directory must not already exist.
  121. If exception(s) occur, an Error is raised with a list of reasons.
  122. If the optional symlinks flag is true, symbolic links in the
  123. source tree result in symbolic links in the destination tree; if
  124. it is false, the contents of the files pointed to by symbolic
  125. links are copied.
  126. The optional ignore argument is a callable. If given, it
  127. is called with the `src` parameter, which is the directory
  128. being visited by copytree(), and `names` which is the list of
  129. `src` contents, as returned by os.listdir():
  130. callable(src, names) -> ignored_names
  131. Since copytree() is called recursively, the callable will be
  132. called once for each directory that is copied. It returns a
  133. list of names relative to the `src` directory that should
  134. not be copied.
  135. XXX Consider this example code rather than the ultimate tool.
  136. """
  137. names = os.listdir(src)
  138. if ignore is not None:
  139. ignored_names = ignore(src, names)
  140. else:
  141. ignored_names = set()
  142. os.makedirs(dst)
  143. errors = []
  144. for name in names:
  145. if name in ignored_names:
  146. continue
  147. srcname = os.path.join(src, name)
  148. dstname = os.path.join(dst, name)
  149. try:
  150. if symlinks and os.path.islink(srcname):
  151. linkto = os.readlink(srcname)
  152. os.symlink(linkto, dstname)
  153. elif os.path.isdir(srcname):
  154. copytree(srcname, dstname, symlinks, ignore)
  155. else:
  156. # Will raise a SpecialFileError for unsupported file types
  157. copy2(srcname, dstname)
  158. # catch the Error from the recursive copytree so that we can
  159. # continue with other files
  160. except Error, err:
  161. errors.extend(err.args[0])
  162. except EnvironmentError, why:
  163. errors.append((srcname, dstname, str(why)))
  164. try:
  165. copystat(src, dst)
  166. except OSError, why:
  167. if WindowsError is not None and isinstance(why, WindowsError):
  168. # Copying file access times may fail on Windows
  169. pass
  170. else:
  171. errors.append((src, dst, str(why)))
  172. if errors:
  173. raise Error, errors
  174. def rmtree(path, ignore_errors=False, onerror=None):
  175. """Recursively delete a directory tree.
  176. If ignore_errors is set, errors are ignored; otherwise, if onerror
  177. is set, it is called to handle the error with arguments (func,
  178. path, exc_info) where func is os.listdir, os.remove, or os.rmdir;
  179. path is the argument to that function that caused it to fail; and
  180. exc_info is a tuple returned by sys.exc_info(). If ignore_errors
  181. is false and onerror is None, an exception is raised.
  182. """
  183. if ignore_errors:
  184. def onerror(*args):
  185. pass
  186. elif onerror is None:
  187. def onerror(*args):
  188. raise
  189. try:
  190. if os.path.islink(path):
  191. # symlinks to directories are forbidden, see bug #1669
  192. raise OSError("Cannot call rmtree on a symbolic link")
  193. except OSError:
  194. onerror(os.path.islink, path, sys.exc_info())
  195. # can't continue even if onerror hook returns
  196. return
  197. names = []
  198. try:
  199. names = os.listdir(path)
  200. except os.error, err:
  201. onerror(os.listdir, path, sys.exc_info())
  202. for name in names:
  203. fullname = os.path.join(path, name)
  204. try:
  205. mode = os.lstat(fullname).st_mode
  206. except os.error:
  207. mode = 0
  208. if stat.S_ISDIR(mode):
  209. rmtree(fullname, ignore_errors, onerror)
  210. else:
  211. try:
  212. os.remove(fullname)
  213. except os.error, err:
  214. onerror(os.remove, fullname, sys.exc_info())
  215. try:
  216. os.rmdir(path)
  217. except os.error:
  218. onerror(os.rmdir, path, sys.exc_info())
  219. def _basename(path):
  220. # A basename() variant which first strips the trailing slash, if present.
  221. # Thus we always get the last component of the path, even for directories.
  222. sep = os.path.sep + (os.path.altsep or '')
  223. return os.path.basename(path.rstrip(sep))
  224. def move(src, dst):
  225. """Recursively move a file or directory to another location. This is
  226. similar to the Unix "mv" command.
  227. If the destination is a directory or a symlink to a directory, the source
  228. is moved inside the directory. The destination path must not already
  229. exist.
  230. If the destination already exists but is not a directory, it may be
  231. overwritten depending on os.rename() semantics.
  232. If the destination is on our current filesystem, then rename() is used.
  233. Otherwise, src is copied to the destination and then removed.
  234. A lot more could be done here... A look at a mv.c shows a lot of
  235. the issues this implementation glosses over.
  236. """
  237. real_dst = dst
  238. if os.path.isdir(dst):
  239. if _samefile(src, dst):
  240. # We might be on a case insensitive filesystem,
  241. # perform the rename anyway.
  242. os.rename(src, dst)
  243. return
  244. real_dst = os.path.join(dst, _basename(src))
  245. if os.path.exists(real_dst):
  246. raise Error, "Destination path '%s' already exists" % real_dst
  247. try:
  248. os.rename(src, real_dst)
  249. except OSError:
  250. if os.path.isdir(src):
  251. if _destinsrc(src, dst):
  252. raise Error, "Cannot move a directory '%s' into itself '%s'." % (src, dst)
  253. copytree(src, real_dst, symlinks=True)
  254. rmtree(src)
  255. else:
  256. copy2(src, real_dst)
  257. os.unlink(src)
  258. def _destinsrc(src, dst):
  259. src = abspath(src)
  260. dst = abspath(dst)
  261. if not src.endswith(os.path.sep):
  262. src += os.path.sep
  263. if not dst.endswith(os.path.sep):
  264. dst += os.path.sep
  265. return dst.startswith(src)
  266. def _get_gid(name):
  267. """Returns a gid, given a group name."""
  268. if getgrnam is None or name is None:
  269. return None
  270. try:
  271. result = getgrnam(name)
  272. except KeyError:
  273. result = None
  274. if result is not None:
  275. return result[2]
  276. return None
  277. def _get_uid(name):
  278. """Returns an uid, given a user name."""
  279. if getpwnam is None or name is None:
  280. return None
  281. try:
  282. result = getpwnam(name)
  283. except KeyError:
  284. result = None
  285. if result is not None:
  286. return result[2]
  287. return None
  288. def _make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0,
  289. owner=None, group=None, logger=None):
  290. """Create a (possibly compressed) tar file from all the files under
  291. 'base_dir'.
  292. 'compress' must be "gzip" (the default), "bzip2", or None.
  293. 'owner' and 'group' can be used to define an owner and a group for the
  294. archive that is being built. If not provided, the current owner and group
  295. will be used.
  296. The output tar file will be named 'base_name' + ".tar", possibly plus
  297. the appropriate compression extension (".gz", or ".bz2").
  298. Returns the output filename.
  299. """
  300. tar_compression = {'gzip': 'gz', 'bzip2': 'bz2', None: ''}
  301. compress_ext = {'gzip': '.gz', 'bzip2': '.bz2'}
  302. # flags for compression program, each element of list will be an argument
  303. if compress is not None and compress not in compress_ext.keys():
  304. raise ValueError, \
  305. ("bad value for 'compress': must be None, 'gzip' or 'bzip2'")
  306. archive_name = base_name + '.tar' + compress_ext.get(compress, '')
  307. archive_dir = os.path.dirname(archive_name)
  308. if archive_dir and not os.path.exists(archive_dir):
  309. if logger is not None:
  310. logger.info("creating %s", archive_dir)
  311. if not dry_run:
  312. os.makedirs(archive_dir)
  313. # creating the tarball
  314. import tarfile # late import so Python build itself doesn't break
  315. if logger is not None:
  316. logger.info('Creating tar archive')
  317. uid = _get_uid(owner)
  318. gid = _get_gid(group)
  319. def _set_uid_gid(tarinfo):
  320. if gid is not None:
  321. tarinfo.gid = gid
  322. tarinfo.gname = group
  323. if uid is not None:
  324. tarinfo.uid = uid
  325. tarinfo.uname = owner
  326. return tarinfo
  327. if not dry_run:
  328. tar = tarfile.open(archive_name, 'w|%s' % tar_compression[compress])
  329. try:
  330. tar.add(base_dir, filter=_set_uid_gid)
  331. finally:
  332. tar.close()
  333. return archive_name
  334. def _call_external_zip(base_dir, zip_filename, verbose=False, dry_run=False):
  335. # XXX see if we want to keep an external call here
  336. if verbose:
  337. zipoptions = "-r"
  338. else:
  339. zipoptions = "-rq"
  340. from distutils.errors import DistutilsExecError
  341. from distutils.spawn import spawn
  342. try:
  343. spawn(["zip", zipoptions, zip_filename, base_dir], dry_run=dry_run)
  344. except DistutilsExecError:
  345. # XXX really should distinguish between "couldn't find
  346. # external 'zip' command" and "zip failed".
  347. raise ExecError, \
  348. ("unable to create zip file '%s': "
  349. "could neither import the 'zipfile' module nor "
  350. "find a standalone zip utility") % zip_filename
  351. def _make_zipfile(base_name, base_dir, verbose=0, dry_run=0, logger=None):
  352. """Create a zip file from all the files under 'base_dir'.
  353. The output zip file will be named 'base_name' + ".zip". Uses either the
  354. "zipfile" Python module (if available) or the InfoZIP "zip" utility
  355. (if installed and found on the default search path). If neither tool is
  356. available, raises ExecError. Returns the name of the output zip
  357. file.
  358. """
  359. zip_filename = base_name + ".zip"
  360. archive_dir = os.path.dirname(base_name)
  361. if archive_dir and not os.path.exists(archive_dir):
  362. if logger is not None:
  363. logger.info("creating %s", archive_dir)
  364. if not dry_run:
  365. os.makedirs(archive_dir)
  366. # If zipfile module is not available, try spawning an external 'zip'
  367. # command.
  368. try:
  369. import zipfile
  370. except ImportError:
  371. zipfile = None
  372. if zipfile is None:
  373. _call_external_zip(base_dir, zip_filename, verbose, dry_run)
  374. else:
  375. if logger is not None:
  376. logger.info("creating '%s' and adding '%s' to it",
  377. zip_filename, base_dir)
  378. if not dry_run:
  379. with zipfile.ZipFile(zip_filename, "w",
  380. compression=zipfile.ZIP_DEFLATED) as zf:
  381. path = os.path.normpath(base_dir)
  382. zf.write(path, path)
  383. if logger is not None:
  384. logger.info("adding '%s'", path)
  385. for dirpath, dirnames, filenames in os.walk(base_dir):
  386. for name in sorted(dirnames):
  387. path = os.path.normpath(os.path.join(dirpath, name))
  388. zf.write(path, path)
  389. if logger is not None:
  390. logger.info("adding '%s'", path)
  391. for name in filenames:
  392. path = os.path.normpath(os.path.join(dirpath, name))
  393. if os.path.isfile(path):
  394. zf.write(path, path)
  395. if logger is not None:
  396. logger.info("adding '%s'", path)
  397. return zip_filename
  398. _ARCHIVE_FORMATS = {
  399. 'gztar': (_make_tarball, [('compress', 'gzip')], "gzip'ed tar-file"),
  400. 'bztar': (_make_tarball, [('compress', 'bzip2')], "bzip2'ed tar-file"),
  401. 'tar': (_make_tarball, [('compress', None)], "uncompressed tar file"),
  402. 'zip': (_make_zipfile, [],"ZIP file")
  403. }
  404. def get_archive_formats():
  405. """Returns a list of supported formats for archiving and unarchiving.
  406. Each element of the returned sequence is a tuple (name, description)
  407. """
  408. formats = [(name, registry[2]) for name, registry in
  409. _ARCHIVE_FORMATS.items()]
  410. formats.sort()
  411. return formats
  412. def register_archive_format(name, function, extra_args=None, description=''):
  413. """Registers an archive format.
  414. name is the name of the format. function is the callable that will be
  415. used to create archives. If provided, extra_args is a sequence of
  416. (name, value) tuples that will be passed as arguments to the callable.
  417. description can be provided to describe the format, and will be returned
  418. by the get_archive_formats() function.
  419. """
  420. if extra_args is None:
  421. extra_args = []
  422. if not isinstance(function, collections.Callable):
  423. raise TypeError('The %s object is not callable' % function)
  424. if not isinstance(extra_args, (tuple, list)):
  425. raise TypeError('extra_args needs to be a sequence')
  426. for element in extra_args:
  427. if not isinstance(element, (tuple, list)) or len(element) !=2 :
  428. raise TypeError('extra_args elements are : (arg_name, value)')
  429. _ARCHIVE_FORMATS[name] = (function, extra_args, description)
  430. def unregister_archive_format(name):
  431. del _ARCHIVE_FORMATS[name]
  432. def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,
  433. dry_run=0, owner=None, group=None, logger=None):
  434. """Create an archive file (eg. zip or tar).
  435. 'base_name' is the name of the file to create, minus any format-specific
  436. extension; 'format' is the archive format: one of "zip", "tar", "bztar"
  437. or "gztar".
  438. 'root_dir' is a directory that will be the root directory of the
  439. archive; ie. we typically chdir into 'root_dir' before creating the
  440. archive. 'base_dir' is the directory where we start archiving from;
  441. ie. 'base_dir' will be the common prefix of all files and
  442. directories in the archive. 'root_dir' and 'base_dir' both default
  443. to the current directory. Returns the name of the archive file.
  444. 'owner' and 'group' are used when creating a tar archive. By default,
  445. uses the current owner and group.
  446. """
  447. save_cwd = os.getcwd()
  448. if root_dir is not None:
  449. if logger is not None:
  450. logger.debug("changing into '%s'", root_dir)
  451. base_name = os.path.abspath(base_name)
  452. if not dry_run:
  453. os.chdir(root_dir)
  454. if base_dir is None:
  455. base_dir = os.curdir
  456. kwargs = {'dry_run': dry_run, 'logger': logger}
  457. try:
  458. format_info = _ARCHIVE_FORMATS[format]
  459. except KeyError:
  460. raise ValueError, "unknown archive format '%s'" % format
  461. func = format_info[0]
  462. for arg, val in format_info[1]:
  463. kwargs[arg] = val
  464. if format != 'zip':
  465. kwargs['owner'] = owner
  466. kwargs['group'] = group
  467. try:
  468. filename = func(base_name, base_dir, **kwargs)
  469. finally:
  470. if root_dir is not None:
  471. if logger is not None:
  472. logger.debug("changing back to '%s'", save_cwd)
  473. os.chdir(save_cwd)
  474. return filename