1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.car.test.stressfs; 17 18 import android.app.Service; 19 import android.app.Notification; 20 import android.app.NotificationChannel; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.net.Uri; 24 import android.os.Binder; 25 import android.os.Bundle; 26 import android.os.Environment; 27 import android.os.IBinder; 28 import android.util.Log; 29 import android.R.drawable; 30 31 import java.io.*; 32 import java.util.*; 33 34 /** 35 * Used to stress the file writing before and during shutdown to help ensure 36 * that the filesystem shuts down at the right time, in a consistent manner, 37 * and does not get corrupted. 38 * 39 * Writes to two files - one on the data partition, one on the external storage 40 * partition - simultaneous in two separate threads; starting over after a 41 * certain amount of data is written. 42 * 43 * This class is intended to be invoked from the shell. For a 64KB file 44 * written in 1KB chunks, invoke from the host workstation: 45 * adb install -g StressFS.apk 46 * adb shell am start \ 47 * -n com.android.car.test.stressfs/.WritingActivity \ 48 * -a com.android.car.test.stressfs.START 49 * -d "stressfs://start?block=1024\&file=65536" 50 * 51 * After reboot: 52 * adb uninstall com.android.car.test.stressfs 53 * adb shell "rm -rf /storage/emulated/0/stressfs_data*" 54 * 55 * On boot after running this while shutting down, fsck should flag any 56 * corruption that it sees resulting from this running. The goal is to set the 57 * shutdown sequence in a manner that does not corrupt so that this check can 58 * be avoided. 59 */ 60 public class WritingService extends Service { 61 62 private static final String TAG = "StressFS"; 63 64 private static final String ACTION_START = "com.android.car.test.stressfs.START"; 65 66 private static final int DEFAULT_BLOCK_SIZE = 4096; 67 private static final int DEFAULT_FILE_SIZE = 16 * 1024 * 1024; 68 69 private static final String FILENAME_PREFIX = "stressfs_data_"; 70 71 private static final int NOTIFICATION_ID = 100; 72 73 /** Continuously writes test data to a specified file. */ 74 private static class WriteToDisk extends Thread { 75 private final String mLogTag; 76 private final File mFile; 77 private final int mFileSize; 78 private final byte[] mTestData; 79 80 public WriteToDisk( 81 String logTag, 82 File file, 83 int fileSize, 84 byte[] testData) { 85 mLogTag = logTag; 86 mFile = file; 87 mFileSize = fileSize; 88 mTestData = testData; 89 } 90 91 /** Writes data to a file, restarting once the maximum amount of data is reached.*/ 92 @Override 93 public void run() { 94 Log.d(TAG, mLogTag + " thread started"); 95 while (true) { 96 try { 97 FileOutputStream fos = new FileOutputStream(mFile); 98 // Write in chunks until the amount of data requested 99 // is written. 100 for (int j = 0; j < mFileSize; j += mTestData.length) { 101 fos.write(mTestData); 102 } 103 fos.close(); 104 } catch (FileNotFoundException e) { 105 Log.e(TAG, "File not found: ", e); 106 } catch (IOException e) { 107 Log.e(TAG, "IO error: ", e); 108 } 109 } 110 } 111 } 112 113 /** Raises service priority and starts the writing threads. */ 114 @Override 115 public IBinder onBind(Intent intent) { 116 Notification notification = 117 new Notification.Builder(this, NotificationChannel.DEFAULT_CHANNEL_ID) 118 .setContentTitle("Stress Filesystem service running.") 119 .setSmallIcon(drawable.ic_menu_save) 120 .build(); 121 122 // Raise priority of this service. 123 startForeground(NOTIFICATION_ID, notification); 124 125 File dataPartitionFile = getFileStreamPath(FILENAME_PREFIX + UUID.randomUUID()); 126 File externalPartitionFile = new File( 127 Environment.getExternalStorageDirectory(), FILENAME_PREFIX + UUID.randomUUID()); 128 129 Log.i(TAG, "External storage state: " + 130 Environment.getExternalStorageState(externalPartitionFile)); 131 132 Uri data = intent.getData(); 133 if (data != null) { 134 Log.i(TAG, "Data: " + data.toString()); 135 } 136 int blockSize = getQueryParam(data, "block", DEFAULT_BLOCK_SIZE); 137 int fileSize = getQueryParam(data, "file", DEFAULT_FILE_SIZE); 138 Log.i(TAG, "Block Size: " + blockSize); 139 Log.i(TAG, "File Size: " + fileSize); 140 141 if (fileSize % blockSize != 0) { 142 Log.w(TAG, "File size should be a multiple of block size."); 143 } 144 145 // Populate some test data. 146 StringBuilder builder = new StringBuilder(blockSize); 147 for (int i = 0; i < builder.capacity(); i++) { 148 builder.append((char)(i % 26 + 'A')); 149 } 150 byte[] testData = new String(builder).getBytes(); 151 152 // Spawn two threads - one to write to the /data partition, one to 153 // write to the SD card. 154 new WriteToDisk("data", dataPartitionFile, fileSize, testData).start(); 155 new WriteToDisk("external", externalPartitionFile, fileSize, testData).start(); 156 157 // No need to return a binder interface, since there is no more 158 // interaction needed from the activity starting the service. 159 return null; 160 } 161 162 /** Keeps service alive once started. */ 163 @Override 164 public int onStartCommand(Intent intent, int flags, int startId) { 165 return START_STICKY; 166 } 167 168 /** Parses an integer query parameter from the input Uri. */ 169 private int getQueryParam(Uri data, String key, int defaultValue) { 170 if (data == null) { 171 return defaultValue; 172 } 173 String inValString = data.getQueryParameter(key); 174 if (inValString != null) { 175 try { 176 int inVal = Integer.parseInt(inValString); 177 if (inVal != 0) { 178 return inVal; 179 } 180 } catch (NumberFormatException e) { 181 return defaultValue; 182 } 183 } 184 return defaultValue; 185 } 186 } 187