blob: 3b130279f899ca3021495e6a06ad9a2afcffea42 [file] [log] [blame]
#!/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()