#!/usr/bin/env python2 # coding: utf-8 import binascii import errno import hashlib import os import re import sys import time import psutil from pykit import config from pykit import timeutil READ_BLOCK = 32 * 1024 * 1024 WRITE_BLOCK = 32 * 1024 * 1024 class FSUtilError(Exception): pass class NotMountPoint(FSUtilError): pass def assert_mountpoint(path): if not os.path.ismount(path): raise NotMountPoint(path) def get_all_mountpoint(all=False): partitions = psutil.disk_partitions(all=all) prt_by_mp = [x.mountpoint for x in partitions] return prt_by_mp def get_mountpoint(path): path = os.path.realpath(path) prt_by_mountpoint = get_disk_partitions() while path != '/' and path not in prt_by_mountpoint: path = os.path.dirname(path) return path def get_device(path): prt_by_mountpoint = get_disk_partitions() mp = get_mountpoint(path) return prt_by_mountpoint[mp]['device'] def get_device_fs(device): prt_by_mp = get_disk_partitions() for prt in prt_by_mp.values(): if device == prt['device']: return prt['fstype'] else: return 'unknown' def get_disk_partitions(all=True): partitions = psutil.disk_partitions(all=all) by_mount_point = {} for pt in partitions: # OrderedDict([ # ('device', '/dev/disk1'), # ('mountpoint', '/'), # ('fstype', 'hfs'), # ('opts', 'rw,local,rootfs,dovolfs,journaled,multilabel')]) by_mount_point[pt.mountpoint] = _to_dict(pt) return by_mount_point def get_path_fs(path): mp = get_mountpoint(path) prt_by_mp = get_disk_partitions() return prt_by_mp[mp]['fstype'] def get_path_usage(path): space_st = os.statvfs(path) # f_bavail: without blocks reserved for super users # f_bfree: with blocks reserved for super users avail = space_st.f_frsize * space_st.f_bavail capa = space_st.f_frsize * space_st.f_blocks used = capa - avail return { 'total': capa, 'used': used, 'available': avail, 'percent': float(used) / capa, } def get_path_inode_usage(path): inode_st = os.statvfs(path) available = inode_st.f_favail total = inode_st.f_files used = total - available return { 'total': total, 'used': used, 'available': available, 'percent': float(used) / total, } def makedirs(*paths, **kwargs): mode = kwargs.get('mode', 0755) uid = kwargs.get('uid') or config.uid gid = kwargs.get('gid') or config.gid path = os.path.join(*paths) # retry to deal with concurrent check-and-then-set issue for ii in range(2): if os.path.isdir(path): if uid is not None and gid is not None: os.chown(path, uid, gid) return try: os.makedirs(path, mode=mode) if uid is not None and gid is not None: os.chown(path, uid, gid) except OSError as e: if e.errno == errno.EEXIST: # concurrent if-exist and makedirs pass else: raise else: raise def get_sub_dirs(path): files = os.listdir(path) sub_dirs = [] for f in files: if os.path.isdir(os.path.join(path, f)): sub_dirs.append(f) sub_dirs.sort() return sub_dirs def list_fns(path, pattern='.*'): fns = os.listdir(path) pt = re.compile(pattern) fns = [x for x in fns if re.search(pt, x) is not None and os.path.isfile(os.path.join(path, x)) ] fns.sort() return fns def read_file(path): with open(path, 'r') as f: return f.read() def write_file(path, fcont, uid=None, gid=None, atomic=False, fsync=True): if not atomic: return _write_file(path, fcont, uid, gid, fsync) tmp_path = '{path}._tmp_.{pid}_{timestamp}'.format( path=path, pid=os.getpid(), timestamp=timeutil.ns(), ) _write_file(tmp_path, fcont, uid, gid, fsync) try: os.rename(tmp_path, path) except EnvironmentError: os.remove(tmp_path) raise def _write_file(path, fcont, uid=None, gid=None, fsync=True): uid = uid or config.uid gid = gid or config.gid with open(path, 'w') as f: f.write(fcont) f.flush() if fsync: os.fsync(f.fileno()) if uid is not None and gid is not None: os.chown(path, uid, gid) def remove(path, ignore_errors=False, onerror=None): if onerror is None: onerror = _on_error_raise if ignore_errors: onerror = _on_error_pass try: is_dir = os.path.isdir(path) except os.error: onerror(os.path.isdir, path, sys.exc_info()) return if not is_dir: try: os.remove(path) except os.error: onerror(os.remove, path, sys.exc_info()) return names = [] try: names = os.listdir(path) except os.error: onerror(os.listdir, path, sys.exc_info()) for name in names: fullname = os.path.join(path, name) remove(fullname, ignore_errors, onerror) try: os.rmdir(path) except os.error: onerror(os.rmdir, path, sys.exc_info()) def _on_error_pass(*args, **kwargs): pass def _on_error_raise(*args, **kwargs): raise def calc_checksums(path, sha1=False, md5=False, crc32=False, sha256=False, block_size=READ_BLOCK, io_limit=READ_BLOCK): checksums = { 'sha1': None, 'md5': None, 'crc32': None, 'sha256': None } if (sha1 or md5 or crc32 or sha256) is False: return checksums if block_size <= 0: raise FSUtilError('block_size must be positive integer') if io_limit == 0: raise FSUtilError('io_limit shoud not be zero') min_io_time = float(block_size) / io_limit sum_sha1 = hashlib.sha1() sum_md5 = hashlib.md5() sum_crc32 = 0 sum_sha256 = hashlib.sha256() with open(path, 'rb') as f_path: while True: t0 = time.time() buf = f_path.read(block_size) if buf == '': break t1 = time.time() time_sleep = max(0, min_io_time - (t1 - t0)) if time_sleep > 0: time.sleep(time_sleep) if sha1: sum_sha1.update(buf) if md5: sum_md5.update(buf) if crc32: sum_crc32 = binascii.crc32(buf, sum_crc32) if sha256: sum_sha256.update(buf) if sha1: checksums['sha1'] = sum_sha1.hexdigest() if md5: checksums['md5'] = sum_md5.hexdigest() if crc32: checksums['crc32'] = '%08x' % (sum_crc32 & 0xffffffff) if sha256: checksums['sha256'] = sum_sha256.hexdigest() return checksums def _to_dict(_namedtuple): return dict(_namedtuple._asdict())