Skip to content

Commit 2f04319

Browse files
committed
Cache cleanup methods
1 parent 793878a commit 2f04319

File tree

6 files changed

+132
-1
lines changed

6 files changed

+132
-1
lines changed

librootjava/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,18 @@ non-root code.
263263
(See the [example project](../librootjava_example) for a more elaborate
264264
example for this entire process)
265265

266+
#### Cleanup
267+
268+
To execute our code as root, files may need to be created in our app's
269+
cache directory. While the library does its best to clean up after
270+
itself, there are situations (such as a reboot mid-process) where some
271+
files may not automatically be removed.
272+
273+
It is recommended to periodically call ```RootJava.cleanupCache()```
274+
to remove these leftover files. This method will cleanup all of our
275+
files in the cache directory that predate the current boot. As I/O is
276+
performed, this method should not be called from the main UI thread.
277+
266278
#### Debugging
267279

268280
Debugging is supported since version 1.1.0, but disabled by default.

librootjava/src/main/java/eu/chainfire/librootjava/RootJava.java

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@
2121
import android.content.pm.ApplicationInfo;
2222
import android.content.pm.PackageManager;
2323
import android.os.Build;
24+
import android.os.SystemClock;
2425

2526
import java.io.File;
27+
import java.io.FilenameFilter;
2628
import java.util.ArrayList;
2729
import java.util.List;
2830
import java.util.Locale;
@@ -247,6 +249,69 @@ public static List<String> getLaunchScript(Context context, Class<?> clazz, Stri
247249
return script;
248250
}
249251

252+
/** Prefixes of filename to remove from the app's cache directory */
253+
public static final String[] CLEANUP_CACHE_PREFIXES = new String[] { ".app_process32_", ".app_process64_" };
254+
255+
/**
256+
* Clean up leftover files from our cache directory.<br>
257+
* <br>
258+
* In ideal circumstances no files should be left dangling, but in practise it happens sooner
259+
* or later anyway. Periodically (once per app launch or per boot) calling this method is
260+
* advised.<br>
261+
* <br>
262+
* This method should be called from a background thread, as it performs disk i/o.<br>
263+
* <br>
264+
* It is difficult to determine which of these files may actually be in use, especially in
265+
* daemon mode. We try to determine device boot time, and wipe everything from before that
266+
* time. For safety we explicitly keep files using our current UUID.
267+
*
268+
* @param context Context to retrieve cache directory from
269+
*/
270+
public static void cleanupCache(Context context) {
271+
cleanupCache(context, CLEANUP_CACHE_PREFIXES);
272+
}
273+
274+
/**
275+
* Clean up leftover files from our cache directory.<br>
276+
* <br>
277+
* This version is for internal use, see {@link #cleanupCache(Context)} instead.
278+
*
279+
* @param context Context to retrieve cache directory from
280+
* @param prefixes List of prefixes to scrub
281+
*/
282+
public static void cleanupCache(Context context, final String[] prefixes) {
283+
try {
284+
File cacheDir = context.getCacheDir();
285+
if (cacheDir.exists()) {
286+
// determine time of last boot
287+
long boot = System.currentTimeMillis() - SystemClock.elapsedRealtime();
288+
289+
// find our files
290+
for (File file : cacheDir.listFiles(new FilenameFilter() {
291+
@Override
292+
public boolean accept(File dir, String name) {
293+
boolean accept = false;
294+
for (String prefix : prefixes) {
295+
// just in case: don't return files that contain our current uuid
296+
if (name.startsWith(prefix) && !name.endsWith(AppProcess.UUID)) {
297+
accept = true;
298+
break;
299+
}
300+
}
301+
return accept;
302+
}
303+
})) {
304+
if (file.lastModified() < boot) {
305+
//noinspection ResultOfMethodCallIgnored
306+
file.delete();
307+
}
308+
}
309+
}
310+
} catch (Exception e) {
311+
Logger.ex(e);
312+
}
313+
}
314+
250315
// ------------------------ calls for root ------------------------
251316

252317
/**

librootjava_example/src/main/java/eu/chainfire/librootjava_example/MainActivity.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434

3535
import eu.chainfire.librootjava.Logger;
3636
import eu.chainfire.librootjava.RootIPCReceiver;
37+
import eu.chainfire.librootjava.RootJava;
3738
import eu.chainfire.librootjava_example.root.IIPC;
3839
import eu.chainfire.librootjava_example.root.IPingCallback;
3940
import eu.chainfire.librootjava_example.root.PassedData;
@@ -80,6 +81,15 @@ it inside a method (in this example), the context passed to its constructor is a
8081
wrapper. We need to update it to a proper context, so we may actually receive the Binder
8182
object from our root code. */
8283
ipcReceiver.setContext(this);
84+
85+
/* Cleanup leftover files from our cache directory. This is not exactly an elegant way to
86+
do it, but it illustrates that this should be done off of the main UI thread. */
87+
(new Thread(new Runnable() {
88+
@Override
89+
public void run() {
90+
RootJava.cleanupCache(MainActivity.this);
91+
}
92+
})).start();
8393
}
8494

8595
@Override

librootjavadaemon/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,16 @@ non-root end when called through IPC.
126126
See the [example project](../librootjavadaemon_example) for further
127127
details.
128128

129+
#### Cleanup
130+
131+
As with running code as root in normal (non-daemon) mode, files may need
132+
to be created in our app's cache directory. The chances of leftover
133+
files are actually higher in daemon mode, and the number of files is
134+
higher too.
135+
136+
To clean up, call ```RootDaemon.cleanupCache()``` instead of
137+
```RootJava.cleanupCache()```. It is *not* needed to call both.
138+
129139
## abiFilters
130140

131141
This library includes native code for all platforms the NDK supports.

librootjavadaemon/src/main/java/eu/chainfire/librootjavadaemon/RootDaemon.java

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,23 @@ public static List<String> getLaunchScript(Context context, Class<?> clazz, Stri
117117
return patchLaunchScript(context, RootJava.getLaunchScript(context, clazz, app_process, relocate_path, params, niceName));
118118
}
119119

120+
/** Prefixes of filename to remove from the app's cache directory */
121+
public static final String[] CLEANUP_CACHE_PREFIXES = new String[] { ".daemonize_" };
122+
123+
/**
124+
* Clean up leftover files from our cache directory.<br>
125+
* <br>
126+
* Call this method instead of (not in addition to) RootJava#cleanupCache(Context).
127+
*
128+
* @param context Context to retrieve cache directory from
129+
*/
130+
public static void cleanupCache(Context context) {
131+
String[] prefixes = new String[RootJava.CLEANUP_CACHE_PREFIXES.length + CLEANUP_CACHE_PREFIXES.length];
132+
System.arraycopy(RootJava.CLEANUP_CACHE_PREFIXES, 0, prefixes, 0, RootJava.CLEANUP_CACHE_PREFIXES.length);
133+
System.arraycopy(CLEANUP_CACHE_PREFIXES, 0, prefixes, RootJava.CLEANUP_CACHE_PREFIXES.length, CLEANUP_CACHE_PREFIXES.length);
134+
RootJava.cleanupCache(context, prefixes);
135+
}
136+
120137
// ------------------------ calls for root ------------------------
121138

122139
/** Registered interfaces */
@@ -183,7 +200,7 @@ public static void daemonize(String packageName, int code, boolean surviveFramew
183200
} else {
184201
Logger.dp(LOG_PREFIX, "Service already running, requesting re-broadcast and aborting");
185202
ipc.broadcast();
186-
System.exit(0);
203+
exit();
187204
}
188205
}
189206

@@ -327,6 +344,16 @@ not clean up our relocated app_process binary after executing (which might preve
327344
if (app_process.exists() && !app_process.getAbsolutePath().startsWith("/system/bin/")) {
328345
//noinspection ResultOfMethodCallIgnored
329346
app_process.delete();
347+
348+
// See if we can also find a copy of the daemonize binary
349+
String daemonize_path = app_process.getAbsolutePath();
350+
daemonize_path = daemonize_path.replace(".app_process32_", ".daemonize_");
351+
daemonize_path = daemonize_path.replace(".app_process64_", ".daemonize_");
352+
File daemonize = new File(daemonize_path);
353+
if (daemonize.exists()) {
354+
//noinspection ResultOfMethodCallIgnored
355+
daemonize.delete();
356+
}
330357
}
331358
} catch (IOException e) {
332359
// should never actually happen

librootjavadaemon_example/src/main/java/eu/chainfire/librootjavadaemon_example/MainActivity.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
import eu.chainfire.librootjava.Logger;
3030
import eu.chainfire.librootjava.RootIPCReceiver;
31+
import eu.chainfire.librootjavadaemon.RootDaemon;
3132
import eu.chainfire.librootjavadaemon_example.root.IIPC;
3233
import eu.chainfire.librootjavadaemon_example.root.RootMain;
3334
import eu.chainfire.libsuperuser.Debug;
@@ -70,6 +71,12 @@ protected void onCreate(Bundle savedInstanceState) {
7071
textView.setHorizontallyScrolling(true);
7172
textView.setMovementMethod(new ScrollingMovementMethod());
7273
ipcReceiver.setContext(this);
74+
(new Thread(new Runnable() {
75+
@Override
76+
public void run() {
77+
RootDaemon.cleanupCache(MainActivity.this);
78+
}
79+
})).start();
7380
}
7481

7582
@Override

0 commit comments

Comments
 (0)