Skip to content

Commit 13bb319

Browse files
Merge pull request #550 from appdevforall/feat/K2GO-386-app-backstop
K2GO-386 feat(app-backstop): app-side disk-fill backstop that keeps the system alive (L3b)
2 parents 4e97e4c + c3ab2e9 commit 13bb319

10 files changed

Lines changed: 662 additions & 2 deletions

File tree

‎controller/app/src/debug/AndroidManifest.xml‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<!--
3-
DEBUG-ONLY overlay: registers a receiver so the delivery backbone can be exercised
4-
from adb (see DebugDeliveryReceiver). Merged into debug builds only; never in release.
3+
DEBUG-ONLY overlay: registers receivers so parts of the app can be exercised from adb
4+
(delivery backbone, disk-guard device-verify). Merged into debug builds only; never in release.
55
-->
66
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
77
<application>
@@ -12,5 +12,13 @@
1212
<action android:name="org.appdevforall.k2go.DEBUG_DELIVERY" />
1313
</intent-filter>
1414
</receiver>
15+
<!-- K2GO-386: adb-reachable trigger for the disk-guard device-verify (DebugDiskGuardReceiver). -->
16+
<receiver
17+
android:name="org.appdevforall.k2go.diskguard.debug.DebugDiskGuardReceiver"
18+
android:exported="true">
19+
<intent-filter>
20+
<action android:name="org.appdevforall.k2go.DEBUG_DISK_GUARD" />
21+
</intent-filter>
22+
</receiver>
1523
</application>
1624
</manifest>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package org.appdevforall.k2go.diskguard.debug;
2+
3+
import android.content.BroadcastReceiver;
4+
import android.content.Context;
5+
import android.content.Intent;
6+
import android.util.Log;
7+
8+
import org.appdevforall.k2go.diskguard.DiskGuard;
9+
10+
/**
11+
* DEBUG-ONLY. K2GO-386 device-verify hook. Forces one disk-guard check with an injected floor so the
12+
* protective path (reap the box -> reclaim the runaway log -> restart) can be verified on device WITHOUT
13+
* first filling ~58 GB. Lives in src/debug, so it never ships in release.
14+
*
15+
* <p>Exported (it is the whole point — an adb-reachable surface, unlike the app's non-exported
16+
* services), mirroring {@link org.appdevforall.k2go.delivery.debug.DebugDeliveryReceiver}. A huge
17+
* floor makes any real free-space reading CRITICAL, tripping the guard for real. Example:
18+
*
19+
* <pre>
20+
* adb shell am broadcast \
21+
* -a org.appdevforall.k2go.DEBUG_DISK_GUARD \
22+
* -n org.appdevforall.k2go/org.appdevforall.k2go.diskguard.debug.DebugDiskGuardReceiver \
23+
* --el floor_bytes 999999999999
24+
* </pre>
25+
*
26+
* Watch it act in logcat: {@code adb logcat -s K2Go-DiskGuard}. The debug hook runs the FORCED path,
27+
* which always CONTAINs: it reaps and reclaims, then leaves the server desired=UP and asks the
28+
* reconciler to relaunch a fresh box. It never advances the real escalation count, so repeated
29+
* triggers cannot stop the box.
30+
*/
31+
public final class DebugDiskGuardReceiver extends BroadcastReceiver {
32+
33+
private static final String TAG = "K2Go-DiskGuard";
34+
35+
@Override
36+
public void onReceive(Context context, Intent intent) {
37+
final Context app = context.getApplicationContext();
38+
final long floor = intent.getLongExtra("floor_bytes", Long.MAX_VALUE);
39+
Log.w(TAG, "K2GO-386: debug disk-guard test hook fired (floor_bytes=" + floor + ")");
40+
new Thread(() -> {
41+
try {
42+
DiskGuard.checkWithFloor(app, floor);
43+
} catch (Throwable t) {
44+
Log.w(TAG, "K2GO-386: debug disk-guard test hook failed", t);
45+
}
46+
}, "debug-disk-guard").start();
47+
}
48+
}

‎controller/app/src/main/java/org/appdevforall/k2go/WatchdogService.java‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@
2626

2727
import androidx.core.app.NotificationCompat;
2828

29+
import java.util.concurrent.Executors;
30+
import java.util.concurrent.ScheduledExecutorService;
31+
import java.util.concurrent.TimeUnit;
32+
2933
public class WatchdogService extends Service {
3034
private static final String TAG = "IIAB-Watchdog";
3135
private static final String CHANNEL_ID = "watchdog_channel";
@@ -46,6 +50,12 @@ public class WatchdogService extends Service {
4650
private PowerManager.WakeLock wakeLock;
4751
private WifiManager.WifiLock wifiLock;
4852

53+
// K2GO-386 (Layer 3): a single background poller checks free space while the box is up. On a critical
54+
// reading DiskGuard reaps and reclaims, then by default keeps the system alive. Started once per
55+
// protected session, stopped on destroy.
56+
private ScheduledExecutorService diskGuardPoller;
57+
private static final long DISK_GUARD_INTERVAL_S = 25;
58+
4959
@Override
5060
public void onCreate() {
5161
super.onCreate();
@@ -80,6 +90,9 @@ private void startWatchdog() {
8090
// 2. Acquire CPU WakeLock to prevent sleep during heavy operations (e.g., Tar extraction, Rsync)
8191
acquireHardwareLocks();
8292

93+
// K2GO-386 (barrier 2): guard free space for the life of this protected session.
94+
startDiskGuard();
95+
8396
// 3. Notify the UI (MainActivity) that the engine is protected and running
8497
IIABWatchdog.logSessionStart(this);
8598
Intent startIntent = new Intent(ACTION_STATE_STARTED);
@@ -116,6 +129,28 @@ private void releaseHardwareLocks() {
116129
}
117130
}
118131

132+
// K2GO-386 (Layer 3): the free-space guard. One background poller ticks every DISK_GUARD_INTERVAL_S.
133+
// On a CRITICAL reading DiskGuard confirms, reaps the box, reclaims the runaway log, and by default
134+
// lets it restart; the in-box layers cannot stop an off-proot orphan. Started once per session.
135+
private void startDiskGuard() {
136+
if (diskGuardPoller != null) return;
137+
diskGuardPoller = Executors.newSingleThreadScheduledExecutor();
138+
diskGuardPoller.scheduleWithFixedDelay(() -> {
139+
try {
140+
org.appdevforall.k2go.diskguard.DiskGuard.check(getApplicationContext());
141+
} catch (Throwable t) {
142+
Log.w(TAG, "K2GO-386: disk-guard tick failed", t);
143+
}
144+
}, DISK_GUARD_INTERVAL_S, DISK_GUARD_INTERVAL_S, TimeUnit.SECONDS);
145+
}
146+
147+
private void stopDiskGuard() {
148+
if (diskGuardPoller != null) {
149+
diskGuardPoller.shutdownNow();
150+
diskGuardPoller = null;
151+
}
152+
}
153+
119154
@Override
120155
public void onDestroy() {
121156
RUNNING = false; // ADFA-5343 (Phase 4b): protection is ending — clear the promoter's state signal
@@ -124,6 +159,9 @@ public void onDestroy() {
124159
stopIntent.setPackage(getPackageName());
125160
sendBroadcast(stopIntent);
126161

162+
// K2GO-386 (barrier 2): stop the free-space guard — this protected session (box up) is ending.
163+
stopDiskGuard();
164+
127165
// 2. Release Hardware Locks so the phone can sleep again
128166
releaseHardwareLocks();
129167

0 commit comments

Comments
 (0)