1#!/usr/bin/env python3 2 3# 4# Copyright (C) 2019 The Android Open Source Project 5# 6# Licensed under the Apache License, Version 2.0 (the "License"); 7# you may not use this file except in compliance with the License. 8# You may obtain a copy of the License at 9# 10# http://www.apache.org/licenses/LICENSE-2.0 11# 12# Unless required by applicable law or agreed to in writing, software 13# distributed under the License is distributed on an "AS IS" BASIS, 14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15# See the License for the specific language governing permissions and 16# limitations under the License. 17# 18 19from typing import Any, Callable, Dict, Generic, Iterable, List, NamedTuple, TextIO, Tuple, TypeVar, Optional, Union, TextIO 20 21import re 22 23class Inode2Filename: 24 """ 25 Parses a text file of the format 26 "uint(dev_t) uint(ino_t) int(file_size) string(filepath)\\n"* 27 28 Lines not matching this format are ignored. 29 """ 30 31 def __init__(self, inode_data_file: TextIO): 32 """ 33 Create an Inode2Filename that reads cached inode from a file saved earlier 34 (e.g. with pagecache.py -d or with inode2filename --format=textcache) 35 36 :param inode_data_file: a file object (e.g. created with open or StringIO). 37 38 Lifetime: inode_data_file is only used during the construction of the object. 39 """ 40 self._inode_table = Inode2Filename.build_inode_lookup_table(inode_data_file) 41 42 @classmethod 43 def new_from_filename(cls, textcache_filename: str) -> 'Inode2Filename': 44 """ 45 Create an Inode2Filename that reads cached inode from a file saved earlier 46 (e.g. with pagecache.py -d or with inode2filename --format=textcache) 47 48 :param textcache_filename: path to textcache 49 """ 50 with open(textcache_filename) as inode_data_file: 51 return cls(inode_data_file) 52 53 @staticmethod 54 def build_inode_lookup_table(inode_data_file: TextIO) -> Dict[Tuple[int, int], Tuple[str, str]]: 55 """ 56 :return: map { (device_int, inode_int) -> (filename_str, size_str) } 57 """ 58 inode2filename = {} 59 for line in inode_data_file: 60 # stat -c "%d %i %s %n 61 # device number, inode number, total size in bytes, file name 62 result = re.match('([0-9]+)d? ([0-9]+) -?([0-9]+) (.*)', line) 63 if result: 64 inode2filename[(int(result.group(1)), int(result.group(2)))] = \ 65 (result.group(4), result.group(3)) 66 67 return inode2filename 68 69 def resolve(self, dev_t: int, ino_t: int) -> Optional[str]: 70 """ 71 Return a filename (str) from a (dev_t, ino_t) inode pair. 72 73 Returns None if the lookup fails. 74 """ 75 maybe_result = self._inode_table.get((dev_t, ino_t)) 76 77 if not maybe_result: 78 return None 79 80 return maybe_result[0] # filename str 81 82 def __len__(self) -> int: 83 """ 84 :return: the number of inode entries parsed from the file. 85 """ 86 return len(self._inode_table) 87 88 def __repr__(self) -> str: 89 """ 90 :return: string representation for debugging/test failures. 91 """ 92 return "Inode2Filename%s" %(repr(self._inode_table)) 93 94 # end of class. 95