Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c48b34af authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Rewrite AtomicDirectory."

parents 958b748f f592aa85
Loading
Loading
Loading
Loading
+81 −72
Original line number Original line Diff line number Diff line
@@ -17,15 +17,17 @@
package com.android.internal.os;
package com.android.internal.os;


import android.annotation.NonNull;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.FileUtils;
import android.os.FileUtils;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
import android.util.ArrayMap;
import android.util.ArrayMap;
import android.util.Log;


import com.android.internal.util.Preconditions;
import com.android.internal.util.Preconditions;


import libcore.io.IoUtils;

import java.io.File;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.IOException;
import java.util.Arrays;
import java.util.Arrays;
@@ -48,12 +50,13 @@ import java.util.Arrays;
 * backing directory when checking existence, making changes, and deleting.
 * backing directory when checking existence, making changes, and deleting.
 */
 */
public final class AtomicDirectory {
public final class AtomicDirectory {
    private final @NonNull ArrayMap<File, FileOutputStream> mOpenFiles = new ArrayMap<>();

    private static final String LOG_TAG = AtomicDirectory.class.getSimpleName();

    private final @NonNull File mBaseDirectory;
    private final @NonNull File mBaseDirectory;
    private final @NonNull File mBackupDirectory;
    private final @NonNull File mBackupDirectory;


    private int mBaseDirectoryFd = -1;
    private final @NonNull ArrayMap<File, FileOutputStream> mOpenFiles = new ArrayMap<>();
    private int mBackupDirectoryFd = -1;


    /**
    /**
     * Creates a new instance.
     * Creates a new instance.
@@ -67,15 +70,16 @@ public final class AtomicDirectory {
    }
    }


    /**
    /**
     * Gets the backup directory if present. This could be useful if you are
     * Gets the backup directory which may or may not exist. This could be
     * writing new state to the dir but need to access the last persisted state
     * useful if you are writing new state to the directory but need to access
     * at the same time. This means that this call is useful in between
     * the last persisted state at the same time. This means that this call is
     * {@link #startWrite()} and {@link #finishWrite()} or {@link #failWrite()}.
     * useful in between {@link #startWrite()} and {@link #finishWrite()} or
     * You should not modify the content returned by this method.
     * {@link #failWrite()}. You should not modify the content returned by this
     * method.
     *
     *
     * @see #startRead()
     * @see #startRead()
     */
     */
    public @Nullable File getBackupDirectory() {
    public @NonNull File getBackupDirectory() {
        return mBackupDirectory;
        return mBackupDirectory;
    }
    }


@@ -90,7 +94,8 @@ public final class AtomicDirectory {
     */
     */
    public @NonNull File startRead() throws IOException {
    public @NonNull File startRead() throws IOException {
        restore();
        restore();
        return getOrCreateBaseDirectory();
        ensureBaseDirectory();
        return mBaseDirectory;
    }
    }


    /**
    /**
@@ -99,10 +104,7 @@ public final class AtomicDirectory {
     * @see #startRead()
     * @see #startRead()
     * @see #startWrite()
     * @see #startWrite()
     */
     */
    public void finishRead() {
    public void finishRead() {}
        mBaseDirectoryFd = -1;
        mBackupDirectoryFd = -1;
    }


    /**
    /**
     * Starts editing this directory. After calling this method you should
     * Starts editing this directory. After calling this method you should
@@ -121,7 +123,8 @@ public final class AtomicDirectory {
     */
     */
    public @NonNull File startWrite() throws IOException {
    public @NonNull File startWrite() throws IOException {
        backup();
        backup();
        return getOrCreateBaseDirectory();
        ensureBaseDirectory();
        return mBaseDirectory;
    }
    }


    /**
    /**
@@ -135,11 +138,11 @@ public final class AtomicDirectory {
     * @see #closeWrite(FileOutputStream)
     * @see #closeWrite(FileOutputStream)
     */
     */
    public @NonNull FileOutputStream openWrite(@NonNull File file) throws IOException {
    public @NonNull FileOutputStream openWrite(@NonNull File file) throws IOException {
        if (file.isDirectory() || !file.getParentFile().equals(getOrCreateBaseDirectory())) {
        if (file.isDirectory() || !file.getParentFile().equals(mBaseDirectory)) {
            throw new IllegalArgumentException("Must be a file in " + getOrCreateBaseDirectory());
            throw new IllegalArgumentException("Must be a file in " + mBaseDirectory);
        }
        }
        if (mOpenFiles.containsKey(file)) {
        if (mOpenFiles.containsKey(file)) {
            throw new IllegalArgumentException("Already open file" + file.getCanonicalPath());
            throw new IllegalArgumentException("Already open file " + file.getAbsolutePath());
        }
        }
        final FileOutputStream destination = new FileOutputStream(file);
        final FileOutputStream destination = new FileOutputStream(file);
        mOpenFiles.put(file, destination);
        mOpenFiles.put(file, destination);
@@ -160,7 +163,7 @@ public final class AtomicDirectory {
        }
        }
        mOpenFiles.removeAt(indexOfValue);
        mOpenFiles.removeAt(indexOfValue);
        FileUtils.sync(destination);
        FileUtils.sync(destination);
        IoUtils.closeQuietly(destination);
        FileUtils.closeQuietly(destination);
    }
    }


    public void failWrite(@NonNull FileOutputStream destination) {
    public void failWrite(@NonNull FileOutputStream destination) {
@@ -169,7 +172,7 @@ public final class AtomicDirectory {
            throw new IllegalArgumentException("Unknown file stream " + destination);
            throw new IllegalArgumentException("Unknown file stream " + destination);
        }
        }
        mOpenFiles.removeAt(indexOfValue);
        mOpenFiles.removeAt(indexOfValue);
        IoUtils.closeQuietly(destination);
        FileUtils.closeQuietly(destination);
    }
    }


    /**
    /**
@@ -177,15 +180,15 @@ public final class AtomicDirectory {
     *
     *
     * @see #startWrite()
     * @see #startWrite()
     *
     *
     * @throws IllegalStateException is some files are not closed.
     * @throws IllegalStateException if some files are not closed.
     */
     */
    public void finishWrite() {
    public void finishWrite() {
        throwIfSomeFilesOpen();
        throwIfSomeFilesOpen();
        fsyncDirectoryFd(mBaseDirectoryFd);

        syncDirectory(mBaseDirectory);
        syncParentDirectory();
        deleteDirectory(mBackupDirectory);
        deleteDirectory(mBackupDirectory);
        fsyncDirectoryFd(mBackupDirectoryFd);
        syncParentDirectory();
        mBaseDirectoryFd = -1;
        mBackupDirectoryFd = -1;
    }
    }


    /**
    /**
@@ -195,11 +198,12 @@ public final class AtomicDirectory {
     */
     */
    public void failWrite() {
    public void failWrite() {
        throwIfSomeFilesOpen();
        throwIfSomeFilesOpen();

        try{
        try{
            restore();
            restore();
        } catch (IOException ignored) {}
        } catch (IOException e) {
        mBaseDirectoryFd = -1;
            Log.e(LOG_TAG, "Failed to restore in failWrite()", e);
        mBackupDirectoryFd = -1;
        }
    }
    }


    /**
    /**
@@ -213,29 +217,28 @@ public final class AtomicDirectory {
     * Deletes this directory.
     * Deletes this directory.
     */
     */
    public void delete() {
    public void delete() {
        boolean deleted = false;
        if (mBaseDirectory.exists()) {
        if (mBaseDirectory.exists()) {
            deleteDirectory(mBaseDirectory);
            deleted |= deleteDirectory(mBaseDirectory);
            fsyncDirectoryFd(mBaseDirectoryFd);
        }
        }
        if (mBackupDirectory.exists()) {
        if (mBackupDirectory.exists()) {
            deleteDirectory(mBackupDirectory);
            deleted |= deleteDirectory(mBackupDirectory);
            fsyncDirectoryFd(mBackupDirectoryFd);
        }
        if (deleted) {
            syncParentDirectory();
        }
        }
    }
    }


    private @NonNull File getOrCreateBaseDirectory() throws IOException {
    private void ensureBaseDirectory() throws IOException {
        if (!mBaseDirectory.exists()) {
        if (mBaseDirectory.exists()) {
            return;
        }

        if (!mBaseDirectory.mkdirs()) {
        if (!mBaseDirectory.mkdirs()) {
                throw new IOException("Couldn't create directory " + mBaseDirectory);
            throw new IOException("Failed to create directory " + mBaseDirectory);
        }
        }
        FileUtils.setPermissions(mBaseDirectory.getPath(),
        FileUtils.setPermissions(mBaseDirectory.getPath(),
                    FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IXOTH,
                FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IXOTH, -1, -1);
                    -1, -1);
        }
        if (mBaseDirectoryFd < 0) {
            mBaseDirectoryFd = getDirectoryFd(mBaseDirectory.getCanonicalPath());
        }
        return mBaseDirectory;
    }
    }


    private void throwIfSomeFilesOpen() {
    private void throwIfSomeFilesOpen() {
@@ -249,50 +252,56 @@ public final class AtomicDirectory {
        if (!mBaseDirectory.exists()) {
        if (!mBaseDirectory.exists()) {
            return;
            return;
        }
        }
        if (mBaseDirectoryFd < 0) {

            mBaseDirectoryFd = getDirectoryFd(mBaseDirectory.getCanonicalPath());
        }
        if (mBackupDirectory.exists()) {
        if (mBackupDirectory.exists()) {
            deleteDirectory(mBackupDirectory);
            deleteDirectory(mBackupDirectory);
        }
        }
        if (!mBaseDirectory.renameTo(mBackupDirectory)) {
        if (!mBaseDirectory.renameTo(mBackupDirectory)) {
            throw new IOException("Couldn't backup " + mBaseDirectory
            throw new IOException("Failed to backup " + mBaseDirectory + " to " + mBackupDirectory);
                    + " to " + mBackupDirectory);
        }
        }
        mBackupDirectoryFd = mBaseDirectoryFd;
        syncParentDirectory();
        mBaseDirectoryFd = -1;
        fsyncDirectoryFd(mBackupDirectoryFd);
    }
    }


    private void restore() throws IOException {
    private void restore() throws IOException {
        if (!mBackupDirectory.exists()) {
        if (!mBackupDirectory.exists()) {
            return;
            return;
        }
        }
        if (mBackupDirectoryFd == -1) {

            mBackupDirectoryFd = getDirectoryFd(mBackupDirectory.getCanonicalPath());
        }
        if (mBaseDirectory.exists()) {
        if (mBaseDirectory.exists()) {
            deleteDirectory(mBaseDirectory);
            deleteDirectory(mBaseDirectory);
        }
        }
        if (!mBackupDirectory.renameTo(mBaseDirectory)) {
        if (!mBackupDirectory.renameTo(mBaseDirectory)) {
            throw new IOException("Couldn't restore " + mBackupDirectory
            throw new IOException("Failed to restore " + mBackupDirectory + " to "
                    + " to " + mBaseDirectory);
                    + mBaseDirectory);
        }
        }
        mBaseDirectoryFd = mBackupDirectoryFd;
        syncParentDirectory();
        mBackupDirectoryFd = -1;
        fsyncDirectoryFd(mBaseDirectoryFd);
    }
    }


    private static void deleteDirectory(@NonNull File file) {
    private static boolean deleteDirectory(@NonNull File directory) {
        final File[] children = file.listFiles();
        return FileUtils.deleteContentsAndDir(directory);
        if (children != null) {
            for (File child : children) {
                deleteDirectory(child);
    }
    }
        }

        file.delete();
    private void syncParentDirectory() {
        syncDirectory(mBaseDirectory.getParentFile());
    }
    }


    private static native int getDirectoryFd(String path);
    // Standard Java IO doesn't allow opening a directory (will throw a FileNotFoundException
    private static native void fsyncDirectoryFd(int fd);
    // instead), so we have to do it manually.
    private static void syncDirectory(@NonNull File directory) {
        String path = directory.getAbsolutePath();
        FileDescriptor fd;
        try {
            fd = Os.open(path, OsConstants.O_RDONLY, 0);
        } catch (ErrnoException e) {
            Log.e(LOG_TAG, "Failed to open " + path, e);
            return;
        }
        try {
            Os.fsync(fd);
        } catch (ErrnoException e) {
            Log.e(LOG_TAG, "Failed to fsync " + path, e);
        } finally {
            FileUtils.closeQuietly(fd);
        }
    }
}
}
+0 −1
Original line number Original line Diff line number Diff line
@@ -195,7 +195,6 @@ cc_library_shared {
                "android_content_res_ObbScanner.cpp",
                "android_content_res_ObbScanner.cpp",
                "android_content_res_Configuration.cpp",
                "android_content_res_Configuration.cpp",
                "android_security_Scrypt.cpp",
                "android_security_Scrypt.cpp",
                "com_android_internal_os_AtomicDirectory.cpp",
                "com_android_internal_os_ClassLoaderFactory.cpp",
                "com_android_internal_os_ClassLoaderFactory.cpp",
                "com_android_internal_os_FuseAppLoop.cpp",
                "com_android_internal_os_FuseAppLoop.cpp",
                "com_android_internal_os_Zygote.cpp",
                "com_android_internal_os_Zygote.cpp",
+0 −2
Original line number Original line Diff line number Diff line
@@ -227,7 +227,6 @@ extern int register_android_content_res_Configuration(JNIEnv* env);
extern int register_android_animation_PropertyValuesHolder(JNIEnv *env);
extern int register_android_animation_PropertyValuesHolder(JNIEnv *env);
extern int register_android_security_Scrypt(JNIEnv *env);
extern int register_android_security_Scrypt(JNIEnv *env);
extern int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env);
extern int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env);
extern int register_com_android_internal_os_AtomicDirectory(JNIEnv *env);
extern int register_com_android_internal_os_ClassLoaderFactory(JNIEnv* env);
extern int register_com_android_internal_os_ClassLoaderFactory(JNIEnv* env);
extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env);
extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env);
extern int register_com_android_internal_os_Zygote(JNIEnv *env);
extern int register_com_android_internal_os_Zygote(JNIEnv *env);
@@ -1602,7 +1601,6 @@ static const RegJNIRec gRegJNI[] = {
    REG_JNI(register_android_animation_PropertyValuesHolder),
    REG_JNI(register_android_animation_PropertyValuesHolder),
    REG_JNI(register_android_security_Scrypt),
    REG_JNI(register_android_security_Scrypt),
    REG_JNI(register_com_android_internal_content_NativeLibraryHelper),
    REG_JNI(register_com_android_internal_content_NativeLibraryHelper),
    REG_JNI(register_com_android_internal_os_AtomicDirectory),
    REG_JNI(register_com_android_internal_os_FuseAppLoop),
    REG_JNI(register_com_android_internal_os_FuseAppLoop),
};
};


+0 −66
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2018 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.
 */

#include <nativehelper/ScopedUtfChars.h>
#include "jni.h"

#include "core_jni_helpers.h"

namespace android {

static jint com_android_internal_os_AtomicDirectory_getDirectoryFd(JNIEnv* env,
        jobject /*clazz*/, jstring path) {
    ScopedUtfChars path8(env, path);
    if (path8.c_str() == NULL) {
        ALOGE("Invalid path: %s", path8.c_str());
        return -1;
    }
    int fd;
    if ((fd = TEMP_FAILURE_RETRY(open(path8.c_str(), O_DIRECTORY | O_RDONLY | O_CLOEXEC))) == -1) {
        ALOGE("Cannot open directory %s, error: %s\n", path8.c_str(), strerror(errno));
        return -1;
    }
    return fd;
}

static void com_android_internal_os_AtomicDirectory_fsyncDirectoryFd(JNIEnv* env,
        jobject /*clazz*/, jint fd) {
    if (TEMP_FAILURE_RETRY(fsync(fd)) == -1) {
        ALOGE("Cannot fsync directory %d, error: %s\n", fd, strerror(errno));
    }
}

/*
 * JNI registration.
 */
static const JNINativeMethod gRegisterMethods[] = {
    /* name, signature, funcPtr */
    { "fsyncDirectoryFd",
      "(I)V",
       (void*) com_android_internal_os_AtomicDirectory_fsyncDirectoryFd
    },
    { "getDirectoryFd",
      "(Ljava/lang/String;)I",
       (void*) com_android_internal_os_AtomicDirectory_getDirectoryFd
    },
};

int register_com_android_internal_os_AtomicDirectory(JNIEnv* env) {
    return RegisterMethodsOrDie(env, "com/android/internal/os/AtomicDirectory",
            gRegisterMethods, NELEM(gRegisterMethods));
}

}; // namespace android