From 780d3167a427d6964806694ef5282be0c6f4cd33 Mon Sep 17 00:00:00 2001 From: Lena Date: Sun, 23 Aug 2026 00:00:00 +0000 Subject: doc: correct drift and move the build notes to the root The Rsync comment about draining a hostile remote's pipe sat on the Tally type, where it means nothing; it describes boundedLines. Scheduler explained itself with what happened during development instead of the constraint that WorkManager persists unique names across upgrades, and MainActivity and SyncLog did the same. Keys pointed at a pin() that does not exist. The App header restated the class name and omitted half of what onCreate does. OUT_FORMAT had no caller outside its file. The README layout list omitted fastlane. The e2e test hardcodes a copy of the app's rsync vector with nothing saying so. useradd already writes a locked password field, so "skip passwd -l" does not leave a non-locked one and key auth is still refused; verified against sshd with UsePAM no. The CI file also runs verify-repro on a tag, and the NDK and build-tools are pinned by revision and verified against Google's manifest rather than a checksum this repository owns, which the reproducibility notes claimed for every input. metadata/ held one document and nothing else, and reads as a store metadata directory beside fastlane/metadata. Root, extensionless, beside THIRD_PARTY. --- app/src/main/java/invalid/lena/rsend/App.kt | 1 - app/src/main/java/invalid/lena/rsend/Keys.kt | 6 +++--- app/src/main/java/invalid/lena/rsend/MainActivity.kt | 7 +++---- app/src/main/java/invalid/lena/rsend/Rsync.kt | 15 +++++++-------- app/src/main/java/invalid/lena/rsend/Scheduler.kt | 10 +++++----- app/src/main/java/invalid/lena/rsend/SyncLog.kt | 4 ++-- 6 files changed, 20 insertions(+), 23 deletions(-) (limited to 'app/src/main/java') diff --git a/app/src/main/java/invalid/lena/rsend/App.kt b/app/src/main/java/invalid/lena/rsend/App.kt index 443229d..0ba1604 100644 --- a/app/src/main/java/invalid/lena/rsend/App.kt +++ b/app/src/main/java/invalid/lena/rsend/App.kt @@ -5,7 +5,6 @@ import android.app.NotificationChannel import android.app.NotificationManager import kotlin.concurrent.thread -// App creates the notification channel used by the foreground sync service. class App : Application() { override fun onCreate() { diff --git a/app/src/main/java/invalid/lena/rsend/Keys.kt b/app/src/main/java/invalid/lena/rsend/Keys.kt index 23a3b97..72f9654 100644 --- a/app/src/main/java/invalid/lena/rsend/Keys.kt +++ b/app/src/main/java/invalid/lena/rsend/Keys.kt @@ -27,7 +27,7 @@ object Keys { // generate creates a key pair via rsh -keygen (the plaintext key never // touches disk), stores it encrypted, and returns the public key in - // authorized_keys format. Storage and validation reuse importKey. + // authorized_keys format. Validation and storage are shared with importKey. fun generate(ctx: Context): String = synchronized(keyLock) { val result = Native.run(Native.rsh(ctx), listOf("-keygen")) if (result.code != 0) { @@ -97,8 +97,8 @@ object Keys { data class Scan(val ok: Boolean, val fingerprint: String, val line: String, val error: String) // scan connects (proving the key is installed), captures the host key, and - // returns its fingerprint and the known_hosts line to pin. pin() writes it - // once the user accepts. + // returns its fingerprint and the known_hosts line to pin. Nothing is + // stored; RemoteActivity saves the line once the user accepts it. fun scan(ctx: Context, remote: Remote): Scan = synchronized(keyLock) { // Point the scan at this remote's own pin, so rsh offers the key type // already pinned and an unchanged server reproduces its stored line. A diff --git a/app/src/main/java/invalid/lena/rsend/MainActivity.kt b/app/src/main/java/invalid/lena/rsend/MainActivity.kt index f523a04..bba3ada 100644 --- a/app/src/main/java/invalid/lena/rsend/MainActivity.kt +++ b/app/src/main/java/invalid/lena/rsend/MainActivity.kt @@ -55,8 +55,8 @@ class MainActivity : AppCompatActivity() { private lateinit var syncStatus: TextView // Last drawn schedule and the observed state of the periodic job. Observing - // it keeps the dashboard off the 500ms blocking WorkManager query that used - // to run on the main thread for every refresh. + // it keeps the dashboard off WorkManager's blocking query, which costs + // hundreds of milliseconds and must not run on the main thread. private var schedule: Schedule = Schedule() private var jobState: String = "none" private var recoveryShown = false @@ -281,8 +281,7 @@ class MainActivity : AppCompatActivity() { ) // Every field above comes from an app-private file, so read them on a worker - // thread and draw the result. Reading them during layout was the same - // main-thread stall the WorkManager query was moved off. + // thread and draw the result. private fun refresh() { val current = ++generation thread { diff --git a/app/src/main/java/invalid/lena/rsend/Rsync.kt b/app/src/main/java/invalid/lena/rsend/Rsync.kt index 93ce623..d4c8f40 100644 --- a/app/src/main/java/invalid/lena/rsend/Rsync.kt +++ b/app/src/main/java/invalid/lena/rsend/Rsync.kt @@ -44,11 +44,10 @@ object Rsync { // Relative, so rsync excludes it automatically and --delete leaves it alone. const val PARTIAL_DIR = ".rsend-partial" - // " ": %o is send, recv or del. - const val OUT_FORMAT = "%o %n" - - // Transfers are counted rather than logged one line each. Deletions and - // everything else go to the log verbatim. + // " ": %o is send, recv or del. Transfers are counted + // rather than logged one line each; deletions and everything else go to the + // log verbatim. + private const val OUT_FORMAT = "%o %n" private const val SENT = "send " private const val DELETED = "del. " internal const val MAX_OUTPUT_LINE_BYTES = 16 * 1024 @@ -140,9 +139,6 @@ object Rsync { } } - // A hostile or broken remote can print an arbitrarily long line. Keep - // draining its pipe so rsync cannot deadlock, but retain only a bounded - // prefix for the log. internal data class Tally(val sent: Long, val deleted: Long, val unreachable: Boolean) // tally reads rsync's merged output, counts what it did, and passes @@ -163,6 +159,9 @@ object Rsync { return Tally(sent, deleted, unreachable) } + // A hostile or broken remote can print an arbitrarily long line. Keep + // draining its pipe so rsync cannot deadlock, but retain only a bounded + // prefix for the log. internal fun boundedLines(input: InputStream, emit: (String) -> Unit) { val line = ByteArrayOutputStream() val buf = ByteArray(4096) diff --git a/app/src/main/java/invalid/lena/rsend/Scheduler.kt b/app/src/main/java/invalid/lena/rsend/Scheduler.kt index c35fb42..46e43e7 100644 --- a/app/src/main/java/invalid/lena/rsend/Scheduler.kt +++ b/app/src/main/java/invalid/lena/rsend/Scheduler.kt @@ -11,9 +11,9 @@ import java.util.concurrent.TimeUnit // Scheduler keeps one periodic sync request aligned with the saved config. object Scheduler { - // Keep this distinct from the retired pre-0.1.3 one-time chain. WorkManager - // persists unique names across app upgrades, so changing it creates a - // second schedule and leaves the first one invisible to the UI. + // WorkManager persists unique names across app upgrades, so changing NAME + // enqueues a second schedule and leaves the installed one running, invisible + // to the UI. RETIRED_NAME is what earlier releases used. const val NAME = "periodic-sync-v2" private const val RETIRED_NAME = "periodic-sync" @@ -22,8 +22,8 @@ object Scheduler { val s = cfg.schedule val wm = WorkManager.getInstance(ctx) - // Idempotently retire both the old one-time chain and any periodic job - // created by the unreleased name regression. + // An install upgraded from an earlier release still holds work under the + // old name. Cancelling is idempotent and costs nothing on a fresh one. wm.cancelUniqueWork(RETIRED_NAME) if (!s.enabled || !cfg.syncReady(Keys.exists(ctx))) { diff --git a/app/src/main/java/invalid/lena/rsend/SyncLog.kt b/app/src/main/java/invalid/lena/rsend/SyncLog.kt index 64de544..b7d9721 100644 --- a/app/src/main/java/invalid/lena/rsend/SyncLog.kt +++ b/app/src/main/java/invalid/lena/rsend/SyncLog.kt @@ -25,8 +25,8 @@ class SyncLog(ctx: Context) { } } - // Roll over rather than truncate. Truncating destroyed every earlier run, - // and a run that outgrew the cap destroyed its own output, including the + // Roll over rather than truncate: truncating drops every earlier run, and a + // run that outgrows the cap would destroy its own output, including the // record of what it had just deleted on the server. private fun rotate() { if (previous.exists() && !previous.delete()) { -- cgit v1.2.3