Erlang 21 - 3. Creating and Upgrading a Target System

3 एक लक्ष्य प्रणाली का निर्माण और उन्नयन




erlang

3 एक लक्ष्य प्रणाली का निर्माण और उन्नयन

Erlang / OTP का उपयोग करके एक सिस्टम बनाते समय, सबसे सरल तरीका है कि Erlang / OTP को कहीं और स्थापित करें, एप्लिकेशन-विशिष्ट कोड को कहीं और इंस्टॉल करें, और फिर Erlang रनटाइम सिस्टम को शुरू करें, यह सुनिश्चित करते हुए कि कोड पथ में एप्लिकेशन-विशिष्ट कोड शामिल है।

एक Erlang / OTP सिस्टम का उपयोग करना अक्सर वांछनीय नहीं होता है। एक डेवलपर किसी विशेष उद्देश्य के लिए नए Erlang / OTP-compliant एप्लिकेशन बना सकता है, और कई मूल Erlang / OTP अनुप्रयोग विचाराधीन उद्देश्य के लिए अप्रासंगिक हो सकते हैं। इस प्रकार, दिए गए एर्लांग / ओटीपी सिस्टम के आधार पर एक नई प्रणाली बनाने में सक्षम होने की आवश्यकता है, जहां डिस्पेंसेबल एप्लिकेशन हटा दिए जाते हैं और नए एप्लिकेशन शामिल किए जाते हैं। प्रलेखन और स्रोत कोड अप्रासंगिक है और इसलिए नई प्रणाली में शामिल नहीं है।

यह अध्याय एक ऐसी प्रणाली बनाने के बारे में है, जिसे लक्ष्य प्रणाली कहा जाता है

निम्नलिखित अनुभाग कार्यक्षमता की विभिन्न आवश्यकताओं के साथ लक्ष्य प्रणालियों से निपटते हैं:

  • एक मूल लक्ष्य प्रणाली जिसे साधारण erl लिपि कहकर शुरू किया जा सकता है।
  • एक सरल लक्ष्य प्रणाली जहां रनटाइम में कोड प्रतिस्थापन भी किया जा सकता है।
  • एक एम्बेडेड लक्ष्य प्रणाली जहां बाद के निरीक्षण के लिए फाइल करने के लिए सिस्टम से लॉगिंग आउटपुट के लिए समर्थन भी है, और जहां सिस्टम बूट समय के दौरान स्वचालित रूप से शुरू किया जा सकता है।

यहां केवल उस मामले पर विचार किया जाता है जब एर्लैंग / ओटीपी एक यूनिक्स प्रणाली पर चल रहा हो।

sasl एप्लिकेशन में उदाहरण के लिए Erlang मॉड्यूल target_system.erl शामिल है, जिसमें लक्ष्य प्रणाली बनाने और स्थापित करने के लिए कार्य शामिल हैं। इस मॉड्यूल का उपयोग निम्नलिखित उदाहरणों में किया जाता है। मॉड्यूल के स्रोत कोड Listing of target_system.erl में सूचीबद्ध किया गया है

3.1 एक लक्ष्य प्रणाली बनाना

यह माना जाता है कि आपके पास ओटीपी डिजाइन सिद्धांतों के अनुसार संरचित एर्लांग / ओटीपी प्रणाली है।

चरण 1. एक .rel फ़ाइल ( .rel में rel(4) मैनुअल पेज देखें rel(4) बनाएं, जो .rel संस्करण को निर्दिष्ट करता है और उन सभी अनुप्रयोगों को सूचीबद्ध करता है जिन्हें नए बुनियादी लक्ष्य प्रणाली में शामिल किया जाना है। एक उदाहरण निम्नलिखित mysystem.rel फ़ाइल है:

%% mysystem.rel
{release,
 {"MYSYSTEM", "FIRST"},
 {erts, "5.10.4"},
 [{kernel, "2.16.4"},
  {stdlib, "1.19.4"},
  {sasl, "2.3.4"},
  {pea, "1.0"}]}.

सूचीबद्ध अनुप्रयोग न केवल मूल Erlang / OTP अनुप्रयोग हैं, बल्कि संभवतः आपके द्वारा लिखे गए नए अनुप्रयोग (यहाँ अनुप्रयोग Pea ( pea ) द्वारा अनुकरणीय) हैं।

चरण 2. उस निर्देशिका से Erlang / OTP प्रारंभ करें जहाँ mysystem.rel फ़ाइल रहती है:

os> erl -pa /home/user/target_system/myapps/pea-1.0/ebin

यहां pea-1.0 ईबीन निर्देशिका का मार्ग भी दिया गया है।

चरण 3. लक्ष्य प्रणाली बनाएँ:

1> target_system:create("mysystem").

फ़ंक्शन target_system:create/1 निम्नलिखित को target_system:create/1 :

  • फ़ाइल mysystem.rel पढ़ता है और एक नई फ़ाइल plain.rel बनाता है। यह पूर्व के समान है, सिवाय इसके कि यह केवल कर्नेल और STDLIB अनुप्रयोगों को सूचीबद्ध करता है।
  • फ़ाइलों से mysystem.rel और plain.rel फ़ाइलों को mysystem.script , mysystem.boot , plain.script और plain.boot को systools:make_script/2 माध्यम से systools:make_script/2
  • फाइल को mysystem.tar.gz द्वारा mysystem.tar.gz कॉल systools:make_tar/2 । उस फ़ाइल में निम्नलिखित सामग्री है:

    erts-5.10.4/bin/
    releases/FIRST/start.boot
    releases/FIRST/mysystem.rel
    releases/mysystem.rel
    lib/kernel-2.16.4/
    lib/stdlib-1.19.4/
    lib/sasl-2.3.4/
    lib/pea-1.0/

    फ़ाइल releases/FIRST/start.boot हमारे mysystem.boot की एक प्रति है

    रिलीज़ संसाधन फ़ाइल mysystem.rel को टार फ़ाइल में डुप्लिकेट किया गया है। मूल रूप से, यह फ़ाइल केवल releases निर्देशिका में संग्रहीत की गई थी ताकि release_handler लिए यह फ़ाइल अलग से निकालना संभव हो सके। टार फ़ाइल को अनपैक करने के बाद, release_handler स्वचालित रूप से फ़ाइल को releases/FIRST कॉपी कर देगा। हालाँकि, कभी-कभी टार्क फ़ाइल बिना release_handler (उदाहरण के लिए, जब पहला टारगेट सिस्टम release_handler ) को शामिल किए बिना अनपैक किया जाता है। इसलिए फाइल अब टार फाइल में डुप्लिकेट हो गई है इसलिए किसी मैनुअल कॉपी की जरूरत नहीं है।

  • अस्थायी निर्देशिका tmp बनाता है और उस निर्देशिका में टार फ़ाइल mysystem.tar.gz निकालता है।
  • फ़ाइलें erl और tmp/erts-5.10.4/bin से start होता है। ये फ़ाइलें रिलीज़ को स्थापित करते समय स्रोत से फिर से बनाई जाती हैं।
  • निर्देशिका tmp/bin बनाता है।
  • पहले से बनाई गई फ़ाइल plain.boot करें। tmp/bin/start.boot
  • डायरेक्टरी tmp/erts-5.10.4/bin से डायरेक्टरी tmp/bin epmd , epmd , और to_erl फाइल कॉपी करता है।
  • निर्देशिका tmp/log बनाता है, जिसका उपयोग अगर सिस्टम को bin/start स्क्रिप्ट के साथ एम्बेडेड के रूप में किया जाता है।
  • "5.10.4 FIRST" सामग्री के साथ फ़ाइल tmp/releases/start_erl.data बनाता है। इस फ़ाइल को start_erl स्क्रिप्ट में डेटा फ़ाइल के रूप में पारित किया जाना है।
  • निर्देशिका tmp में निर्देशिका से फ़ाइल mysystem.tar.gz को mysystem.tar.gz से mysystem.tar.gz है और tmp को निकालता है।

3.2 एक लक्ष्य प्रणाली स्थापित करना

चरण 4. एक उपयुक्त निर्देशिका में बनाई गई लक्ष्य प्रणाली स्थापित करें।

2> target_system:install("mysystem", "/usr/local/erl-target").

फ़ंक्शन target_system:install/2 निम्न कार्य करता है:

  • टार फाइल mysystem.tar.gz को टारगेट डायरेक्टरी /usr/local/erl-target में mysystem.tar.gz
  • लक्ष्य निर्देशिका में Erlang रनटाइम सिस्टम संस्करण ("5.10.4%") को खोजने के लिए फ़ाइल releases/start_erl.data को पढ़ता है।
  • फ़ाइलों के erl.src , start.src , और start_erl.src लक्ष्य erts-5.10.4/bin निर्देशिका, और क्रमशः फ़ाइलों के लिए /usr/local/erl-target %EMU% /usr/local/erl-target और beam लिए %FINAL_ROOTDIR% और %EMU% लक्ष्य bin निर्देशिका में परिणामी फ़ाइलें erl , start , और run_erl डालता है।
  • अंत में लक्ष्य releases/mysystem.rel फ़ाइल फ़ाइल releases/mysystem.rel में डेटा से बनाई गई है।

3.3 एक लक्ष्य प्रणाली शुरू करना

अब हमारे पास एक लक्ष्य प्रणाली है जिसे विभिन्न तरीकों से शुरू किया जा सकता है। हम इसे एक मूल लक्ष्य प्रणाली के रूप में शुरू करते हैं:

os> /usr/local/erl-target/bin/erl

यहां केवल कर्नेल और STDLIB एप्लिकेशन शुरू किए गए हैं, अर्थात, सिस्टम को एक साधारण विकास प्रणाली के रूप में शुरू किया गया है। यह सब काम करने के लिए केवल दो फ़ाइलों की आवश्यकता है:

  • bin/erl ( erts-5.10.4/bin/erl.src )
  • bin/start.boot ( bin/start.boot एक प्रति। plain.boot )

हम एक वितरित प्रणाली भी शुरू कर सकते हैं ( bin/epmd आवश्यकता होती है)।

मूल mysystem.rel फ़ाइल में निर्दिष्ट सभी अनुप्रयोगों को शुरू करने के लिए, निम्नानुसार फ्लैग -boot उपयोग करें:

os> /usr/local/erl-target/bin/erl -boot /usr/local/erl-target/releases/FIRST/start

हम ऊपर के रूप में एक सरल लक्ष्य प्रणाली शुरू करते हैं। फर्क सिर्फ इतना है कि काम करने के लिए रनटाइम में कोड रिप्लेसमेंट के लिए फाइल releases/RELEASES भी मौजूद है।

एक एम्बेडेड लक्ष्य प्रणाली शुरू करने के लिए, शेल स्क्रिप्ट bin/start का उपयोग किया जाता है। स्क्रिप्ट bin/start_erl कॉल करती है, जो बदले में bin/start_erl (लगभग, start_erl erl का एक एम्बेडेड संस्करण है)।

शेल स्क्रिप्ट की start , जो स्थापना के दौरान erts-5.10.4 / bin / start.src से उत्पन्न होती है, केवल एक उदाहरण है। अपनी आवश्यकताओं के अनुरूप इसे संपादित करें। आमतौर पर यह निष्पादित किया जाता है जब UNIX सिस्टम बूट होता है।

run_erl एक आवरण है जो रनटाइम सिस्टम से फ़ाइल में आउटपुट लॉगिंग प्रदान करता है। यह Erlang खोल ( to_erl ) को संलग्न करने के लिए एक सरल तंत्र भी प्रदान करता है।

start_erl आवश्यकता है:

  • रूट निर्देशिका ( "/usr/local/erl-target" )
  • रिलीज़ निर्देशिका ( "/usr/local/erl-target/releases"
  • फ़ाइल का स्थान start_erl.data

यह निम्नलिखित कार्य करता है:

  • रनटाइम सिस्टम संस्करण ( "5.10.4" ) और फ़ाइल start_erl.data से रिलीज़ संस्करण ( "FIRST" ) start_erl.data
  • संस्करण के रनटाइम सिस्टम को शुरू करता है।
  • जारी किए गए संस्करण की बूट फ़ाइल ( "releases/FIRST/start.boot" ) को निर्दिष्ट करने वाली फ़्लैग -boot प्रदान करता है।

start_erl यह भी मानता है कि रिलीज़ संस्करण निर्देशिका में "releases/FIRST/sys.config" ( "releases/FIRST/sys.config" )। वह अगले भाग का विषय है।

start_erl शेल स्क्रिप्ट आमतौर पर उपयोगकर्ता द्वारा परिवर्तित नहीं की जाती है।

3.4 सिस्टम कॉन्फ़िगरेशन पैरामीटर

जैसा कि पिछले अनुभाग में उल्लेख किया गया था, start_erl को रिलीज़ संस्करण निर्देशिका ( "releases/FIRST/sys.config" ) में "releases/FIRST/sys.config" । यदि ऐसी कोई फ़ाइल नहीं है, तो सिस्टम प्रारंभ विफल रहता है। इसलिए ऐसी फाइल को भी जोड़ना चाहिए।

यदि आपके पास सिस्टम कॉन्फ़िगरेशन डेटा है जो न तो फ़ाइल-स्थान-निर्भर है और न ही साइट-निर्भर है, तो यह sys.config जल्दी बनाने के लिए सुविधाजनक हो सकता है, इसलिए यह target_system:create/1 द्वारा बनाई गई लक्ष्य प्रणाली टार फ़ाइल का हिस्सा बन जाता है target_system:create/1 । वास्तव में, यदि आप वर्तमान निर्देशिका में न केवल फ़ाइल mysystem.rel , बल्कि फ़ाइल sys.config भी sys.config , तो बाद की फ़ाइल को उचित निर्देशिका में सुरक्षित रूप से रखा जाता है।

हालांकि, यह भी unpacking के बाद लक्ष्य पर एक sys.config भीतर चर को बदलने के लिए सुविधाजनक हो सकता है लेकिन रिलीज चलाने से पहले। यदि आपके पास sys.config.src इसे शामिल किया जाएगा और sys.config तरह एक मान्य Erlang शब्द फ़ाइल होना आवश्यक नहीं है। रिलीज़ को चलाने से पहले आपके पास एक ही डायरेक्टरी में एक वैध sys.config होना चाहिए, इसलिए sys.config.src का उपयोग करने के लिए कुछ टूल की ज़रूरत होती है जो रिलीज़ होने से पहले डिस्क को बूट करने के लिए sys.config को sys.config करने के लिए लिखें।

3.5 स्क्रिप्ट स्थापित करने से अंतर

पिछली install/2 प्रक्रिया साधारण Install शेल स्क्रिप्ट से कुछ अलग है। वास्तव में, create/1 रिलीज़ पैकेज को यथासंभव पूर्ण बनाता है, और केवल स्थान-निर्भर फ़ाइलों पर विचार करके समाप्त करने के लिए install/2 प्रक्रिया पर छोड़ दें।

3.6 अगला संस्करण बनाना

इस उदाहरण में मटर एप्लिकेशन को बदल दिया गया है, और इसलिए ईआरटीएस, कर्नेल, एसटीडीआईएलआईबी और एसएएसएल अनुप्रयोग हैं।

चरण 1. फ़ाइल .rel बनाएँ:

%% mysystem2.rel
{release,
 {"MYSYSTEM", "SECOND"},
 {erts, "6.0"},
 [{kernel, "3.0"},
  {stdlib, "2.0"},
  {sasl, "2.4"},
  {pea, "2.0"}]}.

चरण 2 , उदाहरण के लिए, मटर के लिए एप्लिकेशन अपग्रेड फ़ाइल ( appup(4) में appup(4) मैनुअल पेज देखें appup(4) बनाएँ:

%% pea.appup
{"2.0",
 [{"1.0",[{load_module,pea_lib}]}],
 [{"1.0",[{load_module,pea_lib}]}]}.

चरण 3. उस निर्देशिका से, जहां फ़ाइल mysystem2.rel रहती है, Erlang / OTP सिस्टम को प्रारंभ करें, मटर के नए संस्करण को रास्ता दे रहा है:

os> erl -pa /home/user/target_system/myapps/pea-2.0/ebin

चरण 4. रिलीज़ अपग्रेड फ़ाइल बनाएं ( relup(4) में relup(4) मैनुअल पेज देखें):

1> systools:make_relup("mysystem2",["mysystem"],["mysystem"], [{path,["/home/user/target_system/myapps/pea-1.0/ebin", "/my/old/erlang/lib/*/ebin"]}]).

यहाँ "mysystem" बेस रिलीज़ है और "mysystem2" अपग्रेड करने के लिए रिलीज़ है।

path विकल्प का उपयोग सभी अनुप्रयोगों के पुराने संस्करण को इंगित करने के लिए किया जाता है। (नए संस्करण पहले से ही कोड पाथ में हैं - यह मानते हुए कि एर्लैंग नोड जिस पर यह निष्पादित किया गया है, एर्लैंग / ओटीपी का सही संस्करण चला रहा है।)

चरण 5. नया रिलीज़ बनाएं:

2> target_system:create("mysystem2").

यह देखते हुए कि चरण 4 में जनरेट की गई फ़ाइल relup वर्तमान निर्देशिका में स्थित है, यह स्वचालित रूप से रिलीज़ पैकेज में शामिल है।

3.7 लक्ष्य प्रणाली का उन्नयन

यह हिस्सा लक्ष्य नोड पर किया जाता है, और इस उदाहरण के लिए हम चाहते हैं कि नोड को -heart विकल्प के साथ एक एम्बेडेड सिस्टम के रूप में चलाया जाए, जिससे नोड का स्वचालित पुनरारंभ हो सके। अधिक जानकारी के लिए, Starting a Target System देखें।

हम bin/start जोड़ते हैं:

#!/bin/sh
ROOTDIR=/usr/local/erl-target/

if [ -z "$RELDIR" ]
then
   RELDIR=$ROOTDIR/releases
fi

START_ERL_DATA=${1:-$RELDIR/start_erl.data}

$ROOTDIR/bin/run_erl -daemon /tmp/ $ROOTDIR/log "exec $ROOTDIR/bin/start_erl $ROOTDIR\
$RELDIR $START_ERL_DATA -heart

हम सरलतम संभव sys.config उपयोग करते हैं, जिसे हम releases/FIRST में संग्रहीत करते हैं:

%% sys.config
[].

अंत में, अपग्रेड तैयार करने के लिए, हमें नए रिलीज़ पैकेज को पहले लक्ष्य प्रणाली की releases डायरेक्टरी में रखना चाहिए:

os> cp mysystem2.tar.gz /usr/local/erl-target/releases

यह मानते हुए कि नोड निम्नानुसार शुरू किया गया है:

os> /usr/local/erl-target/bin/start

इसे इस प्रकार एक्सेस किया जा सकता है:

os> /usr/local/erl-target/bin/to_erl /tmp/erlang.pipe.1

लॉग /usr/local/erl-target/log में पाया जा सकता है। यह निर्देशिका ऊपर सूचीबद्ध स्क्रिप्ट में run_erl तर्क के रूप में निर्दिष्ट है।

चरण 1. रिलीज को अनपैक करें:

1> {ok,Vsn} = release_handler:unpack_release("mysystem2").

चरण 2. रिलीज स्थापित करें:

2> release_handler:install_release(Vsn).
{continue_after_restart,"FIRST",[]}
heart: Tue Apr  1 12:15:10 2014: Erlang has closed.
heart: Tue Apr  1 12:15:11 2014: Executed "/usr/local/erl-target/bin/start /usr/local/erl-target/releases/new_start_erl.data" -> 0. Terminating.
[End]

release_handler:install_release/1 कॉल के बाद उपरोक्त रिटर्न मान और आउटपुट release_handler:install_release/1 अर्थ है कि release_handler ने heart का उपयोग करके नोड को पुनरारंभ किया है। यह हमेशा किया जाता है जब उन्नयन में ईआरटीएस, कर्नेल, एसटीडीआईएलआईबी या एसएएसएल अनुप्रयोगों का एक परिवर्तन शामिल होता है। अधिक जानकारी के लिए, Upgrade when Erlang/OTP has Changed , तो Upgrade when Erlang/OTP has Changed देखें।

नोड एक नए पाइप के माध्यम से सुलभ है:

os> /usr/local/erl-target/bin/to_erl /tmp/erlang.pipe.2

जाँच करें कि सिस्टम में कौन से रिलीज़ हैं:

1> release_handler:which_releases().
[{"MYSYSTEM","SECOND",
  ["kernel-3.0","stdlib-2.0","sasl-2.4","pea-2.0"],
  current},
 {"MYSYSTEM","FIRST",
  ["kernel-2.16.4","stdlib-1.19.4","sasl-2.3.4","pea-1.0"],
  permanent}]

हमारी नई रिलीज़, "सेकंड", अब वर्तमान रिलीज़ है, लेकिन हम यह भी देख सकते हैं कि हमारी "FIRST" रिलीज़ अभी भी स्थायी है। इसका मतलब है कि अगर नोड अब फिर से शुरू किया जाएगा, तो यह "FIRST" रिलीज को फिर से चलाने के लिए आएगा।

चरण 3. नई रिलीज़ को स्थायी बनाएं:

2> release_handler:make_permanent("SECOND").

पुनः रिलीज़ की जाँच करें:

3> release_handler:which_releases().
[{"MYSYSTEM","SECOND",
  ["kernel-3.0","stdlib-2.0","sasl-2.4","pea-2.0"],
  permanent},
 {"MYSYSTEM","FIRST",
  ["kernel-2.16.4","stdlib-1.19.4","sasl-2.3.4","pea-1.0"],
  old}]

हम देखते हैं कि नया रिलीज़ संस्करण permanent , इसलिए नोड को पुनरारंभ करना सुरक्षित होगा।

3.8 target_system.erl की सूची बनाना

यह मॉड्यूल एसएएसएल एप्लिकेशन के examples निर्देशिका में भी पाया जा सकता है।

-module(target_system).
-export([create/1, create/2, install/2]).

%% Note: RelFileName below is the *stem* without trailing .rel,
%% .script etc.
%%

%% create(RelFileName)
%%
create(RelFileName) ->
    create(RelFileName,[]).

create(RelFileName,SystoolsOpts) ->
    RelFile = RelFileName ++ ".rel", 
    Dir = filename:dirname(RelFileName),
    PlainRelFileName = filename:join(Dir,"plain"),
    PlainRelFile = PlainRelFileName ++ ".rel",
    io:fwrite("Reading file: ~tp ...~n", [RelFile]),
    {ok, [RelSpec]} = file:consult(RelFile),
    io:fwrite("Creating file: ~tp from ~tp ...~n",
              [PlainRelFile, RelFile]),
    {release,
     {RelName, RelVsn},
     {erts, ErtsVsn},
     AppVsns} = RelSpec,
    PlainRelSpec = {release, 
                    {RelName, RelVsn},
                    {erts, ErtsVsn},
                    lists:filter(fun({kernel, _}) -> 
                                         true;
                                    ({stdlib, _}) ->
                                         true;
                                    (_) ->
                                         false
                                 end, AppVsns)
                   },
    {ok, Fd} = file:open(PlainRelFile, [write]),
    io:fwrite(Fd, "~p.~n", [PlainRelSpec]),
    file:close(Fd),

    io:fwrite("Making \"~ts.script\" and \"~ts.boot\" files ...~n",
	      [PlainRelFileName,PlainRelFileName]),
    make_script(PlainRelFileName,SystoolsOpts),

    io:fwrite("Making \"~ts.script\" and \"~ts.boot\" files ...~n",
              [RelFileName, RelFileName]),
    make_script(RelFileName,SystoolsOpts),

    TarFileName = RelFileName ++ ".tar.gz",
    io:fwrite("Creating tar file ~tp ...~n", [TarFileName]),
    make_tar(RelFileName,SystoolsOpts),

    TmpDir = filename:join(Dir,"tmp"),
    io:fwrite("Creating directory ~tp ...~n",[TmpDir]),
    file:make_dir(TmpDir), 

    io:fwrite("Extracting ~tp into directory ~tp ...~n", [TarFileName,TmpDir]),
    extract_tar(TarFileName, TmpDir),

    TmpBinDir = filename:join([TmpDir, "bin"]),
    ErtsBinDir = filename:join([TmpDir, "erts-" ++ ErtsVsn, "bin"]),
    io:fwrite("Deleting \"erl\" and \"start\" in directory ~tp ...~n",
              [ErtsBinDir]),
    file:delete(filename:join([ErtsBinDir, "erl"])),
    file:delete(filename:join([ErtsBinDir, "start"])),

    io:fwrite("Creating temporary directory ~tp ...~n", [TmpBinDir]),
    file:make_dir(TmpBinDir),

    io:fwrite("Copying file \"~ts.boot\" to ~tp ...~n",
              [PlainRelFileName, filename:join([TmpBinDir, "start.boot"])]),
    copy_file(PlainRelFileName++".boot",filename:join([TmpBinDir, "start.boot"])),

    io:fwrite("Copying files \"epmd\", \"run_erl\" and \"to_erl\" from \n"
              "~tp to ~tp ...~n",
              [ErtsBinDir, TmpBinDir]),
    copy_file(filename:join([ErtsBinDir, "epmd"]), 
              filename:join([TmpBinDir, "epmd"]), [preserve]),
    copy_file(filename:join([ErtsBinDir, "run_erl"]), 
              filename:join([TmpBinDir, "run_erl"]), [preserve]),
    copy_file(filename:join([ErtsBinDir, "to_erl"]), 
              filename:join([TmpBinDir, "to_erl"]), [preserve]),

    %% This is needed if 'start' script created from 'start.src' shall
    %% be used as it points out this directory as log dir for 'run_erl'
    TmpLogDir = filename:join([TmpDir, "log"]),
    io:fwrite("Creating temporary directory ~tp ...~n", [TmpLogDir]),
    ok = file:make_dir(TmpLogDir),

    StartErlDataFile = filename:join([TmpDir, "releases", "start_erl.data"]),
    io:fwrite("Creating ~tp ...~n", [StartErlDataFile]),
    StartErlData = io_lib:fwrite("~s ~s~n", [ErtsVsn, RelVsn]),
    write_file(StartErlDataFile, StartErlData),
    
    io:fwrite("Recreating tar file ~tp from contents in directory ~tp ...~n",
	      [TarFileName,TmpDir]),
    {ok, Tar} = erl_tar:open(TarFileName, [write, compressed]),
    %% {ok, Cwd} = file:get_cwd(),
    %% file:set_cwd("tmp"),
    ErtsDir = "erts-"++ErtsVsn,
    erl_tar:add(Tar, filename:join(TmpDir,"bin"), "bin", []),
    erl_tar:add(Tar, filename:join(TmpDir,ErtsDir), ErtsDir, []),
    erl_tar:add(Tar, filename:join(TmpDir,"releases"), "releases", []),
    erl_tar:add(Tar, filename:join(TmpDir,"lib"), "lib", []),
    erl_tar:add(Tar, filename:join(TmpDir,"log"), "log", []),
    erl_tar:close(Tar),
    %% file:set_cwd(Cwd),
    io:fwrite("Removing directory ~tp ...~n",[TmpDir]),
    remove_dir_tree(TmpDir),
    ok.


install(RelFileName, RootDir) ->
    TarFile = RelFileName ++ ".tar.gz", 
    io:fwrite("Extracting ~tp ...~n", [TarFile]),
    extract_tar(TarFile, RootDir),
    StartErlDataFile = filename:join([RootDir, "releases", "start_erl.data"]),
    {ok, StartErlData} = read_txt_file(StartErlDataFile),
    [ErlVsn, _RelVsn| _] = string:tokens(StartErlData, " \n"),
    ErtsBinDir = filename:join([RootDir, "erts-" ++ ErlVsn, "bin"]),
    BinDir = filename:join([RootDir, "bin"]),
    io:fwrite("Substituting in erl.src, start.src and start_erl.src to "
              "form erl, start and start_erl ...\n"),
    subst_src_scripts(["erl", "start", "start_erl"], ErtsBinDir, BinDir, 
                      [{"FINAL_ROOTDIR", RootDir}, {"EMU", "beam"}],
                      [preserve]),
    %%! Workaround for pre OTP 17.0: start.src and start_erl.src did
    %%! not have correct permissions, so the above 'preserve' option did not help
    ok = file:change_mode(filename:join(BinDir,"start"),8#0755),
    ok = file:change_mode(filename:join(BinDir,"start_erl"),8#0755),

    io:fwrite("Creating the RELEASES file ...\n"),
    create_RELEASES(RootDir, filename:join([RootDir, "releases",
					    filename:basename(RelFileName)])).

%% LOCALS 

%% make_script(RelFileName,Opts)
%%
make_script(RelFileName,Opts) ->
    systools:make_script(RelFileName, [no_module_tests,
				       {outdir,filename:dirname(RelFileName)}
				       |Opts]).

%% make_tar(RelFileName,Opts)
%%
make_tar(RelFileName,Opts) ->
    RootDir = code:root_dir(),
    systools:make_tar(RelFileName, [{erts, RootDir},
				    {outdir,filename:dirname(RelFileName)}
				    |Opts]).

%% extract_tar(TarFile, DestDir)
%%
extract_tar(TarFile, DestDir) ->
    erl_tar:extract(TarFile, [{cwd, DestDir}, compressed]).

create_RELEASES(DestDir, RelFileName) ->
    release_handler:create_RELEASES(DestDir, RelFileName ++ ".rel").

subst_src_scripts(Scripts, SrcDir, DestDir, Vars, Opts) -> 
    lists:foreach(fun(Script) ->
                          subst_src_script(Script, SrcDir, DestDir, 
                                           Vars, Opts)
                  end, Scripts).

subst_src_script(Script, SrcDir, DestDir, Vars, Opts) -> 
    subst_file(filename:join([SrcDir, Script ++ ".src"]),
               filename:join([DestDir, Script]),
               Vars, Opts).

subst_file(Src, Dest, Vars, Opts) ->
    {ok, Conts} = read_txt_file(Src),
    NConts = subst(Conts, Vars),
    write_file(Dest, NConts),
    case lists:member(preserve, Opts) of
        true ->
            {ok, FileInfo} = file:read_file_info(Src),
            file:write_file_info(Dest, FileInfo);
        false ->
            ok
    end.

%% subst(Str, Vars)
%% Vars = [{Var, Val}]
%% Var = Val = string()
%% Substitute all occurrences of %Var% for Val in Str, using the list
%% of variables in Vars.
%%
subst(Str, Vars) ->
    subst(Str, Vars, []).

subst([$%, C| Rest], Vars, Result) when $A =< C, C =< $Z ->
    subst_var([C| Rest], Vars, Result, []);
subst([$%, C| Rest], Vars, Result) when $a =< C, C =< $z ->
    subst_var([C| Rest], Vars, Result, []);
subst([$%, C| Rest], Vars, Result) when  C == $_ ->
    subst_var([C| Rest], Vars, Result, []);
subst([C| Rest], Vars, Result) ->
    subst(Rest, Vars, [C| Result]);
subst([], _Vars, Result) ->
    lists:reverse(Result).

subst_var([$%| Rest], Vars, Result, VarAcc) ->
    Key = lists:reverse(VarAcc),
    case lists:keysearch(Key, 1, Vars) of
        {value, {Key, Value}} ->
            subst(Rest, Vars, lists:reverse(Value, Result));
        false ->
            subst(Rest, Vars, [$%| VarAcc ++ [$%| Result]])
    end;
subst_var([C| Rest], Vars, Result, VarAcc) ->
    subst_var(Rest, Vars, Result, [C| VarAcc]);
subst_var([], Vars, Result, VarAcc) ->
    subst([], Vars, [VarAcc ++ [$%| Result]]).

copy_file(Src, Dest) ->
    copy_file(Src, Dest, []).

copy_file(Src, Dest, Opts) ->
    {ok,_} = file:copy(Src, Dest),
    case lists:member(preserve, Opts) of
        true ->
            {ok, FileInfo} = file:read_file_info(Src),
            file:write_file_info(Dest, FileInfo);
        false ->
            ok
    end.
       
write_file(FName, Conts) ->
    Enc = file:native_name_encoding(),
    {ok, Fd} = file:open(FName, [write]),
    file:write(Fd, unicode:characters_to_binary(Conts,Enc,Enc)),
    file:close(Fd).

read_txt_file(File) ->
    {ok, Bin} = file:read_file(File),
    {ok, binary_to_list(Bin)}.

remove_dir_tree(Dir) ->
    remove_all_files(".", [Dir]).

remove_all_files(Dir, Files) ->
    lists:foreach(fun(File) ->
                          FilePath = filename:join([Dir, File]),
                          case filelib:is_dir(FilePath) of
                              true ->
                                  {ok, DirFiles} = file:list_dir(FilePath), 
                                  remove_all_files(FilePath, DirFiles),
                                  file:del_dir(FilePath);
                              _ ->
                                  file:delete(FilePath)
                          end
                  end, Files).