1# Microsoft Azure Linux Agent 2# 3# Copyright 2018 Microsoft Corporation 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16# 17# Requires Python 2.6+ and Openssl 1.0+ 18# 19 20""" 21File operation util functions 22""" 23 24import errno as errno 25import glob 26import os 27import pwd 28import re 29import shutil 30 31import azurelinuxagent.common.logger as logger 32import azurelinuxagent.common.utils.textutil as textutil 33 34from azurelinuxagent.common.future import ustr 35 36KNOWN_IOERRORS = [ 37 errno.EIO, # I/O error 38 errno.ENOMEM, # Out of memory 39 errno.ENFILE, # File table overflow 40 errno.EMFILE, # Too many open files 41 errno.ENOSPC, # Out of space 42 errno.ENAMETOOLONG, # Name too long 43 errno.ELOOP, # Too many symbolic links encountered 44 121 # Remote I/O error (errno.EREMOTEIO -- not present in all Python 2.7+) 45] 46 47 48def read_file(filepath, asbin=False, remove_bom=False, encoding='utf-8'): 49 """ 50 Read and return contents of 'filepath'. 51 """ 52 mode = 'rb' 53 with open(filepath, mode) as in_file: 54 data = in_file.read() 55 if data is None: 56 return None 57 58 if asbin: 59 return data 60 61 if remove_bom: 62 # remove bom on bytes data before it is converted into string. 63 data = textutil.remove_bom(data) 64 data = ustr(data, encoding=encoding) 65 return data 66 67 68def write_file(filepath, contents, asbin=False, encoding='utf-8', append=False): 69 """ 70 Write 'contents' to 'filepath'. 71 """ 72 mode = "ab" if append else "wb" 73 data = contents 74 if not asbin: 75 data = contents.encode(encoding) 76 with open(filepath, mode) as out_file: 77 out_file.write(data) 78 79 80def append_file(filepath, contents, asbin=False, encoding='utf-8'): 81 """ 82 Append 'contents' to 'filepath'. 83 """ 84 write_file(filepath, contents, asbin=asbin, encoding=encoding, append=True) 85 86 87def base_name(path): 88 head, tail = os.path.split(path) # pylint: disable=W0612 89 return tail 90 91 92def get_line_startingwith(prefix, filepath): 93 """ 94 Return line from 'filepath' if the line startswith 'prefix' 95 """ 96 for line in read_file(filepath).split('\n'): 97 if line.startswith(prefix): 98 return line 99 return None 100 101 102def mkdir(dirpath, mode=None, owner=None): 103 if not os.path.isdir(dirpath): 104 os.makedirs(dirpath) 105 if mode is not None: 106 chmod(dirpath, mode) 107 if owner is not None: 108 chowner(dirpath, owner) 109 110 111def chowner(path, owner): 112 if not os.path.exists(path): 113 logger.error("Path does not exist: {0}".format(path)) 114 else: 115 owner_info = pwd.getpwnam(owner) 116 os.chown(path, owner_info[2], owner_info[3]) 117 118 119def chmod(path, mode): 120 if not os.path.exists(path): 121 logger.error("Path does not exist: {0}".format(path)) 122 else: 123 os.chmod(path, mode) 124 125 126def rm_files(*args): 127 for paths in args: 128 # find all possible file paths 129 for path in glob.glob(paths): 130 if os.path.isfile(path): 131 os.remove(path) 132 133 134def rm_dirs(*args): 135 """ 136 Remove the contents of each directory 137 """ 138 for p in args: 139 if not os.path.isdir(p): 140 continue 141 142 for pp in os.listdir(p): 143 path = os.path.join(p, pp) 144 if os.path.isfile(path): 145 os.remove(path) 146 elif os.path.islink(path): 147 os.unlink(path) 148 elif os.path.isdir(path): 149 shutil.rmtree(path) 150 151 152def trim_ext(path, ext): 153 if not ext.startswith("."): 154 ext = "." + ext 155 return path.split(ext)[0] if path.endswith(ext) else path 156 157 158def update_conf_file(path, line_start, val, chk_err=False): 159 conf = [] 160 if not os.path.isfile(path) and chk_err: 161 raise IOError("Can't find config file:{0}".format(path)) 162 conf = read_file(path).split('\n') 163 conf = [x for x in conf 164 if x is not None and len(x) > 0 and not x.startswith(line_start)] 165 conf.append(val) 166 write_file(path, '\n'.join(conf) + '\n') 167 168 169def search_file(target_dir_name, target_file_name): 170 for root, dirs, files in os.walk(target_dir_name): # pylint: disable=W0612 171 for file_name in files: 172 if file_name == target_file_name: 173 return os.path.join(root, file_name) 174 return None 175 176 177def chmod_tree(path, mode): 178 for root, dirs, files in os.walk(path): # pylint: disable=W0612 179 for file_name in files: 180 os.chmod(os.path.join(root, file_name), mode) 181 182 183def findstr_in_file(file_path, line_str): 184 """ 185 Return True if the line is in the file; False otherwise. 186 (Trailing whitespace is ignored.) 187 """ 188 try: 189 with open(file_path, 'r') as fh: 190 for line in fh.readlines(): 191 if line_str == line.rstrip(): 192 return True 193 except Exception: 194 # swallow exception 195 pass 196 return False 197 198 199def findre_in_file(file_path, line_re): 200 """ 201 Return match object if found in file. 202 """ 203 try: 204 with open(file_path, 'r') as fh: 205 pattern = re.compile(line_re) 206 for line in fh.readlines(): 207 match = re.search(pattern, line) 208 if match: 209 return match 210 except: # pylint: disable=W0702 211 pass 212 213 return None 214 215 216def get_all_files(root_path): 217 """ 218 Find all files under the given root path 219 """ 220 result = [] 221 for root, dirs, files in os.walk(root_path): # pylint: disable=W0612 222 result.extend([os.path.join(root, file) for file in files]) # pylint: disable=redefined-builtin 223 224 return result 225 226 227def clean_ioerror(e, paths=None): 228 """ 229 Clean-up possibly bad files and directories after an IO error. 230 The code ignores *all* errors since disk state may be unhealthy. 231 """ 232 if paths is None: 233 paths = [] 234 if isinstance(e, IOError) and e.errno in KNOWN_IOERRORS: 235 for path in paths: 236 if path is None: 237 continue 238 239 try: 240 if os.path.isdir(path): 241 shutil.rmtree(path, ignore_errors=True) 242 else: 243 os.remove(path) 244 except Exception: 245 # swallow exception 246 pass 247