blob: bd993e5010041c7f4970397ae56a83c15345104a [file] [log] [blame]
# Copyright 2019 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2
__all__ = ["compare_files"]
import io
import os
import stat
import sys
from portage import _encodings
from portage import _unicode_encode
from portage.util._xattr import xattr
def compare_files(file1, file2, skipped_types=()):
"""
Compare metadata and contents of two files.
@param file1: File 1
@type file1: str
@param file2: File 2
@type file2: str
@param skipped_types: Tuple of strings specifying types of properties excluded from comparison.
Supported strings: type, mode, owner, group, device_number, xattr, atime, mtime, ctime, size, content
@type skipped_types: tuple of str
@rtype: tuple of str
@return: Tuple of strings specifying types of properties different between compared files
"""
file1_stat = os.lstat(_unicode_encode(file1, encoding=_encodings["fs"], errors="strict"))
file2_stat = os.lstat(_unicode_encode(file2, encoding=_encodings["fs"], errors="strict"))
differences = []
if (file1_stat.st_dev, file1_stat.st_ino) == (file2_stat.st_dev, file2_stat.st_ino):
return ()
if "type" not in skipped_types and stat.S_IFMT(file1_stat.st_mode) != stat.S_IFMT(file2_stat.st_mode):
differences.append("type")
if "mode" not in skipped_types and stat.S_IMODE(file1_stat.st_mode) != stat.S_IMODE(file2_stat.st_mode):
differences.append("mode")
if "owner" not in skipped_types and file1_stat.st_uid != file2_stat.st_uid:
differences.append("owner")
if "group" not in skipped_types and file1_stat.st_gid != file2_stat.st_gid:
differences.append("group")
if "device_number" not in skipped_types and file1_stat.st_rdev != file2_stat.st_rdev:
differences.append("device_number")
if "xattr" not in skipped_types and sorted(xattr.get_all(file1, nofollow=True)) != sorted(xattr.get_all(file2, nofollow=True)):
differences.append("xattr")
if sys.hexversion >= 0x3030000:
if "atime" not in skipped_types and file1_stat.st_atime_ns != file2_stat.st_atime_ns:
differences.append("atime")
if "mtime" not in skipped_types and file1_stat.st_mtime_ns != file2_stat.st_mtime_ns:
differences.append("mtime")
if "ctime" not in skipped_types and file1_stat.st_ctime_ns != file2_stat.st_ctime_ns:
differences.append("ctime")
else:
if "atime" not in skipped_types and file1_stat.st_atime != file2_stat.st_atime:
differences.append("atime")
if "mtime" not in skipped_types and file1_stat.st_mtime != file2_stat.st_mtime:
differences.append("mtime")
if "ctime" not in skipped_types and file1_stat.st_ctime != file2_stat.st_ctime:
differences.append("ctime")
if "type" in differences:
pass
elif file1_stat.st_size != file2_stat.st_size:
if "size" not in skipped_types:
differences.append("size")
if "content" not in skipped_types:
differences.append("content")
else:
if "content" not in skipped_types:
if stat.S_ISLNK(file1_stat.st_mode):
file1_stream = io.BytesIO(os.readlink(_unicode_encode(file1,
encoding=_encodings["fs"],
errors="strict")))
else:
file1_stream = open(_unicode_encode(file1,
encoding=_encodings["fs"],
errors="strict"), "rb")
if stat.S_ISLNK(file2_stat.st_mode):
file2_stream = io.BytesIO(os.readlink(_unicode_encode(file2,
encoding=_encodings["fs"],
errors="strict")))
else:
file2_stream = open(_unicode_encode(file2,
encoding=_encodings["fs"],
errors="strict"), "rb")
while True:
file1_content = file1_stream.read(4096)
file2_content = file2_stream.read(4096)
if file1_content != file2_content:
differences.append("content")
break
if not file1_content or not file2_content:
break
file1_stream.close()
file2_stream.close()
return tuple(differences)