mirror of
https://github.com/syncthing/syncthing-android.git
synced 2024-12-23 03:11:30 +00:00
Added option to run syncthing as root (fixes #48).
This commit is contained in:
parent
f0daeb0cf8
commit
2fb425b441
6 changed files with 76 additions and 31 deletions
|
@ -19,13 +19,15 @@ apply plugin: 'com.github.ben-manes.versions'
|
|||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url {
|
||||
'https://raw.github.com/kolavar/android-support-v4-preferencefragment/master/maven-repository/'
|
||||
}
|
||||
url 'https://raw.github.com/kolavar/android-support-v4-preferencefragment/master/maven-repository/'
|
||||
}
|
||||
maven {
|
||||
url 'http://jcenter.bintray.com'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'eu.chainfire:libsuperuser:1.0.0.201504231659'
|
||||
compile 'com.android.support:appcompat-v7:22.0.0'
|
||||
compile 'com.android.support:support-v4-preferencefragment:1.0.0@aar'
|
||||
androidTestCompile 'com.squareup.okhttp:mockwebserver:2.3.0'
|
||||
|
|
|
@ -25,6 +25,8 @@ import com.nutomic.syncthingandroid.activities.SyncthingActivity;
|
|||
import com.nutomic.syncthingandroid.syncthing.RestApi;
|
||||
import com.nutomic.syncthingandroid.syncthing.SyncthingService;
|
||||
|
||||
import eu.chainfire.libsuperuser.Shell;
|
||||
|
||||
public class SettingsFragment extends PreferenceFragment
|
||||
implements SyncthingActivity.OnServiceConnectedListener,
|
||||
SyncthingService.OnApiChangeListener, Preference.OnPreferenceChangeListener,
|
||||
|
@ -126,6 +128,7 @@ public class SettingsFragment extends PreferenceFragment
|
|||
mSyncOnlyCharging = (CheckBoxPreference)
|
||||
findPreference(SyncthingService.PREF_SYNC_ONLY_CHARGING);
|
||||
mSyncOnlyWifi = (CheckBoxPreference) findPreference(SyncthingService.PREF_SYNC_ONLY_WIFI);
|
||||
Preference useRoot = findPreference(SyncthingService.PREF_USE_ROOT);
|
||||
Preference appVersion = screen.findPreference(APP_VERSION_KEY);
|
||||
mOptionsScreen = (PreferenceScreen) screen.findPreference(SYNCTHING_OPTIONS_KEY);
|
||||
mGuiScreen = (PreferenceScreen) screen.findPreference(SYNCTHING_GUI_KEY);
|
||||
|
@ -143,6 +146,10 @@ public class SettingsFragment extends PreferenceFragment
|
|||
mAlwaysRunInBackground.setOnPreferenceChangeListener(this);
|
||||
mSyncOnlyCharging.setOnPreferenceChangeListener(this);
|
||||
mSyncOnlyWifi.setOnPreferenceChangeListener(this);
|
||||
if (!Shell.SU.available()) {
|
||||
screen.removePreference(useRoot);
|
||||
}
|
||||
useRoot.setOnPreferenceChangeListener(this);
|
||||
screen.findPreference(EXPORT_CONFIG).setOnPreferenceClickListener(this);
|
||||
screen.findPreference(IMPORT_CONFIG).setOnPreferenceClickListener(this);
|
||||
screen.findPreference(SYNCTHING_RESET).setOnPreferenceClickListener(this);
|
||||
|
@ -234,23 +241,25 @@ public class SettingsFragment extends PreferenceFragment
|
|||
RestApi.TYPE_GUI, preference.getKey(), o, false, getActivity());
|
||||
}
|
||||
|
||||
boolean requireRestart = false;
|
||||
|
||||
// Avoid any code injection.
|
||||
int error = 0;
|
||||
if (preference.getKey().equals(STTRACE)) {
|
||||
if (((String) o).matches("[a-z, ]*"))
|
||||
mSyncthingService.getApi().requireRestart(getActivity());
|
||||
requireRestart = true;
|
||||
else
|
||||
error = R.string.toast_invalid_sttrace;
|
||||
} else if (preference.getKey().equals(GUI_USER)) {
|
||||
String s = (String) o;
|
||||
if (!s.contains(":") && !s.contains("'"))
|
||||
mSyncthingService.getApi().requireRestart(getActivity());
|
||||
requireRestart = true;
|
||||
else
|
||||
error = R.string.toast_invalid_username;
|
||||
} else if (preference.getKey().equals(GUI_PASSWORD)) {
|
||||
String s = (String) o;
|
||||
if (!s.contains(":") && !s.contains("'"))
|
||||
mSyncthingService.getApi().requireRestart(getActivity());
|
||||
requireRestart = true;
|
||||
else
|
||||
error = R.string.toast_invalid_password;
|
||||
}
|
||||
|
@ -259,6 +268,13 @@ public class SettingsFragment extends PreferenceFragment
|
|||
return false;
|
||||
}
|
||||
|
||||
if (preference.getKey().equals(SyncthingService.PREF_USE_ROOT))
|
||||
requireRestart = true;
|
||||
|
||||
if (requireRestart)
|
||||
mSyncthingService.getApi().requireRestart(getActivity());
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -267,7 +267,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener,
|
|||
}
|
||||
|
||||
/**
|
||||
* Stops syncthing. You should probably use SyncthingService.stopService() instead.
|
||||
* Stops syncthing and cancels notification. For use by {@link SyncthingService}.
|
||||
*/
|
||||
public void shutdown() {
|
||||
// Happens in unit tests.
|
||||
|
@ -277,7 +277,6 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener,
|
|||
NotificationManager nm = (NotificationManager)
|
||||
mContext.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
nm.cancel(NOTIFICATION_RESTART);
|
||||
SyncthingRunnable.killSyncthing();
|
||||
mRestartPostponed = false;
|
||||
}
|
||||
|
||||
|
@ -740,7 +739,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener,
|
|||
case "": return c.getString(R.string.state_unknown);
|
||||
}
|
||||
if (BuildConfig.DEBUG) {
|
||||
throw new AssertionError("Unexpected folder state");
|
||||
throw new AssertionError("Unexpected folder state " + state);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import android.content.Intent;
|
|||
import android.content.SharedPreferences;
|
||||
import android.os.Environment;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
|
@ -15,6 +16,8 @@ import java.io.InputStreamReader;
|
|||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import eu.chainfire.libsuperuser.Shell;
|
||||
|
||||
/**
|
||||
* Runs the syncthing binary from command line, and prints its output to logcat.
|
||||
*/
|
||||
|
@ -88,8 +91,7 @@ public class SyncthingRunnable implements Runnable {
|
|||
try {
|
||||
ProcessBuilder pb = new ProcessBuilder("chmod", "+x", mSyncthingBinary);
|
||||
Process p = pb.start();
|
||||
if (p != null)
|
||||
p.waitFor();
|
||||
p.waitFor();
|
||||
} catch (IOException|InterruptedException e) {
|
||||
Log.w(TAG, "Failed to chmod Syncthing", e);
|
||||
}
|
||||
|
@ -98,7 +100,10 @@ public class SyncthingRunnable implements Runnable {
|
|||
try {
|
||||
// Loop to handle Syncthing restarts (these always have an error code of 3).
|
||||
do {
|
||||
ProcessBuilder pb = new ProcessBuilder(mCommand);
|
||||
ProcessBuilder pb = (useRoot())
|
||||
? new ProcessBuilder("su", "-c", TextUtils.join(" ", mCommand))
|
||||
: new ProcessBuilder(mCommand);
|
||||
|
||||
Map<String, String> env = pb.environment();
|
||||
// Set home directory to data folder for web GUI folder picker.
|
||||
env.put("HOME", Environment.getExternalStorageDirectory().getAbsolutePath());
|
||||
|
@ -138,6 +143,14 @@ public class SyncthingRunnable implements Runnable {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if root is available and enabled in settings.
|
||||
*/
|
||||
private boolean useRoot() {
|
||||
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
|
||||
return sp.getBoolean(SyncthingService.PREF_USE_ROOT, false) && Shell.SU.available();
|
||||
}
|
||||
|
||||
/**
|
||||
* Look for a running libsyncthing.so process and nice its IO.
|
||||
*/
|
||||
|
@ -149,7 +162,7 @@ public class SyncthingRunnable implements Runnable {
|
|||
int ret = 1;
|
||||
try {
|
||||
Thread.sleep(1000); // Wait a second before getting the pid
|
||||
nice = Runtime.getRuntime().exec("sh");
|
||||
nice = Runtime.getRuntime().exec((useRoot()) ? "su" : "sh");
|
||||
niceOut = new DataOutputStream(nice.getOutputStream());
|
||||
niceOut.writeBytes("set `ps | grep libsyncthing.so`\n");
|
||||
niceOut.writeBytes("ionice $2 be 7\n"); // best-effort, low priority
|
||||
|
@ -182,9 +195,8 @@ public class SyncthingRunnable implements Runnable {
|
|||
/**
|
||||
* Look for running libsyncthing.so processes and kill them.
|
||||
* Try a SIGTERM once, then try again (twice) with SIGKILL.
|
||||
*
|
||||
*/
|
||||
public static void killSyncthing() {
|
||||
public void killSyncthing() {
|
||||
final Process p = mSyncthing.get();
|
||||
if (p != null) {
|
||||
mSyncthing.set(null);
|
||||
|
@ -201,7 +213,7 @@ public class SyncthingRunnable implements Runnable {
|
|||
Process ps = null;
|
||||
DataOutputStream psOut = null;
|
||||
try {
|
||||
ps = Runtime.getRuntime().exec("sh");
|
||||
ps = Runtime.getRuntime().exec((useRoot()) ? "su" : "sh");
|
||||
psOut = new DataOutputStream(ps.getOutputStream());
|
||||
psOut.writeBytes("ps | grep libsyncthing.so\n");
|
||||
psOut.writeBytes("exit\n");
|
||||
|
|
|
@ -92,6 +92,8 @@ public class SyncthingService extends Service {
|
|||
|
||||
public static final String PREF_SYNC_ONLY_CHARGING = "sync_only_charging";
|
||||
|
||||
public static final String PREF_USE_ROOT = "use_root";
|
||||
|
||||
private static final int NOTIFICATION_ACTIVE = 1;
|
||||
|
||||
private ConfigXml mConfig;
|
||||
|
@ -142,6 +144,8 @@ public class SyncthingService extends Service {
|
|||
|
||||
private DeviceStateHolder mDeviceStateHolder;
|
||||
|
||||
private SyncthingRunnable mRunnable;
|
||||
|
||||
/**
|
||||
* Handles intents, either {@link #ACTION_RESTART}, or intents having
|
||||
* {@link DeviceStateHolder#EXTRA_HAS_WIFI} or {@link DeviceStateHolder#EXTRA_IS_CHARGING}
|
||||
|
@ -153,11 +157,11 @@ public class SyncthingService extends Service {
|
|||
return START_STICKY;
|
||||
|
||||
if (ACTION_RESTART.equals(intent.getAction()) && mCurrentState == State.ACTIVE) {
|
||||
mApi.shutdown();
|
||||
shutdown();
|
||||
mCurrentState = State.INIT;
|
||||
updateState();
|
||||
} else if (ACTION_RESET.equals(intent.getAction())) {
|
||||
mApi.shutdown();
|
||||
shutdown();
|
||||
new SyncthingRunnable(this, SyncthingRunnable.Command.reset).run();
|
||||
mCurrentState = State.INIT;
|
||||
updateState();
|
||||
|
@ -203,14 +207,15 @@ public class SyncthingService extends Service {
|
|||
// HACK: Make sure there is no syncthing binary left running from an improper
|
||||
// shutdown (eg Play Store update).
|
||||
// NOTE: This will log an exception if syncthing is not actually running.
|
||||
mApi.shutdown();
|
||||
shutdown();
|
||||
|
||||
Log.i(TAG, "Starting syncthing according to current state and preferences");
|
||||
mConfig = new ConfigXml(SyncthingService.this);
|
||||
mCurrentState = State.STARTING;
|
||||
registerOnWebGuiAvailableListener(mApi);
|
||||
new PollWebGuiAvailableTaskImpl(getFilesDir() + "/" + HTTPS_CERT_FILE).execute(mConfig.getWebGuiUrl());
|
||||
new Thread(new SyncthingRunnable(this, SyncthingRunnable.Command.main)).start();
|
||||
mRunnable = new SyncthingRunnable(this, SyncthingRunnable.Command.main);
|
||||
new Thread(mRunnable).start();
|
||||
Notification n = new NotificationCompat.Builder(this)
|
||||
.setContentTitle(getString(R.string.syncthing_active))
|
||||
.setSmallIcon(R.drawable.ic_stat_notify)
|
||||
|
@ -229,14 +234,7 @@ public class SyncthingService extends Service {
|
|||
Log.i(TAG, "Stopping syncthing according to current state and preferences");
|
||||
mCurrentState = State.DISABLED;
|
||||
|
||||
if (mApi != null) {
|
||||
mApi.shutdown();
|
||||
for (FolderObserver ro : mObservers) {
|
||||
ro.stopWatching();
|
||||
}
|
||||
mObservers.clear();
|
||||
}
|
||||
nm.cancel(NOTIFICATION_ACTIVE);
|
||||
shutdown();
|
||||
}
|
||||
onApiChange();
|
||||
}
|
||||
|
@ -331,11 +329,23 @@ public class SyncthingService extends Service {
|
|||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
Log.i(TAG, "Shutting down service");
|
||||
if (mApi != null) {
|
||||
shutdown();
|
||||
}
|
||||
|
||||
private void shutdown() {
|
||||
if (mRunnable != null)
|
||||
mRunnable.killSyncthing();
|
||||
|
||||
if (mApi != null)
|
||||
mApi.shutdown();
|
||||
}
|
||||
|
||||
NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||
nm.cancel(NOTIFICATION_ACTIVE);
|
||||
|
||||
for (FolderObserver ro : mObservers) {
|
||||
ro.stopWatching();
|
||||
}
|
||||
mObservers.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -400,7 +410,7 @@ public class SyncthingService extends Service {
|
|||
if (mStopScheduled) {
|
||||
mCurrentState = State.DISABLED;
|
||||
onApiChange();
|
||||
mApi.shutdown();
|
||||
shutdown();
|
||||
mStopScheduled = false;
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -25,6 +25,12 @@
|
|||
android:summary="@string/advanced_folder_picker_summary"
|
||||
android:defaultValue="false" />
|
||||
|
||||
<CheckBoxPreference
|
||||
android:key="use_root"
|
||||
android:title="Sync as root"
|
||||
android:summary="Run syncthing as superuser"
|
||||
android:defaultValue="false" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
|
|
Loading…
Reference in a new issue