वर्तमान निष्पादन योग्य पथ को बिना / proc / self / exe के ढूंढना


190

यह मुझे लगता है कि लिनक्स के साथ / proc / self / exe यह आसान है। लेकिन मैं जानना चाहता हूं कि क्या क्रॉस-प्लेटफ़ॉर्म इंटरफेस के साथ सी / सी ++ में वर्तमान एप्लिकेशन की निर्देशिका को खोजने का एक सुविधाजनक तरीका है। मैंने कुछ प्रोजेक्ट्स को argv [0] के साथ घूमते हुए देखा है, लेकिन यह पूरी तरह से विश्वसनीय नहीं लगता है।

यदि आपको कभी मैक ओएस एक्स का समर्थन करना था, जिसमें आपके पास / proc / नहीं है, तो आपने क्या किया होगा? प्लेटफ़ॉर्म-विशिष्ट कोड (NSBundle, उदाहरण के लिए) को अलग करने के लिए #ifdefs का उपयोग करें? या एर्गव [0], $ पथ और व्हाट्सन से निष्पादन योग्य मार्ग को कम करने की कोशिश करें, जो कि किनारे के मामलों में कीड़े ढूंढ रहे हैं?



मैं गुगली: मेरी ps -o comm। मुझे यहाँ लाया गया है: "/proc/pid/path/a.out"
बेसिन

IMHO गर्वआउट का उत्तर शीर्ष पर होना चाहिए, क्योंकि यह "क्रॉस-प्लेटफ़ॉर्म इंटरफेस" आवश्यकता को सही ढंग से संबोधित करता है और एकीकृत करना बहुत आसान है।
स्टीफन गौरिचोन

जवाबों:


348

कुछ ओएस-विशिष्ट इंटरफेस:

पोर्टेबल (लेकिन कम विश्वसनीय) विधि का उपयोग करना है argv[0]। हालाँकि यह कॉलिंग प्रोग्राम द्वारा किसी भी चीज़ पर सेट किया जा सकता है, लेकिन कन्वेंशन द्वारा इसे एक्जीक्यूटेबल या तो उपयोग किए जाने वाले एक नाम के लिए निर्धारित किया जाता है $PATH

बैश और क्ष सहित कुछ गोले, निष्पादन से पहले पर्यावरण चर को " _" निष्पादन योग्य के पूर्ण पथ पर सेट करते हैं । उस स्थिति में आप getenv("_")इसे प्राप्त करने के लिए उपयोग कर सकते हैं । हालांकि यह अविश्वसनीय है क्योंकि सभी गोले ऐसा नहीं करते हैं, और इसे किसी भी चीज़ पर सेट किया जा सकता है या इसे एक मूल प्रक्रिया से छोड़ा जा सकता है, जो आपके प्रोग्राम को निष्पादित करने से पहले इसे नहीं बदला था।


3
और यह भी ध्यान दें कि _NSGetExecutablePath () सीमलिंक का पालन नहीं करता है।
बजे

1
NetBSD: readlink / proc / curproc / exe DragonFly BSD: readlink / proc / curproc / फ़ाइल
naruse

6
सोलारिस char exepath[MAXPATHLEN]; sprintf(exepath, "/proc/%d/path/a.out", getpid()); readlink(exepath, exepath, sizeof(exepath));:; कि से अलग है getexecname(), जिनमें से समतुल्य है - pargs -x <PID> | grep AT_SUN_EXECNAME...
FrankH।

4
"QDesktopServices :: StorageLocation (QDesktopServices :: DataLocation)" यह निष्पादन योग्य पथ नहीं है, यह प्रति उपयोगकर्ता निर्देशिका का पथनाम है जहां डेटा संग्रहीत किया जाना चाहिए।

2
OpenBSD केवल एक ही है जहाँ आप अभी भी 2017 में नहीं कर सकते। आपको PATH और argv का उपयोग करना है [0] तरीका है
Lothar

27

के उपयोग /proc/self/exeगैर पोर्टेबल और अविश्वसनीय है। मेरे उबंटू 12.04 सिस्टम पर, आपको सिमलिंक पढ़ने / पालन करने के लिए रूट होना चाहिए। यह बूस्ट का उदाहरण देगा और शायद whereami()पोस्ट किए गए समाधान विफल हो जाएंगे ।

यह पद बहुत लंबा है, लेकिन वास्तविक मुद्दों पर चर्चा करता है और कोड प्रस्तुत करता है जो वास्तव में एक परीक्षण सूट के खिलाफ सत्यापन के साथ काम करता है।

आपके प्रोग्राम को खोजने का सबसे अच्छा तरीका सिस्टम द्वारा उपयोग किए जाने वाले चरणों को वापस करना है। यह argv[0]फ़ाइल सिस्टम रूट, pwd, पथ वातावरण और सिम्बलिंक्स और pathname विहितीकरण पर विचार करके हल किया गया है। यह स्मृति से है, लेकिन मैंने अतीत में इसे सफलतापूर्वक किया है और विभिन्न स्थितियों में इसका परीक्षण किया है। यह काम करने की गारंटी नहीं है, लेकिन अगर यह आपके पास बहुत बड़ी समस्याएं नहीं है और यह चर्चा की गई अन्य विधियों की तुलना में अधिक विश्वसनीय है। एक यूनिक्स सुसंगत प्रणाली पर स्थितियां हैं जिनमें से निपटने का उचित प्रबंध हैargv[0]अपने कार्यक्रम के लिए नहीं मिलेगा, लेकिन फिर आप एक प्रमाणित रूप से टूटे हुए वातावरण में निष्पादित कर रहे हैं। यह 1970 के आसपास से सभी यूनिक्स व्युत्पन्न प्रणालियों और यहां तक ​​कि कुछ गैर-यूनिक्स व्युत्पन्न प्रणालियों के लिए भी काफी पोर्टेबल है क्योंकि यह मूल रूप से libc () मानक कार्यक्षमता और मानक कमांड लाइन कार्यक्षमता पर निर्भर है। यह लिनक्स (सभी संस्करणों), एंड्रॉइड, क्रोम ओएस, मिनिक्स, मूल बेल लैब्स यूनिक्स, फ्रीबीएसडी, नेटबीएसडी, ओपनबीएसडी, बीएसडी एक्सएक्सएक्स, सनोस, सोलारिस, एसवाईएसवी, एचपीयूएक्स, कॉन्सेंट्रिक्स, एससीओ, डार्विन, एआईएक्स, ओएस एक्स पर काम करना चाहिए। Nextstep, आदि और थोड़ा संशोधन के साथ शायद VMS, VM / CMS, DOS / Windows, ReactOS, OS / 2, आदि। यदि कोई प्रोग्राम सीधे GUI वातावरण से लॉन्च किया गया था, तो इसे argv[0]एक निरपेक्ष पथ पर सेट किया जाना चाहिए ।

यह समझें कि हर यूनिक्स संगत ऑपरेटिंग सिस्टम पर लगभग हर शेल जो कभी जारी किया गया है, मूल रूप से प्रोग्राम उसी तरह पाता है और ऑपरेटिंग वातावरण को लगभग उसी तरह सेट करता है (कुछ वैकल्पिक एक्स्ट्रा के साथ)। और किसी प्रोग्राम को लॉन्च करने वाले किसी अन्य प्रोग्राम से उस प्रोग्राम के लिए एक ही वातावरण (argv, एन्वायरनमेंट स्ट्रिंग्स, इत्यादि) बनाने की उम्मीद की जाती है, जैसे कि इसे किसी वैकल्पिक एक्स्ट्रा के साथ शेल से चलाया गया हो। एक प्रोग्राम या उपयोगकर्ता एक पर्यावरण को सेटअप कर सकता है जो इस सम्मेलन से अन्य अधीनस्थ कार्यक्रमों के लिए विचलित होता है जो इसे लॉन्च करता है लेकिन अगर ऐसा होता है, तो यह एक बग है और कार्यक्रम को कोई उचित उम्मीद नहीं है कि अधीनस्थ कार्यक्रम या इसके अधीनस्थ सही ढंग से काम करेंगे।

शामिल करने के संभावित मान argv[0]:

  • /path/to/executable - पूर्ण पथ
  • ../bin/executable - pwd के सापेक्ष
  • bin/executable - pwd के सापेक्ष
  • ./foo - pwd के सापेक्ष
  • executable - बेसन, पथ में खोजें
  • bin//executable - पीडब्ल्यूडी के सापेक्ष, गैर-विहित
  • src/../bin/executable - पीडब्ल्यूडी, गैर-विहित, बैकट्रैकिंग के सापेक्ष
  • bin/./echoargc - पीडब्ल्यूडी के सापेक्ष, गैर-विहित

मान जिन्हें आपको नहीं देखना चाहिए:

  • ~/bin/executable - अपने कार्यक्रम को चलाने से पहले फिर से लिखा।
  • ~user/bin/executable - अपने कार्यक्रम को चलाने से पहले फिर से लिखा
  • alias - अपने कार्यक्रम को चलाने से पहले फिर से लिखा
  • $shellvariable - अपने कार्यक्रम को चलाने से पहले फिर से लिखा
  • *foo* - वाइल्डकार्ड, आपके प्रोग्राम के चलने से पहले फिर से लिखे, बहुत उपयोगी नहीं है
  • ?foo? - वाइल्डकार्ड, आपके प्रोग्राम के चलने से पहले फिर से लिखे, बहुत उपयोगी नहीं है

इसके अलावा, इनमें गैर-विहित पथ नाम और प्रतीकात्मक लिंक की कई परतें हो सकती हैं। कुछ मामलों में, एक ही कार्यक्रम के लिए कई हार्ड लिंक हो सकते हैं। उदाहरण के लिए, /bin/ls, /bin/ps, /bin/chmod, /bin/rm, आदि के लिए हार्ड लिंक हो सकता है /bin/busybox

अपने आप को खोजने के लिए, नीचे दिए गए चरणों का पालन करें:

  • Pwd, PATH, और argv [0] को अपने प्रोग्राम में प्रवेश करने पर (या अपने पुस्तकालय के आरंभ में) सहेजें क्योंकि वे बाद में बदल सकते हैं।

  • वैकल्पिक: विशेष रूप से गैर-यूनिक्स प्रणालियों के लिए, अलग-अलग लेकिन पथनाम होस्ट / उपयोगकर्ता / ड्राइव उपसर्ग भाग को न छोड़ें, यदि मौजूद हो; वह हिस्सा जो अक्सर एक बृहदान्त्र से पहले या एक प्रारंभिक "//" का अनुसरण करता है।

  • यदि argv[0]एक निरपेक्ष मार्ग है, तो उसे एक प्रारंभिक बिंदु के रूप में उपयोग करें। एक निरपेक्ष पथ शायद "/" से शुरू होता है, लेकिन कुछ गैर-यूनिक्स प्रणालियों पर यह "\" या एक ड्राइव लेटर या नाम उपसर्ग के बाद शुरू हो सकता है।

  • यदि argv[0]कोई रिश्तेदार पथ है ("/" या "\" शामिल है, लेकिन इसके साथ शुरू नहीं होता है, जैसे "../../bin/foo", तो pwd + "/" + argv [0] को संयोजित करें (उपयोग करें) वर्तमान कार्यशील निर्देशिका जब कार्यक्रम शुरू हुआ, वर्तमान नहीं)।

  • इसके अलावा अगर argv [0] एक सादा बेसन (कोई स्लैश नहीं) है, तो इसे बारी-बारी से PATH पर्यावरण चर में प्रत्येक प्रविष्टि के साथ मिलाएं और उन लोगों को आज़माएं और पहले वाले का उपयोग करें जो सफल होता है।

  • वैकल्पिक: एल्से बहुत ही विशिष्ट प्लेटफ़ॉर्म /proc/self/exe, /proc/curproc/file(BSD) और (char *)getauxval(AT_EXECFN), और dlgetname(...)यदि मौजूद हो, कोशिश करें। argv[0]यदि आप उपलब्ध हैं और आप अनुमति के मुद्दों का सामना नहीं करते हैं, तो आप इन- आधारित विधियों की कोशिश कर सकते हैं। कुछ हद तक अप्रत्याशित घटना में (जब आप सभी प्रणालियों के सभी संस्करणों पर विचार करते हैं) कि वे मौजूद हैं और असफल नहीं होते हैं, तो वे अधिक आधिकारिक हो सकते हैं।

  • वैकल्पिक: कमांड लाइन पैरामीटर का उपयोग करके पारित किए गए पथ के नाम की जांच करें।

  • वैकल्पिक: यदि आपके आवरण स्क्रिप्ट द्वारा स्पष्ट रूप से पारित कर दिया गया है, तो पर्यावरण में एक मार्गनाम के लिए जाँच करें।

  • वैकल्पिक: अंतिम उपाय के रूप में पर्यावरण चर "_" का प्रयास करें। यह पूरी तरह से एक अलग कार्यक्रम को इंगित कर सकता है, जैसे कि उपयोगकर्ता शेल।

  • सीमलिंक का समाधान करें, कई परतें हो सकती हैं। अनंत छोरों की संभावना है, हालांकि यदि वे आपके कार्यक्रम में मौजूद हैं, तो संभवत: आह्वान नहीं किया जाएगा।

  • "/Foo/../bar/" से "/ बार /" जैसे सब्सट्रिंग को हल करके कैनन का नाम दें। ध्यान दें कि यह संभावित रूप से अर्थ बदल सकता है यदि आप एक नेटवर्क माउंट बिंदु को पार करते हैं, तो केनोनेज़ेशन हमेशा एक अच्छी बात नहीं है। नेटवर्क सर्वर पर, सिम्लिंक में ".." का उपयोग क्लाइंट के बजाय सर्वर संदर्भ में किसी अन्य फ़ाइल के पथ को पार करने के लिए किया जा सकता है। इस मामले में, आप संभवतः ग्राहक संदर्भ चाहते हैं ताकि विहितकरण ठीक हो। साथ ही "/ //" को "/" और "//" को "/" जैसे पैटर्न में परिवर्तित करें। शेल में, readlink --canonicalizeकई सीमलिंक और कैनोनिकलाइज़ नाम को हल करेगा। चेस समान हो सकता है लेकिन स्थापित नहीं है। realpath()या canonicalize_file_name(), यदि मौजूद हो, तो मदद कर सकता है।

यदि realpath()संकलन समय पर मौजूद नहीं है, तो आप एक लाइसेंस प्राप्त लायसेंसधारी लायब्रेरी वितरण से एक प्रति उधार ले सकते हैं, और इसे पहिया को फिर से स्थापित करने के बजाय अपने आप में संकलित कर सकते हैं। यदि आप PATH_MAX से कम बफर का उपयोग कर रहे हैं, तो संभावित बफर ओवरफ़्लो को पास करें (साइज़ोफ़ आउटपुट बफर में पास करें, strncpy () बनाम strcpy ()) के बारे में सोचें। यदि यह मौजूद है तो परीक्षण के बजाय इसका नाम बदलकर निजी कॉपी का उपयोग करना आसान हो सकता है। Android / darwin / bsd से अनुज्ञापत्र लाइसेंस प्रति: https://android.googlesource.com/platform/bionic/+/f077784/libc/upstream-freebsd/lib/libc/stdlib/realpath.c

इस बात से अवगत रहें कि कई प्रयास सफल या आंशिक रूप से सफल हो सकते हैं और वे सभी एक ही निष्पादन योग्य नहीं हो सकते हैं, इसलिए अपने निष्पादन योग्य को सत्यापित करने पर विचार करें; हालाँकि, आपने पढ़ने की अनुमति नहीं दी है - यदि आप इसे नहीं पढ़ सकते हैं, तो इसे असफलता न मानें। या अपने निष्पादन योग्य निकटता में कुछ सत्यापित करें जैसे कि "../lib/" निर्देशिका जिसे आप ढूंढने का प्रयास कर रहे हैं। आपके पास कई संस्करण, पैकेज्ड और स्थानीय रूप से संकलित संस्करण, स्थानीय और नेटवर्क संस्करण, और स्थानीय और यूएसबी-ड्राइव पोर्टेबल संस्करण, आदि हो सकते हैं और एक छोटी सी संभावना है कि आपको पता लगाने के विभिन्न तरीकों से दो असंगत परिणाम प्राप्त हो सकते हैं। और "_" केवल गलत प्रोग्राम को इंगित कर सकता है।

एक प्रोग्राम का उपयोग करके execveजानबूझकर प्रोग्राम argv[0]को लोड करने के लिए उपयोग किए जाने वाले वास्तविक पथ के साथ असंगत होने के लिए सेट किया जा सकता है और PATH, "_", pwd इत्यादि को लोड करने के लिए उपयोग किया जाता है, हालांकि आमतौर पर ऐसा करने का कोई कारण नहीं है; लेकिन इसका सुरक्षा निहितार्थ हो सकता है यदि आपके पास असुरक्षित कोड है जो इस तथ्य को नजरअंदाज करता है कि आपके निष्पादन का वातावरण सहित विभिन्न तरीकों से बदला जा सकता है, लेकिन यह सीमित नहीं है, इस एक के लिए (चुरोट, फ़्यूज़ फाइल सिस्टम, हार्ड लिंक, आदि) संभव है। PATH को सेट करने के लिए शेल कमांड के लिए, लेकिन इसे निर्यात करने में विफल।

गैर-यूनिक्स प्रणालियों के लिए आपको कोड की आवश्यकता नहीं है, लेकिन कुछ विशिष्टताओं के बारे में पता होना एक अच्छा विचार होगा, ताकि आप कोड को इस तरह से लिख सकें कि बाद में पोर्ट करना किसी के लिए उतना कठिन न हो। । विदित हो कि कुछ सिस्टम (DEC VMS, DOS, URL इत्यादि) में ड्राइव नाम या अन्य उपसर्ग हो सकते हैं जो कि "C: \", "sys $ ड्राइव: [foo] बार", और "फ़ाइल" : /// foo / बार / baz "। पुराने DEC VMS सिस्टम पथ के निर्देशिका भाग को संलग्न करने के लिए "[" और "]" का उपयोग करते हैं, हालांकि यदि आपका कार्यक्रम POSIX वातावरण में संकलित किया गया है तो यह बदल सकता है। कुछ सिस्टम, जैसे VMS, का फ़ाइल संस्करण हो सकता है (अंत में अर्धविराम द्वारा अलग किया गया)। कुछ सिस्टम "// ड्राइव / पाथ / टू / फाइल" या "यूजर @ होस्ट: / पाथ / टू / फाइल" (scp कमांड) या "फाइल:" के रूप में दो लगातार स्लैश का उपयोग करते हैं। (रिक्त स्थान के साथ सीमांकित) और "पेट" कॉलोनों के साथ सीमांकित किया गया है, लेकिन आपके कार्यक्रम को पेटीएम प्राप्त करना चाहिए ताकि आपको पथ के लिए चिंता करने की आवश्यकता न हो। डॉस और कुछ अन्य प्रणालियों में एक पथ उपसर्ग के साथ शुरू होने वाले सापेक्ष पथ हो सकते हैं। C: foo.exe ड्राइव C पर वर्तमान निर्देशिका में foo.exe को संदर्भित करता है, इसलिए आपको C पर वर्तमान निर्देशिका देखने की आवश्यकता है: और pwd के लिए इसका उपयोग करें। (रिक्त स्थान के साथ सीमांकित) और "पेट" कॉलोनों के साथ सीमांकित किया गया है, लेकिन आपके कार्यक्रम को पेटीएम प्राप्त करना चाहिए ताकि आपको पथ के लिए चिंता करने की आवश्यकता न हो। डॉस और कुछ अन्य प्रणालियों में एक पथ उपसर्ग के साथ शुरू होने वाले सापेक्ष पथ हो सकते हैं। C: foo.exe ड्राइव C पर वर्तमान निर्देशिका में foo.exe को संदर्भित करता है, इसलिए आपको C पर वर्तमान निर्देशिका देखने की आवश्यकता है: और pwd के लिए इसका उपयोग करें।

मेरे सिस्टम पर सहानुभूति और आवरण का एक उदाहरण:

/usr/bin/google-chrome is symlink to
/etc/alternatives/google-chrome  which is symlink to
/usr/bin/google-chrome-stable which is symlink to
/opt/google/chrome/google-chrome which is a bash script which runs
/opt/google/chome/chrome

ध्यान दें कि उपयोगकर्ता बिल HP के एक प्रोग्राम के ऊपर एक लिंक पोस्ट करता है जो तीन बुनियादी मामलों को संभालता है argv[0]। हालांकि इसमें कुछ बदलावों की जरूरत है:

  • यह सब के पुनर्लेखन के लिए जरूरी होगा strcat()और strcpy()उपयोग करने के लिए strncat()और strncpy()। भले ही चर लंबाई PATHMAX की घोषित की गई हो, लंबाई का इनपुट मान PATHMAX-1 प्लस समतल स्ट्रैंथ की लंबाई है> PATHMAX और लंबाई PATHMAX का एक इनपुट मान है।
  • इसे केवल एक लाइब्रेरी फंक्शन के रूप में फिर से लिखने की आवश्यकता है, न कि केवल परिणामों को प्रिंट करने के लिए।
    • यह नामों को कैनोनिकलाइज़ करने में विफल रहता है (ऊपर दिए गए लिंक से संबंधित realpath कोड का उपयोग करें)
    • यह प्रतीकात्मक लिंक को हल करने में विफल रहता है (realpath कोड का उपयोग करें)

इसलिए, यदि आप एचपी कोड और रियलपथ कोड दोनों को मिलाते हैं और बफर ओवरफ्लो के लिए प्रतिरोधी होने के लिए दोनों को ठीक करते हैं, तो आपके पास कुछ ऐसा होना चाहिए जो ठीक से व्याख्या कर सके argv[0]

निम्नलिखित argv[0]उबंटू 12.04 पर एक ही कार्यक्रम को लागू करने के विभिन्न तरीकों के वास्तविक मूल्यों को दर्शाता है । और हां, कार्यक्रम को गलती से गूंज का नाम दिया गया था। यह साफ नकल के लिए एक स्क्रिप्ट का उपयोग करके किया गया था, लेकिन इसे मैन्युअल रूप से शेल में करना एक ही परिणाम प्राप्त करता है (जब तक आप स्पष्ट रूप से उन्हें सक्षम नहीं करते हैं, तब तक एलियास स्क्रिप्ट में काम नहीं करता है)।

cat ~/src/echoargc.c
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
main(int argc, char **argv)
{
  printf("  argv[0]=\"%s\"\n", argv[0]);
  sleep(1);  /* in case run from desktop */
}
tcc -o ~/bin/echoargc ~/src/echoargc.c 
cd ~
/home/whitis/bin/echoargc
  argv[0]="/home/whitis/bin/echoargc"
echoargc
  argv[0]="echoargc"
bin/echoargc
  argv[0]="bin/echoargc"
bin//echoargc
  argv[0]="bin//echoargc"
bin/./echoargc
  argv[0]="bin/./echoargc"
src/../bin/echoargc
  argv[0]="src/../bin/echoargc"
cd ~/bin
*echo*
  argv[0]="echoargc"
e?hoargc
  argv[0]="echoargc"
./echoargc
  argv[0]="./echoargc"
cd ~/src
../bin/echoargc
  argv[0]="../bin/echoargc"
cd ~/junk
~/bin/echoargc
  argv[0]="/home/whitis/bin/echoargc"
~whitis/bin/echoargc
  argv[0]="/home/whitis/bin/echoargc"
alias echoit=~/bin/echoargc
echoit
  argv[0]="/home/whitis/bin/echoargc"
echoarg=~/bin/echoargc
$echoarg
  argv[0]="/home/whitis/bin/echoargc"
ln -s ~/bin/echoargc junk1
./junk1
  argv[0]="./junk1"
ln -s /home/whitis/bin/echoargc junk2
./junk2
  argv[0]="./junk2"
ln -s junk1 junk3
./junk3
  argv[0]="./junk3"


gnome-desktop-item-edit --create-new ~/Desktop
# interactive, create desktop link, then click on it
  argv[0]="/home/whitis/bin/echoargc"
# interactive, right click on gnome application menu, pick edit menus
# add menu item for echoargc, then run it from gnome menu
 argv[0]="/home/whitis/bin/echoargc"

 cat ./testargcscript 2>&1 | sed -e 's/^/    /g'
#!/bin/bash
# echoargc is in ~/bin/echoargc
# bin is in path
shopt -s expand_aliases
set -v
cat ~/src/echoargc.c
tcc -o ~/bin/echoargc ~/src/echoargc.c 
cd ~
/home/whitis/bin/echoargc
echoargc
bin/echoargc
bin//echoargc
bin/./echoargc
src/../bin/echoargc
cd ~/bin
*echo*
e?hoargc
./echoargc
cd ~/src
../bin/echoargc
cd ~/junk
~/bin/echoargc
~whitis/bin/echoargc
alias echoit=~/bin/echoargc
echoit
echoarg=~/bin/echoargc
$echoarg
ln -s ~/bin/echoargc junk1
./junk1
ln -s /home/whitis/bin/echoargc junk2
./junk2
ln -s junk1 junk3
./junk3

ये उदाहरण बताते हैं कि इस पोस्ट में वर्णित तकनीकों को विस्तृत परिस्थितियों में काम करना चाहिए और कुछ कदम क्यों आवश्यक हैं।

संपादित करें: अब, जो प्रोग्राम प्रिंट करता है वह argv [0] को वास्तव में खुद को खोजने के लिए अपडेट किया गया है।

// Copyright 2015 by Mark Whitis.  License=MIT style
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <limits.h>
#include <assert.h>
#include <string.h>
#include <errno.h>

// "look deep into yourself, Clarice"  -- Hanibal Lector
char findyourself_save_pwd[PATH_MAX];
char findyourself_save_argv0[PATH_MAX];
char findyourself_save_path[PATH_MAX];
char findyourself_path_separator='/';
char findyourself_path_separator_as_string[2]="/";
char findyourself_path_list_separator[8]=":";  // could be ":; "
char findyourself_debug=0;

int findyourself_initialized=0;

void findyourself_init(char *argv0)
{

  getcwd(findyourself_save_pwd, sizeof(findyourself_save_pwd));

  strncpy(findyourself_save_argv0, argv0, sizeof(findyourself_save_argv0));
  findyourself_save_argv0[sizeof(findyourself_save_argv0)-1]=0;

  strncpy(findyourself_save_path, getenv("PATH"), sizeof(findyourself_save_path));
  findyourself_save_path[sizeof(findyourself_save_path)-1]=0;
  findyourself_initialized=1;
}


int find_yourself(char *result, size_t size_of_result)
{
  char newpath[PATH_MAX+256];
  char newpath2[PATH_MAX+256];

  assert(findyourself_initialized);
  result[0]=0;

  if(findyourself_save_argv0[0]==findyourself_path_separator) {
    if(findyourself_debug) printf("  absolute path\n");
     realpath(findyourself_save_argv0, newpath);
     if(findyourself_debug) printf("  newpath=\"%s\"\n", newpath);
     if(!access(newpath, F_OK)) {
        strncpy(result, newpath, size_of_result);
        result[size_of_result-1]=0;
        return(0);
     } else {
    perror("access failed 1");
      }
  } else if( strchr(findyourself_save_argv0, findyourself_path_separator )) {
    if(findyourself_debug) printf("  relative path to pwd\n");
    strncpy(newpath2, findyourself_save_pwd, sizeof(newpath2));
    newpath2[sizeof(newpath2)-1]=0;
    strncat(newpath2, findyourself_path_separator_as_string, sizeof(newpath2));
    newpath2[sizeof(newpath2)-1]=0;
    strncat(newpath2, findyourself_save_argv0, sizeof(newpath2));
    newpath2[sizeof(newpath2)-1]=0;
    realpath(newpath2, newpath);
    if(findyourself_debug) printf("  newpath=\"%s\"\n", newpath);
    if(!access(newpath, F_OK)) {
        strncpy(result, newpath, size_of_result);
        result[size_of_result-1]=0;
        return(0);
     } else {
    perror("access failed 2");
      }
  } else {
    if(findyourself_debug) printf("  searching $PATH\n");
    char *saveptr;
    char *pathitem;
    for(pathitem=strtok_r(findyourself_save_path, findyourself_path_list_separator,  &saveptr); pathitem; pathitem=strtok_r(NULL, findyourself_path_list_separator, &saveptr) ) {
       if(findyourself_debug>=2) printf("pathitem=\"%s\"\n", pathitem);
       strncpy(newpath2, pathitem, sizeof(newpath2));
       newpath2[sizeof(newpath2)-1]=0;
       strncat(newpath2, findyourself_path_separator_as_string, sizeof(newpath2));
       newpath2[sizeof(newpath2)-1]=0;
       strncat(newpath2, findyourself_save_argv0, sizeof(newpath2));
       newpath2[sizeof(newpath2)-1]=0;
       realpath(newpath2, newpath);
       if(findyourself_debug) printf("  newpath=\"%s\"\n", newpath);
      if(!access(newpath, F_OK)) {
          strncpy(result, newpath, size_of_result);
          result[size_of_result-1]=0;
          return(0);
      } 
    } // end for
    perror("access failed 3");

  } // end else
  // if we get here, we have tried all three methods on argv[0] and still haven't succeeded.   Include fallback methods here.
  return(1);
}

main(int argc, char **argv)
{
  findyourself_init(argv[0]);

  char newpath[PATH_MAX];
  printf("  argv[0]=\"%s\"\n", argv[0]);
  realpath(argv[0], newpath);
  if(strcmp(argv[0],newpath)) { printf("  realpath=\"%s\"\n", newpath); }
  find_yourself(newpath, sizeof(newpath));
  if(1 || strcmp(argv[0],newpath)) { printf("  findyourself=\"%s\"\n", newpath); }
  sleep(1);  /* in case run from desktop */
}

और यहां आउटपुट है जो दर्शाता है कि पिछले परीक्षणों में से प्रत्येक में यह वास्तव में खुद को पाया था।

tcc -o ~/bin/echoargc ~/src/echoargc.c 
cd ~
/home/whitis/bin/echoargc
  argv[0]="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
echoargc
  argv[0]="echoargc"
  realpath="/home/whitis/echoargc"
  findyourself="/home/whitis/bin/echoargc"
bin/echoargc
  argv[0]="bin/echoargc"
  realpath="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
bin//echoargc
  argv[0]="bin//echoargc"
  realpath="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
bin/./echoargc
  argv[0]="bin/./echoargc"
  realpath="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
src/../bin/echoargc
  argv[0]="src/../bin/echoargc"
  realpath="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
cd ~/bin
*echo*
  argv[0]="echoargc"
  realpath="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
e?hoargc
  argv[0]="echoargc"
  realpath="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
./echoargc
  argv[0]="./echoargc"
  realpath="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
cd ~/src
../bin/echoargc
  argv[0]="../bin/echoargc"
  realpath="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
cd ~/junk
~/bin/echoargc
  argv[0]="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
~whitis/bin/echoargc
  argv[0]="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
alias echoit=~/bin/echoargc
echoit
  argv[0]="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
echoarg=~/bin/echoargc
$echoarg
  argv[0]="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
rm junk1 junk2 junk3
ln -s ~/bin/echoargc junk1
./junk1
  argv[0]="./junk1"
  realpath="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
ln -s /home/whitis/bin/echoargc junk2
./junk2
  argv[0]="./junk2"
  realpath="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"
ln -s junk1 junk3
./junk3
  argv[0]="./junk3"
  realpath="/home/whitis/bin/echoargc"
  findyourself="/home/whitis/bin/echoargc"

ऊपर वर्णित दो जीयूआई लॉन्च भी प्रोग्राम को सही ढंग से ढूंढते हैं।

एक संभावित नुकसान है। access()समारोह अनुमतियाँ चला जाता है, तो कार्यक्रम परीक्षण से पहले setuid है। यदि ऐसी स्थिति है जहां कार्यक्रम को एक उन्नत उपयोगकर्ता के रूप में पाया जा सकता है, लेकिन एक नियमित उपयोगकर्ता के रूप में नहीं, तो ऐसी स्थिति हो सकती है जहां ये परीक्षण विफल होंगे, हालांकि यह संभावना नहीं है कि कार्यक्रम वास्तव में उन परिस्थितियों में निष्पादित किया जा सकता है। इसके बजाय यूरोपिडास () का उपयोग किया जा सकता है। हालाँकि, यह संभव है कि यह वास्तविक उपयोगकर्ता की तुलना में पहले पथ पर एक दुर्गम कार्यक्रम पा सकता है।


1
आपने इसमें बहुत प्रयास किए - अच्छा काम किया। दुर्भाग्य से, न तो strncpy()और न ही (विशेष रूप से) strncat()कोड में सुरक्षित रूप से उपयोग किया जाता है। strncpy()शून्य समाप्ति की गारंटी नहीं देता है; यदि स्रोत स्ट्रिंग लक्ष्य स्थान से अधिक लंबी है, तो स्ट्रिंग को समाप्त नहीं किया गया है। strncat()उपयोग करने के लिए बहुत कठिन है; strncat(target, source, sizeof(target))गलत है (भले ही targetशुरू करने के लिए एक खाली स्ट्रिंग है) यदि sourceलक्ष्य से अधिक लंबा है। लंबाई पात्रों की संख्या है जो सुरक्षित रूप से अनुगामी नल को छोड़कर लक्ष्य के साथ जोड़ सकते हैं, इसलिए sizeof(target)-1अधिकतम है।
जोनाथन लेफ़लर

4
Strncpy कोड सही है, आपके द्वारा मेरे द्वारा उपयोग की जाने वाली विधि के विपरीत। मेरा सुझाव है कि आप कोड को अधिक ध्यान से पढ़ें। यह न तो बफर्स ​​को ओवरफ्लो करता है और न ही उन्हें बेपर्दा करता है। Strncpy () / stncat () का प्रत्येक उपयोग साइज़ोफ़ (बफर) की प्रतिलिपि द्वारा सीमित है, जो मान्य है, और फिर बफर का अंतिम वर्ण बफर के अंतिम वर्ण को लिखकर एक शून्य से भर जाता है। strncat (), हालांकि, एक गिनती के रूप में गलत तरीके से आकार पैरामीटर का उपयोग करता है और इस तथ्य के कारण अतिप्रवाह हो सकता है कि यह बफर ओवरफ्लो हमलों से पहले होता है।
व्हिटिस

"sudo apt-get install libbsd0 libbsd-dev", फिर s / strncat / strlcat /
whitis

1
PATH_MAX का उपयोग न करें। इसने 30 साल पहले काम करना बंद कर दिया था, हमेशा मॉलोक का उपयोग करें।
लोथर

इसके अलावा यदि आप एक init कॉल का उपयोग करते हैं। पूरी तरह से निर्वासन पर और न केवल एक भाग के लिए पथ को हल करें और फिर बाद में कॉल करें। यदि आप रिज़ॉल्वर में रियलपैथ का उपयोग करते हैं तो यहां कोई आलसी मूल्यांकन संभव नहीं है। साथ में अन्य इरोर के साथ बस सबसे खराब कोड है जिसे मैंने लंबे जवाब में स्टैकओवरफ्लो पर देखा है।
लोथर

13

की जाँच करें whereami ग्रेगरी Pakosz (जो सिर्फ एक सी फ़ाइल है) से पुस्तकालय; यह आपको विभिन्न प्लेटफार्मों पर वर्तमान निष्पादन योग्य के लिए पूर्ण पथ प्राप्त करने की अनुमति देता है। वर्तमान में, यह यहाँ गितुब पर रेपो के रूप में उपलब्ध है


8

लिनक्स पर एक विकल्प /proc/self/exeया तो उपयोग करने के लिए या argv[0]ELF दुभाषिया द्वारा पारित की गई जानकारी का उपयोग कर रहा है, जैसे glibc द्वारा उपलब्ध कराया गया है:

#include <stdio.h>
#include <sys/auxv.h>

int main(int argc, char **argv)
{
    printf("%s\n", (char *)getauxval(AT_EXECFN));
    return(0);
}

ध्यान दें कि getauxvalयह एक शानदार विस्तार है, और आपको मजबूत होना चाहिए ताकि आपको यह देखना चाहिए कि यह वापस नहीं आता है NULL(यह दर्शाता है कि ELF दुभाषिया ने AT_EXECFNपैरामीटर प्रदान नहीं किया है), लेकिन मुझे नहीं लगता कि यह वास्तव में लिनक्स पर एक समस्या है।


मुझे यह पसंद है क्योंकि यह सरल है और glibc Gtk + के साथ शामिल है (वैसे भी जो मैं उपयोग कर रहा हूं)।
कॉलिन कीनन

4

अगर आपको कभी मैक ओएस एक्स को सपोर्ट करना पड़े, जिसके पास / proc / नहीं है, तो आपने क्या किया होगा? प्लेटफ़ॉर्म-विशिष्ट कोड (NSBundle, उदाहरण के लिए) को अलग करने के लिए #ifdefs का उपयोग करें?

हां, विशिष्ट मंच के साथ #ifdefsपारंपरिक कोड को अलग-अलग किया जाता है।

एक अन्य दृष्टिकोण यह होगा कि स्वच्छ-रहित #ifdefहेडर के साथ विच में फंक्शन डिक्लेरेशन हो और कार्यान्वयन को प्लेटफ़ॉर्म के विशिष्ट स्रोत फ़ाइलों में रखा जाए। उदाहरण के लिए, देखें कि कैसे पोको सी ++ लाइब्रेरी अपने पर्यावरण वर्ग के लिए कुछ ऐसा ही करती है ।


4

प्लेटफार्मों भर में यह काम मज़बूती से करने के लिए #ifdef कथनों का उपयोग करने की आवश्यकता होती है।

नीचे दिए गए कोड से विंडोज, लिनक्स, मैकओएस, सोलारिस या फ्रीबीएसडी में निष्पादन योग्य मार्ग का पता चलता है (हालांकि फ्रीबीएसडी अप्राप्त है)। यह कोड को सरल बनाने के लिए बूस्ट = = 1.55.0 का उपयोग करता है लेकिन यदि आप चाहें तो इसे हटाना काफी आसान है। जैसे ओएस और कंपाइलर की आवश्यकता होती है वैसे ही _MSC_VER और __linux जैसे डिफाइन का उपयोग करें।

#include <string>
#include <boost/predef/os.h>

#if (BOOST_OS_WINDOWS)
#  include <stdlib.h>
#elif (BOOST_OS_SOLARIS)
#  include <stdlib.h>
#  include <limits.h>
#elif (BOOST_OS_LINUX)
#  include <unistd.h>
#  include <limits.h>
#elif (BOOST_OS_MACOS)
#  include <mach-o/dyld.h>
#elif (BOOST_OS_BSD_FREE)
#  include <sys/types.h>
#  include <sys/sysctl.h>
#endif

/*
 * Returns the full path to the currently running executable,
 * or an empty string in case of failure.
 */
std::string getExecutablePath() {
#if (BOOST_OS_WINDOWS)
    char *exePath;
    if (_get_pgmptr(&exePath) != 0)
        exePath = "";
#elif (BOOST_OS_SOLARIS)
    char exePath[PATH_MAX];
    if (realpath(getexecname(), exePath) == NULL)
        exePath[0] = '\0';
#elif (BOOST_OS_LINUX)
    char exePath[PATH_MAX];
    ssize_t len = ::readlink("/proc/self/exe", exePath, sizeof(exePath));
    if (len == -1 || len == sizeof(exePath))
        len = 0;
    exePath[len] = '\0';
#elif (BOOST_OS_MACOS)
    char exePath[PATH_MAX];
    uint32_t len = sizeof(exePath);
    if (_NSGetExecutablePath(exePath, &len) != 0) {
        exePath[0] = '\0'; // buffer too small (!)
    } else {
        // resolve symlinks, ., .. if possible
        char *canonicalPath = realpath(exePath, NULL);
        if (canonicalPath != NULL) {
            strncpy(exePath,canonicalPath,len);
            free(canonicalPath);
        }
    }
#elif (BOOST_OS_BSD_FREE)
    char exePath[2048];
    int mib[4];  mib[0] = CTL_KERN;  mib[1] = KERN_PROC;  mib[2] = KERN_PROC_PATHNAME;  mib[3] = -1;
    size_t len = sizeof(exePath);
    if (sysctl(mib, 4, exePath, &len, NULL, 0) != 0)
        exePath[0] = '\0';
#endif
    return std::string(exePath);
}

उपरोक्त संस्करण निष्पादन योग्य नाम सहित पूर्ण पथ देता है। यदि इसके बजाय आप निष्पादन योग्य नाम के बिना पथ चाहते हैं, #include boost/filesystem.hpp>और वापसी विवरण को इसमें बदल दें:

return strlen(exePath)>0 ? boost::filesystem::path(exePath).remove_filename().make_preferred().string() : std::string();

@Frank, यकीन नहीं होता कि आप ऐसा क्यों कहते हैं। मेरे लिये कार्य करता है। मैंने एक और प्रतिक्रिया का दावा किया कि आपको एक्सेस / प्रूफ़ / सेल्फ / एक्सई करने के लिए रूट की आवश्यकता है, लेकिन मैंने नहीं पाया है कि किसी भी लिनक्स सिस्टम पर मैंने कोशिश की है (CentOS या मिंट)।
jtbr

2

QNX न्यूट्रिनो के संस्करण के आधार पर , निष्पादन योग्य फ़ाइल का पूर्ण पथ और नाम खोजने के लिए अलग-अलग तरीके हैं जो रनिंग प्रक्रिया को शुरू करने के लिए उपयोग किया गया था। मैं प्रक्रिया पहचानकर्ता के रूप में निरूपित करता हूं <PID>। निम्नलिखित प्रयास करें:

  1. यदि फ़ाइल /proc/self/exefileमौजूद है, तो इसकी सामग्री अनुरोधित जानकारी है।
  2. यदि फ़ाइल /proc/<PID>/exefileमौजूद है, तो इसकी सामग्री अनुरोधित जानकारी है।
  3. यदि फ़ाइल /proc/self/asमौजूद है, तो:
    1. open() फ़ाइल।
    2. कम से कम, का एक बफर आवंटित करें sizeof(procfs_debuginfo) + _POSIX_PATH_MAX
    3. उस बफ़र को इनपुट के रूप में दें devctl(fd, DCMD_PROC_MAPDEBUG_BASE,...
    4. में बफर डाले procfs_debuginfo*
    5. अनुरोधित जानकारी संरचना के pathक्षेत्र में है procfs_debuginfoचेतावनी : किसी कारण से, कभी-कभी, QNX /फ़ाइल पथ के पहले स्लैश को छोड़ देता है । /जरूरत पड़ने पर आगे बढ़ें ।
    6. सफाई करें (फ़ाइल को बंद करें, बफर, आदि को बंद करें)।
  4. 3.फ़ाइल के साथ प्रक्रिया का प्रयास करें /proc/<PID>/as
  5. कोशिश करें dladdr(dlsym(RTLD_DEFAULT, "main"), &dlinfo)कि dlinfoऐसी Dl_infoसंरचना कहाँ dli_fnameहो जिसमें अनुरोधित जानकारी हो।

आशा है कि ये आपकी मदद करेगा।


1

AFAIK, ऐसा कोई रास्ता नहीं है। और एक अम्बुइगुटी भी है: यदि आप एक ही निष्पादन योग्य कई हार्ड-लिंक को "इंगित" कर रहे हैं, तो आप उत्तर के रूप में क्या प्राप्त करना चाहेंगे? (हार्ड-लिंक नहीं वास्तव में "बिंदु" करते हैं, वे कर रहे हैं एक बार execve सिर्फ एफएस पदानुक्रम में किसी अन्य जगह पर एक ही फाइल,।) () को सफलतापूर्वक एक नए द्विआधारी कार्यान्वित करता है, अपने तर्कों के बारे में सभी जानकारी खो दिया है।


1
"एक बार निष्पादित () सफलतापूर्वक एक नया बाइनरी निष्पादित करता है, इसके तर्कों के बारे में सभी जानकारी खो जाती है।" दरअसल, argp और envp तर्क खो नहीं रहे हैं, वे argv [] और पर्यावरण के रूप में पारित कर रहे हैं और, कुछ UN * Xes में, pathname तर्क या उससे निर्मित कुछ भी या तो argp और envp (OS X) के साथ पारित किया गया है / iOS, Solaris) या mark4o के उत्तर में सूचीबद्ध किसी एक तंत्र के माध्यम से उपलब्ध कराया गया है। लेकिन, हाँ, यह सिर्फ आपको एक कड़ी देता है अगर एक से अधिक है।

1

आप argv [0] का उपयोग कर सकते हैं और PATH पर्यावरण चर का विश्लेषण कर सकते हैं। इसे देखें: एक कार्यक्रम का एक नमूना जो स्वयं को पा सकता है


7
यह वास्तव में विश्वसनीय नहीं है (हालांकि यह आम तौर पर सामान्य गोले द्वारा लॉन्च किए गए कार्यक्रमों के साथ काम करेगा), क्योंकि execvऔर परिजन निष्पादन योग्य तरीके सेargv
dmckee --- पूर्व-मध्यस्थ बिल्ली के बच्चे

9
यह गलत उत्तर है। यह आपको बता सकता है कि आप एक ही नाम के साथ एक कार्यक्रम कहां पा सकते हैं । लेकिन यह आपको इस बारे में कुछ नहीं बताता है कि वर्तमान में चल रहे निष्पादन वास्तव में कहां रहते हैं।
लैरी ग्रिट्ज़

0

निष्पादन योग्य छवि का पथ नाम प्राप्त करने के लिए अधिक पोर्टेबल तरीका:

ps आपको निष्पादन योग्य का मार्ग दे सकता है, बशर्ते आपके पास प्रक्रिया आईडी हो। इसके अलावा ps एक POSIX उपयोगिता है इसलिए यह पोर्टेबल होना चाहिए

इसलिए यदि प्रक्रिया आईडी 249297 है तो यह कमांड आपको केवल पथ नाम देता है।

    ps -p 24297 -o comm --no-heading

तर्कों की व्याख्या

-p - दी गई प्रक्रिया का चयन करता है

-o comm - कमांड नाम प्रदर्शित करता है (-o cmd संपूर्ण कमांड लाइन का चयन करता है)

-कोई-हेडिंग - एक हेडिंग लाइन, सिर्फ आउटपुट प्रदर्शित नहीं करते हैं।

एसी प्रोग्राम पॉपेन के माध्यम से इसे चला सकते हैं।


यह परम लॉन्च के साथ फुल लॉन्च स्ट्रिंग देता है।
ईटेक

- कोई हेडिंग नॉन-पोर्टेबल है
Good Person

1
काम नहीं करता है, तो पहले तर्क करने के लिए एक निरपेक्ष पथ नहीं है।
8 सितंबर को hroptatyr

-4

यदि आप C का उपयोग करते हैं, तो आप getwd फ़ंक्शन का उपयोग कर सकते हैं:

int main()
{       
 char buf[4096];
 getwd(buf);
 printf(buf);
}

यह आप मानक आउटपुट, निष्पादन योग्य की वर्तमान निर्देशिका पर प्रिंट करेंगे।


3
कम से कम विंडोज पर, वर्तमान कार्यशील निर्देशिका का चल रहे निष्पादन से कोई विशेष संबंध नहीं है। उदाहरण के लिए, CreateProcess एक .exe लॉन्च कर सकता है और इसकी कार्यशील निर्देशिका को पूरी तरह से स्वतंत्र रूप से सेट कर सकता है।
स्पाइकएक्सएक्सएफ

हर दूसरे ओएस पर स्थिति समान है: वर्तमान निर्देशिका कभी-कभी निष्पादन के द्वारा निष्पादन योग्य निर्देशिका के समान होती है, लेकिन पूरी तरह से अलग हो सकती है।
लस्सी

-10

किसी प्रोग्राम का पूर्ण मान पथ आपके मुख्य फ़ंक्शन के एनडब्ल्यूपी के पीडब्ल्यूडी में है, सी में एक फ़ंक्शन भी है जिसे गेटेनव कहा जाता है, इसलिए वहां ऐसा है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.