कल टनल काम कर रहा था, और आज सुबह unit मृत है, जर्नल में Failed at step EXEC spawning /usr/local/bin/vpnclient है। क्रेडेंशियल, रूट और खाता कभी उस वाक्य में नहीं आते। systemd ने ExecStart को एक पथ में हल किया, कर्नेल से उसे निष्पादित करने को कहा, और कर्नेल ने मना कर दिया, अधिकतर इसलिए कि उस पथ पर कुछ भी नहीं है या फ़ाइल निष्पादन योग्य नहीं है। विश्लेषित कमांड के विपरीत सिद्ध होने तक खराबी को फ़ाइल और argv की समस्या मानें।

प्रमाणीकरण शुरू होने से पहले EXEC क्यों विफल होता है

systemd ExecStart को किसी शेल से नहीं चलाता। वह fork करता है, पंक्ति से तर्क-सदिश बनाता है और पहले तत्व पर execve कॉल करता है। यदि वह कॉल विफल हो जाए, तो सेवा कभी VPN क्लाइंट के अपने कोड तक नहीं पहुँचती, इसलिए प्रोफ़ाइल संपादन या क्रेडेंशियल जाँच से कोई सहायता नहीं मिलती।

स्थिति कोड इस परिवार को सामान्य क्रैश से अलग करता है। status=203/EXEC के साथ निकलने वाले unit ने कभी प्रक्रिया शुरू नहीं की; जो unit शुरू होकर फिर status=1 के साथ निकलता है, वह शायद प्रमाणीकरण, DNS या रूटिंग में विफल हुआ। क्लाइंट लॉग पढ़ने से पहले कोड पढ़ें, क्योंकि पहले मामले में क्लाइंट लॉग खाली रहेगा।

जर्नल में जो पथ है वह systemd ने इस्तेमाल किया, ज़रूरी नहीं कि वही जो आपने संपादित किया। drop-in फ़ाइलें और जनरेटर का आउटपुट विक्रेता की फ़ाइल को ओवरराइड कर सकते हैं, और पुराना daemon-reload पुरानी पंक्ति स्मृति में रहने देता है। इसीलिए पहला निदान यह है कि systemd से पूछें कि वह कमांड को क्या मानता है, न कि वह फ़ाइल खोलें जो आपको याद है कि आपने बदली थी।

तीन जगहें जहाँ बाइनरी का पथ लिखा जाता है

डिस्ट्रीब्यूशन पैकेज, विक्रेता स्क्रिप्ट और रिलीज़ tarball से इंस्टॉल किया गया VPN क्लाइंट तीन अलग उपसर्गों में पहुँच सकता है। unit, विशेषाधिकार नियम और इंस्टॉलर ने इनमें से एक-एक उपसर्ग दर्ज किया था, और विफलता तब दिखती है जब तीन में से दो मेल नहीं खाते।

RecordCommon valueHow to read it
Unit or drop-in/usr/local/bin/vpnclientsystemctl cat vpnclient.service
Installed binary/usr/bin/vpnclientcommand -v vpnclient, then ls -l the result
Sudoers grant/opt/vpnclient/bin/vpnclientsudo grep -r vpnclient /etc/sudoers.d/

ठोस असंगति ऐसी दिखती है: विक्रेता स्क्रिप्ट /usr/local/bin में इंस्टॉल करती है, tarball /opt के नीचे खोला गया था, और unit अब भी पिछले पैकेज का पुराना /usr/bin पथ बताता है। उन फ़ाइलों में से केवल एक मौजूद है, और शायद ही वह ExecStart वाली फ़ाइल हो।

कोई भी फ़ाइल बदलने से पहले तय करें कि कौन-सा पथ विहित है। यदि बाइनरी डिस्ट्रीब्यूशन पैकेज की है, तो पैकेज का पथ विहित है और unit को उसका पालन करना चाहिए। यदि वह विक्रेता स्क्रिप्ट की है, तो वही नियम विक्रेता उपसर्ग पर लागू होता है। उपसर्गों के बीच बाइनरी की प्रतिलिपि बनाने से दो प्रतियाँ बनती हैं जो अगले अपग्रेड में अलग हो जाती हैं।

जब उद्धरण में लिखा कमांड फ़ाइल नाम बन जाता है

दूसरा विफलता ढंग उसी जर्नल पंक्ति के पीछे छिपा है। कोई इसे unit में लिखता है और अपेक्षा करता है कि क्लाइंट बूट के दौरान जुड़ जाएगा:

ExecStart=/usr/bin/"vpnclient connect office"

systemd मान को शब्दों में बाँटता है, इसलिए पहला शब्द /usr/bin/vpnclient connect office बन जाता है, और उद्धरण समूहन वाक्यविन्यास के रूप में हट जाते हैं। फिर कर्नेल ऐसी फ़ाइल ढूँढता है जिसके नाम में स्थान हों, और ऐसी फ़ाइल नहीं है। त्रुटि गुम बाइनरी जैसी ही है, भले ही बाइनरी इंस्टॉल हो।

समर्थित रूप है वास्तविक निष्पादन योग्य फ़ाइल के बाद अलग तर्क, जैसे ExecStart=/usr/bin/vpnclient connect office। यदि कार्यप्रवाह को सचमुच पाइपलाइन या श्रृंखला चाहिए, तो उसे स्क्रिप्ट में रखें और स्क्रिप्ट बुलाएँ, क्योंकि ExecStart में && नहीं है, न पाइप हैं और न glob विस्तार।

फ़ाइलसिस्टम नहीं, unit ठीक करें

लुभावना उपाय अपेक्षित पथ से वास्तविक बाइनरी तक सांकेतिक लिंक है। यह तब तक काम करता है जब तक पैकेज अपडेट होकर लिंक हटाई गई फ़ाइल की ओर इशारा न करे, या SELinux लेबल लक्ष्य से मेल न खाए। unit को drop-in से संपादित करना बेहतर है, ताकि विक्रेता की फ़ाइल अछूती रहे और अपग्रेड पूर्वानुमेय रहें।

विशेषाधिकार नियम को उसी पथ और उन्हीं उप-कमांडों से मेल खाना चाहिए। /usr/local/bin/vpnclient के लिए sudoers प्रविष्टि /usr/bin/vpnclient को अधिकृत नहीं करती, और हर तर्क की अनुमति देने वाली प्रविष्टि किसी भी स्थानीय प्रक्रिया को टनल को फिर से कॉन्फ़िगर या बंद करने देती है। unit को आवश्यक दो क्रियाएँ दें और यदि सेवा बंद करने से इंटरफ़ेस पहले ही बंद हो जाता है तो विसर्जन को उससे बाहर रखें।

यहीं तुलना फल देती है: unit, पैकेज डेटाबेस और sudoers — सभी को एक ही पथ बताना चाहिए। जब वे ऐसा करते हैं, तो रीबूट के बिना और तीसरी जगह बाइनरी की प्रतिलिपि के बिना EXEC त्रुटि गायब हो जाती है।

रीस्टार्ट से पहले विश्लेषित कमांड सिद्ध करें

sudo systemctl daemon-reload
sudo systemctl cat vpnclient.service
systemctl show -p ExecStart vpnclient.service
systemd-analyze verify /etc/systemd/system/vpnclient.service
sudo systemctl restart vpnclient.service
systemctl status vpnclient.service --no-pager

systemctl show निर्णायक आउटपुट है, क्योंकि यह वह argv छापता है जिसे systemd ने वास्तव में विश्लेषित किया, किसी भी drop-in सहित। systemd-analyze verify रीस्टार्ट से पहले गुम या गैर-निष्पादन योग्य पथ पकड़ लेता है, और रीस्टार्ट के बाद status कमांड बताता है कि प्रक्रिया अब मौजूद है या नहीं।

यदि unit शुरू होकर फिर विफल हो जाए, तो आप EXEC परिवार छोड़कर क्लाइंट के अपने त्रुटि प्रबंधन में आ गए हैं। जर्नल में क्लाइंट का संदेश खोजें, कोई और execve विफलता नहीं। unit का diff और sudoers का diff परिवर्तन रिकॉर्ड में साथ रखें, ताकि अगला अपग्रेड विभाजित पथ को चुपचाप फिर न ले आए।

संबंधित लेख