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