Merge "Freezer: fix exception when parsing /proc/locks" into sc-dev

This commit is contained in:
Li Li
2021-07-30 16:35:16 +00:00
committed by Android (Google) Code Review
6 changed files with 261 additions and 50 deletions

View File

@@ -34,12 +34,9 @@ import dalvik.system.VMRuntime;
import libcore.io.IoUtils;
import java.io.BufferedReader;
import java.io.FileDescriptor;
import java.io.FileReader;
import java.io.IOException;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.concurrent.TimeoutException;
/**
@@ -1472,43 +1469,4 @@ public class Process {
}
private static native int nativePidFdOpen(int pid, int flags) throws ErrnoException;
/**
* Checks if a process corresponding to a specific pid owns any file locks.
* @param pid The process ID for which we want to know the existence of file locks.
* @return true If the process holds any file locks, false otherwise.
* @throws IOException if /proc/locks can't be accessed.
*
* @hide
*/
public static boolean hasFileLocks(int pid) throws Exception {
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader("/proc/locks"));
String line;
while ((line = br.readLine()) != null) {
StringTokenizer st = new StringTokenizer(line);
for (int i = 0; i < 5 && st.hasMoreTokens(); i++) {
String str = st.nextToken();
try {
if (i == 4 && Integer.parseInt(str) == pid) {
return true;
}
} catch (NumberFormatException nfe) {
throw new Exception("Exception parsing /proc/locks at \" "
+ line + " \", token #" + i);
}
}
}
return false;
} finally {
if (br != null) {
br.close();
}
}
}
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.os;
import com.android.internal.util.ProcFileReader;
import libcore.io.IoUtils;
import java.io.FileInputStream;
import java.io.IOException;
/**
* Reads and parses {@code locks} files in the {@code proc} filesystem.
* A typical example of /proc/locks
*
* 1: POSIX ADVISORY READ 18403 fd:09:9070 1073741826 1073742335
* 2: POSIX ADVISORY WRITE 18292 fd:09:34062 0 EOF
* 2: -> POSIX ADVISORY WRITE 18291 fd:09:34062 0 EOF
* 2: -> POSIX ADVISORY WRITE 18293 fd:09:34062 0 EOF
* 3: POSIX ADVISORY READ 3888 fd:09:13992 128 128
* 4: POSIX ADVISORY READ 3888 fd:09:14230 1073741826 1073742335
*/
public class ProcLocksReader {
private final String mPath;
public ProcLocksReader() {
mPath = "/proc/locks";
}
public ProcLocksReader(String path) {
mPath = path;
}
/**
* Checks if a process corresponding to a specific pid owns any file locks.
* @param pid The process ID for which we want to know the existence of file locks.
* @return true If the process holds any file locks, false otherwise.
* @throws IOException if /proc/locks can't be accessed.
*/
public boolean hasFileLocks(int pid) throws Exception {
ProcFileReader reader = null;
long last = -1;
long id; // ordinal position of the lock in the list
int owner; // the PID of the process that owns the lock
try {
reader = new ProcFileReader(new FileInputStream(mPath));
while (reader.hasMoreData()) {
id = reader.nextLong(true); // lock id
if (id == last) {
reader.finishLine(); // blocked lock
continue;
}
reader.nextIgnored(); // lock type: POSIX?
reader.nextIgnored(); // lock type: MANDATORY?
reader.nextIgnored(); // lock type: RW?
owner = reader.nextInt(); // pid
if (owner == pid) {
return true;
}
reader.finishLine();
last = id;
}
} catch (IOException e) {
// TODO: let ProcFileReader log the failed line
throw new Exception("Exception parsing /proc/locks");
} finally {
IoUtils.closeQuietly(reader);
}
return false;
}
}

View File

@@ -28,8 +28,8 @@ import java.nio.charset.StandardCharsets;
* requires each line boundary to be explicitly acknowledged using
* {@link #finishLine()}. Assumes {@link StandardCharsets#US_ASCII} encoding.
* <p>
* Currently doesn't support formats based on {@code \0}, tabs, or repeated
* delimiters.
* Currently doesn't support formats based on {@code \0}, tabs.
* Consecutive spaces are treated as a single delimiter.
*/
public class ProcFileReader implements Closeable {
private final InputStream mStream;
@@ -75,6 +75,11 @@ public class ProcFileReader implements Closeable {
private void consumeBuf(int count) throws IOException {
// TODO: consider moving to read pointer, but for now traceview says
// these copies aren't a bottleneck.
// skip all consecutive delimiters.
while (count < mTail && mBuffer[count] == ' ') {
count++;
}
System.arraycopy(mBuffer, count, mBuffer, 0, mTail - count);
mTail -= count;
if (mTail == 0) {
@@ -159,11 +164,18 @@ public class ProcFileReader implements Closeable {
* Parse and return next token as base-10 encoded {@code long}.
*/
public long nextLong() throws IOException {
return nextLong(false);
}
/**
* Parse and return next token as base-10 encoded {@code long}.
*/
public long nextLong(boolean stopAtInvalid) throws IOException {
final int tokenIndex = nextTokenIndex();
if (tokenIndex == -1) {
throw new ProtocolException("Missing required long");
} else {
return parseAndConsumeLong(tokenIndex);
return parseAndConsumeLong(tokenIndex, stopAtInvalid);
}
}
@@ -176,7 +188,7 @@ public class ProcFileReader implements Closeable {
if (tokenIndex == -1) {
return def;
} else {
return parseAndConsumeLong(tokenIndex);
return parseAndConsumeLong(tokenIndex, false);
}
}
@@ -186,7 +198,10 @@ public class ProcFileReader implements Closeable {
return s;
}
private long parseAndConsumeLong(int tokenIndex) throws IOException {
/**
* If stopAtInvalid is true, don't throw IOException but return whatever parsed so far.
*/
private long parseAndConsumeLong(int tokenIndex, boolean stopAtInvalid) throws IOException {
final boolean negative = mBuffer[0] == '-';
// TODO: refactor into something like IntegralToString
@@ -194,7 +209,11 @@ public class ProcFileReader implements Closeable {
for (int i = negative ? 1 : 0; i < tokenIndex; i++) {
final int digit = mBuffer[i] - '0';
if (digit < 0 || digit > 9) {
throw invalidLong(tokenIndex);
if (stopAtInvalid) {
break;
} else {
throw invalidLong(tokenIndex);
}
}
// always parse as negative number and apply sign later; this
@@ -226,6 +245,18 @@ public class ProcFileReader implements Closeable {
return (int) value;
}
/**
* Bypass the next token.
*/
public void nextIgnored() throws IOException {
final int tokenIndex = nextTokenIndex();
if (tokenIndex == -1) {
throw new ProtocolException("Missing required token");
} else {
consumeBuf(tokenIndex + 1);
}
}
@Override
public void close() throws IOException {
mStream.close();

View File

@@ -0,0 +1,90 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.os;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import android.content.Context;
import android.os.FileUtils;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.File;
import java.nio.file.Files;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class ProcLocksReaderTest {
private File mProcDirectory;
@Before
public void setUp() {
Context context = InstrumentationRegistry.getContext();
mProcDirectory = context.getDir("proc", Context.MODE_PRIVATE);
}
@After
public void tearDown() throws Exception {
FileUtils.deleteContents(mProcDirectory);
}
@Test
public void testRunSimpleLocks() throws Exception {
String simpleLocks =
"1: POSIX ADVISORY READ 18403 fd:09:9070 1073741826 1073742335\n" +
"2: POSIX ADVISORY WRITE 18292 fd:09:34062 0 EOF\n";
assertFalse(runHasFileLocks(simpleLocks, 18402));
assertFalse(runHasFileLocks(simpleLocks, 18404));
assertTrue(runHasFileLocks(simpleLocks, 18403));
assertTrue(runHasFileLocks(simpleLocks, 18292));
}
@Test
public void testRunBlockedLocks() throws Exception {
String blockedLocks =
"1: POSIX ADVISORY READ 18403 fd:09:9070 1073741826 1073742335\n" +
"2: POSIX ADVISORY WRITE 18292 fd:09:34062 0 EOF\n" +
"2: -> POSIX ADVISORY WRITE 18291 fd:09:34062 0 EOF\n" +
"2: -> POSIX ADVISORY WRITE 18293 fd:09:34062 0 EOF\n" +
"3: POSIX ADVISORY READ 3888 fd:09:13992 128 128\n" +
"4: POSIX ADVISORY READ 3888 fd:09:14230 1073741826 1073742335\n";
assertFalse(runHasFileLocks(blockedLocks, 18402));
assertFalse(runHasFileLocks(blockedLocks, 18404));
assertTrue(runHasFileLocks(blockedLocks, 18403));
assertTrue(runHasFileLocks(blockedLocks, 18292));
assertFalse(runHasFileLocks(blockedLocks, 18291));
assertFalse(runHasFileLocks(blockedLocks, 18293));
assertTrue(runHasFileLocks(blockedLocks, 3888));
}
private boolean runHasFileLocks(String fileContents, int pid) throws Exception {
File tempFile = File.createTempFile("locks", null, mProcDirectory);
Files.write(tempFile.toPath(), fileContents.getBytes());
boolean result = new ProcLocksReader(tempFile.toString()).hasFileLocks(pid);
Files.delete(tempFile.toPath());
return result;
}
}

View File

@@ -166,6 +166,46 @@ public class ProcFileReaderTest extends AndroidTestCase {
assertEquals(-1L, reader.nextOptionalLong(-1L));
}
public void testInvalidLongs() throws Exception {
final ProcFileReader reader = buildReader("12: 34\n56 78@#\n");
assertEquals(12L, reader.nextLong(true));
assertEquals(34L, reader.nextLong(true));
reader.finishLine();
assertTrue(reader.hasMoreData());
assertEquals(56L, reader.nextLong(true));
assertEquals(78L, reader.nextLong(true));
reader.finishLine();
assertFalse(reader.hasMoreData());
}
public void testConsecutiveDelimiters() throws Exception {
final ProcFileReader reader = buildReader("1 2 3 4 5\n");
assertEquals(1L, reader.nextLong());
assertEquals(2L, reader.nextLong());
assertEquals(3L, reader.nextLong());
assertEquals(4L, reader.nextLong());
assertEquals(5L, reader.nextLong());
reader.finishLine();
assertFalse(reader.hasMoreData());
}
public void testIgnore() throws Exception {
final ProcFileReader reader = buildReader("a b c\n");
assertEquals("a", reader.nextString());
assertTrue(reader.hasMoreData());
reader.nextIgnored();
assertTrue(reader.hasMoreData());
assertEquals("c", reader.nextString());
reader.finishLine();
assertFalse(reader.hasMoreData());
}
private static ProcFileReader buildReader(String string) throws IOException {
return buildReader(string, 2048);
}

View File

@@ -41,6 +41,7 @@ import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.ProcLocksReader;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.ServiceThread;
@@ -319,6 +320,7 @@ public final class CachedAppOptimizer {
private int mPersistentCompactionCount;
private int mBfgsCompactionCount;
private final ProcessDependencies mProcessDependencies;
private final ProcLocksReader mProcLocksReader;
public CachedAppOptimizer(ActivityManagerService am) {
this(am, null, new DefaultProcessDependencies());
@@ -335,6 +337,7 @@ public final class CachedAppOptimizer {
mProcessDependencies = processDependencies;
mTestCallback = callback;
mSettingsObserver = new SettingsContentObserver();
mProcLocksReader = new ProcLocksReader();
}
/**
@@ -1312,7 +1315,7 @@ public final class CachedAppOptimizer {
try {
// pre-check for locks to avoid unnecessary freeze/unfreeze operations
if (Process.hasFileLocks(pid)) {
if (mProcLocksReader.hasFileLocks(pid)) {
if (DEBUG_FREEZER) {
Slog.d(TAG_AM, name + " (" + pid + ") holds file locks, not freezing");
}
@@ -1399,7 +1402,7 @@ public final class CachedAppOptimizer {
try {
// post-check to prevent races
if (Process.hasFileLocks(pid)) {
if (mProcLocksReader.hasFileLocks(pid)) {
if (DEBUG_FREEZER) {
Slog.d(TAG_AM, name + " (" + pid + ") holds file locks, reverting freeze");
}