Add a PersistentInt class.
This implements a simple integer written to disk backed by AtomicFile. Bug: 230289468 Test: new unit test Ignore-AOSP-First: in a topic with internal-only changes Change-Id: I1c73c8ac2429f92153cfba3b006b4374e75e65df
This commit is contained in:
committed by
Junyu Lai
parent
108d20f84a
commit
76c0f6f0e8
108
service-t/src/com/android/server/net/PersistentInt.java
Normal file
108
service-t/src/com/android/server/net/PersistentInt.java
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 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.server.net;
|
||||||
|
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.annotation.Nullable;
|
||||||
|
import android.util.AtomicFile;
|
||||||
|
import android.util.SystemConfigFileCommitEventLogger;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple integer backed by an on-disk {@link AtomicFile}. Not thread-safe.
|
||||||
|
*/
|
||||||
|
public class PersistentInt {
|
||||||
|
private final String mPath;
|
||||||
|
private final AtomicFile mFile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new {@code PersistentInt}. The counter is set to 0 if the file does not exist.
|
||||||
|
* Before returning, the constructor checks that the file is readable and writable. This
|
||||||
|
* indicates that in the future {@link #get} and {@link #set} are likely to succeed,
|
||||||
|
* though other events (data corruption, other code deleting the file, etc.) may cause these
|
||||||
|
* calls to fail in the future.
|
||||||
|
*
|
||||||
|
* @param path the path of the file to use.
|
||||||
|
* @param logger the logger
|
||||||
|
* @throws IOException the counter could not be read or written
|
||||||
|
*/
|
||||||
|
public PersistentInt(@NonNull String path, @Nullable SystemConfigFileCommitEventLogger logger)
|
||||||
|
throws IOException {
|
||||||
|
mPath = path;
|
||||||
|
mFile = new AtomicFile(new File(path), logger);
|
||||||
|
checkReadWrite();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkReadWrite() throws IOException {
|
||||||
|
int value;
|
||||||
|
try {
|
||||||
|
value = get();
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
// Counter does not exist. Attempt to initialize to 0.
|
||||||
|
// Note that we cannot tell here if the file does not exist or if opening it failed,
|
||||||
|
// because in Java both of those throw FileNotFoundException.
|
||||||
|
value = 0;
|
||||||
|
}
|
||||||
|
set(value);
|
||||||
|
get();
|
||||||
|
// No exceptions? Good.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current value.
|
||||||
|
*
|
||||||
|
* @return the current value of the counter.
|
||||||
|
* @throws IOException if reading the value failed.
|
||||||
|
*/
|
||||||
|
public int get() throws IOException {
|
||||||
|
try (FileInputStream fin = mFile.openRead();
|
||||||
|
DataInputStream din = new DataInputStream(fin)) {
|
||||||
|
return din.readInt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the current value.
|
||||||
|
* @param value the value to set
|
||||||
|
* @throws IOException if writing the value failed.
|
||||||
|
*/
|
||||||
|
public void set(int value) throws IOException {
|
||||||
|
FileOutputStream fout = null;
|
||||||
|
try {
|
||||||
|
fout = mFile.startWrite();
|
||||||
|
DataOutputStream dout = new DataOutputStream(fout);
|
||||||
|
dout.writeInt(value);
|
||||||
|
mFile.finishWrite(fout);
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (fout != null) {
|
||||||
|
mFile.failWrite(fout);
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPath() {
|
||||||
|
return mPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
127
tests/unit/java/com/android/server/net/PersistentIntTest.kt
Normal file
127
tests/unit/java/com/android/server/net/PersistentIntTest.kt
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 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.server.net
|
||||||
|
|
||||||
|
import android.util.SystemConfigFileCommitEventLogger
|
||||||
|
import com.android.testutils.assertThrows
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import java.io.File
|
||||||
|
import java.io.IOException
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.nio.file.attribute.PosixFilePermission
|
||||||
|
import java.nio.file.attribute.PosixFilePermission.OWNER_EXECUTE
|
||||||
|
import java.nio.file.attribute.PosixFilePermission.OWNER_READ
|
||||||
|
import java.nio.file.attribute.PosixFilePermission.OWNER_WRITE
|
||||||
|
import java.util.Random
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class PersistentIntTest {
|
||||||
|
val tempFilesCreated = mutableSetOf<Path>()
|
||||||
|
lateinit var tempDir: Path
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
tempDir = Files.createTempDirectory("tmp.PersistentIntTest.")
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun tearDown() {
|
||||||
|
var permissions = setOf(OWNER_READ, OWNER_WRITE, OWNER_EXECUTE)
|
||||||
|
Files.setPosixFilePermissions(tempDir, permissions)
|
||||||
|
|
||||||
|
for (file in tempFilesCreated) {
|
||||||
|
Files.deleteIfExists(file)
|
||||||
|
}
|
||||||
|
Files.delete(tempDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testNormalReadWrite() {
|
||||||
|
// New, initialized to 0.
|
||||||
|
val pi = createPersistentInt()
|
||||||
|
assertEquals(0, pi.get())
|
||||||
|
pi.set(12345)
|
||||||
|
assertEquals(12345, pi.get())
|
||||||
|
|
||||||
|
// Existing.
|
||||||
|
val pi2 = createPersistentInt(pathOf(pi))
|
||||||
|
assertEquals(12345, pi2.get())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testReadOrWriteFailsInCreate() {
|
||||||
|
setWritable(tempDir, false)
|
||||||
|
assertThrows(IOException::class.java) {
|
||||||
|
createPersistentInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testReadOrWriteFailsAfterCreate() {
|
||||||
|
val pi = createPersistentInt()
|
||||||
|
pi.set(42)
|
||||||
|
assertEquals(42, pi.get())
|
||||||
|
|
||||||
|
val path = pathOf(pi)
|
||||||
|
setReadable(path, false)
|
||||||
|
assertThrows(IOException::class.java) { pi.get() }
|
||||||
|
pi.set(77)
|
||||||
|
|
||||||
|
setReadable(path, true)
|
||||||
|
setWritable(path, false)
|
||||||
|
setWritable(tempDir, false) // Writing creates a new file+renames, make this fail.
|
||||||
|
assertThrows(IOException::class.java) { pi.set(99) }
|
||||||
|
assertEquals(77, pi.get())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addOrRemovePermission(p: Path, permission: PosixFilePermission, add: Boolean) {
|
||||||
|
val permissions = Files.getPosixFilePermissions(p)
|
||||||
|
if (add) {
|
||||||
|
permissions.add(permission)
|
||||||
|
} else {
|
||||||
|
permissions.remove(permission)
|
||||||
|
}
|
||||||
|
Files.setPosixFilePermissions(p, permissions)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setReadable(p: Path, readable: Boolean) {
|
||||||
|
addOrRemovePermission(p, OWNER_READ, readable)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setWritable(p: Path, writable: Boolean) {
|
||||||
|
addOrRemovePermission(p, OWNER_WRITE, writable)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun pathOf(pi: PersistentInt): Path {
|
||||||
|
return File(pi.path).toPath()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createPersistentInt(path: Path = randomTempPath()): PersistentInt {
|
||||||
|
tempFilesCreated.add(path)
|
||||||
|
return PersistentInt(path.toString(),
|
||||||
|
SystemConfigFileCommitEventLogger("PersistentIntTest"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun randomTempPath(): Path {
|
||||||
|
return tempDir.resolve(Integer.toHexString(Random().nextInt())).also {
|
||||||
|
tempFilesCreated.add(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user