diff options
author | Hai Zhang <zhanghai@google.com> | 2020-01-16 20:59:15 -0800 |
---|---|---|
committer | Hai Zhang <zhanghai@google.com> | 2020-01-17 19:05:57 -0800 |
commit | 47c1de2ff831628f0f5d6fcb402b444b1838687b (patch) | |
tree | c7c844d1b1953e9236804e5b1571bd71654a0632 | |
parent | 76f0defebf3c1bc419dc083d2bc65113ecd1f264 (diff) |
Move roles persistence into APEX.
Bug: 136503238
Test: presubmit
Change-Id: Id11842ad7653317d5f0ebf2df0f4c315d0018440
5 files changed, 418 insertions, 84 deletions
diff --git a/apex/permission/service/java/com/android/role/persistence/RolesPersistence.java b/apex/permission/service/java/com/android/role/persistence/RolesPersistence.java new file mode 100644 index 000000000000..63c8eedd6285 --- /dev/null +++ b/apex/permission/service/java/com/android/role/persistence/RolesPersistence.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2020 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.role.persistence; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.UserHandle; + +/** + * Persistence for roles. + * + * TODO(b/147914847): Remove @hide when it becomes the default. + * @hide + */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES, process = SystemApi.Process.SYSTEM_SERVER) +public interface RolesPersistence { + + /** + * Read the roles from persistence. + * + * This will perform I/O operations synchronously. + * + * @param user the user to read for + * @return the roles read + */ + @Nullable + RolesState read(@NonNull UserHandle user); + + /** + * Write the roles to persistence. + * + * This will perform I/O operations synchronously. + * + * @param roles the roles to write + * @param user the user to write for + */ + void write(@NonNull RolesState roles, @NonNull UserHandle user); + + /** + * Delete the roles from persistence. + * + * This will perform I/O operations synchronously. + * + * @param user the user to delete for + */ + void delete(@NonNull UserHandle user); + + /** + * Create a new instance of {@link RolesPersistence} implementation. + * + * @return the new instance. + */ + @NonNull + static RolesPersistence createInstance() { + return new RolesPersistenceImpl(); + } +} diff --git a/apex/permission/service/java/com/android/role/persistence/RolesPersistenceImpl.java b/apex/permission/service/java/com/android/role/persistence/RolesPersistenceImpl.java new file mode 100644 index 000000000000..5061742f4c58 --- /dev/null +++ b/apex/permission/service/java/com/android/role/persistence/RolesPersistenceImpl.java @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2020 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.role.persistence; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.UserHandle; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.AtomicFile; +import android.util.Log; +import android.util.Xml; + +import com.android.permission.persistence.IoUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.Set; + +/** + * Persistence implementation for roles. + * + * TODO(b/147914847): Remove @hide when it becomes the default. + * @hide + */ +public class RolesPersistenceImpl implements RolesPersistence { + + private static final String LOG_TAG = RolesPersistenceImpl.class.getSimpleName(); + + private static final String ROLES_FILE_NAME = "roles.xml"; + + private static final String TAG_ROLES = "roles"; + private static final String TAG_ROLE = "role"; + private static final String TAG_HOLDER = "holder"; + + private static final String ATTRIBUTE_VERSION = "version"; + private static final String ATTRIBUTE_NAME = "name"; + private static final String ATTRIBUTE_PACKAGES_HASH = "packagesHash"; + + @Nullable + @Override + public RolesState read(@NonNull UserHandle user) { + File file = getFile(user); + try (FileInputStream inputStream = new AtomicFile(file).openRead()) { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(inputStream, null); + return parseXml(parser); + } catch (FileNotFoundException e) { + Log.i(LOG_TAG, "roles.xml not found"); + return null; + } catch (XmlPullParserException | IOException e) { + throw new IllegalStateException("Failed to read roles.xml: " + file , e); + } + } + + @NonNull + private static RolesState parseXml(@NonNull XmlPullParser parser) + throws IOException, XmlPullParserException { + int type; + int depth; + int innerDepth = parser.getDepth() + 1; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) { + if (depth > innerDepth || type != XmlPullParser.START_TAG) { + continue; + } + + if (parser.getName().equals(TAG_ROLES)) { + return parseRoles(parser); + } + } + throw new IllegalStateException("Missing <" + TAG_ROLES + "> in roles.xml"); + } + + @NonNull + private static RolesState parseRoles(@NonNull XmlPullParser parser) + throws IOException, XmlPullParserException { + int version = Integer.parseInt(parser.getAttributeValue(null, ATTRIBUTE_VERSION)); + String packagesHash = parser.getAttributeValue(null, ATTRIBUTE_PACKAGES_HASH); + + Map<String, Set<String>> roles = new ArrayMap<>(); + int type; + int depth; + int innerDepth = parser.getDepth() + 1; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) { + if (depth > innerDepth || type != XmlPullParser.START_TAG) { + continue; + } + + if (parser.getName().equals(TAG_ROLE)) { + String roleName = parser.getAttributeValue(null, ATTRIBUTE_NAME); + Set<String> roleHolders = parseRoleHolders(parser); + roles.put(roleName, roleHolders); + } + } + + return new RolesState(version, packagesHash, roles); + } + + @NonNull + private static Set<String> parseRoleHolders(@NonNull XmlPullParser parser) + throws IOException, XmlPullParserException { + Set<String> roleHolders = new ArraySet<>(); + int type; + int depth; + int innerDepth = parser.getDepth() + 1; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) { + if (depth > innerDepth || type != XmlPullParser.START_TAG) { + continue; + } + + if (parser.getName().equals(TAG_HOLDER)) { + String roleHolder = parser.getAttributeValue(null, ATTRIBUTE_NAME); + roleHolders.add(roleHolder); + } + } + return roleHolders; + } + + @Override + public void write(@NonNull RolesState roles, + @NonNull UserHandle user) { + File file = getFile(user); + AtomicFile atomicFile = new AtomicFile(file); + FileOutputStream outputStream = null; + try { + outputStream = atomicFile.startWrite(); + + XmlSerializer serializer = Xml.newSerializer(); + serializer.setOutput(outputStream, StandardCharsets.UTF_8.name()); + serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); + serializer.startDocument(null, true); + + serializeRoles(serializer, roles); + + serializer.endDocument(); + atomicFile.finishWrite(outputStream); + } catch (Exception e) { + Log.wtf(LOG_TAG, "Failed to write roles.xml, restoring backup: " + file, + e); + atomicFile.failWrite(outputStream); + } finally { + IoUtils.closeQuietly(outputStream); + } + } + + private static void serializeRoles(@NonNull XmlSerializer serializer, + @NonNull RolesState roles) throws IOException { + serializer.startTag(null, TAG_ROLES); + + int version = roles.getVersion(); + serializer.attribute(null, ATTRIBUTE_VERSION, Integer.toString(version)); + String packagesHash = roles.getPackagesHash(); + if (packagesHash != null) { + serializer.attribute(null, ATTRIBUTE_PACKAGES_HASH, packagesHash); + } + + for (Map.Entry<String, Set<String>> entry : roles.getRoles().entrySet()) { + String roleName = entry.getKey(); + Set<String> roleHolders = entry.getValue(); + + serializer.startTag(null, TAG_ROLE); + serializer.attribute(null, ATTRIBUTE_NAME, roleName); + serializeRoleHolders(serializer, roleHolders); + serializer.endTag(null, TAG_ROLE); + } + + serializer.endTag(null, TAG_ROLES); + } + + private static void serializeRoleHolders(@NonNull XmlSerializer serializer, + @NonNull Set<String> roleHolders) throws IOException { + for (String roleHolder : roleHolders) { + serializer.startTag(null, TAG_HOLDER); + serializer.attribute(null, ATTRIBUTE_NAME, roleHolder); + serializer.endTag(null, TAG_HOLDER); + } + } + + @Override + public void delete(@NonNull UserHandle user) { + getFile(user).delete(); + } + + @NonNull + private static File getFile(@NonNull UserHandle user) { + // TODO: Use an API for this. + File dataDirectory = new File("/data/misc_de/" + user.getIdentifier() + + "/apexdata/com.android.permission"); + return new File(dataDirectory, ROLES_FILE_NAME); + } +} diff --git a/apex/permission/service/java/com/android/role/persistence/RolesState.java b/apex/permission/service/java/com/android/role/persistence/RolesState.java new file mode 100644 index 000000000000..bff980e2e126 --- /dev/null +++ b/apex/permission/service/java/com/android/role/persistence/RolesState.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2020 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.role.persistence; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; + +import java.util.Map; +import java.util.Set; + +/** + * State of all roles. + * + * TODO(b/147914847): Remove @hide when it becomes the default. + * @hide + */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES, process = SystemApi.Process.SYSTEM_SERVER) +public final class RolesState { + + /** + * The version of the roles. + */ + private final int mVersion; + + /** + * The hash of all packages in the system. + */ + @Nullable + private final String mPackagesHash; + + /** + * The roles. + */ + @NonNull + private final Map<String, Set<String>> mRoles; + + public RolesState(int version, @Nullable String packagesHash, + @NonNull Map<String, Set<String>> roles) { + mVersion = version; + mPackagesHash = packagesHash; + mRoles = roles; + } + + public int getVersion() { + return mVersion; + } + + @Nullable + public String getPackagesHash() { + return mPackagesHash; + } + + @NonNull + public Map<String, Set<String>> getRoles() { + return mRoles; + } +} diff --git a/services/api/current.txt b/services/api/current.txt index 5ca048613e31..8a82e610c233 100644 --- a/services/api/current.txt +++ b/services/api/current.txt @@ -26,6 +26,24 @@ package com.android.permission.persistence { } +package com.android.role.persistence { + + public interface RolesPersistence { + method @NonNull public static com.android.role.persistence.RolesPersistence createInstance(); + method public void delete(@NonNull android.os.UserHandle); + method @Nullable public com.android.role.persistence.RolesState read(@NonNull android.os.UserHandle); + method public void write(@NonNull com.android.role.persistence.RolesState, @NonNull android.os.UserHandle); + } + + public final class RolesState { + ctor public RolesState(int, @Nullable String, @NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.String>>); + method @Nullable public String getPackagesHash(); + method @NonNull public java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getRoles(); + method public int getVersion(); + } + +} + package com.android.server { public abstract class SystemService { diff --git a/services/core/java/com/android/server/role/RoleUserState.java b/services/core/java/com/android/server/role/RoleUserState.java index d33c10c99c08..9f4ca3c6c4ea 100644 --- a/services/core/java/com/android/server/role/RoleUserState.java +++ b/services/core/java/com/android/server/role/RoleUserState.java @@ -23,6 +23,7 @@ import android.annotation.UserIdInt; import android.annotation.WorkerThread; import android.os.Environment; import android.os.Handler; +import android.os.UserHandle; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; @@ -34,22 +35,21 @@ import com.android.internal.os.BackgroundThread; import com.android.internal.util.CollectionUtils; import com.android.internal.util.dump.DualDumpOutputStream; import com.android.internal.util.function.pooled.PooledLambda; - -import libcore.io.IoUtils; +import com.android.role.persistence.RolesPersistence; +import com.android.role.persistence.RolesState; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlSerializer; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; -import java.io.FileOutputStream; import java.io.IOException; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.Set; /** * Stores the state of roles for a user. @@ -71,6 +71,8 @@ public class RoleUserState { private static final String ATTRIBUTE_NAME = "name"; private static final String ATTRIBUTE_PACKAGES_HASH = "packagesHash"; + private final RolesPersistence mPersistence = RolesPersistence.createInstance(); + @UserIdInt private final int mUserId; @@ -350,9 +352,7 @@ public class RoleUserState { @WorkerThread private void writeFile() { - int version; - String packagesHash; - ArrayMap<String, ArraySet<String>> roles; + RolesState roles; synchronized (mLock) { if (mDestroyed) { return; @@ -360,90 +360,45 @@ public class RoleUserState { mWriteScheduled = false; - version = mVersion; - packagesHash = mPackagesHash; - roles = snapshotRolesLocked(); + roles = new RolesState(mVersion, mPackagesHash, + (Map<String, Set<String>>) (Map<String, ?>) snapshotRolesLocked()); } - AtomicFile atomicFile = new AtomicFile(getFile(mUserId), "roles-" + mUserId); - FileOutputStream out = null; - try { - out = atomicFile.startWrite(); - - XmlSerializer serializer = Xml.newSerializer(); - serializer.setOutput(out, StandardCharsets.UTF_8.name()); - serializer.setFeature( - "http://xmlpull.org/v1/doc/features.html#indent-output", true); - serializer.startDocument(null, true); - - serializeRoles(serializer, version, packagesHash, roles); - - serializer.endDocument(); - atomicFile.finishWrite(out); - Slog.i(LOG_TAG, "Wrote roles.xml successfully"); - } catch (IllegalArgumentException | IllegalStateException | IOException e) { - Slog.wtf(LOG_TAG, "Failed to write roles.xml, restoring backup", e); - if (out != null) { - atomicFile.failWrite(out); - } - } finally { - IoUtils.closeQuietly(out); - } + mPersistence.write(roles, UserHandle.of(mUserId)); } - @WorkerThread - private void serializeRoles(@NonNull XmlSerializer serializer, int version, - @Nullable String packagesHash, @NonNull ArrayMap<String, ArraySet<String>> roles) - throws IOException { - serializer.startTag(null, TAG_ROLES); - - serializer.attribute(null, ATTRIBUTE_VERSION, Integer.toString(version)); - - if (packagesHash != null) { - serializer.attribute(null, ATTRIBUTE_PACKAGES_HASH, packagesHash); - } - - for (int i = 0, size = roles.size(); i < size; ++i) { - String roleName = roles.keyAt(i); - ArraySet<String> roleHolders = roles.valueAt(i); - - serializer.startTag(null, TAG_ROLE); - serializer.attribute(null, ATTRIBUTE_NAME, roleName); - serializeRoleHolders(serializer, roleHolders); - serializer.endTag(null, TAG_ROLE); - } + private void readFile() { + synchronized (mLock) { + RolesState roles = mPersistence.read(UserHandle.of(mUserId)); + if (roles == null) { + readLegacyFileLocked(); + scheduleWriteFileLocked(); + return; + } - serializer.endTag(null, TAG_ROLES); - } + mVersion = roles.getVersion(); + mPackagesHash = roles.getPackagesHash(); - @WorkerThread - private void serializeRoleHolders(@NonNull XmlSerializer serializer, - @NonNull ArraySet<String> roleHolders) throws IOException { - for (int i = 0, size = roleHolders.size(); i < size; ++i) { - String roleHolder = roleHolders.valueAt(i); - - serializer.startTag(null, TAG_HOLDER); - serializer.attribute(null, ATTRIBUTE_NAME, roleHolder); - serializer.endTag(null, TAG_HOLDER); + mRoles.clear(); + for (Map.Entry<String, Set<String>> entry : roles.getRoles().entrySet()) { + String roleName = entry.getKey(); + ArraySet<String> roleHolders = new ArraySet<>(entry.getValue()); + mRoles.put(roleName, roleHolders); + } } } - /** - * Read the state from file. - */ - private void readFile() { - synchronized (mLock) { - File file = getFile(mUserId); - try (FileInputStream in = new AtomicFile(file).openRead()) { - XmlPullParser parser = Xml.newPullParser(); - parser.setInput(in, null); - parseXmlLocked(parser); - Slog.i(LOG_TAG, "Read roles.xml successfully"); - } catch (FileNotFoundException e) { - Slog.i(LOG_TAG, "roles.xml not found"); - } catch (XmlPullParserException | IOException e) { - throw new IllegalStateException("Failed to parse roles.xml: " + file, e); - } + private void readLegacyFileLocked() { + File file = getFile(mUserId); + try (FileInputStream in = new AtomicFile(file).openRead()) { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(in, null); + parseXmlLocked(parser); + Slog.i(LOG_TAG, "Read roles.xml successfully"); + } catch (FileNotFoundException e) { + Slog.i(LOG_TAG, "roles.xml not found"); + } catch (XmlPullParserException | IOException e) { + throw new IllegalStateException("Failed to parse roles.xml: " + file, e); } } @@ -590,7 +545,7 @@ public class RoleUserState { throw new IllegalStateException("This RoleUserState has already been destroyed"); } mWriteHandler.removeCallbacksAndMessages(null); - getFile(mUserId).delete(); + mPersistence.delete(UserHandle.of(mUserId)); mDestroyed = true; } } |