Commit 6f69e00a authored by swift_gan's avatar swift_gan Committed by swift_gan

fix XSharePreference

parent ee13b7b5
package com.android.internal.util;
import org.xmlpull.v1.XmlPullParserException;
import java.io.InputStream;
import java.util.Map;
public class XmlUtils {
public static Map readMapXml(InputStream inputStream) throws XmlPullParserException {
return null;
}
}
package de.robv.android.xposed;
import com.swift.sandhook.xposedcompat.XposedCompat;
import de.robv.android.xposed.services.BaseService;
import de.robv.android.xposed.services.DirectAccessService;
/**
* A helper to work with (or without) SELinux, abstracting much of its big complexity.
*/
public final class SELinuxHelper {
static {
initForProcess(XposedCompat.processName);
}
private SELinuxHelper() {}
/**
* Determines whether SELinux is disabled or enabled.
*
* @return A boolean indicating whether SELinux is enabled.
*/
public static boolean isSELinuxEnabled() {
return sIsSELinuxEnabled;
}
/**
* Determines whether SELinux is permissive or enforcing.
*
* @return A boolean indicating whether SELinux is enforcing.
*/
public static boolean isSELinuxEnforced() {
return sIsSELinuxEnabled;
}
/**
* Gets the security context of the current process.
*
* @return A String representing the security context of the current process.
*/
public static String getContext() {
return null;
}
/**
* Retrieve the service to be used when accessing files in {@code /data/data/*}.
*
* <p class="caution"><strong>IMPORTANT:</strong> If you call this from the Zygote process,
* don't re-use the result in different process!
*
* @return An instance of the service.
*/
public static BaseService getAppDataFileService() {
if (sServiceAppDataFile != null)
return sServiceAppDataFile;
throw new UnsupportedOperationException();
}
// ----------------------------------------------------------------------------
private static boolean sIsSELinuxEnabled = false;
private static BaseService sServiceAppDataFile = null;
/*package*/ static void initOnce() {
try {
//sIsSELinuxEnabled = SELinux.isSELinuxEnabled();
} catch (NoClassDefFoundError ignored) {}
}
/*package*/ static void initForProcess(String packageName) {
if (sIsSELinuxEnabled) {
if (packageName == null) { // Zygote
//sServiceAppDataFile = new ZygoteService();
} else if (packageName.equals("android")) { //system_server
//sServiceAppDataFile = BinderService.getService(BinderService.TARGET_APP);
} else { // app
sServiceAppDataFile = new DirectAccessService();
}
} else {
sServiceAppDataFile = new DirectAccessService();
}
}
}
......@@ -5,12 +5,21 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.os.Environment;
import android.preference.PreferenceManager;
import android.util.Log;
import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import de.robv.android.xposed.services.FileResult;
/**
* This class is basically the same as SharedPreferencesImpl from AOSP, but
* read-only and without listeners support. Instead, it is made to be
......@@ -69,7 +78,13 @@ public final class XSharedPreferences implements SharedPreferences {
*/
@SuppressLint("SetWorldReadable")
public boolean makeWorldReadable() {
return false;
if (!SELinuxHelper.getAppDataFileService().hasDirectFileAccess())
return false; // It doesn't make much sense to make the file readable if we wouldn't be able to access it anyway.
if (!mFile.exists()) // Just in case - the file should never be created if it doesn't exist.
return false;
return mFile.setReadable(true, false);
}
/**
......@@ -97,7 +112,47 @@ public final class XSharedPreferences implements SharedPreferences {
@SuppressWarnings({ "rawtypes", "unchecked" })
private void loadFromDiskLocked() {
if (mLoaded) {
return;
}
Map map = null;
FileResult result = null;
try {
result = SELinuxHelper.getAppDataFileService().getFileInputStream(mFilename, mFileSize, mLastModified);
if (result.stream != null) {
map = XmlUtils.readMapXml(result.stream);
result.stream.close();
} else {
// The file is unchanged, keep the current values
map = mMap;
}
} catch (XmlPullParserException e) {
Log.w(TAG, "getSharedPreferences", e);
} catch (FileNotFoundException ignored) {
// SharedPreferencesImpl has a canRead() check, so it doesn't log anything in case the file doesn't exist
} catch (IOException e) {
Log.w(TAG, "getSharedPreferences", e);
} finally {
if (result != null && result.stream != null) {
try {
result.stream.close();
} catch (RuntimeException rethrown) {
throw rethrown;
} catch (Exception ignored) {
}
}
}
mLoaded = true;
if (map != null) {
mMap = map;
mLastModified = result.mtime;
mFileSize = result.size;
} else {
mMap = new HashMap<>();
}
notifyAll();
}
/**
......@@ -116,7 +171,16 @@ public final class XSharedPreferences implements SharedPreferences {
* <p><strong>Warning:</strong> With enforcing SELinux, this call might be quite expensive.
*/
public synchronized boolean hasFileChanged() {
return false;
try {
FileResult result = SELinuxHelper.getAppDataFileService().statFile(mFilename);
return mLastModified != result.mtime || mFileSize != result.size;
} catch (FileNotFoundException ignored) {
// SharedPreferencesImpl doesn't log anything in case the file doesn't exist
return true;
} catch (IOException e) {
Log.w(TAG, "hasFileChanged", e);
return true;
}
}
private void awaitLoadedLocked() {
......
package de.robv.android.xposed.services;
import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import de.robv.android.xposed.SELinuxHelper;
/**
* General definition of a file access service provided by the Xposed framework.
*
* <p>References to a concrete subclass should generally be retrieved from {@link SELinuxHelper}.
*/
public abstract class BaseService {
/** Flag for {@link #checkFileAccess}: Read access. */
public static final int R_OK = 4;
/** Flag for {@link #checkFileAccess}: Write access. */
public static final int W_OK = 2;
/** Flag for {@link #checkFileAccess}: Executable access. */
public static final int X_OK = 1;
/** Flag for {@link #checkFileAccess}: File/directory exists. */
public static final int F_OK = 0;
/**
* Checks whether the services accesses files directly (instead of using IPC).
*
* @return {@code true} in case direct access is possible.
*/
public boolean hasDirectFileAccess() {
return false;
}
/**
* Check whether a file is accessible. SELinux might enforce stricter checks.
*
* @param filename The absolute path of the file to check.
* @param mode The mode for POSIX's {@code access()} function.
* @return The result of the {@code access()} function.
*/
public abstract boolean checkFileAccess(String filename, int mode);
/**
* Check whether a file exists.
*
* @param filename The absolute path of the file to check.
* @return The result of the {@code access()} function.
*/
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public boolean checkFileExists(String filename) {
return checkFileAccess(filename, F_OK);
}
/**
* Determine the size and modification time of a file.
*
* @param filename The absolute path of the file to check.
* @return A {@link FileResult} object holding the result.
* @throws IOException In case an error occurred while retrieving the information.
*/
public abstract FileResult statFile(String filename) throws IOException;
/**
* Determine the size time of a file.
*
* @param filename The absolute path of the file to check.
* @return The file size.
* @throws IOException In case an error occurred while retrieving the information.
*/
public long getFileSize(String filename) throws IOException {
return statFile(filename).size;
}
/**
* Determine the size time of a file.
*
* @param filename The absolute path of the file to check.
* @return The file modification time.
* @throws IOException In case an error occurred while retrieving the information.
*/
public long getFileModificationTime(String filename) throws IOException {
return statFile(filename).mtime;
}
/**
* Read a file into memory.
*
* @param filename The absolute path of the file to read.
* @return A {@code byte} array with the file content.
* @throws IOException In case an error occurred while reading the file.
*/
public abstract byte[] readFile(String filename) throws IOException;
/**
* Read a file into memory, but only if it has changed since the last time.
*
* @param filename The absolute path of the file to read.
* @param previousSize File size of last read.
* @param previousTime File modification time of last read.
* @return A {@link FileResult} object holding the result.
* <p>The {@link FileResult#content} field might be {@code null} if the file
* is unmodified ({@code previousSize} and {@code previousTime} are still valid).
* @throws IOException In case an error occurred while reading the file.
*/
public abstract FileResult readFile(String filename, long previousSize, long previousTime) throws IOException;
/**
* Read a file into memory, optionally only if it has changed since the last time.
*
* @param filename The absolute path of the file to read.
* @param offset Number of bytes to skip at the beginning of the file.
* @param length Number of bytes to read (0 means read to end of file).
* @param previousSize Optional: File size of last read.
* @param previousTime Optional: File modification time of last read.
* @return A {@link FileResult} object holding the result.
* <p>The {@link FileResult#content} field might be {@code null} if the file
* is unmodified ({@code previousSize} and {@code previousTime} are still valid).
* @throws IOException In case an error occurred while reading the file.
*/
public abstract FileResult readFile(String filename, int offset, int length,
long previousSize, long previousTime) throws IOException;
/**
* Get a stream to the file content.
* Depending on the service, it may or may not be read completely into memory.
*
* @param filename The absolute path of the file to read.
* @return An {@link InputStream} to the file content.
* @throws IOException In case an error occurred while reading the file.
*/
public InputStream getFileInputStream(String filename) throws IOException {
return new ByteArrayInputStream(readFile(filename));
}
/**
* Get a stream to the file content, but only if it has changed since the last time.
* Depending on the service, it may or may not be read completely into memory.
*
* @param filename The absolute path of the file to read.
* @param previousSize Optional: File size of last read.
* @param previousTime Optional: File modification time of last read.
* @return A {@link FileResult} object holding the result.
* <p>The {@link FileResult#stream} field might be {@code null} if the file
* is unmodified ({@code previousSize} and {@code previousTime} are still valid).
* @throws IOException In case an error occurred while reading the file.
*/
public FileResult getFileInputStream(String filename, long previousSize, long previousTime) throws IOException {
FileResult result = readFile(filename, previousSize, previousTime);
if (result.content == null)
return result;
return new FileResult(new ByteArrayInputStream(result.content), result.size, result.mtime);
}
// ----------------------------------------------------------------------------
/*package*/ BaseService() {}
/*package*/ static void ensureAbsolutePath(String filename) {
if (!filename.startsWith("/")) {
throw new IllegalArgumentException("Only absolute filenames are allowed: " + filename);
}
}
/*package*/ static void throwCommonIOException(int errno, String errorMsg, String filename, String defaultText) throws IOException {
switch (errno) {
case 1: // EPERM
case 13: // EACCES
throw new FileNotFoundException(errorMsg != null ? errorMsg : "Permission denied: " + filename);
case 2: // ENOENT
throw new FileNotFoundException(errorMsg != null ? errorMsg : "No such file or directory: " + filename);
case 12: // ENOMEM
throw new OutOfMemoryError(errorMsg);
case 21: // EISDIR
throw new FileNotFoundException(errorMsg != null ? errorMsg : "Is a directory: " + filename);
default:
throw new IOException(errorMsg != null ? errorMsg : "Error " + errno + defaultText + filename);
}
}
}
package de.robv.android.xposed.services;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
/** @hide */
public final class DirectAccessService extends BaseService {
@Override
public boolean hasDirectFileAccess() {
return true;
}
@SuppressWarnings("RedundantIfStatement")
@Override
public boolean checkFileAccess(String filename, int mode) {
File file = new File(filename);
if (mode == F_OK && !file.exists()) return false;
if ((mode & R_OK) != 0 && !file.canRead()) return false;
if ((mode & W_OK) != 0 && !file.canWrite()) return false;
if ((mode & X_OK) != 0 && !file.canExecute()) return false;
return true;
}
@Override
public boolean checkFileExists(String filename) {
return new File(filename).exists();
}
@Override
public FileResult statFile(String filename) throws IOException {
File file = new File(filename);
return new FileResult(file.length(), file.lastModified());
}
@Override
public byte[] readFile(String filename) throws IOException {
File file = new File(filename);
byte content[] = new byte[(int)file.length()];
FileInputStream fis = new FileInputStream(file);
fis.read(content);
fis.close();
return content;
}
@Override
public FileResult readFile(String filename, long previousSize, long previousTime) throws IOException {
File file = new File(filename);
long size = file.length();
long time = file.lastModified();
if (previousSize == size && previousTime == time)
return new FileResult(size, time);
return new FileResult(readFile(filename), size, time);
}
@Override
public FileResult readFile(String filename, int offset, int length, long previousSize, long previousTime) throws IOException {
File file = new File(filename);
long size = file.length();
long time = file.lastModified();
if (previousSize == size && previousTime == time)
return new FileResult(size, time);
// Shortcut for the simple case
if (offset <= 0 && length <= 0)
return new FileResult(readFile(filename), size, time);
// Check range
if (offset > 0 && offset >= size) {
throw new IllegalArgumentException("Offset " + offset + " is out of range for " + filename);
} else if (offset < 0) {
offset = 0;
}
if (length > 0 && (offset + length) > size) {
throw new IllegalArgumentException("Length " + length + " is out of range for " + filename);
} else if (length <= 0) {
length = (int) (size - offset);
}
byte content[] = new byte[length];
FileInputStream fis = new FileInputStream(file);
fis.skip(offset);
fis.read(content);
fis.close();
return new FileResult(content, size, time);
}
/**
* {@inheritDoc}
* <p>This implementation returns a BufferedInputStream instead of loading the file into memory.
*/
@Override
public InputStream getFileInputStream(String filename) throws IOException {
return new BufferedInputStream(new FileInputStream(filename), 16*1024);
}
/**
* {@inheritDoc}
* <p>This implementation returns a BufferedInputStream instead of loading the file into memory.
*/
@Override
public FileResult getFileInputStream(String filename, long previousSize, long previousTime) throws IOException {
File file = new File(filename);
long size = file.length();
long time = file.lastModified();
if (previousSize == size && previousTime == time)
return new FileResult(size, time);
return new FileResult(new BufferedInputStream(new FileInputStream(filename), 16*1024), size, time);
}
}
package de.robv.android.xposed.services;
import java.io.InputStream;
/**
* Holder for the result of a {@link BaseService#readFile} or {@link BaseService#statFile} call.
*/
public final class FileResult {
/** File content, might be {@code null} if the file wasn't read. */
public final byte[] content;
/** File input stream, might be {@code null} if the file wasn't read. */
public final InputStream stream;
/** File size. */
public final long size;
/** File last modification time. */
public final long mtime;
/*package*/ public FileResult(long size, long mtime) {
this.content = null;
this.stream = null;
this.size = size;
this.mtime = mtime;
}
/*package*/ public FileResult(byte[] content, long size, long mtime) {
this.content = content;
this.stream = null;
this.size = size;
this.mtime = mtime;
}
/*package*/ public FileResult(InputStream stream, long size, long mtime) {
this.content = null;
this.stream = stream;
this.size = size;
this.mtime = mtime;
}
/** @hide */
@Override
public String toString() {
StringBuilder sb = new StringBuilder("{");
if (content != null) {
sb.append("content.length: ");
sb.append(content.length);
sb.append(", ");
}
if (stream != null) {
sb.append("stream: ");
sb.append(stream.toString());
sb.append(", ");
}
sb.append("size: ");
sb.append(size);
sb.append(", mtime: ");
sb.append(mtime);
sb.append("}");
return sb.toString();
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment