Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions controller/app/src/debug/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
DEBUG-ONLY overlay: registers a receiver so the delivery backbone can be exercised
from adb (see DebugDeliveryReceiver). Merged into debug builds only; never in release.
DEBUG-ONLY overlay: registers receivers so parts of the app can be exercised from adb
(delivery backbone, disk-guard device-verify). Merged into debug builds only; never in release.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
Expand All @@ -12,5 +12,13 @@
<action android:name="org.appdevforall.k2go.DEBUG_DELIVERY" />
</intent-filter>
</receiver>
<!-- K2GO-386: adb-reachable trigger for the disk-guard device-verify (DebugDiskGuardReceiver). -->
<receiver
android:name="org.appdevforall.k2go.diskguard.debug.DebugDiskGuardReceiver"
android:exported="true">
<intent-filter>
<action android:name="org.appdevforall.k2go.DEBUG_DISK_GUARD" />
</intent-filter>
</receiver>
</application>
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package org.appdevforall.k2go.diskguard.debug;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

import org.appdevforall.k2go.diskguard.DiskGuard;

/**
* DEBUG-ONLY. K2GO-386 device-verify hook. Forces one disk-guard check with an injected floor so the
* protective path (reap the box -> reclaim the runaway log -> restart) can be verified on device WITHOUT
* first filling ~58 GB. Lives in src/debug, so it never ships in release.
*
* <p>Exported (it is the whole point — an adb-reachable surface, unlike the app's non-exported
* services), mirroring {@link org.appdevforall.k2go.delivery.debug.DebugDeliveryReceiver}. A huge
* floor makes any real free-space reading CRITICAL, tripping the guard for real. Example:
*
* <pre>
* adb shell am broadcast \
* -a org.appdevforall.k2go.DEBUG_DISK_GUARD \
* -n org.appdevforall.k2go/org.appdevforall.k2go.diskguard.debug.DebugDiskGuardReceiver \
* --el floor_bytes 999999999999
* </pre>
*
* Watch it act in logcat: {@code adb logcat -s K2Go-DiskGuard}. The debug hook runs the FORCED path,
* which always CONTAINs: it reaps and reclaims, then leaves the server desired=UP and asks the
* reconciler to relaunch a fresh box. It never advances the real escalation count, so repeated
* triggers cannot stop the box.
*/
public final class DebugDiskGuardReceiver extends BroadcastReceiver {

private static final String TAG = "K2Go-DiskGuard";

@Override
public void onReceive(Context context, Intent intent) {
final Context app = context.getApplicationContext();
final long floor = intent.getLongExtra("floor_bytes", Long.MAX_VALUE);
Log.w(TAG, "K2GO-386: debug disk-guard test hook fired (floor_bytes=" + floor + ")");
new Thread(() -> {
try {
DiskGuard.checkWithFloor(app, floor);
} catch (Throwable t) {
Log.w(TAG, "K2GO-386: debug disk-guard test hook failed", t);
}
}, "debug-disk-guard").start();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@

import androidx.core.app.NotificationCompat;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class WatchdogService extends Service {
private static final String TAG = "IIAB-Watchdog";
private static final String CHANNEL_ID = "watchdog_channel";
Expand All @@ -46,6 +50,12 @@ public class WatchdogService extends Service {
private PowerManager.WakeLock wakeLock;
private WifiManager.WifiLock wifiLock;

// K2GO-386 (Layer 3): a single background poller checks free space while the box is up. On a critical
// reading DiskGuard reaps and reclaims, then by default keeps the system alive. Started once per
// protected session, stopped on destroy.
private ScheduledExecutorService diskGuardPoller;
private static final long DISK_GUARD_INTERVAL_S = 25;

@Override
public void onCreate() {
super.onCreate();
Expand Down Expand Up @@ -80,6 +90,9 @@ private void startWatchdog() {
// 2. Acquire CPU WakeLock to prevent sleep during heavy operations (e.g., Tar extraction, Rsync)
acquireHardwareLocks();

// K2GO-386 (barrier 2): guard free space for the life of this protected session.
startDiskGuard();

// 3. Notify the UI (MainActivity) that the engine is protected and running
IIABWatchdog.logSessionStart(this);
Intent startIntent = new Intent(ACTION_STATE_STARTED);
Expand Down Expand Up @@ -116,6 +129,28 @@ private void releaseHardwareLocks() {
}
}

// K2GO-386 (Layer 3): the free-space guard. One background poller ticks every DISK_GUARD_INTERVAL_S.
// On a CRITICAL reading DiskGuard confirms, reaps the box, reclaims the runaway log, and by default
// lets it restart; the in-box layers cannot stop an off-proot orphan. Started once per session.
private void startDiskGuard() {
if (diskGuardPoller != null) return;
diskGuardPoller = Executors.newSingleThreadScheduledExecutor();
diskGuardPoller.scheduleWithFixedDelay(() -> {
try {
org.appdevforall.k2go.diskguard.DiskGuard.check(getApplicationContext());
} catch (Throwable t) {
Log.w(TAG, "K2GO-386: disk-guard tick failed", t);
}
}, DISK_GUARD_INTERVAL_S, DISK_GUARD_INTERVAL_S, TimeUnit.SECONDS);
}

private void stopDiskGuard() {
if (diskGuardPoller != null) {
diskGuardPoller.shutdownNow();
diskGuardPoller = null;
}
}

@Override
public void onDestroy() {
RUNNING = false; // ADFA-5343 (Phase 4b): protection is ending — clear the promoter's state signal
Expand All @@ -124,6 +159,9 @@ public void onDestroy() {
stopIntent.setPackage(getPackageName());
sendBroadcast(stopIntent);

// K2GO-386 (barrier 2): stop the free-space guard — this protected session (box up) is ending.
stopDiskGuard();

// 2. Release Hardware Locks so the phone can sleep again
releaseHardwareLocks();

Expand Down
Loading
Loading