hardware_TrimIntegrity.py revision 79a010971e2d9c053130202a2ad35eca4081762a
1# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import logging, os, fcntl, struct, random
6
7from autotest_lib.client.bin import test, utils
8from autotest_lib.client.common_lib import error
9
10
11class hardware_TrimIntegrity(test.test):
12    """
13    Performs data integrity trim test on an unmounted partition.
14
15    This test will write 1 GB of data and verify that trimmed data are gone and
16    untrimmed data are unaffected. The verification will be run in 5 passes with
17    0%, 25%, 50%, 75%, and 100% of data trimmed.
18    """
19
20    version = 1
21    FILE_SIZE = 1024 * 1024 * 1024
22    CHUNK_SIZE = 64 * 1024
23    TRIM_RATIO = [0, 0.25, 0.5, 0.75, 1]
24
25    # Use hash value to check integrity of the random data.
26    HASH_CMD = 'sha256sum | cut -d" " -f 1'
27    # 0x1277 is ioctl BLKDISCARD command
28    IOCTL_TRIM_CMD = 0x1277
29    IOCTL_NOT_SUPPORT_ERRNO = 95
30
31    def _find_free_root_partition(self):
32        """
33        Locate the spare root partition that we didn't boot off.
34        """
35
36        spare_root_map = {
37            '3': '5',
38            '5': '3',
39        }
40        rootdev = utils.system_output('rootdev -s')
41        spare_root = rootdev[:-1] + spare_root_map[rootdev[-1]]
42        self._filename = spare_root
43
44    def _get_hash(self, chunk_count, chunk_size):
45        """
46        Get hash for every chunk of data.
47        """
48        cmd = str('for i in $(seq 0 %d); do dd if=%s of=/dev/stdout bs=%d'
49                  ' count=1 skip=$i iflag=direct | %s; done' %
50                  (chunk_count - 1, self._filename, chunk_size, self.HASH_CMD))
51        return utils.run(cmd).stdout.split()
52
53    def _do_trim(self, fd, offset, size):
54        """
55        Invoke ioctl to trim command.
56        """
57        fcntl.ioctl(fd, self.IOCTL_TRIM_CMD, struct.pack('QQ', offset, size))
58
59    def run_once(self, file_size=FILE_SIZE, chunk_size=CHUNK_SIZE,
60                 trim_ratio=TRIM_RATIO):
61        """
62        Executes the test and logs the output.
63        """
64
65        self._find_free_root_partition()
66
67        # Check for trim support in ioctl. Gracefully exit if not support.
68        try:
69            fd = os.open(self._filename, os.O_RDWR, 0666)
70            self._do_trim(fd, 0, chunk_size)
71        except IOError, err:
72            if err.errno == self.IOCTL_NOT_SUPPORT_ERRNO:
73                logging.info("IOCTL Does not support trim.")
74                return 0
75            else:
76                raise
77        finally:
78            os.close(fd)
79
80        # Write random data to disk
81        chunk_count = file_size / chunk_size
82        cmd = str('dd if=/dev/urandom of=%s bs=%d count=%d oflag=direct' %
83                  (self._filename, chunk_size, chunk_count))
84        utils.run(cmd)
85
86        # Calculate hash value for zero'ed and one'ed data
87        cmd = str('dd if=/dev/zero of=/dev/stdout bs=%d count=1 | %s' %
88                  (chunk_size, self.HASH_CMD))
89        zero_hash = utils.run(cmd).stdout.strip()
90
91        cmd = str('dd if=/dev/ibe of=/dev/stdout bs=%d count=1 | %s' %
92                  (chunk_size, self.HASH_CMD))
93        one_hash = utils.run(cmd).stdout.strip()
94
95        trim_hash = ""
96
97        ref_hash = self._get_hash(chunk_count, chunk_size)
98
99        # Generate random order of chunk to trim
100        trim_order = list(range(0, chunk_count))
101        random.shuffle(trim_order)
102        trim_status = [False] * chunk_size
103
104        # Init stat variable
105        data_verify_count = 0
106        data_verify_match = 0
107        trim_verify_count = 0
108        trim_verify_zero = 0
109        trim_verify_one = 0
110        trim_verify_non_delete = 0
111        trim_deterministic = True
112
113        last_ratio = 0
114        for ratio in trim_ratio:
115
116            # Do trim
117            begin_trim_chunk = int(last_ratio * chunk_count)
118            end_trim_chunk = int(ratio * chunk_count)
119            fd = os.open(self._filename, os.O_RDWR, 0666)
120            for chunk in trim_order[begin_trim_chunk:end_trim_chunk]:
121                self._do_trim(fd, chunk * chunk_size, chunk_size)
122                trim_status[chunk] = True
123            os.close(fd)
124            last_ratio = ratio
125
126            cur_hash = self._get_hash(chunk_count, chunk_size)
127
128            trim_verify_count += int(ratio * chunk_count)
129            data_verify_count += chunk_count - int(ratio * chunk_count)
130
131            # Verify hash
132            for cur, ref, trim in zip(cur_hash, ref_hash, trim_status):
133                if trim:
134                    if not trim_hash:
135                        trim_hash = cur
136                    elif cur != trim_hash:
137                        trim_deterministic = False
138
139                    if cur == zero_hash:
140                        trim_verify_zero += 1
141                    elif cur == one_hash:
142                        trim_verify_one += 1
143                    elif cur == ref:
144                        trim_verify_non_delete += 1
145                else:
146                    if cur == ref:
147                        data_verify_match += 1
148
149        keyval = dict()
150        keyval['data_verify_count'] = data_verify_count
151        keyval['data_verify_match'] = data_verify_match
152        keyval['trim_verify_count'] = trim_verify_count
153        keyval['trim_verify_zero'] = trim_verify_zero
154        keyval['trim_verify_one'] = trim_verify_one
155        keyval['trim_verify_non_delete'] = trim_verify_non_delete
156        keyval['trim_deterministic'] = trim_deterministic
157        self.write_perf_keyval(keyval)
158
159        # Raise error when untrimmed data changed only.
160        # Don't care about trimmed data.
161        if data_verify_match < data_verify_count:
162            error.testFail("Fail to verify untrimmed data.")
163        if trim_verify_non_delete > 0 :
164            error.testFail("Trimmed data are not deleted.")
165