1 /* 2 * Copyright (C) 2018 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.storagemonitoring; 17 18 import android.annotation.NonNull; 19 import android.annotation.Nullable; 20 import android.car.storagemonitoring.LifetimeWriteInfo; 21 import android.util.Log; 22 23 import com.android.internal.annotations.VisibleForTesting; 24 25 import java.io.File; 26 import java.io.IOException; 27 import java.nio.file.Files; 28 import java.util.ArrayList; 29 import java.util.Arrays; 30 import java.util.List; 31 import java.util.Objects; 32 33 /** 34 * <p>Loads lifetime write data for mounted filesystems via sysfs.</p> 35 * 36 * <p>ext4 and f2fs currently offer this information via 37 * /sys/fs/<i>type</i>/<i>partition</i>/lifetime_write_kbytes VFS entry points.</p> 38 */ 39 public class SysfsLifetimeWriteInfoProvider implements LifetimeWriteInfoProvider { 40 private static final String TAG = SysfsLifetimeWriteInfoProvider.class.getSimpleName(); 41 42 private static final String DEFAULT_PATH = "/sys/fs/"; 43 private static final String[] KNOWN_FILESYSTEMS = new String[] { 44 "ext4", 45 "f2fs" 46 }; 47 private static final String FILENAME = "lifetime_write_kbytes"; 48 49 private final File mWriteInfosPath; 50 SysfsLifetimeWriteInfoProvider()51 public SysfsLifetimeWriteInfoProvider() { 52 this(new File(DEFAULT_PATH)); 53 } 54 55 @VisibleForTesting SysfsLifetimeWriteInfoProvider(File writeInfosPath)56 SysfsLifetimeWriteInfoProvider(File writeInfosPath) { 57 mWriteInfosPath = writeInfosPath; 58 } 59 60 @Nullable tryParse(File dir)61 private LifetimeWriteInfo tryParse(File dir) { 62 File writefile = new File(dir, FILENAME); 63 if (!writefile.exists() || !writefile.isFile()) { 64 Log.d(TAG, writefile + " not a valid source of lifetime writes"); 65 return null; 66 } 67 List<String> datalines; 68 try { 69 datalines = Files.readAllLines(writefile.toPath()); 70 } catch (IOException e) { 71 Log.e(TAG, "unable to read write info from " + writefile, e); 72 return null; 73 } 74 if (datalines == null || datalines.size() != 1) { 75 Log.e(TAG, "unable to read valid write info from " + writefile); 76 return null; 77 } 78 String data = datalines.get(0); 79 try { 80 long writtenBytes = 1024L * Long.parseLong(data); 81 if (writtenBytes < 0) { 82 Log.e(TAG, "file at location " + writefile + 83 " contained a negative data amount " + data + ". Ignoring."); 84 return null; 85 } else { 86 return new LifetimeWriteInfo( 87 dir.getName(), 88 dir.getParentFile().getName(), 89 writtenBytes); 90 } 91 } catch (NumberFormatException e) { 92 Log.e(TAG, "unable to read valid write info from " + writefile, e); 93 return null; 94 } 95 } 96 97 @Override 98 @NonNull load()99 public LifetimeWriteInfo[] load() { 100 List<LifetimeWriteInfo> writeInfos = new ArrayList<>(); 101 102 for (String fstype : KNOWN_FILESYSTEMS) { 103 File fspath = new File(mWriteInfosPath, fstype); 104 if (!fspath.exists() || !fspath.isDirectory()) continue; 105 File[] files = fspath.listFiles(File::isDirectory); 106 if (files == null) { 107 Log.e(TAG, "there are no directories at location " + fspath.getAbsolutePath()); 108 continue; 109 } 110 Arrays.stream(files) 111 .map(this::tryParse) 112 .filter(Objects::nonNull) 113 .forEach(writeInfos::add); 114 } 115 116 return writeInfos.toArray(new LifetimeWriteInfo[0]); 117 } 118 } 119