xref: /aosp_15_r20/external/autotest/client/bin/fs_sync.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1*9c5db199SXin Li#!/usr/bin/python3
2*9c5db199SXin Li
3*9c5db199SXin Li# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
4*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be
5*9c5db199SXin Li# found in the LICENSE file.
6*9c5db199SXin Li
7*9c5db199SXin Li"""
8*9c5db199SXin LiSync all SCSI (USB/SATA), NVMe, and eMMC devices.  All logging is via
9*9c5db199SXin Listdout and stderr, to avoid creating new disk writes on the DUT that would
10*9c5db199SXin Lithen need to be synced.
11*9c5db199SXin Li
12*9c5db199SXin LiIf --freeze is set, this will also block writes to the stateful partition,
13*9c5db199SXin Lito ensure the disk is in a consistent state before a hard reset.
14*9c5db199SXin Li"""
15*9c5db199SXin Li
16*9c5db199SXin Li
17*9c5db199SXin Liimport argparse
18*9c5db199SXin Liimport collections
19*9c5db199SXin Liimport glob
20*9c5db199SXin Liimport logging
21*9c5db199SXin Liimport logging.handlers
22*9c5db199SXin Liimport os
23*9c5db199SXin Liimport subprocess
24*9c5db199SXin Liimport sys
25*9c5db199SXin Liimport six
26*9c5db199SXin Li
27*9c5db199SXin LiSTATEFUL_MOUNT = '/mnt/stateful_partition'
28*9c5db199SXin LiENCSTATEFUL_DEV = '/dev/mapper/encstateful'
29*9c5db199SXin LiENCSTATEFUL_MOUNT = '/mnt/stateful_partition/encrypted'
30*9c5db199SXin Li
31*9c5db199SXin Li
32*9c5db199SXin LiResult = collections.namedtuple('Result', ['command', 'rc', 'stdout', 'stderr'])
33*9c5db199SXin Li
34*9c5db199SXin Li
35*9c5db199SXin Lidef run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
36*9c5db199SXin Li        strip=False):
37*9c5db199SXin Li    """Run the given command, and return a Result (namedtuple) for it.
38*9c5db199SXin Li
39*9c5db199SXin Li    @param cmd: the command to run
40*9c5db199SXin Li    @param stdout: an open file to capture stdout in, or subprocess.PIPE
41*9c5db199SXin Li    @param stderr: an open file to capture stderr in, or subprocess.PIPE
42*9c5db199SXin Li    @param strip: if True, remove certain escape sequences from stdout
43*9c5db199SXin Li    @type stdout: file | int | None
44*9c5db199SXin Li    @type stderr: file | int | None
45*9c5db199SXin Li    """
46*9c5db199SXin Li    logging.info("+ %s", cmd)
47*9c5db199SXin Li
48*9c5db199SXin Li    proc = subprocess.Popen(cmd, shell=True, stdout=stdout, stderr=stderr)
49*9c5db199SXin Li    (stdout, stderr) = proc.communicate()
50*9c5db199SXin Li    if stdout is not None:
51*9c5db199SXin Li        stdout = six.ensure_text(stdout, errors='replace')
52*9c5db199SXin Li    if stdout:
53*9c5db199SXin Li        if strip:
54*9c5db199SXin Li            stdout = stdout.replace('\x1b[0m', '')
55*9c5db199SXin Li            stdout = stdout.replace('\x1b[1m', '')
56*9c5db199SXin Li        logging.debug('    stdout: %s', repr(stdout))
57*9c5db199SXin Li    if stderr is not None:
58*9c5db199SXin Li        stderr = six.ensure_text(stderr, errors='replace')
59*9c5db199SXin Li    if stderr:
60*9c5db199SXin Li        logging.debug('    stderr: %s', repr(stderr))
61*9c5db199SXin Li    if proc.returncode != 0:
62*9c5db199SXin Li        logging.debug('    rc: %s', proc.returncode)
63*9c5db199SXin Li    return Result(cmd, proc.returncode, stdout, stderr)
64*9c5db199SXin Li
65*9c5db199SXin Li
66*9c5db199SXin Lidef run_background(cmd):
67*9c5db199SXin Li    """Run a command in the background, with stdout, and stderr detached."""
68*9c5db199SXin Li    logging.info("+ %s &", cmd)
69*9c5db199SXin Li    with open(os.devnull, 'w') as null:
70*9c5db199SXin Li        subprocess.Popen(cmd, shell=True, stdout=null, stderr=null)
71*9c5db199SXin Li
72*9c5db199SXin Li
73*9c5db199SXin Lidef _freeze_fs(fs):
74*9c5db199SXin Li    """Run fsfreeze --freeze or --unfreezeto block writes.
75*9c5db199SXin Li
76*9c5db199SXin Li    @param fs: the mountpoint path of the filesystem to freeze
77*9c5db199SXin Li    """
78*9c5db199SXin Li    # ioctl: FIFREEZE
79*9c5db199SXin Li    logging.warning("FREEZING THE FILESYSTEM: %s", fs)
80*9c5db199SXin Li    run('fsfreeze --freeze %s' % fs)
81*9c5db199SXin Li
82*9c5db199SXin Li
83*9c5db199SXin Lidef _unfreeze_fs_later(fs):
84*9c5db199SXin Li    """ Trigger a background (stdin/out/err closed) run of unfreeze later.
85*9c5db199SXin Li
86*9c5db199SXin Li    In case a test dies after freeze, this should prevent the freeze from
87*9c5db199SXin Li    breaking the repair logic for a long time.
88*9c5db199SXin Li
89*9c5db199SXin Li    @param fs: the mountpoint path of the filesystem to unfreeze
90*9c5db199SXin Li    """
91*9c5db199SXin Li    # ioctl: FITHAW
92*9c5db199SXin Li    run_background('sleep 120 && fsfreeze --unfreeze %s' % fs)
93*9c5db199SXin Li
94*9c5db199SXin Li
95*9c5db199SXin Lidef _flush_blockdev(device, wildcard=None):
96*9c5db199SXin Li    """Run /sbin/blockdev to flush buffers
97*9c5db199SXin Li
98*9c5db199SXin Li    @param device: The base block device (/dev/nvme0n1, /dev/mmcblk0, /dev/sda)
99*9c5db199SXin Li    @param wildcard: The wildcard pattern to match and iterate.
100*9c5db199SXin Li                      (e.g. the 'p*' in '/dev/mmcblk0p*')
101*9c5db199SXin Li    """
102*9c5db199SXin Li    # ioctl: BLKFLSBUF
103*9c5db199SXin Li    run('blockdev --flushbufs %s' % device)
104*9c5db199SXin Li
105*9c5db199SXin Li    if wildcard:
106*9c5db199SXin Li        partitions = glob.glob(device + wildcard)
107*9c5db199SXin Li        if device in partitions:
108*9c5db199SXin Li            # sda* matches sda too, so avoid flushing it twice
109*9c5db199SXin Li            partitions.remove(device)
110*9c5db199SXin Li        if partitions:
111*9c5db199SXin Li            run('for part in %s; do blockdev --flushbufs $part; done'
112*9c5db199SXin Li                % ' '.join(partitions))
113*9c5db199SXin Li
114*9c5db199SXin Li
115*9c5db199SXin Lidef _do_blocking_sync(device):
116*9c5db199SXin Li    """Run a blocking sync command.
117*9c5db199SXin Li
118*9c5db199SXin Li    'sync' only sends SYNCHRONIZE_CACHE but doesn't check the status.
119*9c5db199SXin Li    This function will perform a device-specific sync command.
120*9c5db199SXin Li
121*9c5db199SXin Li    @param device: Name of the block dev: /dev/sda, /dev/nvme0n1, /dev/mmcblk0.
122*9c5db199SXin Li                   The value is assumed to be the full block device,
123*9c5db199SXin Li                   not a partition or the nvme controller char device.
124*9c5db199SXin Li    """
125*9c5db199SXin Li    if 'mmcblk' in device:
126*9c5db199SXin Li        # For mmc devices, use `mmc status get` command to send an
127*9c5db199SXin Li        # empty command to wait for the disk to be available again.
128*9c5db199SXin Li
129*9c5db199SXin Li        # Flush device and partitions, ex. mmcblk0 and mmcblk0p1, mmcblk0p2, ...
130*9c5db199SXin Li        _flush_blockdev(device, 'p*')
131*9c5db199SXin Li
132*9c5db199SXin Li        # mmc status get <device>: Print the response to STATUS_SEND (CMD13)
133*9c5db199SXin Li        # ioctl: MMC_IOC_CMD, <hex value>
134*9c5db199SXin Li        run('mmc status get %s' % device)
135*9c5db199SXin Li
136*9c5db199SXin Li    elif 'nvme' in device:
137*9c5db199SXin Li        # For NVMe devices, use `nvme flush` command to commit data
138*9c5db199SXin Li        # and metadata to non-volatile media.
139*9c5db199SXin Li
140*9c5db199SXin Li        # The flush command is sent to the namespace, not the char device:
141*9c5db199SXin Li        # https://chromium.googlesource.com/chromiumos/third_party/kernel/+/bfd8947194b2e2a53db82bbc7eb7c15d028c46db
142*9c5db199SXin Li
143*9c5db199SXin Li        # Flush device and partitions, ex. nvme0n1, nvme0n1p1, nvme0n1p2, ...
144*9c5db199SXin Li        _flush_blockdev(device, 'p*')
145*9c5db199SXin Li
146*9c5db199SXin Li        # Get a list of NVMe namespaces, and flush them individually.
147*9c5db199SXin Li        # The output is assumed to be in the following format:
148*9c5db199SXin Li        # [ 0]:0x1
149*9c5db199SXin Li        # [ 1]:0x2
150*9c5db199SXin Li        list_result = run("nvme list-ns %s" % device, strip=True)
151*9c5db199SXin Li        available_ns = list_result.stdout.strip()
152*9c5db199SXin Li
153*9c5db199SXin Li        if list_result.rc != 0:
154*9c5db199SXin Li            logging.warning("Listing namespaces failed (rc=%s); assuming default.",
155*9c5db199SXin Li                         list_result.rc)
156*9c5db199SXin Li            available_ns = ''
157*9c5db199SXin Li
158*9c5db199SXin Li        elif available_ns.startswith('Usage:'):
159*9c5db199SXin Li            logging.warning("Listing namespaces failed (just printed --help);"
160*9c5db199SXin Li                         " assuming default.")
161*9c5db199SXin Li            available_ns = ''
162*9c5db199SXin Li
163*9c5db199SXin Li        elif not available_ns:
164*9c5db199SXin Li            logging.warning("Listing namespaces failed (empty output).")
165*9c5db199SXin Li
166*9c5db199SXin Li        if not available_ns:
167*9c5db199SXin Li            # -n Defaults to 0xffffffff, indicating flush for all namespaces.
168*9c5db199SXin Li            flush_result = run('nvme flush %s' % device, strip=True)
169*9c5db199SXin Li
170*9c5db199SXin Li            if flush_result.rc != 0:
171*9c5db199SXin Li                logging.warning("Flushing %s failed (rc=%s).",
172*9c5db199SXin Li                             device, flush_result.rc)
173*9c5db199SXin Li
174*9c5db199SXin Li        for line in available_ns.splitlines():
175*9c5db199SXin Li            ns = line.split(':')[-1]
176*9c5db199SXin Li
177*9c5db199SXin Li            # ioctl NVME_IOCTL_IO_CMD, <hex value>
178*9c5db199SXin Li            flush_result = run('nvme flush %s -n %s' % (device, ns), strip=True)
179*9c5db199SXin Li
180*9c5db199SXin Li            if flush_result.rc != 0:
181*9c5db199SXin Li                logging.warning("Flushing %s namespace %s failed (rc=%s).",
182*9c5db199SXin Li                             device, ns, flush_result.rc)
183*9c5db199SXin Li
184*9c5db199SXin Li    elif 'sd' in device:
185*9c5db199SXin Li        # For other devices, use hdparm to attempt a sync.
186*9c5db199SXin Li
187*9c5db199SXin Li        # flush device and partitions, ex. sda, sda1, sda2, sda3, ...
188*9c5db199SXin Li        _flush_blockdev(device, '*')
189*9c5db199SXin Li
190*9c5db199SXin Li        # -f  Flush buffer cache for device on exit
191*9c5db199SXin Li        #   ioctl: BLKFLSBUF: flush buffer cache
192*9c5db199SXin Li        #   ioctl: HDIO_DRIVE_CMD(0): wait for flush complete (unsupported)
193*9c5db199SXin Li        run('hdparm --verbose -f %s' % device, stderr=subprocess.PIPE)
194*9c5db199SXin Li
195*9c5db199SXin Li        # -F  Flush drive write cache (unsupported on many flash drives)
196*9c5db199SXin Li        #   ioctl: SG_IO, ata_op=0xec (ATA_OP_IDENTIFY)
197*9c5db199SXin Li        #   ioctl: SG_IO, ata_op=0xea (ATA_OP_FLUSHCACHE_EXT)
198*9c5db199SXin Li        # run('hdparm --verbose -F %s' % device, stderr=subprocess.PIPE)
199*9c5db199SXin Li
200*9c5db199SXin Li    else:
201*9c5db199SXin Li        logging.warning("Unhandled device type: %s", device)
202*9c5db199SXin Li        _flush_blockdev(device, '*')
203*9c5db199SXin Li
204*9c5db199SXin Li
205*9c5db199SXin Lidef blocking_sync(freeze=False):
206*9c5db199SXin Li    """Sync all known disk devices.  If freeze is True, also block writes."""
207*9c5db199SXin Li
208*9c5db199SXin Li    # Reverse alphabetical order, to give USB more time: sd*, nvme*, mmcblk*
209*9c5db199SXin Li    ls_result = run('ls /dev/mmcblk? /dev/nvme?n? /dev/sd? | sort -r')
210*9c5db199SXin Li
211*9c5db199SXin Li    devices = ls_result.stdout.splitlines()
212*9c5db199SXin Li    if freeze:
213*9c5db199SXin Li        description = 'Syncing and freezing device(s)'
214*9c5db199SXin Li    else:
215*9c5db199SXin Li        description = 'Syncing device(s)'
216*9c5db199SXin Li    logging.info('%s: %s', description, ', '.join(devices) or '(none?)')
217*9c5db199SXin Li
218*9c5db199SXin Li    # The double call to sync fakes a blocking call.
219*9c5db199SXin Li    # The first call returns before the flush is complete,
220*9c5db199SXin Li    # but the second will wait for the first to finish.
221*9c5db199SXin Li    run('sync && sync')
222*9c5db199SXin Li
223*9c5db199SXin Li    if freeze:
224*9c5db199SXin Li        _unfreeze_fs_later(ENCSTATEFUL_MOUNT)
225*9c5db199SXin Li        _freeze_fs(ENCSTATEFUL_MOUNT)
226*9c5db199SXin Li        _flush_blockdev(ENCSTATEFUL_DEV)
227*9c5db199SXin Li
228*9c5db199SXin Li        _unfreeze_fs_later(STATEFUL_MOUNT)
229*9c5db199SXin Li        _freeze_fs(STATEFUL_MOUNT)
230*9c5db199SXin Li        # No need to figure out which partition is the stateful one,
231*9c5db199SXin Li        # because _do_blocking_sync syncs every partition.
232*9c5db199SXin Li
233*9c5db199SXin Li    else:
234*9c5db199SXin Li        _flush_blockdev(ENCSTATEFUL_DEV)
235*9c5db199SXin Li
236*9c5db199SXin Li    for dev in devices:
237*9c5db199SXin Li        _do_blocking_sync(dev)
238*9c5db199SXin Li
239*9c5db199SXin Li
240*9c5db199SXin Lidef main():
241*9c5db199SXin Li    """Main method (see module docstring for purpose of this script)"""
242*9c5db199SXin Li    parser = argparse.ArgumentParser(description=__doc__)
243*9c5db199SXin Li    parser.add_argument('--freeze', '--for-reset', '--block-writes',
244*9c5db199SXin Li                        dest='freeze', action='store_true',
245*9c5db199SXin Li                        help='Block writes to prepare for hard reset.')
246*9c5db199SXin Li
247*9c5db199SXin Li    logging.root.setLevel(logging.NOTSET)
248*9c5db199SXin Li
249*9c5db199SXin Li    stdout_handler = logging.StreamHandler(stream=sys.stdout)
250*9c5db199SXin Li    stdout_handler.setFormatter(logging.Formatter(
251*9c5db199SXin Li            '%(asctime)s %(levelname)-5.5s| %(message)s'))
252*9c5db199SXin Li    logging.root.addHandler(stdout_handler)
253*9c5db199SXin Li
254*9c5db199SXin Li    opts = parser.parse_args()
255*9c5db199SXin Li    blocking_sync(freeze=opts.freeze)
256*9c5db199SXin Li
257*9c5db199SXin Li
258*9c5db199SXin Liif __name__ == '__main__':
259*9c5db199SXin Li    sys.exit(main())
260