diff options
6 files changed, 261 insertions, 50 deletions
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 6bca336dae91..9f37c4877199 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -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(); - } - } - } } diff --git a/core/java/com/android/internal/os/ProcLocksReader.java b/core/java/com/android/internal/os/ProcLocksReader.java new file mode 100644 index 000000000000..bd3115fc5d4c --- /dev/null +++ b/core/java/com/android/internal/os/ProcLocksReader.java @@ -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; + } +} diff --git a/core/java/com/android/internal/util/ProcFileReader.java b/core/java/com/android/internal/util/ProcFileReader.java index ead58c7de611..0dd8ad89df61 100644 --- a/core/java/com/android/internal/util/ProcFileReader.java +++ b/core/java/com/android/internal/util/ProcFileReader.java @@ -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(); diff --git a/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java b/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java new file mode 100644 index 000000000000..d800c2c3c66e --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java @@ -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; + } +} diff --git a/core/tests/utiltests/src/com/android/internal/util/ProcFileReaderTest.java b/core/tests/utiltests/src/com/android/internal/util/ProcFileReaderTest.java index b6da1954ba79..0532628ba16d 100644 --- a/core/tests/utiltests/src/com/android/internal/util/ProcFileReaderTest.java +++ b/core/tests/utiltests/src/com/android/internal/util/ProcFileReaderTest.java @@ -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); } diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index 9dbb70757cf7..7c336d768006 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -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"); } |