Skip to content

Commit a1c16fb

Browse files
authored
fix(terminal): handle axs symlink runtime checks (#2204)
Update `system.fileExists` so normal checks follow valid symlinks while still allowing callers to count symlink entries explicitly. Refresh the packaged Play Store axs symlink before Alpine-backed executor commands so LSP and plugin flows can resolve `$PREFIX/axs` without requiring a terminal tab to repair it first.
1 parent f7b7639 commit a1c16fb

2 files changed

Lines changed: 60 additions & 23 deletions

File tree

src/plugins/system/android/com/foxdebug/system/System.java

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1015,30 +1015,20 @@ private void hasPermission(String permission, CallbackContext callback) {
10151015
}
10161016

10171017
public boolean fileExists(String path, String countSymlinks) {
1018-
boolean followSymlinks = !Boolean.parseBoolean(countSymlinks);
1018+
boolean countSymbolicLinks = Boolean.parseBoolean(countSymlinks);
10191019
File file = new File(path);
10201020

10211021
// Android < O does not implement File#toPath(), fall back to legacy checks
10221022
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
1023-
if (!file.exists()) return false;
1024-
if (followSymlinks) {
1025-
try {
1026-
// If canonical and absolute paths differ, it's a symlink
1027-
return file.getCanonicalPath().equals(file.getAbsolutePath());
1028-
} catch (IOException ignored) {
1029-
return false;
1030-
}
1031-
}
1032-
return true;
1023+
return file.exists();
10331024
}
10341025

10351026
Path p = file.toPath();
10361027
try {
1037-
if (followSymlinks) {
1038-
return Files.exists(p) && !Files.isSymbolicLink(p);
1039-
} else {
1028+
if (countSymbolicLinks) {
10401029
return Files.exists(p, LinkOption.NOFOLLOW_LINKS);
10411030
}
1031+
return Files.exists(p);
10421032
} catch (Exception e) {
10431033
return false;
10441034
}

src/plugins/terminal/src/android/ProcessManager.java

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@
22

33
import android.content.Context;
44
import android.content.pm.PackageManager;
5+
import android.os.Build;
56
import java.io.*;
67
import java.util.Map;
78
import java.util.TimeZone;
9+
import java.nio.file.Files;
10+
import java.nio.file.Path;
11+
import java.nio.file.Paths;
812
import com.foxdebug.acode.rk.exec.terminal.*;
913

1014
public class ProcessManager {
@@ -19,11 +23,61 @@ public ProcessManager(Context context) {
1923
* Creates a ProcessBuilder with common environment setup
2024
*/
2125
public ProcessBuilder createProcessBuilder(String cmd, boolean useAlpine) {
26+
if (useAlpine) {
27+
refreshAxsSymlink();
28+
}
2229
String xcmd = useAlpine ? "source $PREFIX/init-sandbox.sh " + cmd : cmd;
2330
ProcessBuilder builder = new ProcessBuilder("sh", "-c", xcmd);
2431
setupEnvironment(builder.environment());
2532
return builder;
2633
}
34+
35+
/**
36+
* Play Store builds package axs as a native library. Keep the legacy
37+
* $PREFIX/axs path valid for scripts and plugins that execute it directly.
38+
*/
39+
private void refreshAxsSymlink() {
40+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O || isFdroidBuild()) {
41+
return;
42+
}
43+
44+
Path axsPath = Paths.get(context.getFilesDir().getAbsolutePath(), "axs");
45+
Path nativeAxsPath = Paths.get(context.getApplicationInfo().nativeLibraryDir, "libaxs.so");
46+
47+
if (!Files.exists(nativeAxsPath)) {
48+
return;
49+
}
50+
51+
try {
52+
if (Files.isSymbolicLink(axsPath)) {
53+
Path currentTarget = Files.readSymbolicLink(axsPath);
54+
if (currentTarget.equals(nativeAxsPath)) {
55+
return;
56+
}
57+
}
58+
59+
Files.deleteIfExists(axsPath);
60+
Files.createSymbolicLink(axsPath, nativeAxsPath);
61+
} catch (Exception ignored) {
62+
// init-sandbox.sh will surface the execution error if the link is unusable.
63+
}
64+
}
65+
66+
private boolean isFdroidBuild() {
67+
// F-Droid builds are intentionally pinned to targetSdkVersion 28.
68+
// This convention is also exposed to scripts through the FDROID env var.
69+
return getTargetSdkVersion() <= 28;
70+
}
71+
72+
private int getTargetSdkVersion() {
73+
try {
74+
return context.getPackageManager()
75+
.getPackageInfo(context.getPackageName(), 0)
76+
.applicationInfo.targetSdkVersion;
77+
} catch (PackageManager.NameNotFoundException e) {
78+
return Build.VERSION_CODES.P;
79+
}
80+
}
2781

2882
/**
2983
* Sets up common environment variables
@@ -35,14 +89,7 @@ private void setupEnvironment(Map<String, String> env) {
3589
TimeZone tz = TimeZone.getDefault();
3690
env.put("ANDROID_TZ", tz.getID());
3791

38-
try {
39-
int target = context.getPackageManager()
40-
.getPackageInfo(context.getPackageName(), 0)
41-
.applicationInfo.targetSdkVersion;
42-
env.put("FDROID", String.valueOf(target <= 28));
43-
} catch (PackageManager.NameNotFoundException e) {
44-
e.printStackTrace();
45-
}
92+
env.put("FDROID", String.valueOf(isFdroidBuild()));
4693
}
4794

4895
/**
@@ -97,4 +144,4 @@ public String getErrorMessage() {
97144
return "Command exited with code: " + exitCode;
98145
}
99146
}
100-
}
147+
}

0 commit comments

Comments
 (0)