1*9c5db199SXin Li# -*- coding: utf-8 -*- 2*9c5db199SXin Li# Copyright (c) 2012 The Chromium OS Authors. All rights reserved. 3*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be 4*9c5db199SXin Li# found in the LICENSE file. 5*9c5db199SXin Li 6*9c5db199SXin Li"""Common file and os related utilities, including tempdir manipulation.""" 7*9c5db199SXin Li 8*9c5db199SXin Lifrom __future__ import print_function 9*9c5db199SXin Li 10*9c5db199SXin Liimport collections 11*9c5db199SXin Liimport contextlib 12*9c5db199SXin Liimport ctypes 13*9c5db199SXin Liimport ctypes.util 14*9c5db199SXin Liimport datetime 15*9c5db199SXin Liimport errno 16*9c5db199SXin Liimport glob 17*9c5db199SXin Liimport hashlib 18*9c5db199SXin Liimport os 19*9c5db199SXin Liimport pwd 20*9c5db199SXin Liimport re 21*9c5db199SXin Liimport shutil 22*9c5db199SXin Liimport stat 23*9c5db199SXin Liimport subprocess 24*9c5db199SXin Liimport tempfile 25*9c5db199SXin Li 26*9c5db199SXin Liimport six 27*9c5db199SXin Li 28*9c5db199SXin Lifrom autotest_lib.utils.frozen_chromite.lib import cros_build_lib 29*9c5db199SXin Lifrom autotest_lib.utils.frozen_chromite.lib import cros_logging as logging 30*9c5db199SXin Lifrom autotest_lib.utils.frozen_chromite.lib import retry_util 31*9c5db199SXin Lifrom autotest_lib.utils.frozen_chromite.utils import key_value_store 32*9c5db199SXin Li 33*9c5db199SXin Li 34*9c5db199SXin Li# Env vars that tempdir can be gotten from; minimally, this 35*9c5db199SXin Li# needs to match python's tempfile module and match normal 36*9c5db199SXin Li# unix standards. 37*9c5db199SXin Li_TEMPDIR_ENV_VARS = ('TMPDIR', 'TEMP', 'TMP') 38*9c5db199SXin Li 39*9c5db199SXin Li 40*9c5db199SXin Lidef GetNonRootUser(): 41*9c5db199SXin Li """Returns a non-root user. Defaults to the current user. 42*9c5db199SXin Li 43*9c5db199SXin Li If the current user is root, returns the username of the person who 44*9c5db199SXin Li ran the emerge command. If running using sudo, returns the username 45*9c5db199SXin Li of the person who ran the sudo command. If no non-root user is 46*9c5db199SXin Li found, returns None. 47*9c5db199SXin Li """ 48*9c5db199SXin Li uid = os.getuid() 49*9c5db199SXin Li if uid == 0: 50*9c5db199SXin Li user = os.environ.get('PORTAGE_USERNAME', os.environ.get('SUDO_USER')) 51*9c5db199SXin Li else: 52*9c5db199SXin Li user = pwd.getpwuid(os.getuid()).pw_name 53*9c5db199SXin Li 54*9c5db199SXin Li if user == 'root': 55*9c5db199SXin Li return None 56*9c5db199SXin Li else: 57*9c5db199SXin Li return user 58*9c5db199SXin Li 59*9c5db199SXin Li 60*9c5db199SXin Lidef IsChildProcess(pid, name=None): 61*9c5db199SXin Li """Return True if pid is a child of the current process. 62*9c5db199SXin Li 63*9c5db199SXin Li Args: 64*9c5db199SXin Li pid: Child pid to search for in current process's pstree. 65*9c5db199SXin Li name: Name of the child process. 66*9c5db199SXin Li 67*9c5db199SXin Li Note: 68*9c5db199SXin Li This function is not fool proof. If the process tree contains wierd names, 69*9c5db199SXin Li an incorrect match might be possible. 70*9c5db199SXin Li """ 71*9c5db199SXin Li cmd = ['pstree', '-Ap', str(os.getpid())] 72*9c5db199SXin Li pstree = cros_build_lib.run(cmd, capture_output=True, print_cmd=False, 73*9c5db199SXin Li encoding='utf-8').stdout 74*9c5db199SXin Li if name is None: 75*9c5db199SXin Li match = '(%d)' % pid 76*9c5db199SXin Li else: 77*9c5db199SXin Li match = '-%s(%d)' % (name, pid) 78*9c5db199SXin Li return match in pstree 79*9c5db199SXin Li 80*9c5db199SXin Li 81*9c5db199SXin Lidef ExpandPath(path): 82*9c5db199SXin Li """Returns path after passing through realpath and expanduser.""" 83*9c5db199SXin Li return os.path.realpath(os.path.expanduser(path)) 84*9c5db199SXin Li 85*9c5db199SXin Li 86*9c5db199SXin Lidef IsSubPath(path, other): 87*9c5db199SXin Li """Returns whether |path| is a sub path of |other|.""" 88*9c5db199SXin Li path = os.path.abspath(path) 89*9c5db199SXin Li other = os.path.abspath(other) 90*9c5db199SXin Li if path == other: 91*9c5db199SXin Li return True 92*9c5db199SXin Li return path.startswith(other + os.sep) 93*9c5db199SXin Li 94*9c5db199SXin Li 95*9c5db199SXin Lidef AllocateFile(path, size, makedirs=False): 96*9c5db199SXin Li """Allocates a file of a certain |size| in |path|. 97*9c5db199SXin Li 98*9c5db199SXin Li Args: 99*9c5db199SXin Li path: Path to allocate the file. 100*9c5db199SXin Li size: The length, in bytes, of the desired file. 101*9c5db199SXin Li makedirs: If True, create missing leading directories in the path. 102*9c5db199SXin Li """ 103*9c5db199SXin Li if makedirs: 104*9c5db199SXin Li SafeMakedirs(os.path.dirname(path)) 105*9c5db199SXin Li 106*9c5db199SXin Li with open(path, 'w') as out: 107*9c5db199SXin Li out.truncate(size) 108*9c5db199SXin Li 109*9c5db199SXin Li 110*9c5db199SXin Li# All the modes that we allow people to pass to WriteFile. This allows us to 111*9c5db199SXin Li# make assumptions about the input so we can update it if needed. 112*9c5db199SXin Li_VALID_WRITE_MODES = { 113*9c5db199SXin Li # Read & write, but no truncation, and file offset is 0. 114*9c5db199SXin Li 'r+', 'r+b', 115*9c5db199SXin Li # Writing (and maybe reading) with truncation. 116*9c5db199SXin Li 'w', 'wb', 'w+', 'w+b', 117*9c5db199SXin Li # Writing (and maybe reading), but no truncation, and file offset is at end. 118*9c5db199SXin Li 'a', 'ab', 'a+', 'a+b', 119*9c5db199SXin Li} 120*9c5db199SXin Li 121*9c5db199SXin Li 122*9c5db199SXin Lidef WriteFile(path, content, mode='w', encoding=None, errors=None, atomic=False, 123*9c5db199SXin Li makedirs=False, sudo=False): 124*9c5db199SXin Li """Write the given content to disk. 125*9c5db199SXin Li 126*9c5db199SXin Li Args: 127*9c5db199SXin Li path: Pathway to write the content to. 128*9c5db199SXin Li content: Content to write. May be either an iterable, or a string. 129*9c5db199SXin Li mode: The mode to use when opening the file. 'w' is for text files (see the 130*9c5db199SXin Li following settings) and 'wb' is for binary files. If appending, pass 131*9c5db199SXin Li 'w+', etc... 132*9c5db199SXin Li encoding: The encoding of the file content. Text files default to 'utf-8'. 133*9c5db199SXin Li errors: How to handle encoding errors. Text files default to 'strict'. 134*9c5db199SXin Li atomic: If the updating of the file should be done atomically. Note this 135*9c5db199SXin Li option is incompatible w/ append mode. 136*9c5db199SXin Li makedirs: If True, create missing leading directories in the path. 137*9c5db199SXin Li sudo: If True, write the file as root. 138*9c5db199SXin Li """ 139*9c5db199SXin Li if mode not in _VALID_WRITE_MODES: 140*9c5db199SXin Li raise ValueError('mode must be one of {"%s"}, not %r' % 141*9c5db199SXin Li ('", "'.join(sorted(_VALID_WRITE_MODES)), mode)) 142*9c5db199SXin Li 143*9c5db199SXin Li if sudo and atomic and ('a' in mode or '+' in mode): 144*9c5db199SXin Li raise ValueError('append mode does not work in sudo+atomic mode') 145*9c5db199SXin Li 146*9c5db199SXin Li if 'b' in mode: 147*9c5db199SXin Li if encoding is not None or errors is not None: 148*9c5db199SXin Li raise ValueError('binary mode does not use encoding/errors') 149*9c5db199SXin Li else: 150*9c5db199SXin Li if encoding is None: 151*9c5db199SXin Li encoding = 'utf-8' 152*9c5db199SXin Li if errors is None: 153*9c5db199SXin Li errors = 'strict' 154*9c5db199SXin Li 155*9c5db199SXin Li if makedirs: 156*9c5db199SXin Li SafeMakedirs(os.path.dirname(path), sudo=sudo) 157*9c5db199SXin Li 158*9c5db199SXin Li # TODO(vapier): We can merge encoding/errors into the open call once we are 159*9c5db199SXin Li # Python 3 only. Until then, we have to handle it ourselves. 160*9c5db199SXin Li if 'b' in mode: 161*9c5db199SXin Li write_wrapper = lambda x: x 162*9c5db199SXin Li else: 163*9c5db199SXin Li mode += 'b' 164*9c5db199SXin Li def write_wrapper(iterable): 165*9c5db199SXin Li for item in iterable: 166*9c5db199SXin Li yield item.encode(encoding, errors) 167*9c5db199SXin Li 168*9c5db199SXin Li # If the file needs to be written as root and we are not root, write to a temp 169*9c5db199SXin Li # file, move it and change the permission. 170*9c5db199SXin Li if sudo and os.getuid() != 0: 171*9c5db199SXin Li if 'a' in mode or '+' in mode: 172*9c5db199SXin Li # Use dd to run through sudo & append the output, and write the new data 173*9c5db199SXin Li # to it through stdin. 174*9c5db199SXin Li cros_build_lib.sudo_run( 175*9c5db199SXin Li ['dd', 'conv=notrunc', 'oflag=append', 'status=none', 176*9c5db199SXin Li 'of=%s' % (path,)], print_cmd=False, input=content) 177*9c5db199SXin Li 178*9c5db199SXin Li else: 179*9c5db199SXin Li with tempfile.NamedTemporaryFile(mode=mode, delete=False) as temp: 180*9c5db199SXin Li write_path = temp.name 181*9c5db199SXin Li temp.writelines(write_wrapper( 182*9c5db199SXin Li cros_build_lib.iflatten_instance(content))) 183*9c5db199SXin Li os.chmod(write_path, 0o644) 184*9c5db199SXin Li 185*9c5db199SXin Li try: 186*9c5db199SXin Li mv_target = path if not atomic else path + '.tmp' 187*9c5db199SXin Li cros_build_lib.sudo_run(['mv', write_path, mv_target], 188*9c5db199SXin Li print_cmd=False, stderr=True) 189*9c5db199SXin Li Chown(mv_target, user='root', group='root') 190*9c5db199SXin Li if atomic: 191*9c5db199SXin Li cros_build_lib.sudo_run(['mv', mv_target, path], 192*9c5db199SXin Li print_cmd=False, stderr=True) 193*9c5db199SXin Li 194*9c5db199SXin Li except cros_build_lib.RunCommandError: 195*9c5db199SXin Li SafeUnlink(write_path) 196*9c5db199SXin Li SafeUnlink(mv_target) 197*9c5db199SXin Li raise 198*9c5db199SXin Li 199*9c5db199SXin Li else: 200*9c5db199SXin Li # We have the right permissions, simply write the file in python. 201*9c5db199SXin Li write_path = path 202*9c5db199SXin Li if atomic: 203*9c5db199SXin Li write_path = path + '.tmp' 204*9c5db199SXin Li with open(write_path, mode) as f: 205*9c5db199SXin Li f.writelines(write_wrapper(cros_build_lib.iflatten_instance(content))) 206*9c5db199SXin Li 207*9c5db199SXin Li if not atomic: 208*9c5db199SXin Li return 209*9c5db199SXin Li 210*9c5db199SXin Li try: 211*9c5db199SXin Li os.rename(write_path, path) 212*9c5db199SXin Li except EnvironmentError: 213*9c5db199SXin Li SafeUnlink(write_path) 214*9c5db199SXin Li raise 215*9c5db199SXin Li 216*9c5db199SXin Li 217*9c5db199SXin Lidef Touch(path, makedirs=False, mode=None): 218*9c5db199SXin Li """Simulate unix touch. Create if doesn't exist and update its timestamp. 219*9c5db199SXin Li 220*9c5db199SXin Li Args: 221*9c5db199SXin Li path: a string, file name of the file to touch (creating if not present). 222*9c5db199SXin Li makedirs: If True, create missing leading directories in the path. 223*9c5db199SXin Li mode: The access permissions to set. In the style of chmod. Defaults to 224*9c5db199SXin Li using the umask. 225*9c5db199SXin Li """ 226*9c5db199SXin Li if makedirs: 227*9c5db199SXin Li SafeMakedirs(os.path.dirname(path)) 228*9c5db199SXin Li 229*9c5db199SXin Li # Create the file if nonexistant. 230*9c5db199SXin Li open(path, 'a').close() 231*9c5db199SXin Li if mode is not None: 232*9c5db199SXin Li os.chmod(path, mode) 233*9c5db199SXin Li # Update timestamp to right now. 234*9c5db199SXin Li os.utime(path, None) 235*9c5db199SXin Li 236*9c5db199SXin Li 237*9c5db199SXin Lidef Chown(path, user=None, group=None, recursive=False): 238*9c5db199SXin Li """Simple sudo chown path to the user. 239*9c5db199SXin Li 240*9c5db199SXin Li Defaults to user running command. Does nothing if run as root user unless 241*9c5db199SXin Li a new owner is provided. 242*9c5db199SXin Li 243*9c5db199SXin Li Args: 244*9c5db199SXin Li path: str - File/directory to chown. 245*9c5db199SXin Li user: str|int|None - User to chown the file to. Defaults to current user. 246*9c5db199SXin Li group: str|int|None - Group to assign the file to. 247*9c5db199SXin Li recursive: Also chown child files/directories recursively. 248*9c5db199SXin Li """ 249*9c5db199SXin Li if user is None: 250*9c5db199SXin Li user = GetNonRootUser() or '' 251*9c5db199SXin Li else: 252*9c5db199SXin Li user = str(user) 253*9c5db199SXin Li 254*9c5db199SXin Li group = '' if group is None else str(group) 255*9c5db199SXin Li 256*9c5db199SXin Li if user or group: 257*9c5db199SXin Li cmd = ['chown'] 258*9c5db199SXin Li if recursive: 259*9c5db199SXin Li cmd += ['-R'] 260*9c5db199SXin Li cmd += ['%s:%s' % (user, group), path] 261*9c5db199SXin Li cros_build_lib.sudo_run(cmd, print_cmd=False, 262*9c5db199SXin Li stderr=True, stdout=True) 263*9c5db199SXin Li 264*9c5db199SXin Li 265*9c5db199SXin Lidef ReadFile(path, mode='r', encoding=None, errors=None): 266*9c5db199SXin Li """Read a given file on disk. Primarily useful for one off small files. 267*9c5db199SXin Li 268*9c5db199SXin Li The defaults are geared towards reading UTF-8 encoded text. 269*9c5db199SXin Li 270*9c5db199SXin Li Args: 271*9c5db199SXin Li path: The file to read. 272*9c5db199SXin Li mode: The mode to use when opening the file. 'r' is for text files (see the 273*9c5db199SXin Li following settings) and 'rb' is for binary files. 274*9c5db199SXin Li encoding: The encoding of the file content. Text files default to 'utf-8'. 275*9c5db199SXin Li errors: How to handle encoding errors. Text files default to 'strict'. 276*9c5db199SXin Li 277*9c5db199SXin Li Returns: 278*9c5db199SXin Li The content of the file, either as bytes or a string (with the specified 279*9c5db199SXin Li encoding). 280*9c5db199SXin Li """ 281*9c5db199SXin Li if mode not in ('r', 'rb'): 282*9c5db199SXin Li raise ValueError('mode may only be "r" or "rb", not %r' % (mode,)) 283*9c5db199SXin Li 284*9c5db199SXin Li if 'b' in mode: 285*9c5db199SXin Li if encoding is not None or errors is not None: 286*9c5db199SXin Li raise ValueError('binary mode does not use encoding/errors') 287*9c5db199SXin Li else: 288*9c5db199SXin Li if encoding is None: 289*9c5db199SXin Li encoding = 'utf-8' 290*9c5db199SXin Li if errors is None: 291*9c5db199SXin Li errors = 'strict' 292*9c5db199SXin Li 293*9c5db199SXin Li with open(path, 'rb') as f: 294*9c5db199SXin Li # TODO(vapier): We can merge encoding/errors into the open call once we are 295*9c5db199SXin Li # Python 3 only. Until then, we have to handle it ourselves. 296*9c5db199SXin Li ret = f.read() 297*9c5db199SXin Li if 'b' not in mode: 298*9c5db199SXin Li ret = ret.decode(encoding, errors) 299*9c5db199SXin Li return ret 300*9c5db199SXin Li 301*9c5db199SXin Li 302*9c5db199SXin Lidef MD5HashFile(path): 303*9c5db199SXin Li """Calculate the md5 hash of a given file path. 304*9c5db199SXin Li 305*9c5db199SXin Li Args: 306*9c5db199SXin Li path: The path of the file to hash. 307*9c5db199SXin Li 308*9c5db199SXin Li Returns: 309*9c5db199SXin Li The hex digest of the md5 hash of the file. 310*9c5db199SXin Li """ 311*9c5db199SXin Li contents = ReadFile(path, mode='rb') 312*9c5db199SXin Li return hashlib.md5(contents).hexdigest() 313*9c5db199SXin Li 314*9c5db199SXin Li 315*9c5db199SXin Lidef SafeSymlink(source, dest, sudo=False): 316*9c5db199SXin Li """Create a symlink at |dest| pointing to |source|. 317*9c5db199SXin Li 318*9c5db199SXin Li This will override the |dest| if the symlink exists. This operation is not 319*9c5db199SXin Li atomic. 320*9c5db199SXin Li 321*9c5db199SXin Li Args: 322*9c5db199SXin Li source: source path. 323*9c5db199SXin Li dest: destination path. 324*9c5db199SXin Li sudo: If True, create the link as root. 325*9c5db199SXin Li """ 326*9c5db199SXin Li if sudo and os.getuid() != 0: 327*9c5db199SXin Li cros_build_lib.sudo_run(['ln', '-sfT', source, dest], 328*9c5db199SXin Li print_cmd=False, stderr=True) 329*9c5db199SXin Li else: 330*9c5db199SXin Li SafeUnlink(dest) 331*9c5db199SXin Li os.symlink(source, dest) 332*9c5db199SXin Li 333*9c5db199SXin Li 334*9c5db199SXin Lidef SafeUnlink(path, sudo=False): 335*9c5db199SXin Li """Unlink a file from disk, ignoring if it doesn't exist. 336*9c5db199SXin Li 337*9c5db199SXin Li Returns: 338*9c5db199SXin Li True if the file existed and was removed, False if it didn't exist. 339*9c5db199SXin Li """ 340*9c5db199SXin Li try: 341*9c5db199SXin Li os.unlink(path) 342*9c5db199SXin Li return True 343*9c5db199SXin Li except EnvironmentError as e: 344*9c5db199SXin Li if e.errno == errno.ENOENT: 345*9c5db199SXin Li return False 346*9c5db199SXin Li 347*9c5db199SXin Li if not sudo: 348*9c5db199SXin Li raise 349*9c5db199SXin Li 350*9c5db199SXin Li # If we're still here, we're falling back to sudo. 351*9c5db199SXin Li cros_build_lib.sudo_run(['rm', '--', path], print_cmd=False, stderr=True) 352*9c5db199SXin Li return True 353*9c5db199SXin Li 354*9c5db199SXin Li 355*9c5db199SXin Lidef SafeMakedirs(path, mode=0o775, sudo=False, user='root'): 356*9c5db199SXin Li """Make parent directories if needed. Ignore if existing. 357*9c5db199SXin Li 358*9c5db199SXin Li Args: 359*9c5db199SXin Li path: The path to create. Intermediate directories will be created as 360*9c5db199SXin Li needed. This can be either a |Path| or |str|. 361*9c5db199SXin Li mode: The access permissions in the style of chmod. 362*9c5db199SXin Li sudo: If True, create it via sudo, thus root owned. 363*9c5db199SXin Li user: If |sudo| is True, run sudo as |user|. 364*9c5db199SXin Li 365*9c5db199SXin Li Returns: 366*9c5db199SXin Li True if the directory had to be created, False if otherwise. 367*9c5db199SXin Li 368*9c5db199SXin Li Raises: 369*9c5db199SXin Li EnvironmentError: If the makedir failed. 370*9c5db199SXin Li RunCommandError: If using run and the command failed for any reason. 371*9c5db199SXin Li """ 372*9c5db199SXin Li if sudo and not (os.getuid() == 0 and user == 'root'): 373*9c5db199SXin Li if os.path.isdir(path): 374*9c5db199SXin Li return False 375*9c5db199SXin Li cros_build_lib.sudo_run( 376*9c5db199SXin Li ['mkdir', '-p', '--mode', '%o' % mode, str(path)], user=user, 377*9c5db199SXin Li print_cmd=False, stderr=True, stdout=True) 378*9c5db199SXin Li cros_build_lib.sudo_run( 379*9c5db199SXin Li ['chmod', '%o' % mode, str(path)], 380*9c5db199SXin Li print_cmd=False, stderr=True, stdout=True) 381*9c5db199SXin Li return True 382*9c5db199SXin Li 383*9c5db199SXin Li try: 384*9c5db199SXin Li os.makedirs(path, mode) 385*9c5db199SXin Li # If we made the directory, force the mode. 386*9c5db199SXin Li os.chmod(path, mode) 387*9c5db199SXin Li return True 388*9c5db199SXin Li except EnvironmentError as e: 389*9c5db199SXin Li if e.errno != errno.EEXIST or not os.path.isdir(path): 390*9c5db199SXin Li raise 391*9c5db199SXin Li 392*9c5db199SXin Li # If the mode on the directory does not match the request, log it. 393*9c5db199SXin Li # It is the callers responsibility to coordinate mode values if there is a 394*9c5db199SXin Li # need for that. 395*9c5db199SXin Li if stat.S_IMODE(os.stat(path).st_mode) != mode: 396*9c5db199SXin Li try: 397*9c5db199SXin Li os.chmod(path, mode) 398*9c5db199SXin Li except EnvironmentError: 399*9c5db199SXin Li # Just make sure it's a directory. 400*9c5db199SXin Li if not os.path.isdir(path): 401*9c5db199SXin Li raise 402*9c5db199SXin Li return False 403*9c5db199SXin Li 404*9c5db199SXin Li 405*9c5db199SXin Liclass MakingDirsAsRoot(Exception): 406*9c5db199SXin Li """Raised when creating directories as root.""" 407*9c5db199SXin Li 408*9c5db199SXin Li 409*9c5db199SXin Lidef SafeMakedirsNonRoot(path, mode=0o775, user=None): 410*9c5db199SXin Li """Create directories and make sure they are not owned by root. 411*9c5db199SXin Li 412*9c5db199SXin Li See SafeMakedirs for the arguments and returns. 413*9c5db199SXin Li """ 414*9c5db199SXin Li if user is None: 415*9c5db199SXin Li user = GetNonRootUser() 416*9c5db199SXin Li 417*9c5db199SXin Li if user is None or user == 'root': 418*9c5db199SXin Li raise MakingDirsAsRoot('Refusing to create %s as user %s!' % (path, user)) 419*9c5db199SXin Li 420*9c5db199SXin Li created = False 421*9c5db199SXin Li should_chown = False 422*9c5db199SXin Li try: 423*9c5db199SXin Li created = SafeMakedirs(path, mode=mode, user=user) 424*9c5db199SXin Li if not created: 425*9c5db199SXin Li # Sometimes, the directory exists, but is owned by root. As a HACK, we 426*9c5db199SXin Li # will chown it to the requested user. 427*9c5db199SXin Li stat_info = os.stat(path) 428*9c5db199SXin Li should_chown = (stat_info.st_uid == 0) 429*9c5db199SXin Li except OSError as e: 430*9c5db199SXin Li if e.errno == errno.EACCES: 431*9c5db199SXin Li # Sometimes, (a prefix of the) path we're making the directory in may be 432*9c5db199SXin Li # owned by root, and so we fail. As a HACK, use da power to create 433*9c5db199SXin Li # directory and then chown it. 434*9c5db199SXin Li created = should_chown = SafeMakedirs(path, mode=mode, sudo=True) 435*9c5db199SXin Li 436*9c5db199SXin Li if should_chown: 437*9c5db199SXin Li Chown(path, user=user) 438*9c5db199SXin Li 439*9c5db199SXin Li return created 440*9c5db199SXin Li 441*9c5db199SXin Li 442*9c5db199SXin Liclass BadPathsException(Exception): 443*9c5db199SXin Li """Raised by various osutils path manipulation functions on bad input.""" 444*9c5db199SXin Li 445*9c5db199SXin Li 446*9c5db199SXin Lidef CopyDirContents(from_dir, to_dir, symlinks=False, allow_nonempty=False): 447*9c5db199SXin Li """Copy contents of from_dir to to_dir. Both should exist. 448*9c5db199SXin Li 449*9c5db199SXin Li shutil.copytree allows one to copy a rooted directory tree along with the 450*9c5db199SXin Li containing directory. OTOH, this function copies the contents of from_dir to 451*9c5db199SXin Li an existing directory. For example, for the given paths: 452*9c5db199SXin Li 453*9c5db199SXin Li from/ 454*9c5db199SXin Li inside/x.py 455*9c5db199SXin Li y.py 456*9c5db199SXin Li to/ 457*9c5db199SXin Li 458*9c5db199SXin Li shutil.copytree('from', 'to') 459*9c5db199SXin Li # Raises because 'to' already exists. 460*9c5db199SXin Li 461*9c5db199SXin Li shutil.copytree('from', 'to/non_existent_dir') 462*9c5db199SXin Li to/non_existent_dir/ 463*9c5db199SXin Li inside/x.py 464*9c5db199SXin Li y.py 465*9c5db199SXin Li 466*9c5db199SXin Li CopyDirContents('from', 'to') 467*9c5db199SXin Li to/ 468*9c5db199SXin Li inside/x.py 469*9c5db199SXin Li y.py 470*9c5db199SXin Li 471*9c5db199SXin Li Args: 472*9c5db199SXin Li from_dir: The directory whose contents should be copied. Must exist. Either 473*9c5db199SXin Li a |Path| or a |str|. 474*9c5db199SXin Li to_dir: The directory to which contents should be copied. Must exist. 475*9c5db199SXin Li Either a |Path| or a |str|. 476*9c5db199SXin Li symlinks: Whether symlinks should be copied or dereferenced. When True, all 477*9c5db199SXin Li symlinks will be copied as symlinks into the destination. When False, 478*9c5db199SXin Li the symlinks will be dereferenced and the contents copied over. 479*9c5db199SXin Li allow_nonempty: If True, do not die when to_dir is nonempty. 480*9c5db199SXin Li 481*9c5db199SXin Li Raises: 482*9c5db199SXin Li BadPathsException: if the source / target directories don't exist, or if 483*9c5db199SXin Li target directory is non-empty when allow_nonempty=False. 484*9c5db199SXin Li OSError: on esoteric permission errors. 485*9c5db199SXin Li """ 486*9c5db199SXin Li if not os.path.isdir(from_dir): 487*9c5db199SXin Li raise BadPathsException('Source directory %s does not exist.' % from_dir) 488*9c5db199SXin Li if not os.path.isdir(to_dir): 489*9c5db199SXin Li raise BadPathsException('Destination directory %s does not exist.' % to_dir) 490*9c5db199SXin Li if os.listdir(to_dir) and not allow_nonempty: 491*9c5db199SXin Li raise BadPathsException('Destination directory %s is not empty.' % to_dir) 492*9c5db199SXin Li 493*9c5db199SXin Li for name in os.listdir(from_dir): 494*9c5db199SXin Li from_path = os.path.join(from_dir, name) 495*9c5db199SXin Li to_path = os.path.join(to_dir, name) 496*9c5db199SXin Li if symlinks and os.path.islink(from_path): 497*9c5db199SXin Li os.symlink(os.readlink(from_path), to_path) 498*9c5db199SXin Li elif os.path.isdir(from_path): 499*9c5db199SXin Li shutil.copytree(from_path, to_path, symlinks=symlinks) 500*9c5db199SXin Li elif os.path.isfile(from_path): 501*9c5db199SXin Li shutil.copy2(from_path, to_path) 502*9c5db199SXin Li 503*9c5db199SXin Li 504*9c5db199SXin Lidef RmDir(path, ignore_missing=False, sudo=False): 505*9c5db199SXin Li """Recursively remove a directory. 506*9c5db199SXin Li 507*9c5db199SXin Li Args: 508*9c5db199SXin Li path: Path of directory to remove. Either a |Path| or |str|. 509*9c5db199SXin Li ignore_missing: Do not error when path does not exist. 510*9c5db199SXin Li sudo: Remove directories as root. 511*9c5db199SXin Li """ 512*9c5db199SXin Li # Using `sudo` is a bit expensive, so try to delete everything natively first. 513*9c5db199SXin Li try: 514*9c5db199SXin Li shutil.rmtree(path) 515*9c5db199SXin Li return 516*9c5db199SXin Li except EnvironmentError as e: 517*9c5db199SXin Li if ignore_missing and e.errno == errno.ENOENT: 518*9c5db199SXin Li return 519*9c5db199SXin Li 520*9c5db199SXin Li if not sudo: 521*9c5db199SXin Li raise 522*9c5db199SXin Li 523*9c5db199SXin Li # If we're still here, we're falling back to sudo. 524*9c5db199SXin Li try: 525*9c5db199SXin Li cros_build_lib.sudo_run( 526*9c5db199SXin Li ['rm', '-r%s' % ('f' if ignore_missing else '',), '--', str(path)], 527*9c5db199SXin Li debug_level=logging.DEBUG, stdout=True, stderr=True) 528*9c5db199SXin Li except cros_build_lib.RunCommandError: 529*9c5db199SXin Li if not ignore_missing or os.path.exists(path): 530*9c5db199SXin Li # If we're not ignoring the rm ENOENT equivalent, throw it; 531*9c5db199SXin Li # if the pathway still exists, something failed, thus throw it. 532*9c5db199SXin Li raise 533*9c5db199SXin Li 534*9c5db199SXin Li 535*9c5db199SXin Liclass EmptyDirNonExistentException(BadPathsException): 536*9c5db199SXin Li """EmptyDir was called on a non-existent directory without ignore_missing.""" 537*9c5db199SXin Li 538*9c5db199SXin Li 539*9c5db199SXin Lidef EmptyDir(path, ignore_missing=False, sudo=False, exclude=()): 540*9c5db199SXin Li """Remove all files inside a directory, including subdirs. 541*9c5db199SXin Li 542*9c5db199SXin Li Args: 543*9c5db199SXin Li path: Path of directory to empty. 544*9c5db199SXin Li ignore_missing: Do not error when path does not exist. 545*9c5db199SXin Li sudo: Remove directories as root. 546*9c5db199SXin Li exclude: Iterable of file names to exclude from the cleanup. They should 547*9c5db199SXin Li exactly match the file or directory name in path. 548*9c5db199SXin Li e.g. ['foo', 'bar'] 549*9c5db199SXin Li 550*9c5db199SXin Li Raises: 551*9c5db199SXin Li EmptyDirNonExistentException: if ignore_missing false, and dir is missing. 552*9c5db199SXin Li OSError: If the directory is not user writable. 553*9c5db199SXin Li """ 554*9c5db199SXin Li path = ExpandPath(path) 555*9c5db199SXin Li exclude = set(exclude) 556*9c5db199SXin Li 557*9c5db199SXin Li if not os.path.exists(path): 558*9c5db199SXin Li if ignore_missing: 559*9c5db199SXin Li return 560*9c5db199SXin Li raise EmptyDirNonExistentException( 561*9c5db199SXin Li 'EmptyDir called non-existent: %s' % path) 562*9c5db199SXin Li 563*9c5db199SXin Li # We don't catch OSError if path is not a directory. 564*9c5db199SXin Li for candidate in os.listdir(path): 565*9c5db199SXin Li if candidate not in exclude: 566*9c5db199SXin Li subpath = os.path.join(path, candidate) 567*9c5db199SXin Li # Both options can throw OSError if there is a permission problem. 568*9c5db199SXin Li if os.path.isdir(subpath): 569*9c5db199SXin Li RmDir(subpath, ignore_missing=ignore_missing, sudo=sudo) 570*9c5db199SXin Li else: 571*9c5db199SXin Li SafeUnlink(subpath, sudo) 572*9c5db199SXin Li 573*9c5db199SXin Li 574*9c5db199SXin Lidef Which(binary, path=None, mode=os.X_OK, root=None): 575*9c5db199SXin Li """Return the absolute path to the specified binary. 576*9c5db199SXin Li 577*9c5db199SXin Li Args: 578*9c5db199SXin Li binary: The binary to look for. 579*9c5db199SXin Li path: Search path. Defaults to os.environ['PATH']. 580*9c5db199SXin Li mode: File mode to check on the binary. 581*9c5db199SXin Li root: Path to automatically prefix to every element of |path|. 582*9c5db199SXin Li 583*9c5db199SXin Li Returns: 584*9c5db199SXin Li The full path to |binary| if found (with the right mode). Otherwise, None. 585*9c5db199SXin Li """ 586*9c5db199SXin Li if path is None: 587*9c5db199SXin Li path = os.environ.get('PATH', '') 588*9c5db199SXin Li for p in path.split(os.pathsep): 589*9c5db199SXin Li if root and p.startswith('/'): 590*9c5db199SXin Li # Don't prefix relative paths. We might want to support this at some 591*9c5db199SXin Li # point, but it's not worth the coding hassle currently. 592*9c5db199SXin Li p = os.path.join(root, p.lstrip('/')) 593*9c5db199SXin Li p = os.path.join(p, binary) 594*9c5db199SXin Li if os.path.isfile(p) and os.access(p, mode): 595*9c5db199SXin Li return p 596*9c5db199SXin Li return None 597*9c5db199SXin Li 598*9c5db199SXin Li 599*9c5db199SXin Lidef FindMissingBinaries(needed_tools): 600*9c5db199SXin Li """Verifies that the required tools are present on the system. 601*9c5db199SXin Li 602*9c5db199SXin Li This is especially important for scripts that are intended to run 603*9c5db199SXin Li outside the chroot. 604*9c5db199SXin Li 605*9c5db199SXin Li Args: 606*9c5db199SXin Li needed_tools: an array of string specified binaries to look for. 607*9c5db199SXin Li 608*9c5db199SXin Li Returns: 609*9c5db199SXin Li If all tools are found, returns the empty list. Otherwise, returns the 610*9c5db199SXin Li list of missing tools. 611*9c5db199SXin Li """ 612*9c5db199SXin Li return [binary for binary in needed_tools if Which(binary) is None] 613*9c5db199SXin Li 614*9c5db199SXin Li 615*9c5db199SXin Lidef DirectoryIterator(base_path): 616*9c5db199SXin Li """Iterates through the files and subdirs of a directory.""" 617*9c5db199SXin Li for root, dirs, files in os.walk(base_path): 618*9c5db199SXin Li for e in [d + os.sep for d in dirs] + files: 619*9c5db199SXin Li yield os.path.join(root, e) 620*9c5db199SXin Li 621*9c5db199SXin Li 622*9c5db199SXin Lidef IteratePaths(end_path): 623*9c5db199SXin Li """Generator that iterates down to |end_path| from root /. 624*9c5db199SXin Li 625*9c5db199SXin Li Args: 626*9c5db199SXin Li end_path: The destination. If this is a relative path, it will be resolved 627*9c5db199SXin Li to absolute path. In all cases, it will be normalized. 628*9c5db199SXin Li 629*9c5db199SXin Li Yields: 630*9c5db199SXin Li All the paths gradually constructed from / to |end_path|. For example: 631*9c5db199SXin Li IteratePaths("/this/path") yields "/", "/this", and "/this/path". 632*9c5db199SXin Li """ 633*9c5db199SXin Li return reversed(list(IteratePathParents(end_path))) 634*9c5db199SXin Li 635*9c5db199SXin Li 636*9c5db199SXin Lidef IteratePathParents(start_path): 637*9c5db199SXin Li """Generator that iterates through a directory's parents. 638*9c5db199SXin Li 639*9c5db199SXin Li Args: 640*9c5db199SXin Li start_path: The path to start from. 641*9c5db199SXin Li 642*9c5db199SXin Li Yields: 643*9c5db199SXin Li The passed-in path, along with its parents. i.e., 644*9c5db199SXin Li IteratePathParents('/usr/local') would yield '/usr/local', '/usr', and '/'. 645*9c5db199SXin Li """ 646*9c5db199SXin Li path = os.path.abspath(start_path) 647*9c5db199SXin Li # There's a bug that abspath('//') returns '//'. We need to renormalize it. 648*9c5db199SXin Li if path == '//': 649*9c5db199SXin Li path = '/' 650*9c5db199SXin Li yield path 651*9c5db199SXin Li while path.strip('/'): 652*9c5db199SXin Li path = os.path.dirname(path) 653*9c5db199SXin Li yield path 654*9c5db199SXin Li 655*9c5db199SXin Li 656*9c5db199SXin Lidef FindInPathParents(path_to_find, start_path, test_func=None, end_path=None): 657*9c5db199SXin Li """Look for a relative path, ascending through parent directories. 658*9c5db199SXin Li 659*9c5db199SXin Li Ascend through parent directories of current path looking for a relative 660*9c5db199SXin Li path. I.e., given a directory structure like: 661*9c5db199SXin Li -/ 662*9c5db199SXin Li | 663*9c5db199SXin Li --usr 664*9c5db199SXin Li | 665*9c5db199SXin Li --bin 666*9c5db199SXin Li | 667*9c5db199SXin Li --local 668*9c5db199SXin Li | 669*9c5db199SXin Li --google 670*9c5db199SXin Li 671*9c5db199SXin Li the call FindInPathParents('bin', '/usr/local') would return '/usr/bin', and 672*9c5db199SXin Li the call FindInPathParents('google', '/usr/local') would return 673*9c5db199SXin Li '/usr/local/google'. 674*9c5db199SXin Li 675*9c5db199SXin Li Args: 676*9c5db199SXin Li path_to_find: The relative path to look for. 677*9c5db199SXin Li start_path: The path to start the search from. If |start_path| is a 678*9c5db199SXin Li directory, it will be included in the directories that are searched. 679*9c5db199SXin Li test_func: The function to use to verify the relative path. Defaults to 680*9c5db199SXin Li os.path.exists. The function will be passed one argument - the target 681*9c5db199SXin Li path to test. A True return value will cause AscendingLookup to return 682*9c5db199SXin Li the target. 683*9c5db199SXin Li end_path: The path to stop searching. 684*9c5db199SXin Li """ 685*9c5db199SXin Li if end_path is not None: 686*9c5db199SXin Li end_path = os.path.abspath(end_path) 687*9c5db199SXin Li if test_func is None: 688*9c5db199SXin Li test_func = os.path.exists 689*9c5db199SXin Li for path in IteratePathParents(start_path): 690*9c5db199SXin Li if path == end_path: 691*9c5db199SXin Li return None 692*9c5db199SXin Li target = os.path.join(path, path_to_find) 693*9c5db199SXin Li if test_func(target): 694*9c5db199SXin Li return target 695*9c5db199SXin Li return None 696*9c5db199SXin Li 697*9c5db199SXin Li 698*9c5db199SXin Lidef SetGlobalTempDir(tempdir_value, tempdir_env=None): 699*9c5db199SXin Li """Set the global temp directory to the specified |tempdir_value| 700*9c5db199SXin Li 701*9c5db199SXin Li Args: 702*9c5db199SXin Li tempdir_value: The new location for the global temp directory. 703*9c5db199SXin Li tempdir_env: Optional. A list of key/value pairs to set in the 704*9c5db199SXin Li environment. If not provided, set all global tempdir environment 705*9c5db199SXin Li variables to point at |tempdir_value|. 706*9c5db199SXin Li 707*9c5db199SXin Li Returns: 708*9c5db199SXin Li Returns (old_tempdir_value, old_tempdir_env). 709*9c5db199SXin Li 710*9c5db199SXin Li old_tempdir_value: The old value of the global temp directory. 711*9c5db199SXin Li old_tempdir_env: A list of the key/value pairs that control the tempdir 712*9c5db199SXin Li environment and were set prior to this function. If the environment 713*9c5db199SXin Li variable was not set, it is recorded as None. 714*9c5db199SXin Li """ 715*9c5db199SXin Li # pylint: disable=protected-access 716*9c5db199SXin Li with tempfile._once_lock: 717*9c5db199SXin Li old_tempdir_value = GetGlobalTempDir() 718*9c5db199SXin Li old_tempdir_env = tuple((x, os.environ.get(x)) for x in _TEMPDIR_ENV_VARS) 719*9c5db199SXin Li 720*9c5db199SXin Li # Now update TMPDIR/TEMP/TMP, and poke the python 721*9c5db199SXin Li # internals to ensure all subprocess/raw tempfile 722*9c5db199SXin Li # access goes into this location. 723*9c5db199SXin Li if tempdir_env is None: 724*9c5db199SXin Li os.environ.update((x, tempdir_value) for x in _TEMPDIR_ENV_VARS) 725*9c5db199SXin Li else: 726*9c5db199SXin Li for key, value in tempdir_env: 727*9c5db199SXin Li if value is None: 728*9c5db199SXin Li os.environ.pop(key, None) 729*9c5db199SXin Li else: 730*9c5db199SXin Li os.environ[key] = value 731*9c5db199SXin Li 732*9c5db199SXin Li # Finally, adjust python's cached value (we know it's cached by here 733*9c5db199SXin Li # since we invoked _get_default_tempdir from above). Note this 734*9c5db199SXin Li # is necessary since we want *all* output from that point 735*9c5db199SXin Li # forward to go to this location. 736*9c5db199SXin Li tempfile.tempdir = tempdir_value 737*9c5db199SXin Li 738*9c5db199SXin Li return (old_tempdir_value, old_tempdir_env) 739*9c5db199SXin Li 740*9c5db199SXin Li 741*9c5db199SXin Lidef GetGlobalTempDir(): 742*9c5db199SXin Li """Get the path to the current global tempdir. 743*9c5db199SXin Li 744*9c5db199SXin Li The global tempdir path can be modified through calls to SetGlobalTempDir. 745*9c5db199SXin Li """ 746*9c5db199SXin Li # pylint: disable=protected-access 747*9c5db199SXin Li return tempfile._get_default_tempdir() 748*9c5db199SXin Li 749*9c5db199SXin Li 750*9c5db199SXin Lidef _TempDirSetup(self, prefix='tmp', set_global=False, base_dir=None): 751*9c5db199SXin Li """Generate a tempdir, modifying the object, and env to use it. 752*9c5db199SXin Li 753*9c5db199SXin Li Specifically, if set_global is True, then from this invocation forward, 754*9c5db199SXin Li python and all subprocesses will use this location for their tempdir. 755*9c5db199SXin Li 756*9c5db199SXin Li The matching _TempDirTearDown restores the env to what it was. 757*9c5db199SXin Li """ 758*9c5db199SXin Li # Stash the old tempdir that was used so we can 759*9c5db199SXin Li # switch it back on the way out. 760*9c5db199SXin Li self.tempdir = tempfile.mkdtemp(prefix=prefix, dir=base_dir) 761*9c5db199SXin Li os.chmod(self.tempdir, 0o700) 762*9c5db199SXin Li 763*9c5db199SXin Li if set_global: 764*9c5db199SXin Li self._orig_tempdir_value, self._orig_tempdir_env = \ 765*9c5db199SXin Li SetGlobalTempDir(self.tempdir) 766*9c5db199SXin Li 767*9c5db199SXin Li 768*9c5db199SXin Lidef _TempDirTearDown(self, force_sudo, delete=True): 769*9c5db199SXin Li # Note that _TempDirSetup may have failed, resulting in these attributes 770*9c5db199SXin Li # not being set; this is why we use getattr here (and must). 771*9c5db199SXin Li tempdir = getattr(self, 'tempdir', None) 772*9c5db199SXin Li try: 773*9c5db199SXin Li if tempdir is not None and delete: 774*9c5db199SXin Li RmDir(tempdir, ignore_missing=True, sudo=force_sudo) 775*9c5db199SXin Li except EnvironmentError as e: 776*9c5db199SXin Li # Suppress ENOENT since we may be invoked 777*9c5db199SXin Li # in a context where parallel wipes of the tempdir 778*9c5db199SXin Li # may be occuring; primarily during hard shutdowns. 779*9c5db199SXin Li if e.errno != errno.ENOENT: 780*9c5db199SXin Li raise 781*9c5db199SXin Li 782*9c5db199SXin Li # Restore environment modification if necessary. 783*9c5db199SXin Li orig_tempdir_value = getattr(self, '_orig_tempdir_value', None) 784*9c5db199SXin Li if orig_tempdir_value is not None: 785*9c5db199SXin Li # pylint: disable=protected-access 786*9c5db199SXin Li SetGlobalTempDir(orig_tempdir_value, self._orig_tempdir_env) 787*9c5db199SXin Li 788*9c5db199SXin Li 789*9c5db199SXin Liclass TempDir(object): 790*9c5db199SXin Li """Object that creates a temporary directory. 791*9c5db199SXin Li 792*9c5db199SXin Li This object can either be used as a context manager or just as a simple 793*9c5db199SXin Li object. The temporary directory is stored as self.tempdir in the object, and 794*9c5db199SXin Li is returned as a string by a 'with' statement. 795*9c5db199SXin Li """ 796*9c5db199SXin Li 797*9c5db199SXin Li def __init__(self, **kwargs): 798*9c5db199SXin Li """Constructor. Creates the temporary directory. 799*9c5db199SXin Li 800*9c5db199SXin Li Args: 801*9c5db199SXin Li prefix: See tempfile.mkdtemp documentation. 802*9c5db199SXin Li base_dir: The directory to place the temporary directory. 803*9c5db199SXin Li set_global: Set this directory as the global temporary directory. 804*9c5db199SXin Li delete: Whether the temporary dir should be deleted as part of cleanup. 805*9c5db199SXin Li (default: True) 806*9c5db199SXin Li sudo_rm: Whether the temporary dir will need root privileges to remove. 807*9c5db199SXin Li (default: False) 808*9c5db199SXin Li """ 809*9c5db199SXin Li self.kwargs = kwargs.copy() 810*9c5db199SXin Li self.delete = kwargs.pop('delete', True) 811*9c5db199SXin Li self.sudo_rm = kwargs.pop('sudo_rm', False) 812*9c5db199SXin Li self.tempdir = None 813*9c5db199SXin Li _TempDirSetup(self, **kwargs) 814*9c5db199SXin Li 815*9c5db199SXin Li def SetSudoRm(self, enable=True): 816*9c5db199SXin Li """Sets |sudo_rm|, which forces us to delete temporary files as root.""" 817*9c5db199SXin Li self.sudo_rm = enable 818*9c5db199SXin Li 819*9c5db199SXin Li def Cleanup(self): 820*9c5db199SXin Li """Clean up the temporary directory.""" 821*9c5db199SXin Li if self.tempdir is not None: 822*9c5db199SXin Li try: 823*9c5db199SXin Li _TempDirTearDown(self, self.sudo_rm, delete=self.delete) 824*9c5db199SXin Li finally: 825*9c5db199SXin Li self.tempdir = None 826*9c5db199SXin Li 827*9c5db199SXin Li def __enter__(self): 828*9c5db199SXin Li """Return the temporary directory.""" 829*9c5db199SXin Li return self.tempdir 830*9c5db199SXin Li 831*9c5db199SXin Li def __exit__(self, exc_type, exc_value, exc_traceback): 832*9c5db199SXin Li try: 833*9c5db199SXin Li self.Cleanup() 834*9c5db199SXin Li except Exception: 835*9c5db199SXin Li if exc_type: 836*9c5db199SXin Li # If an exception from inside the context was already in progress, 837*9c5db199SXin Li # log our cleanup exception, then allow the original to resume. 838*9c5db199SXin Li logging.error('While exiting %s:', self, exc_info=True) 839*9c5db199SXin Li 840*9c5db199SXin Li if self.tempdir: 841*9c5db199SXin Li # Log all files in tempdir at the time of the failure. 842*9c5db199SXin Li try: 843*9c5db199SXin Li logging.error('Directory contents were:') 844*9c5db199SXin Li for name in os.listdir(self.tempdir): 845*9c5db199SXin Li logging.error(' %s', name) 846*9c5db199SXin Li except OSError: 847*9c5db199SXin Li logging.error(' Directory did not exist.') 848*9c5db199SXin Li 849*9c5db199SXin Li # Log all mounts at the time of the failure, since that's the most 850*9c5db199SXin Li # common cause. 851*9c5db199SXin Li mount_results = cros_build_lib.run( 852*9c5db199SXin Li ['mount'], stdout=True, stderr=subprocess.STDOUT, 853*9c5db199SXin Li check=False) 854*9c5db199SXin Li logging.error('Mounts were:') 855*9c5db199SXin Li logging.error(' %s', mount_results.output) 856*9c5db199SXin Li 857*9c5db199SXin Li else: 858*9c5db199SXin Li # If there was not an exception from the context, raise ours. 859*9c5db199SXin Li raise 860*9c5db199SXin Li 861*9c5db199SXin Li def __del__(self): 862*9c5db199SXin Li self.Cleanup() 863*9c5db199SXin Li 864*9c5db199SXin Li def __str__(self): 865*9c5db199SXin Li return self.tempdir if self.tempdir else '' 866*9c5db199SXin Li 867*9c5db199SXin Li 868*9c5db199SXin Lidef TempDirDecorator(func): 869*9c5db199SXin Li """Populates self.tempdir with path to a temporary writeable directory.""" 870*9c5db199SXin Li def f(self, *args, **kwargs): 871*9c5db199SXin Li with TempDir() as tempdir: 872*9c5db199SXin Li self.tempdir = tempdir 873*9c5db199SXin Li return func(self, *args, **kwargs) 874*9c5db199SXin Li 875*9c5db199SXin Li f.__name__ = func.__name__ 876*9c5db199SXin Li f.__doc__ = func.__doc__ 877*9c5db199SXin Li f.__module__ = func.__module__ 878*9c5db199SXin Li return f 879*9c5db199SXin Li 880*9c5db199SXin Li 881*9c5db199SXin Lidef TempFileDecorator(func): 882*9c5db199SXin Li """Populates self.tempfile with path to a temporary writeable file""" 883*9c5db199SXin Li def f(self, *args, **kwargs): 884*9c5db199SXin Li with tempfile.NamedTemporaryFile(dir=self.tempdir, delete=False) as f: 885*9c5db199SXin Li self.tempfile = f.name 886*9c5db199SXin Li return func(self, *args, **kwargs) 887*9c5db199SXin Li 888*9c5db199SXin Li f.__name__ = func.__name__ 889*9c5db199SXin Li f.__doc__ = func.__doc__ 890*9c5db199SXin Li f.__module__ = func.__module__ 891*9c5db199SXin Li return TempDirDecorator(f) 892*9c5db199SXin Li 893*9c5db199SXin Li 894*9c5db199SXin Li# Flags synced from sys/mount.h. See mount(2) for details. 895*9c5db199SXin LiMS_RDONLY = 1 896*9c5db199SXin LiMS_NOSUID = 2 897*9c5db199SXin LiMS_NODEV = 4 898*9c5db199SXin LiMS_NOEXEC = 8 899*9c5db199SXin LiMS_SYNCHRONOUS = 16 900*9c5db199SXin LiMS_REMOUNT = 32 901*9c5db199SXin LiMS_MANDLOCK = 64 902*9c5db199SXin LiMS_DIRSYNC = 128 903*9c5db199SXin LiMS_NOATIME = 1024 904*9c5db199SXin LiMS_NODIRATIME = 2048 905*9c5db199SXin LiMS_BIND = 4096 906*9c5db199SXin LiMS_MOVE = 8192 907*9c5db199SXin LiMS_REC = 16384 908*9c5db199SXin LiMS_SILENT = 32768 909*9c5db199SXin LiMS_POSIXACL = 1 << 16 910*9c5db199SXin LiMS_UNBINDABLE = 1 << 17 911*9c5db199SXin LiMS_PRIVATE = 1 << 18 912*9c5db199SXin LiMS_SLAVE = 1 << 19 913*9c5db199SXin LiMS_SHARED = 1 << 20 914*9c5db199SXin LiMS_RELATIME = 1 << 21 915*9c5db199SXin LiMS_KERNMOUNT = 1 << 22 916*9c5db199SXin LiMS_I_VERSION = 1 << 23 917*9c5db199SXin LiMS_STRICTATIME = 1 << 24 918*9c5db199SXin LiMS_ACTIVE = 1 << 30 919*9c5db199SXin LiMS_NOUSER = 1 << 31 920*9c5db199SXin Li 921*9c5db199SXin Li 922*9c5db199SXin Lidef Mount(source, target, fstype, flags, data=''): 923*9c5db199SXin Li """Call the mount(2) func; see the man page for details.""" 924*9c5db199SXin Li libc = ctypes.CDLL(ctypes.util.find_library('c'), use_errno=True) 925*9c5db199SXin Li # These fields might be a string or 0 (for NULL). Convert to bytes. 926*9c5db199SXin Li def _MaybeEncode(s): 927*9c5db199SXin Li return s.encode('utf-8') if isinstance(s, six.string_types) else s 928*9c5db199SXin Li if libc.mount(_MaybeEncode(source), _MaybeEncode(target), 929*9c5db199SXin Li _MaybeEncode(fstype), ctypes.c_int(flags), 930*9c5db199SXin Li _MaybeEncode(data)) != 0: 931*9c5db199SXin Li e = ctypes.get_errno() 932*9c5db199SXin Li raise OSError(e, os.strerror(e)) 933*9c5db199SXin Li 934*9c5db199SXin Li 935*9c5db199SXin Lidef MountDir(src_path, dst_path, fs_type=None, sudo=True, makedirs=True, 936*9c5db199SXin Li mount_opts=('nodev', 'noexec', 'nosuid'), skip_mtab=False, 937*9c5db199SXin Li **kwargs): 938*9c5db199SXin Li """Mount |src_path| at |dst_path| 939*9c5db199SXin Li 940*9c5db199SXin Li Args: 941*9c5db199SXin Li src_path: Source of the new mount. 942*9c5db199SXin Li dst_path: Where to mount things. 943*9c5db199SXin Li fs_type: Specify the filesystem type to use. Defaults to autodetect. 944*9c5db199SXin Li sudo: Run through sudo. 945*9c5db199SXin Li makedirs: Create |dst_path| if it doesn't exist. 946*9c5db199SXin Li mount_opts: List of options to pass to `mount`. 947*9c5db199SXin Li skip_mtab: Whether to write new entries to /etc/mtab. 948*9c5db199SXin Li kwargs: Pass all other args to run. 949*9c5db199SXin Li """ 950*9c5db199SXin Li if sudo: 951*9c5db199SXin Li runcmd = cros_build_lib.sudo_run 952*9c5db199SXin Li else: 953*9c5db199SXin Li runcmd = cros_build_lib.run 954*9c5db199SXin Li 955*9c5db199SXin Li if makedirs: 956*9c5db199SXin Li SafeMakedirs(dst_path, sudo=sudo) 957*9c5db199SXin Li 958*9c5db199SXin Li cmd = ['mount', src_path, dst_path] 959*9c5db199SXin Li if skip_mtab: 960*9c5db199SXin Li cmd += ['-n'] 961*9c5db199SXin Li if fs_type: 962*9c5db199SXin Li cmd += ['-t', fs_type] 963*9c5db199SXin Li if mount_opts: 964*9c5db199SXin Li cmd += ['-o', ','.join(mount_opts)] 965*9c5db199SXin Li runcmd(cmd, **kwargs) 966*9c5db199SXin Li 967*9c5db199SXin Li 968*9c5db199SXin Lidef MountTmpfsDir(path, name='osutils.tmpfs', size='5G', 969*9c5db199SXin Li mount_opts=('nodev', 'noexec', 'nosuid'), **kwargs): 970*9c5db199SXin Li """Mount a tmpfs at |path| 971*9c5db199SXin Li 972*9c5db199SXin Li Args: 973*9c5db199SXin Li path: Directory to mount the tmpfs. 974*9c5db199SXin Li name: Friendly name to include in mount output. 975*9c5db199SXin Li size: Size of the temp fs. 976*9c5db199SXin Li mount_opts: List of options to pass to `mount`. 977*9c5db199SXin Li kwargs: Pass all other args to MountDir. 978*9c5db199SXin Li """ 979*9c5db199SXin Li mount_opts = list(mount_opts) + ['size=%s' % size] 980*9c5db199SXin Li MountDir(name, path, fs_type='tmpfs', mount_opts=mount_opts, **kwargs) 981*9c5db199SXin Li 982*9c5db199SXin Li 983*9c5db199SXin Lidef UmountDir(path, lazy=True, sudo=True, cleanup=True): 984*9c5db199SXin Li """Unmount a previously mounted temp fs mount. 985*9c5db199SXin Li 986*9c5db199SXin Li Args: 987*9c5db199SXin Li path: Directory to unmount. 988*9c5db199SXin Li lazy: Whether to do a lazy unmount. 989*9c5db199SXin Li sudo: Run through sudo. 990*9c5db199SXin Li cleanup: Whether to delete the |path| after unmounting. 991*9c5db199SXin Li Note: Does not work when |lazy| is set. 992*9c5db199SXin Li """ 993*9c5db199SXin Li if sudo: 994*9c5db199SXin Li runcmd = cros_build_lib.sudo_run 995*9c5db199SXin Li else: 996*9c5db199SXin Li runcmd = cros_build_lib.run 997*9c5db199SXin Li 998*9c5db199SXin Li cmd = ['umount', '-d', path] 999*9c5db199SXin Li if lazy: 1000*9c5db199SXin Li cmd += ['-l'] 1001*9c5db199SXin Li runcmd(cmd, debug_level=logging.DEBUG) 1002*9c5db199SXin Li 1003*9c5db199SXin Li if cleanup: 1004*9c5db199SXin Li # We will randomly get EBUSY here even when the umount worked. Suspect 1005*9c5db199SXin Li # this is due to the host distro doing stupid crap on us like autoscanning 1006*9c5db199SXin Li # directories when they get mounted. 1007*9c5db199SXin Li def _retry(e): 1008*9c5db199SXin Li # When we're using `rm` (which is required for sudo), we can't cleanly 1009*9c5db199SXin Li # detect the aforementioned failure. This is because `rm` will see the 1010*9c5db199SXin Li # errno, handle itself, and then do exit(1). Which means all we see is 1011*9c5db199SXin Li # that rm failed. Assume it's this issue as -rf will ignore most things. 1012*9c5db199SXin Li if isinstance(e, cros_build_lib.RunCommandError): 1013*9c5db199SXin Li return True 1014*9c5db199SXin Li elif isinstance(e, OSError): 1015*9c5db199SXin Li # When we aren't using sudo, we do the unlink ourselves, so the exact 1016*9c5db199SXin Li # errno is bubbled up to us and we can detect it specifically without 1017*9c5db199SXin Li # potentially ignoring all other possible failures. 1018*9c5db199SXin Li return e.errno == errno.EBUSY 1019*9c5db199SXin Li else: 1020*9c5db199SXin Li # Something else, we don't know so do not retry. 1021*9c5db199SXin Li return False 1022*9c5db199SXin Li retry_util.GenericRetry(_retry, 60, RmDir, path, sudo=sudo, sleep=1) 1023*9c5db199SXin Li 1024*9c5db199SXin Li 1025*9c5db199SXin Lidef UmountTree(path): 1026*9c5db199SXin Li """Unmounts |path| and any submounts under it.""" 1027*9c5db199SXin Li # Scrape it from /proc/mounts since it's easily accessible; 1028*9c5db199SXin Li # additionally, unmount in reverse order of what's listed there 1029*9c5db199SXin Li # rather than trying a reverse sorting; it's possible for 1030*9c5db199SXin Li # mount /z /foon 1031*9c5db199SXin Li # mount /foon/blah -o loop /a 1032*9c5db199SXin Li # which reverse sorting cannot handle. 1033*9c5db199SXin Li path = os.path.realpath(path).rstrip('/') + '/' 1034*9c5db199SXin Li mounts = [mtab.destination for mtab in IterateMountPoints() if 1035*9c5db199SXin Li mtab.destination.startswith(path) or 1036*9c5db199SXin Li mtab.destination == path.rstrip('/')] 1037*9c5db199SXin Li 1038*9c5db199SXin Li for mount_pt in reversed(mounts): 1039*9c5db199SXin Li UmountDir(mount_pt, lazy=False, cleanup=False) 1040*9c5db199SXin Li 1041*9c5db199SXin Li 1042*9c5db199SXin Lidef SetEnvironment(env): 1043*9c5db199SXin Li """Restore the environment variables to that of passed in dictionary.""" 1044*9c5db199SXin Li os.environ.clear() 1045*9c5db199SXin Li os.environ.update(env) 1046*9c5db199SXin Li 1047*9c5db199SXin Li 1048*9c5db199SXin Lidef SourceEnvironment(script, whitelist, ifs=',', env=None, multiline=False): 1049*9c5db199SXin Li """Returns the environment exported by a shell script. 1050*9c5db199SXin Li 1051*9c5db199SXin Li Note that the script is actually executed (sourced), so do not use this on 1052*9c5db199SXin Li files that have side effects (such as modify the file system). Stdout will 1053*9c5db199SXin Li be sent to /dev/null, so just echoing is OK. 1054*9c5db199SXin Li 1055*9c5db199SXin Li Args: 1056*9c5db199SXin Li script: The shell script to 'source'. 1057*9c5db199SXin Li whitelist: An iterable of environment variables to retrieve values for. 1058*9c5db199SXin Li ifs: When showing arrays, what separator to use. 1059*9c5db199SXin Li env: A dict of the initial env to pass down. You can also pass it None 1060*9c5db199SXin Li (to clear the env) or True (to preserve the current env). 1061*9c5db199SXin Li multiline: Allow a variable to span multiple lines. 1062*9c5db199SXin Li 1063*9c5db199SXin Li Returns: 1064*9c5db199SXin Li A dictionary containing the values of the whitelisted environment 1065*9c5db199SXin Li variables that are set. 1066*9c5db199SXin Li """ 1067*9c5db199SXin Li dump_script = ['source "%s" >/dev/null' % script, 1068*9c5db199SXin Li 'IFS="%s"' % ifs] 1069*9c5db199SXin Li for var in whitelist: 1070*9c5db199SXin Li # Note: If we want to get more exact results out of bash, we should switch 1071*9c5db199SXin Li # to using `declare -p "${var}"`. It would require writing a custom parser 1072*9c5db199SXin Li # here, but it would be more robust. 1073*9c5db199SXin Li dump_script.append( 1074*9c5db199SXin Li '[[ "${%(var)s+set}" == "set" ]] && echo "%(var)s=\\"${%(var)s[*]}\\""' 1075*9c5db199SXin Li % {'var': var}) 1076*9c5db199SXin Li dump_script.append('exit 0') 1077*9c5db199SXin Li 1078*9c5db199SXin Li if env is None: 1079*9c5db199SXin Li env = {} 1080*9c5db199SXin Li elif env is True: 1081*9c5db199SXin Li env = None 1082*9c5db199SXin Li output = cros_build_lib.run(['bash'], env=env, capture_output=True, 1083*9c5db199SXin Li print_cmd=False, encoding='utf-8', 1084*9c5db199SXin Li input='\n'.join(dump_script)).output 1085*9c5db199SXin Li return key_value_store.LoadData(output, multiline=multiline) 1086*9c5db199SXin Li 1087*9c5db199SXin Li 1088*9c5db199SXin Lidef ListBlockDevices(device_path=None, in_bytes=False): 1089*9c5db199SXin Li """Lists all block devices. 1090*9c5db199SXin Li 1091*9c5db199SXin Li Args: 1092*9c5db199SXin Li device_path: device path (e.g. /dev/sdc). 1093*9c5db199SXin Li in_bytes: whether to display size in bytes. 1094*9c5db199SXin Li 1095*9c5db199SXin Li Returns: 1096*9c5db199SXin Li A list of BlockDevice items with attributes 'NAME', 'RM', 'TYPE', 1097*9c5db199SXin Li 'SIZE' (RM stands for removable). 1098*9c5db199SXin Li """ 1099*9c5db199SXin Li keys = ['NAME', 'RM', 'TYPE', 'SIZE'] 1100*9c5db199SXin Li BlockDevice = collections.namedtuple('BlockDevice', keys) 1101*9c5db199SXin Li 1102*9c5db199SXin Li cmd = ['lsblk', '--pairs'] 1103*9c5db199SXin Li if in_bytes: 1104*9c5db199SXin Li cmd.append('--bytes') 1105*9c5db199SXin Li 1106*9c5db199SXin Li if device_path: 1107*9c5db199SXin Li cmd.append(device_path) 1108*9c5db199SXin Li 1109*9c5db199SXin Li cmd += ['--output', ','.join(keys)] 1110*9c5db199SXin Li result = cros_build_lib.dbg_run(cmd, capture_output=True, encoding='utf-8') 1111*9c5db199SXin Li devices = [] 1112*9c5db199SXin Li for line in result.stdout.strip().splitlines(): 1113*9c5db199SXin Li d = {} 1114*9c5db199SXin Li for k, v in re.findall(r'(\S+?)=\"(.+?)\"', line): 1115*9c5db199SXin Li d[k] = v 1116*9c5db199SXin Li 1117*9c5db199SXin Li devices.append(BlockDevice(**d)) 1118*9c5db199SXin Li 1119*9c5db199SXin Li return devices 1120*9c5db199SXin Li 1121*9c5db199SXin Li 1122*9c5db199SXin Lidef GetDeviceInfo(device, keyword='model'): 1123*9c5db199SXin Li """Get information of |device| by searching through device path. 1124*9c5db199SXin Li 1125*9c5db199SXin Li Looks for the file named |keyword| in the path upwards from 1126*9c5db199SXin Li /sys/block/|device|/device. This path is a symlink and will be fully 1127*9c5db199SXin Li expanded when searching. 1128*9c5db199SXin Li 1129*9c5db199SXin Li Args: 1130*9c5db199SXin Li device: Device name (e.g. 'sdc'). 1131*9c5db199SXin Li keyword: The filename to look for (e.g. product, model). 1132*9c5db199SXin Li 1133*9c5db199SXin Li Returns: 1134*9c5db199SXin Li The content of the |keyword| file. 1135*9c5db199SXin Li """ 1136*9c5db199SXin Li device_path = os.path.join('/sys', 'block', device) 1137*9c5db199SXin Li if not os.path.isdir(device_path): 1138*9c5db199SXin Li raise ValueError('%s is not a valid device path.' % device_path) 1139*9c5db199SXin Li 1140*9c5db199SXin Li path_list = ExpandPath(os.path.join(device_path, 'device')).split(os.path.sep) 1141*9c5db199SXin Li while len(path_list) > 2: 1142*9c5db199SXin Li target = os.path.join(os.path.sep.join(path_list), keyword) 1143*9c5db199SXin Li if os.path.isfile(target): 1144*9c5db199SXin Li return ReadFile(target).strip() 1145*9c5db199SXin Li 1146*9c5db199SXin Li path_list = path_list[:-1] 1147*9c5db199SXin Li 1148*9c5db199SXin Li 1149*9c5db199SXin Lidef GetDeviceSize(device_path, in_bytes=False): 1150*9c5db199SXin Li """Returns the size of |device|. 1151*9c5db199SXin Li 1152*9c5db199SXin Li Args: 1153*9c5db199SXin Li device_path: Device path (e.g. '/dev/sdc'). 1154*9c5db199SXin Li in_bytes: If set True, returns the size in bytes. 1155*9c5db199SXin Li 1156*9c5db199SXin Li Returns: 1157*9c5db199SXin Li Size of the device in human readable format unless |in_bytes| is set. 1158*9c5db199SXin Li """ 1159*9c5db199SXin Li devices = ListBlockDevices(device_path=device_path, in_bytes=in_bytes) 1160*9c5db199SXin Li for d in devices: 1161*9c5db199SXin Li if d.TYPE == 'disk': 1162*9c5db199SXin Li return int(d.SIZE) if in_bytes else d.SIZE 1163*9c5db199SXin Li 1164*9c5db199SXin Li raise ValueError('No size info of %s is found.' % device_path) 1165*9c5db199SXin Li 1166*9c5db199SXin Li 1167*9c5db199SXin LiFileInfo = collections.namedtuple( 1168*9c5db199SXin Li 'FileInfo', ['path', 'owner', 'size', 'atime', 'mtime']) 1169*9c5db199SXin Li 1170*9c5db199SXin Li 1171*9c5db199SXin Lidef StatFilesInDirectory(path, recursive=False, to_string=False): 1172*9c5db199SXin Li """Stat files in the directory |path|. 1173*9c5db199SXin Li 1174*9c5db199SXin Li Args: 1175*9c5db199SXin Li path: Path to the target directory. 1176*9c5db199SXin Li recursive: Whether to recurisvely list all files in |path|. 1177*9c5db199SXin Li to_string: Whether to return a string containing the metadata of the 1178*9c5db199SXin Li files. 1179*9c5db199SXin Li 1180*9c5db199SXin Li Returns: 1181*9c5db199SXin Li If |to_string| is False, returns a list of FileInfo objects. Otherwise, 1182*9c5db199SXin Li returns a string of metadata of the files. 1183*9c5db199SXin Li """ 1184*9c5db199SXin Li path = ExpandPath(path) 1185*9c5db199SXin Li def ToFileInfo(path, stat_val): 1186*9c5db199SXin Li return FileInfo(path, 1187*9c5db199SXin Li pwd.getpwuid(stat_val.st_uid)[0], 1188*9c5db199SXin Li stat_val.st_size, 1189*9c5db199SXin Li datetime.datetime.fromtimestamp(stat_val.st_atime), 1190*9c5db199SXin Li datetime.datetime.fromtimestamp(stat_val.st_mtime)) 1191*9c5db199SXin Li 1192*9c5db199SXin Li file_infos = [] 1193*9c5db199SXin Li for root, dirs, files in os.walk(path, topdown=True): 1194*9c5db199SXin Li for filename in dirs + files: 1195*9c5db199SXin Li filepath = os.path.join(root, filename) 1196*9c5db199SXin Li file_infos.append(ToFileInfo(filepath, os.lstat(filepath))) 1197*9c5db199SXin Li 1198*9c5db199SXin Li if not recursive: 1199*9c5db199SXin Li # Process only the top-most directory. 1200*9c5db199SXin Li break 1201*9c5db199SXin Li 1202*9c5db199SXin Li if not to_string: 1203*9c5db199SXin Li return file_infos 1204*9c5db199SXin Li 1205*9c5db199SXin Li msg = 'Listing the content of %s' % path 1206*9c5db199SXin Li msg_format = ('Path: {x.path}, Owner: {x.owner}, Size: {x.size} bytes, ' 1207*9c5db199SXin Li 'Accessed: {x.atime}, Modified: {x.mtime}') 1208*9c5db199SXin Li msg = '%s\n%s' % (msg, 1209*9c5db199SXin Li '\n'.join([msg_format.format(x=x) for x in file_infos])) 1210*9c5db199SXin Li return msg 1211*9c5db199SXin Li 1212*9c5db199SXin Li 1213*9c5db199SXin Li@contextlib.contextmanager 1214*9c5db199SXin Lidef ChdirContext(target_dir): 1215*9c5db199SXin Li """A context manager to chdir() into |target_dir| and back out on exit. 1216*9c5db199SXin Li 1217*9c5db199SXin Li Args: 1218*9c5db199SXin Li target_dir: A target directory to chdir into. 1219*9c5db199SXin Li """ 1220*9c5db199SXin Li 1221*9c5db199SXin Li cwd = os.getcwd() 1222*9c5db199SXin Li os.chdir(target_dir) 1223*9c5db199SXin Li try: 1224*9c5db199SXin Li yield 1225*9c5db199SXin Li finally: 1226*9c5db199SXin Li os.chdir(cwd) 1227*9c5db199SXin Li 1228*9c5db199SXin Li 1229*9c5db199SXin Lidef _SameFileSystem(path1, path2): 1230*9c5db199SXin Li """Determine whether two paths are on the same filesystem. 1231*9c5db199SXin Li 1232*9c5db199SXin Li Be resilient to nonsense paths. Return False instead of blowing up. 1233*9c5db199SXin Li """ 1234*9c5db199SXin Li try: 1235*9c5db199SXin Li return os.stat(path1).st_dev == os.stat(path2).st_dev 1236*9c5db199SXin Li except OSError: 1237*9c5db199SXin Li return False 1238*9c5db199SXin Li 1239*9c5db199SXin Li 1240*9c5db199SXin Liclass MountOverlayContext(object): 1241*9c5db199SXin Li """A context manager for mounting an OverlayFS directory. 1242*9c5db199SXin Li 1243*9c5db199SXin Li An overlay filesystem will be mounted at |mount_dir|, and will be unmounted 1244*9c5db199SXin Li when the context exits. 1245*9c5db199SXin Li """ 1246*9c5db199SXin Li 1247*9c5db199SXin Li OVERLAY_FS_MOUNT_ERRORS = (32,) 1248*9c5db199SXin Li def __init__(self, lower_dir, upper_dir, mount_dir, cleanup=False): 1249*9c5db199SXin Li """Initialize. 1250*9c5db199SXin Li 1251*9c5db199SXin Li Args: 1252*9c5db199SXin Li lower_dir: The lower directory (read-only). 1253*9c5db199SXin Li upper_dir: The upper directory (read-write). 1254*9c5db199SXin Li mount_dir: The mount point for the merged overlay. 1255*9c5db199SXin Li cleanup: Whether to remove the mount point after unmounting. This uses an 1256*9c5db199SXin Li internal retry logic for cases where unmount is successful but the 1257*9c5db199SXin Li directory still appears busy, and is generally more resilient than 1258*9c5db199SXin Li removing it independently. 1259*9c5db199SXin Li """ 1260*9c5db199SXin Li self._lower_dir = lower_dir 1261*9c5db199SXin Li self._upper_dir = upper_dir 1262*9c5db199SXin Li self._mount_dir = mount_dir 1263*9c5db199SXin Li self._cleanup = cleanup 1264*9c5db199SXin Li self.tempdir = None 1265*9c5db199SXin Li 1266*9c5db199SXin Li def __enter__(self): 1267*9c5db199SXin Li # Upstream Kernel 3.18 and the ubuntu backport of overlayfs have different 1268*9c5db199SXin Li # APIs. We must support both. 1269*9c5db199SXin Li try_legacy = False 1270*9c5db199SXin Li stashed_e_overlay_str = None 1271*9c5db199SXin Li 1272*9c5db199SXin Li # We must ensure that upperdir and workdir are on the same filesystem. 1273*9c5db199SXin Li if _SameFileSystem(self._upper_dir, GetGlobalTempDir()): 1274*9c5db199SXin Li _TempDirSetup(self) 1275*9c5db199SXin Li elif _SameFileSystem(self._upper_dir, os.path.dirname(self._upper_dir)): 1276*9c5db199SXin Li _TempDirSetup(self, base_dir=os.path.dirname(self._upper_dir)) 1277*9c5db199SXin Li else: 1278*9c5db199SXin Li logging.debug('Could create find a workdir on the same filesystem as %s. ' 1279*9c5db199SXin Li 'Trying legacy API instead.', 1280*9c5db199SXin Li self._upper_dir) 1281*9c5db199SXin Li try_legacy = True 1282*9c5db199SXin Li 1283*9c5db199SXin Li if not try_legacy: 1284*9c5db199SXin Li try: 1285*9c5db199SXin Li MountDir('overlay', self._mount_dir, fs_type='overlay', makedirs=False, 1286*9c5db199SXin Li mount_opts=('lowerdir=%s' % self._lower_dir, 1287*9c5db199SXin Li 'upperdir=%s' % self._upper_dir, 1288*9c5db199SXin Li 'workdir=%s' % self.tempdir), 1289*9c5db199SXin Li quiet=True) 1290*9c5db199SXin Li except cros_build_lib.RunCommandError as e_overlay: 1291*9c5db199SXin Li if e_overlay.result.returncode not in self.OVERLAY_FS_MOUNT_ERRORS: 1292*9c5db199SXin Li raise 1293*9c5db199SXin Li logging.debug('Failed to mount overlay filesystem. Trying legacy API.') 1294*9c5db199SXin Li stashed_e_overlay_str = str(e_overlay) 1295*9c5db199SXin Li try_legacy = True 1296*9c5db199SXin Li 1297*9c5db199SXin Li if try_legacy: 1298*9c5db199SXin Li try: 1299*9c5db199SXin Li MountDir('overlayfs', self._mount_dir, fs_type='overlayfs', 1300*9c5db199SXin Li makedirs=False, 1301*9c5db199SXin Li mount_opts=('lowerdir=%s' % self._lower_dir, 1302*9c5db199SXin Li 'upperdir=%s' % self._upper_dir), 1303*9c5db199SXin Li quiet=True) 1304*9c5db199SXin Li except cros_build_lib.RunCommandError as e_overlayfs: 1305*9c5db199SXin Li logging.error('All attempts at mounting overlay filesystem failed.') 1306*9c5db199SXin Li if stashed_e_overlay_str is not None: 1307*9c5db199SXin Li logging.error('overlay: %s', stashed_e_overlay_str) 1308*9c5db199SXin Li logging.error('overlayfs: %s', str(e_overlayfs)) 1309*9c5db199SXin Li raise 1310*9c5db199SXin Li 1311*9c5db199SXin Li return self 1312*9c5db199SXin Li 1313*9c5db199SXin Li def __exit__(self, exc_type, exc_value, traceback): 1314*9c5db199SXin Li UmountDir(self._mount_dir, cleanup=self._cleanup) 1315*9c5db199SXin Li _TempDirTearDown(self, force_sudo=True) 1316*9c5db199SXin Li 1317*9c5db199SXin Li 1318*9c5db199SXin LiMountInfo = collections.namedtuple( 1319*9c5db199SXin Li 'MountInfo', 1320*9c5db199SXin Li 'source destination filesystem options') 1321*9c5db199SXin Li 1322*9c5db199SXin Li 1323*9c5db199SXin Lidef IterateMountPoints(proc_file='/proc/mounts'): 1324*9c5db199SXin Li """Iterate over all mounts as reported by "/proc/mounts". 1325*9c5db199SXin Li 1326*9c5db199SXin Li Args: 1327*9c5db199SXin Li proc_file: A path to a file whose content is similar to /proc/mounts. 1328*9c5db199SXin Li Default to "/proc/mounts" itself. 1329*9c5db199SXin Li 1330*9c5db199SXin Li Returns: 1331*9c5db199SXin Li A generator that yields MountInfo objects. 1332*9c5db199SXin Li """ 1333*9c5db199SXin Li with open(proc_file) as f: 1334*9c5db199SXin Li for line in f: 1335*9c5db199SXin Li # Escape any \xxx to a char. 1336*9c5db199SXin Li source, destination, filesystem, options, _, _ = [ 1337*9c5db199SXin Li re.sub(r'\\([0-7]{3})', lambda m: chr(int(m.group(1), 8)), x) 1338*9c5db199SXin Li for x in line.split() 1339*9c5db199SXin Li ] 1340*9c5db199SXin Li mtab = MountInfo(source, destination, filesystem, options) 1341*9c5db199SXin Li yield mtab 1342*9c5db199SXin Li 1343*9c5db199SXin Li 1344*9c5db199SXin Lidef IsMounted(path): 1345*9c5db199SXin Li """Determine if |path| is already mounted or not.""" 1346*9c5db199SXin Li path = os.path.realpath(path).rstrip('/') 1347*9c5db199SXin Li mounts = [mtab.destination for mtab in IterateMountPoints()] 1348*9c5db199SXin Li if path in mounts: 1349*9c5db199SXin Li return True 1350*9c5db199SXin Li 1351*9c5db199SXin Li return False 1352*9c5db199SXin Li 1353*9c5db199SXin Li 1354*9c5db199SXin Lidef ResolveSymlinkInRoot(file_name, root): 1355*9c5db199SXin Li """Resolve a symlink |file_name| relative to |root|. 1356*9c5db199SXin Li 1357*9c5db199SXin Li This can be used to resolve absolute symlinks within an alternative root 1358*9c5db199SXin Li path (i.e. chroot). For example: 1359*9c5db199SXin Li 1360*9c5db199SXin Li ROOT-A/absolute_symlink --> /an/abs/path 1361*9c5db199SXin Li ROOT-A/relative_symlink --> a/relative/path 1362*9c5db199SXin Li 1363*9c5db199SXin Li absolute_symlink will be resolved to ROOT-A/an/abs/path 1364*9c5db199SXin Li relative_symlink will be resolved to ROOT-A/a/relative/path 1365*9c5db199SXin Li 1366*9c5db199SXin Li Args: 1367*9c5db199SXin Li file_name (str): A path to the file. 1368*9c5db199SXin Li root (str|None): A path to the root directory. 1369*9c5db199SXin Li 1370*9c5db199SXin Li Returns: 1371*9c5db199SXin Li |file_name| if |file_name| is not a symlink. Otherwise, the ultimate path 1372*9c5db199SXin Li that |file_name| points to, with links resolved relative to |root|. 1373*9c5db199SXin Li """ 1374*9c5db199SXin Li count = 0 1375*9c5db199SXin Li while os.path.islink(file_name): 1376*9c5db199SXin Li count += 1 1377*9c5db199SXin Li if count > 128: 1378*9c5db199SXin Li raise ValueError('Too many link levels for %s.' % file_name) 1379*9c5db199SXin Li link = os.readlink(file_name) 1380*9c5db199SXin Li if link.startswith('/'): 1381*9c5db199SXin Li file_name = os.path.join(root, link[1:]) if root else link 1382*9c5db199SXin Li else: 1383*9c5db199SXin Li file_name = os.path.join(os.path.dirname(file_name), link) 1384*9c5db199SXin Li return file_name 1385*9c5db199SXin Li 1386*9c5db199SXin Li 1387*9c5db199SXin Lidef ResolveSymlink(file_name): 1388*9c5db199SXin Li """Resolve a symlink |file_name| to an absolute path. 1389*9c5db199SXin Li 1390*9c5db199SXin Li This is similar to ResolveSymlinkInRoot, but does not resolve absolute 1391*9c5db199SXin Li symlinks to an alternative root, and normalizes the path before returning. 1392*9c5db199SXin Li 1393*9c5db199SXin Li Args: 1394*9c5db199SXin Li file_name (str): The symlink. 1395*9c5db199SXin Li 1396*9c5db199SXin Li Returns: 1397*9c5db199SXin Li str - |file_name| if |file_name| is not a symlink. Otherwise, the ultimate 1398*9c5db199SXin Li path that |file_name| points to. 1399*9c5db199SXin Li """ 1400*9c5db199SXin Li return os.path.realpath(ResolveSymlinkInRoot(file_name, None)) 1401*9c5db199SXin Li 1402*9c5db199SXin Li 1403*9c5db199SXin Lidef IsInsideVm(): 1404*9c5db199SXin Li """Return True if we are running inside a virtual machine. 1405*9c5db199SXin Li 1406*9c5db199SXin Li The detection is based on the model of the hard drive. 1407*9c5db199SXin Li """ 1408*9c5db199SXin Li for blk_model in glob.glob('/sys/block/*/device/model'): 1409*9c5db199SXin Li if os.path.isfile(blk_model): 1410*9c5db199SXin Li model = ReadFile(blk_model) 1411*9c5db199SXin Li if model.startswith('VBOX') or model.startswith('VMware'): 1412*9c5db199SXin Li return True 1413*9c5db199SXin Li 1414*9c5db199SXin Li return False 1415