ایپ Android 9 پر چلتی تھی اور Android 10 اپ ڈیٹ کے بعد اسی فون پر مر جاتی ہے۔ آپ کا پروفائل اچھوتا ہے، سرٹیفکیٹ وہی ہیں، اور کریش کسی بھی تصدیقی ٹریفک سے پہلے آتا ہے۔ logcat پہلے ایک صفر پوائنٹر دکھاتا ہے، اور اس سے چند سطریں اوپر ہے Cannot run program ...: error=13, Permission denied۔ وہ پیغام کسی فائل کو چلانے کے بارے میں ہے، آپ کی کنفیگریشن پڑھنے کے بارے میں نہیں۔

خرابی 13 دراصل کیا مسترد کرتی ہے

Android 10، API سطح 29، نے ایپس کو ان فائلوں کو چلانے سے روک دیا جو انہوں نے اپنی ہی ڈیٹا ڈائریکٹری میں لکھی تھیں۔ یہ قاعدہ ایسی اسٹوریج کو نشانہ بناتا ہے جو پہلے لکھنے کے قابل اور پھر چلانے کے قابل ہو، اور یہی چیز assets سے نقل کرنے والا لانچر بناتا ہے۔ ics-openvpn کور کی پرانی بلڈز پہلی بار چلانے پر APK سے چلانے کے قابل openvpn فائل نکالتی ہیں اور اسے ProcessBuilder سے شروع کرتی ہیں۔ نکالنا اب بھی کامیاب ہوتا ہے؛ مسترد ہونے والی چیز خود چلانا ہے۔ API 29 کو نشانہ بنانے والی ایپ یہ قاعدہ اس وقت بھی ورثے میں پاتی ہے جب وہ کسی پرانے ریلیز کے ساتھ آنے والے آلے پر چل رہی ہو، اسی لیے ایک اپ ڈیٹ ایسی بلڈ توڑ سکتا ہے جسے کسی نے چھوا بھی نہ ہو۔

اس انکار میں کسی ایک فولڈر نام کے لیے کوئی خاص بات نہیں۔ ایپ کی طرف سے اس کی ڈیٹا ڈائریکٹری کے نیچے بنائی گئی فائل راستہ جو بھی ہو، ناقابلِ عمل سمجھی جاتی ہے، اس لیے getFilesDir()، getCacheDir() اور کوئی حسبِ ضرورت ذیلی ڈائریکٹری سب اسی طرح ناکام ہوتے ہیں۔

معاون کے کریش کو پروفائل مسئلے سے الگ کریں

اوپر سے اسکرول کرنے کی بجائے پروسیس کے آغاز کے آس پاس لاگ پڑھیں۔ جس دستخط کو ڈھونڈنا ہے وہ error=13 سطر ہے، اور اس کے اوپر اسٹیک ٹریس عام طور پر اُس لانچر طریقے کا نام لیتا ہے جس نے ProcessBuilder کو بلایا:

adb logcat -d | grep -n -i -E "cannot run program|permission denied|startOpenVPN"

اگر معاون کبھی شروع ہی نہیں ہوا، تو آپ کو OpenVPN کا کوئی لاگ آؤٹ پٹ دکھائی ہی نہیں دے گا: نہ سائفر گفتگو، نہ دھکیلے گئے راستے، نہ DNS کنفیگریشن۔ اگر وہ سطریں پھر بھی نظر آئیں اور ناکامی بعد میں آئے، تو بائنری چلی اور مسئلہ پروفائل، سرور یا روٹنگ کی تہہ میں ہے۔ یہی ایک فرق .ovpn فائلوں کو ترمیم کرنے میں جانے والا بہت وقت بچاتا ہے جو کبھی قصوروار نہیں تھیں۔ یہ یہ بھی بتاتا ہے کہ نیچے دی گئی دو اصلاحات میں سے آپ کو واقعی کون سی چاہیے۔

آپ جس آلے کی جانچ کر رہے ہیں اس کا API سطح بھی دیکھیں، اپنی بلڈ کنفیگریشن والا نہیں۔ API 28 پر ایمولیٹر امیج کچھ بھی دوبارہ پیدا نہیں کرتی، کیونکہ یہ پابندی صرف 29 سے اوپر تک کاٹتی ہے۔ ساتھ ہی اپنی بلڈ فائل میں ہدف SDK دیکھیں: اسی کو بڑھانا یہ قاعدہ اندر کھینچتا ہے۔

adb shell getprop ro.build.version.sdk

بائنری کو کہیں اور نقل کرنے سے مدد کیوں نہیں ملتی

ظاہر حل، معاون کو کسی اور لکھنے کے قابل ڈائریکٹری میں لکھنا، اسی وجہ سے ناکام ہوتا ہے جس وجہ سے اصل ناکام ہوا۔ Android اب بھی ایپ کی مقامی لائبریری ڈائریکٹری میں نصب مشترکہ لائبریریوں کو چلانے کی اجازت دیتا ہے، کیونکہ انہیں انسٹالر پیک کرتا ہے، رن ٹائم پر لکھا نہیں جاتا۔ وہی ڈائریکٹری واحد جگہ ہے جہاں لانچر کو اشارہ کرنا چاہیے۔ رن ٹائم پر آپ خود جو نکالتے ہیں، چاہے کوئی بھی نام دیں، مسترد زمرے میں ہی رہتا ہے۔

بیرونی اسٹوریج زیادہ بدتر ہے: یہ noexec کے ساتھ ماؤنٹ ہے، اور وہاں رکھا گیا معاون کوئی بھی ایپ شروع نہیں کر سکتی۔ ایپ ڈیٹا صاف کرنا بھی پرانا طرزِ عمل واپس نہیں لاتا، کیونکہ اگلی بار چلانے پر وہی ناقابلِ عمل نقل دوبارہ بن جاتی ہے۔

لانچر کو پیک شدہ لائبریری کی طرف موڑیں

معاون کا نام مشترکہ لائبریری کے نام میں بدلیں اور اسے آپ کی معاونت کردہ ہر ABI کے لیے jniLibs کے تحت بھیجیں، پھر لانچر API 28 سے آگے نصب شدہ لائبریری کا راستہ واپس کرے:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
    return new File(context.getApplicationInfo().nativeLibraryDir,
                    "libovpnexec.so").getPath();
}

اُس راستے میں حقیقی فائل ہونے کے لیے لائبریری کو تنصیب کے وقت نکالنا ضروری ہے، اس لیے android:extractNativeLibs="true" کو <application> عنصر پر سیٹ کریں۔ اگر manifest ضم کرنے والا کوئی اور قدر لائے تو اس کے ساتھ tools:replace="android:extractNativeLibs" شامل کریں۔ application عنصر کا حوالہ دستاویز کرتا ہے کہ یہ خصوصیت کیا کنٹرول کرتی ہے۔

لانچر کو ہاتھ سے پیچ کرتے وقت دہرائے گئے مقامی متغیر سے ہوشیار رہیں۔ پرانے ماخذ اسی طریقے میں بعد میں nativeAPI کا اعلان کرتے ہیں، اور دوسرا اعلان رن ٹائم نہیں بلکہ کمپائل خرابی ہے، اس لیے بلڈ جلدی ناکام ہوتی ہے اور غلطی آسانی سے نظر آتی ہے۔ Gradle بلاک میں ABI فہرست کو اپنے شامل کردہ فولڈرز کے مطابق رکھیں۔

ریلیز آرٹیفیکٹ کو ٹیسٹ بلڈ سے ہم آہنگ کریں

ڈیبگ APK سرنگ اٹھا سکتا ہے جبکہ ریلیز bundle اب بھی کریش کرتا ہے، کیونکہ bundle مقامی لائبریریوں کو مختلف شکل میں پیک کرتا ہے اور انہیں آرکائیو سے سکیڑ کر پیش کر سکتا ہے۔ اگر صرف bundle سے بنی بلڈز ناکام ہوں تو یہ gradle.properties میں شامل کریں اور دوبارہ بلڈ کریں:

android.bundle.enableUncompressedNativeLibs=false

پھر تصدیق کریں کہ نصب شدہ پیکیج وہی ABI بتاتا ہے جس کی آپ توقع کرتے ہیں، اور اگر دونوں بھیجتے ہیں تو 32 بٹ اور 64 بٹ آلے پر جانچ دہرائیں۔ پیکیج منیجر اپنی منتخب کردہ ABI کا نام لیتا ہے، اس لیے 32 بٹ پر واپس آیا آلہ کریش رپورٹ میں نہیں بلکہ یہاں دکھائی دیتا ہے:

adb shell dumpsys package com.example.openvpn | grep -i primaryCpuAbi

ایک ABI کے لیے غائب لائبریری اصل اجازت کی خرابی صرف اسی آلے پر دوبارہ پیدا کرتی ہے، اسی لیے ایک کامیاب ٹیسٹ فون ثبوت نہیں۔ manifest جھنڈا، لائبریری کا نام اور bundle کی ترتیب اکٹھے بھیجیں؛ ایک بدل کر باقی بھول جانے سے آپ اسی کریش پر واپس آ جاتے ہیں۔ پائیدار حل یہ ہے کہ پیچ ہمیشہ اٹھانے کی بجائے کسی برقرار رکھے گئے کور ریلیز پر جائیں۔

متعلقہ مضامین