#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright (C) 2017 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # u"""A test module to verify root directory content. This test checks the root directory content on a device is the same as the following structure, or a subset of it. For symlinks, the target value it points to is checked. For *.rc files, the file content is compared, except init.environ.rc because it's *built* from init.environ.rc.in. For other files (e.g., init, .png), we allow their existence and don't check the file content. Root Directory Content: ├── acct ├── adb_keys ├── bugreports -> /data/user_de/0/com.android.shell/files/bugreports ├── cache OR ├── cache -> /data/cache ├── charger -> /sbin/charger ├── config ├── d -> /sys/kernel/debug ├── data ├── default.prop -> system/etc/prop.default ├── dev ├── etc -> /system/etc ├── init ├── init.*.rc ├── lost+found ├── mnt ├── odm ├── oem ├── postinstall ├── proc ├── res │ └── images │ └── charger │ ├── battery_fail.png │ └── battery_scale.png ├── sbin │ ├── charger │ ├── ueventd -> ../init │ └── watchdogd -> ../init ├── sdcard -> /storage/self/primary ├── storage ├── sys ├── system ├── ueventd.rc ├── vendor ├── verity_key """ import filecmp import glob import logging import os import shutil import tempfile from vts.runners.host import asserts from vts.runners.host import base_test from vts.runners.host import const from vts.runners.host import keys from vts.runners.host import test_runner from vts.utils.python.file import target_file_utils # The allowed directories in three types: # - mount_point: skip checking its content as it's just a mount point. # - skip_check: skip checking its content for special directories. # - check_content: check its content recursively. _ALLOWED_DIRS = { "/acct": "mount_point", # /cache is created when BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE is defined. "/cache": "mount_point", "/config": "mount_point", "/data": "mount_point", "/dev": "mount_point", "/lost+found": "skip_check", # Skip checking this created by fsck. "/mnt": "mount_point", "/odm": "mount_point", "/oem": "mount_point", # The A/B updater uses a top-level /postinstall directory to mount the new # system before reboot. "/postinstall": "skip_check", "/proc": "mount_point", "/res": "check_content", "/res/images": "check_content", "/res/images/charger": "check_content", "/sbin": "check_content", "/storage": "mount_point", "/sys": "mount_point", # /system is a mount point in non-A/B but a directory in A/B. # No need to check its content in both cases. "/system": "skip_check", "/vendor": "mount_point", } # The allowed symlinks and the target it points to. # The test will check the value of the symlink target. _ALLOWED_SYMLINKS = { "/bugreports": "/data/user_de/0/com.android.shell/files/bugreports", "/cache": "/data/cache", "/charger": "/sbin/charger", "/d": "/sys/kernel/debug", "/default.prop": "system/etc/prop.default", "/etc": "/system/etc", "/sbin/ueventd": "../init", "/sbin/watchdogd": "../init", "/sdcard": "/storage/self/primary", } # The allowed files for existence, where file content won't be checked. # Note that init.environ.rc is generated by replacing some build # environment variables (e.g., BOOTCLASSPATH) from its source: # init.environ.rc.in, therefore its content is also not checked. _ALLOWED_EXISTING_FILES = set(["/adb_keys", "/init", "/init.environ.rc", "/res/images/charger/battery_scale.png", "/res/images/charger/battery_fail.png", "/sbin/charger", "/verity_key"]) class VtsKernelRootDirTest(base_test.BaseTestClass): """A test class to verify root directory content. Attributes: data_file_path: The path to VTS data directory. """ _INIT_RC_FILE_PATH = "vts/testcases/kernel/api/rootdir/init_rc_files" def setUpClass(self): """Initializes data file path, device, shell and adb.""" required_params = [keys.ConfigKeys.IKEY_DATA_FILE_PATH] self.getUserParams(required_params) self._dut = self.android_devices[0] self._shell = self._dut.shell self._adb = self._dut.adb self._dirs_to_check = self._TraverseRootDir() logging.info("Dirs to check: %r", self._dirs_to_check) def setUp(self): """Initializes the temp_dir for adb pull.""" self._temp_dir = tempfile.mkdtemp() logging.info("Create %s", self._temp_dir) def tearDown(self): """Deletes the temporary directory.""" logging.info("Delete %s", self._temp_dir) shutil.rmtree(self._temp_dir) def _ListDir(self, dir_path, file_type="all"): """Lists files in dir_path with specific file_type. Args: dir_path: The current directory to list content. file_type: The file type to list, can be one of "dir", "file", "symlink" or "all". Returns: A set of paths under current directory. """ find_option = "-maxdepth 1" # Only list current directory. find_types = { "dir": "d", "file": "f", "symlink": "l", } if file_type != "all": find_option += " -type %s" % find_types[file_type] # FindFiles will include dir_path if file_type is "all" or "dir". # Excludes dir_path before return. return set(target_file_utils.FindFiles( self._shell, dir_path, "*", find_option)) - set([dir_path]) def _ReadLink(self, path): """Executes readlink on device.""" result = self._shell.Execute("readlink %s" % path) asserts.assertEqual(result[const.EXIT_CODE][0], 0) return result[const.STDOUT][0].strip() def _TraverseRootDir(self, dir_path="/"): """Returns a list of dirs to check the content in it.""" # dir_path is eligible to check when being invoked here. dirs = [dir_path] for d in self._ListDir(dir_path, "dir"): if d in _ALLOWED_DIRS and _ALLOWED_DIRS[d] == "check_content": dirs.extend(self._TraverseRootDir(d)) return dirs def testRootDirs(self): """Checks the subdirs under root directory.""" error_msg = [] for dir_path in self._dirs_to_check: current_dirs = self._ListDir(dir_path, "dir") logging.info("Current dirs: %r", current_dirs) unexpected_dirs = current_dirs - set(_ALLOWED_DIRS) error_msg.extend("Unexpected dir: " + d for d in unexpected_dirs) if error_msg: asserts.fail("UNEXPECTED ROOT DIRS:\n%s" % "\n".join(error_msg)) def testRootSymlinks(self): """Checks the symlinks under root directory.""" error_msg = [] def _CheckSymlinks(dir_path): """Checks the symlinks under dir_path.""" current_symlinks = self._ListDir(dir_path, "symlink") logging.info("Current symlinks: %r", current_symlinks) unexpected_symlinks = current_symlinks - set(_ALLOWED_SYMLINKS) error_msg.extend( "Unexpected symlink: " + l for l in unexpected_symlinks) # Checks symlink target. error_msg.extend( "Invalid symlink target: %s -> %s (expected: %s)" % ( l, target, _ALLOWED_SYMLINKS[l]) for l, target in ( (l, self._ReadLink(l)) for l in ( current_symlinks - unexpected_symlinks)) if target != _ALLOWED_SYMLINKS[l]) for dir_path in self._dirs_to_check: _CheckSymlinks(dir_path) if error_msg: asserts.fail("UNEXPECTED ROOT SYMLINKS:\n%s" % "\n".join(error_msg)) def testRootFiles(self): """Checks the files under root directory.""" error_msg = [] init_rc_dir = os.path.join(self.data_file_path, self._INIT_RC_FILE_PATH) allowed_rc_files = set("/" + os.path.basename(rc_file) for rc_file in glob.glob(os.path.join(init_rc_dir, "*.rc"))) def _CheckFiles(dir_path): """Checks the files under dir_path.""" current_files = self._ListDir(dir_path, "file") logging.info("Current files: %r", current_files) unexpected_files = (current_files - allowed_rc_files - _ALLOWED_EXISTING_FILES) error_msg.extend("Unexpected file: " + f for f in unexpected_files) # Checks file content in *.rc files. for f in current_files - unexpected_files: if f in allowed_rc_files: # adb pull the .rc file from the device. logging.info("adb pull %s %s", f, self._temp_dir) pull_output = self._adb.pull(f, self._temp_dir) logging.debug(pull_output) # Compares the content and trim the leading "/" in f. if not filecmp.cmp(os.path.join(init_rc_dir, f[1:]), os.path.join(self._temp_dir, f[1:])): error_msg.append("Unexpected file content: %s" % f) for dir_path in self._dirs_to_check: _CheckFiles(dir_path) if error_msg: asserts.fail("UNEXPECTED ROOT FILES:\n%s" % "\n".join(error_msg)) def testRootAllFileTypes(self): """Checks there is no path other than dirs, symlinks and files.""" error_msg = [] for dir_path in self._dirs_to_check: unknown_files = (self._ListDir(dir_path) - self._ListDir(dir_path, "dir") - self._ListDir(dir_path, "symlink") - self._ListDir(dir_path, "file")) error_msg.extend("Unexpected path: " + p for p in unknown_files) if error_msg: asserts.fail("UNEXPECTED ROOT PATHS:\n%s" % "\n".join(error_msg)) if __name__ == "__main__": test_runner.main()