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