Jihoon Kang | bd7afdc | 2024-12-19 02:06:22 +0000 | [diff] [blame^] | 1 | #!/usr/bin/env python3 |
| 2 | # |
| 3 | # Copyright (C) 2024 The Android Open Source Project |
| 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 | """A tool for calculating the hash of a directory based on file contents and metadata.""" |
| 18 | |
| 19 | import argparse |
| 20 | import hashlib |
| 21 | import os |
| 22 | import stat |
| 23 | |
| 24 | def calculate_hash(directory: str) -> str: |
| 25 | """ |
| 26 | Calculates the hash of a directory, including file contents and metadata. |
| 27 | |
| 28 | Following informations are taken into consideration: |
| 29 | * Name: The file or directory name. |
| 30 | * File Type: Whether it's a regular file, directory, symbolic link, etc. |
| 31 | * Size: The size of the file in bytes. |
| 32 | * Permissions: The file's access permissions (read, write, execute). |
| 33 | * Content Hash (for files): The SHA-1 hash of the file's content. |
| 34 | """ |
| 35 | |
| 36 | output = [] |
| 37 | for root, _, files in os.walk(directory): |
| 38 | for file in files: |
| 39 | filepath = os.path.join(root, file) |
| 40 | file_stat = os.lstat(filepath) |
| 41 | stat_info = f"{filepath} {stat.filemode(file_stat.st_mode)} {file_stat.st_size}" |
| 42 | |
| 43 | if os.path.islink(filepath): |
| 44 | stat_info += os.readlink(filepath) |
| 45 | elif os.path.isfile(filepath): |
| 46 | with open(filepath, "rb") as f: |
| 47 | file_hash = hashlib.sha1(f.read()).hexdigest() |
| 48 | stat_info += f" {file_hash}" |
| 49 | |
| 50 | output.append(stat_info) |
| 51 | |
| 52 | return hashlib.sha1("\n".join(sorted(output)).encode()).hexdigest() |
| 53 | |
| 54 | if __name__ == "__main__": |
| 55 | parser = argparse.ArgumentParser(description="Calculate the hash of a directory.") |
| 56 | parser.add_argument("directory", help="Path to the directory") |
| 57 | parser.add_argument("output_file", help="Path to the output file") |
| 58 | args = parser.parse_args() |
| 59 | |
| 60 | hash_value = calculate_hash(args.directory) |
| 61 | with open(args.output_file, "w") as f: |
| 62 | f.write(hash_value) |