1 /**
2  * @file src/megacmdshell.cpp
3  * @brief MEGAcmd: Interactive CLI and service application
4  * This is the shell application
5  *
6  * (c) 2013 by Mega Limited, Auckland, New Zealand
7  *
8  * This file is distributed under the terms of the GNU General Public
9  * License, see http://www.gnu.org/copyleft/gpl.txt
10  * for details.
11  *
12  * This file is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
15  */
16 
17 #include "megacmdshell.h"
18 #include "megacmdshellcommunications.h"
19 #include "megacmdshellcommunicationsnamedpipes.h"
20 #include "../megacmdcommonutils.h"
21 
22 #define USE_VARARGS
23 #define PREFER_STDARG
24 
25 #ifdef NO_READLINE
26 #include <megaconsole.h>
27 #else
28 #include <readline/readline.h>
29 #include <readline/history.h>
30 #endif
31 
32 #include <iomanip>
33 #include <string>
34 #include <cstring>
35 #include <mutex>
36 #include <condition_variable>
37 #include <set>
38 #include <map>
39 #include <vector>
40 #include <sstream>
41 #include <algorithm>
42 #include <atomic>
43 #include <stdio.h>
44 
45 #define PROGRESS_COMPLETE -2
46 #define SPROGRESS_COMPLETE "-2"
47 #define PROMPT_MAX_SIZE 128
48 
49 #ifndef _WIN32
50 #include <signal.h>
51 #include <sys/types.h>
52 #include <errno.h>
53 #else
54 #include <fcntl.h>
55 #include <io.h>
56 #include <stdio.h>
57 #ifndef _O_U16TEXT
58 #define _O_U16TEXT 0x00020000
59 #endif
60 #ifndef _O_U8TEXT
61 #define _O_U8TEXT 0x00040000
62 #endif
63 #endif
64 
65 #if defined(_WIN32)
66   #define strdup _strdup
67 #endif
68 
69 #define SSTR( x ) static_cast< const std::ostringstream & >( \
70         (  std::ostringstream() << std::dec << x ) ).str()
71 
72 using namespace mega;
73 
74 namespace megacmd {
75 using namespace std;
76 
77 #if defined(NO_READLINE) && defined(_WIN32)
78 CONSOLE_CLASS* console = NULL;
79 #endif
80 
81 
82 // utility functions
83 #ifndef NO_READLINE
getCurrentLine()84 string getCurrentLine()
85 {
86     char *saved_line = rl_copy_text(0, rl_point);
87     string toret(saved_line);
88     free(saved_line);
89     saved_line = NULL;
90     return toret;
91 }
92 #endif
93 
94 
95 // end utily functions
96 
97 string clientID; //identifier for a registered state listener
98 
99 // Console related functions:
console_readpwchar(char * pw_buf,int pw_buf_size,int * pw_buf_pos,char ** line)100 void console_readpwchar(char* pw_buf, int pw_buf_size, int* pw_buf_pos, char** line)
101 {
102 #ifdef _WIN32
103     char c;
104       DWORD cread;
105 
106       if (ReadConsole(GetStdHandle(STD_INPUT_HANDLE), &c, 1, &cread, NULL) == 1)
107       {
108           if ((c == 8) && *pw_buf_pos)
109           {
110               (*pw_buf_pos)--;
111           }
112           else if (c == 13)
113           {
114               *line = (char*)malloc(*pw_buf_pos + 1);
115               memcpy(*line, pw_buf, *pw_buf_pos);
116               (*line)[*pw_buf_pos] = 0;
117           }
118           else if (*pw_buf_pos < pw_buf_size)
119           {
120               pw_buf[(*pw_buf_pos)++] = c;
121           }
122       }
123 #else
124     // FIXME: UTF-8 compatibility
125 
126     char c;
127 
128     if (read(STDIN_FILENO, &c, 1) == 1)
129     {
130         if (c == 8 && *pw_buf_pos)
131         {
132             (*pw_buf_pos)--;
133         }
134         else if (c == 13)
135         {
136             *line = (char*) malloc(*pw_buf_pos + 1);
137             memcpy(*line, pw_buf, *pw_buf_pos);
138             (*line)[*pw_buf_pos] = 0;
139         }
140         else if (*pw_buf_pos < pw_buf_size)
141         {
142             pw_buf[(*pw_buf_pos)++] = c;
143         }
144     }
145 #endif
146 }
console_setecho(bool echo)147 void console_setecho(bool echo)
148 {
149 #ifdef _WIN32
150     HANDLE hCon = GetStdHandle(STD_INPUT_HANDLE);
151     DWORD mode;
152 
153     GetConsoleMode(hCon, &mode);
154 
155     if (echo)
156     {
157         mode |= ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT;
158     }
159     else
160     {
161         mode &= ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT);
162     }
163 
164     SetConsoleMode(hCon, mode);
165 #else
166     //do nth
167 #endif
168 }
169 
170 bool alreadyFinished = false; //flag to show progress
171 float percentDowloaded = 0.0; // to show progress
172 
173 // password change-related state information
174 string oldpasswd;
175 string newpasswd;
176 
177 bool doExit = false;
178 bool doReboot = false;
179 static std::atomic_bool handlerOverridenByExternalThread(false);
180 static std::mutex handlerInstallerMutex;
181 
182 static std::atomic_bool requirepromptinstall(true);
183 
184 bool procesingline = false;
185 bool promptreinstalledwhenprocessingline = false;
186 
187 std::mutex promptLogReceivedMutex;
188 std::condition_variable promtpLogReceivedCV;
189 bool promtpLogReceivedBool = false;
190 bool serverTryingToLog = false;
191 
192 
193 static char dynamicprompt[PROMPT_MAX_SIZE];
194 
195 static char* line;
196 
197 static prompttype prompt = COMMAND;
198 
199 static char pw_buf[256];
200 static int pw_buf_pos = 0;
201 
202 string loginname;
203 bool signingup = false;
204 string signupline;
205 string passwdline;
206 string linktoconfirm;
207 
208 bool confirminglink = false;
209 bool confirmingcancellink = false;
210 
211 // communications with megacmdserver:
212 MegaCmdShellCommunications *comms;
213 
214 std::mutex mutexPrompt;
215 
216 void printWelcomeMsg(unsigned int width = 0);
217 
218 #ifndef NO_READLINE
219 void install_rl_handler(const char *theprompt, bool external = true);
220 #endif
221 
222 std::mutex lastMessageMutex;
223 std::string lastMessage;
notRepeatedMessage(const string & newMessage)224 bool notRepeatedMessage(const string &newMessage)
225 {
226     std::lock_guard<std::mutex> g(lastMessageMutex);
227     bool toret = lastMessage.compare(newMessage);
228     if (toret)
229     {
230         lastMessage = newMessage;
231     }
232     return toret;
233 }
cleanLastMessage()234 void cleanLastMessage()
235 {
236     std::lock_guard<std::mutex> g(lastMessageMutex);
237     lastMessage = string();
238 }
239 
statechangehandle(string statestring)240 void statechangehandle(string statestring)
241 {
242     char statedelim[2]={(char)0x1F,'\0'};
243     size_t nextstatedelimitpos = statestring.find(statedelim);
244     static bool shown_partial_progress = false;
245 
246     unsigned int width = getNumberOfCols(75);
247     if (width > 1 ) width--;
248 
249     while (nextstatedelimitpos!=string::npos && statestring.size())
250     {
251         string newstate = statestring.substr(0,nextstatedelimitpos);
252         statestring=statestring.substr(nextstatedelimitpos+1);
253         nextstatedelimitpos = statestring.find(statedelim);
254         if (newstate.compare(0, strlen("prompt:"), "prompt:") == 0)
255         {
256             if (serverTryingToLog)
257             {
258                 std::unique_lock<std::mutex> lk(MegaCmdShellCommunications::megaCmdStdoutputing);
259                 printCenteredContentsCerr(string(" Server is still trying to log in. Still, some commands are available.\n"
260                              "Type \"help\", to list them.").c_str(), width);
261             }
262             changeprompt(newstate.substr(strlen("prompt:")).c_str(),true);
263 
264             std::unique_lock<std::mutex> lk(promptLogReceivedMutex);
265             promtpLogReceivedCV.notify_one();
266             promtpLogReceivedBool = true;
267         }
268         else if (newstate.compare(0, strlen("endtransfer:"), "endtransfer:") == 0)
269         {
270             string rest = newstate.substr(strlen("endtransfer:"));
271             if (rest.size() >=3)
272             {
273                 bool isdown = rest.at(0) == 'D';
274                 string path = rest.substr(2);
275                 stringstream os;
276                 if (shown_partial_progress)
277                 {
278                     os << endl;
279                 }
280                 os << (isdown?"Download":"Upload") << " finished: " << path << endl;
281 
282 #ifdef _WIN32
283                 wstring wbuffer;
284                 stringtolocalw((const char*)os.str().data(),&wbuffer);
285                 int oldmode;
286                 MegaCmdShellCommunications::megaCmdStdoutputing.lock();
287                 oldmode = _setmode(_fileno(stdout), _O_U8TEXT);
288                 OUTSTREAM << wbuffer << flush;
289                 _setmode(_fileno(stdout), oldmode);
290                 MegaCmdShellCommunications::megaCmdStdoutputing.unlock();
291 #else
292                 OUTSTREAM << os.str();
293 #endif
294             }
295         }
296         else if (newstate.compare(0, strlen("loged:"), "loged:") == 0)
297         {
298             serverTryingToLog = false;
299         }
300         else if (newstate.compare(0, strlen("login:"), "login:") == 0)
301         {
302             serverTryingToLog = true;
303             std::unique_lock<std::mutex> lk(MegaCmdShellCommunications::megaCmdStdoutputing);
304             printCenteredContentsCerr(string("Resuming session ... ").c_str(), width, false);
305         }
306         else if (newstate.compare(0, strlen("message:"), "message:") == 0)
307         {
308             if (notRepeatedMessage(newstate)) //to avoid repeating messages
309             {
310                 MegaCmdShellCommunications::megaCmdStdoutputing.lock();
311 #ifdef _WIN32
312                 int oldmode = _setmode(_fileno(stdout), _O_U8TEXT);
313 #endif
314                 string contents = newstate.substr(strlen("message:"));
315                 if (contents.find("-----") != 0)
316                 {
317                     if (!procesingline || promptreinstalledwhenprocessingline || shown_partial_progress)
318                     {
319                         OUTSTREAM << endl;
320                     }
321                     printCenteredContents(contents, width);
322                     requirepromptinstall = true;
323 #ifndef NO_READLINE
324                     if (prompt == COMMAND && promtpLogReceivedBool)
325                     {
326                         std::lock_guard<std::mutex> g(mutexPrompt);
327                         redisplay_prompt();
328                     }
329 #endif
330 
331                 }
332                 else
333                 {
334                     requirepromptinstall = true;
335                     OUTSTREAM << endl <<  contents << endl;
336                 }
337 #ifdef _WIN32
338                 _setmode(_fileno(stdout), oldmode);
339 #endif
340                 MegaCmdShellCommunications::megaCmdStdoutputing.unlock();
341 
342             }
343         }
344         else if (newstate.compare(0, strlen("clientID:"), "clientID:") == 0)
345         {
346             clientID = newstate.substr(strlen("clientID:")).c_str();
347         }
348         else if (newstate.compare(0, strlen("progress:"), "progress:") == 0)
349         {
350             string rest = newstate.substr(strlen("progress:"));
351 
352             size_t nexdel = rest.find(":");
353             string received = rest.substr(0,nexdel);
354 
355             rest = rest.substr(nexdel+1);
356             nexdel = rest.find(":");
357             string total = rest.substr(0,nexdel);
358 
359             string title;
360             if ( (nexdel != string::npos) && (nexdel < rest.size() ) )
361             {
362                 rest = rest.substr(nexdel+1);
363                 nexdel = rest.find(":");
364                 title = rest.substr(0,nexdel);
365             }
366 
367             if (received!=SPROGRESS_COMPLETE)
368             {
369                 shown_partial_progress = true;
370             }
371             else
372             {
373                 shown_partial_progress = false;
374             }
375 
376             MegaCmdShellCommunications::megaCmdStdoutputing.lock();
377             if (title.size())
378             {
379                 if (received==SPROGRESS_COMPLETE)
380                 {
381                     printprogress(PROGRESS_COMPLETE, charstoll(total.c_str()),title.c_str());
382 
383                 }
384                 else
385                 {
386                     printprogress(charstoll(received.c_str()), charstoll(total.c_str()),title.c_str());
387                 }
388             }
389             else
390             {
391                 if (received==SPROGRESS_COMPLETE)
392                 {
393                     printprogress(PROGRESS_COMPLETE, charstoll(total.c_str()));
394                 }
395                 else
396                 {
397                     printprogress(charstoll(received.c_str()), charstoll(total.c_str()));
398                 }
399             }
400             MegaCmdShellCommunications::megaCmdStdoutputing.unlock();
401         }
402         else if (newstate == "ack")
403         {
404             // do nothing, all good
405         }
406         else if (newstate == "restart")
407         {
408             doExit = true;
409             doReboot = true;
410             if (!comms->updating)
411             {
412                 comms->updating = true; // to avoid mensajes about server down
413             }
414             sleepSeconds(3); // Give a while for server to restart
415             changeprompt("RESTART REQUIRED BY SERVER (due to an update). Press any key to continue.", true);
416         }
417         else
418         {
419             if (shown_partial_progress)
420             {
421                 OUTSTREAM << endl;
422             }
423             cerr << "received unrecognized state change: [" << newstate << "]" << endl;
424             //sleep a while to avoid continuous looping
425             sleepSeconds(1);
426         }
427 
428 
429         if (newstate.compare(0, strlen("progress:"), "progress:") != 0)
430         {
431             shown_partial_progress = false;
432         }
433     }
434 }
435 
436 
sigint_handler(int signum)437 void sigint_handler(int signum)
438 {
439     if (prompt != COMMAND)
440     {
441         setprompt(COMMAND);
442     }
443 
444 #ifndef NO_READLINE
445     // reset position and print prompt
446     rl_replace_line("", 0); //clean contents of actual command
447     rl_crlf(); //move to nextline
448 
449     if (RL_ISSTATE(RL_STATE_ISEARCH) || RL_ISSTATE(RL_STATE_ISEARCH) || RL_ISSTATE(RL_STATE_ISEARCH))
450     {
451         RL_UNSETSTATE(RL_STATE_ISEARCH);
452         RL_UNSETSTATE(RL_STATE_NSEARCH);
453         RL_UNSETSTATE( RL_STATE_SEARCH);
454         history_set_pos(history_length);
455         rl_restore_prompt(); // readline has stored it when searching
456     }
457     else
458     {
459         rl_reset_line_state();
460     }
461     rl_redisplay();
462 #endif
463 }
464 
printprogress(long long completed,long long total,const char * title)465 void printprogress(long long completed, long long total, const char *title)
466 {
467     float oldpercent = percentDowloaded;
468     if (total == 0)
469     {
470         percentDowloaded = 0;
471     }
472     else
473     {
474         percentDowloaded = float(completed * 1.0 / total * 100.0);
475     }
476     if (completed != PROGRESS_COMPLETE && (alreadyFinished || ( ( percentDowloaded == oldpercent ) && ( oldpercent != 0 ) ) ))
477     {
478         return;
479     }
480     if (percentDowloaded < 0)
481     {
482         percentDowloaded = 0;
483     }
484 
485     if (total < 0)
486     {
487         return; // after a 100% this happens
488     }
489     if (completed != PROGRESS_COMPLETE  && completed < 0.001 * total)
490     {
491         return; // after a 100% this happens
492     }
493     if (completed == PROGRESS_COMPLETE)
494     {
495         alreadyFinished = true;
496         completed = total;
497         percentDowloaded = 100;
498     }
499     printPercentageLineCerr(title, completed, total, percentDowloaded, !alreadyFinished);
500 }
501 
502 
503 #ifdef _WIN32
CtrlHandler(DWORD fdwCtrlType)504 BOOL WINAPI CtrlHandler( DWORD fdwCtrlType )
505 {
506   cerr << "Reached CtrlHandler: " << fdwCtrlType << endl;
507 
508   switch( fdwCtrlType )
509   {
510     // Handle the CTRL-C signal.
511     case CTRL_C_EVENT:
512        sigint_handler((int)fdwCtrlType);
513       return( TRUE );
514 
515     default:
516       return FALSE;
517   }
518 }
519 #endif
520 
getprompt()521 prompttype getprompt()
522 {
523     return prompt;
524 }
525 
setprompt(prompttype p,string arg)526 void setprompt(prompttype p, string arg)
527 {
528     prompt = p;
529 
530 #ifndef NO_READLINE
531     if (p == COMMAND)
532     {
533         console_setecho(true);
534     }
535     else
536     {
537         pw_buf_pos = 0;
538         if (arg.size())
539         {
540             OUTSTREAM << arg << flush;
541         }
542         else
543         {
544             OUTSTREAM << prompts[p] << flush;
545         }
546         console_setecho(false);
547     }
548 #else
549     console->setecho(p == COMMAND);
550 
551     if (p != COMMAND)
552     {
553         pw_buf_pos = 0;
554         console->updateInputPrompt(arg.empty() ? prompts[p] : arg);
555     }
556 #endif
557 }
558 
559 #ifndef NO_READLINE
560 // readline callback - exit if EOF, add to history unless password
store_line(char * l)561 static void store_line(char* l)
562 {
563     procesingline = true;
564     if (!l)
565     {
566 #ifndef _WIN32 // to prevent exit with Supr key
567         doExit = true;
568         rl_set_prompt("(CTRL+D) Exiting ...\n");
569 #ifndef NDEBUG
570         if (comms->serverinitiatedfromshell)
571         {
572             OUTSTREAM << " Forwarding exit command to the server, since this cmd shell (most likely) initiated it" << endl;
573             comms->executeCommand("exit", readresponse);
574         }
575 #endif
576 #endif
577         return;
578     }
579 
580     if (*l && ( prompt == COMMAND ))
581     {
582         add_history(l);
583     }
584 
585     line = l;
586 }
587 #endif
588 
589 #ifdef _WIN32
590 
validoptionforreadline(const string & string)591 bool validoptionforreadline(const string& string)
592 {// TODO: this has not been tested in 100% cases (perhaps it is too diligent or too strict)
593     int c,i,ix,n,j;
594     for (i=0, ix=int(string.length()); i < ix; i++)
595     {
596         c = (unsigned char) string[i];
597 
598         //if (c>0xC0) return false;
599         //if (c==0x09 || c==0x0a || c==0x0d || (0x20 <= c && c <= 0x7e) ) n = 0; // is_printable_ascii
600         if (0x00 <= c && c <= 0x7f) n=0; // 0bbbbbbb
601         else if ((c & 0xE0) == 0xC0) n=1; // 110bbbbb
602         else if ( c==0xed && i<(ix-1) && ((unsigned char)string[i+1] & 0xa0)==0xa0) return false; //U+d800 to U+dfff
603         else if ((c & 0xF0) == 0xE0) {return false; n=2;} // 1110bbbb
604         else if ((c & 0xF8) == 0xF0) {return false; n=3;} // 11110bbb
605         //else if (($c & 0xFC) == 0xF8) n=4; // 111110bb //byte 5, unnecessary in 4 byte UTF-8
606         //else if (($c & 0xFE) == 0xFC) n=5; // 1111110b //byte 6, unnecessary in 4 byte UTF-8
607         else return false;
608         for (j=0; j<n && i<ix; j++) { // n bytes matching 10bbbbbb follow ?
609             if ((++i == ix) || (( (unsigned char)string[i] & 0xC0) != 0x80))
610                 return false;
611         }
612     }
613     return true;
614 }
615 
validwcharforeadline(const wchar_t thewchar)616 bool validwcharforeadline(const wchar_t thewchar)
617 {
618     wstring input;
619     input+=thewchar;
620     string output;
621     localwtostring(&input,&output);
622     return validoptionforreadline(output);
623 }
624 
escapereadlinebreakers(const wchar_t * what)625 wstring escapereadlinebreakers(const wchar_t *what)
626 {
627     wstring output;
628     for( unsigned int i = 0; i < wcslen( what ) ; i++ )
629     {
630         if(validwcharforeadline(what[ i ] ))
631         {
632             output.reserve( output.size() + 1 );
633             output += what[ i ];
634         } else {
635 #ifndef __MINGW32__
636             wchar_t code[ 7 ];
637             swprintf( code, 7, L"\\u%0.4X", what[ i ] ); //while this does not work (yet) as what, at least it shows something and does not break
638             //TODO: ideally we would do the conversion from escaped unicode chars \uXXXX back to wchar_t in the server
639             // NOTICE: I was able to execute a command with a literl \x242ee (which correspond to \uD850\uDEEE in UTF16).
640             // So it'll be more interesting to output here the complete unicode char and in unescapeutf16escapedseqs revert it.
641             //     or keep here the UTF16 escaped secs and revert them correctly in the unescapeutf16escapedseqs
642             output.reserve( output.size() + 7 ); // "\u"(2) + 5(uint max digits capacity)
643             output += code;
644 #endif
645         }
646     }
647     return output;
648 }
649 #endif
650 
651 #ifndef NO_READLINE
install_rl_handler(const char * theprompt,bool external)652 void install_rl_handler(const char *theprompt, bool external)
653 {
654     std::lock_guard<std::mutex> lkrlhandler(handlerInstallerMutex);
655     if (procesingline)
656     {
657         promptreinstalledwhenprocessingline = true;
658     }
659 
660 #ifdef _WIN32
661     wstring wswhat;
662     stringtolocalw(theprompt,&wswhat);
663     const wchar_t *what = wswhat.c_str();
664 
665 
666     // escape characters that break readline input (e.g. Chinese ones. e.g \x242ee)
667     wstring output = escapereadlinebreakers(what);
668 
669     // give readline something it understands
670     what = output.c_str();
671     size_t buffer_size;
672 #ifdef _TRUNCATE
673     wcstombs_s(&buffer_size, NULL, 0, what, _TRUNCATE);
674 #else
675     buffer_size=output.size()*sizeof(wchar_t)*2;
676 #endif
677 
678     if (buffer_size) //coversion is ok
679     {
680         // do the actual conversion
681         char *buffer = new char[buffer_size];
682         #ifdef _TRUNCATE
683             wcstombs_s(&buffer_size, buffer, buffer_size,what, _TRUNCATE);
684         #else
685             wcstombs(buffer, what, buffer_size);
686         #endif
687 
688         rl_callback_handler_install(buffer, store_line);
689     }
690     else
691     {
692         rl_callback_handler_install("INVALID_PROMPT: ", store_line);
693     }
694 
695 #else
696 
697     rl_callback_handler_install(theprompt, store_line);
698     handlerOverridenByExternalThread = external;
699     requirepromptinstall = false;
700 
701 #endif
702 }
703 
redisplay_prompt()704 void redisplay_prompt()
705 {
706     int saved_point = rl_point;
707     char *saved_line = rl_copy_text(0, rl_end);
708 
709     rl_clear_message();
710 
711     // enter a new line if not processing sth (otherwise, the newline should already be there)
712     if (!procesingline)
713     {
714         rl_crlf();
715     }
716 
717     if (prompt == COMMAND)
718     {
719         install_rl_handler(*dynamicprompt ? dynamicprompt : prompts[COMMAND]);
720     }
721 
722     // restore line
723     if (saved_line)
724     {
725         rl_replace_line(saved_line, 0);
726         free(saved_line);
727         saved_line = NULL;
728     }
729     rl_point = saved_point;
730     rl_redisplay();
731 }
732 
733 #endif
734 
735 
changeprompt(const char * newprompt,bool redisplay)736 void changeprompt(const char *newprompt, bool redisplay)
737 {
738     std::lock_guard<std::mutex> g(mutexPrompt);
739 
740     if (*dynamicprompt)
741     {
742         if (!strcmp(newprompt,dynamicprompt))
743             return; //same prompt. do nth
744     }
745 
746     strncpy(dynamicprompt, newprompt, sizeof( dynamicprompt ));
747 
748     if (strlen(newprompt) >= PROMPT_MAX_SIZE)
749     {
750         strncpy(dynamicprompt, newprompt, PROMPT_MAX_SIZE/2-1);
751         dynamicprompt[PROMPT_MAX_SIZE/2-1] = '.';
752         dynamicprompt[PROMPT_MAX_SIZE/2] = '.';
753 
754         strncpy(dynamicprompt+PROMPT_MAX_SIZE/2+1, newprompt+(strlen(newprompt)-PROMPT_MAX_SIZE/2+2), PROMPT_MAX_SIZE/2-2);
755         dynamicprompt[PROMPT_MAX_SIZE-1] = '\0';
756     }
757 
758 #ifdef NO_READLINE
759     console->updateInputPrompt(newprompt);
760 #else
761     if (redisplay)
762     {
763         // save line
764         redisplay_prompt();
765     }
766 
767 #endif
768 
769     static bool firstime = true;
770     if (firstime)
771     {
772         firstime = false;
773 #if _WIN32
774         if( !SetConsoleCtrlHandler( CtrlHandler, TRUE ) )
775         {
776             cerr << "Control handler set failed" << endl;
777         }
778 #else
779         // prevent CTRL+C exit
780         signal(SIGINT, sigint_handler);
781 #endif
782     }
783 }
784 
escapeEspace(string & orig)785 void escapeEspace(string &orig)
786 {
787     replaceAll(orig," ", "\\ ");
788 }
789 
unescapeEspace(string & orig)790 void unescapeEspace(string &orig)
791 {
792     replaceAll(orig,"\\ ", " ");
793 }
794 
795 #ifndef NO_READLINE
empty_completion(const char * text,int state)796 char* empty_completion(const char* text, int state)
797 {
798     // we offer 2 different options so that it doesn't complete (no space is inserted)
799     if (state == 0)
800     {
801         return strdup(" ");
802     }
803     if (state == 1)
804     {
805         return strdup(text);
806     }
807     return NULL;
808 }
809 
generic_completion(const char * text,int state,vector<string> validOptions)810 char* generic_completion(const char* text, int state, vector<string> validOptions)
811 {
812     static size_t list_index, len;
813     static bool foundone;
814     string name;
815     if (!validOptions.size()) // no matches
816     {
817         return empty_completion(text,state); //dont fall back to filenames
818     }
819     if (!state)
820     {
821         list_index = 0;
822         foundone = false;
823         len = strlen(text);
824     }
825     while (list_index < validOptions.size())
826     {
827         name = validOptions.at(list_index);
828         if (!rl_completion_quote_character) {
829             escapeEspace(name);
830         }
831 
832         list_index++;
833 
834         if (!( strcmp(text, "")) || (( name.size() >= len ) && ( strlen(text) >= len ) && ( name.find(text) == 0 )))
835         {
836             if (name.size() && (( name.at(name.size() - 1) == '=' ) || ( name.at(name.size() - 1) == '\\' ) || ( name.at(name.size() - 1) == '/' )))
837             {
838                 rl_completion_suppress_append = 1;
839             }
840             foundone = true;
841             return dupstr((char*)name.c_str());
842         }
843     }
844 
845     if (!foundone)
846     {
847         return empty_completion(text,state); //dont fall back to filenames
848     }
849 
850     return((char*)NULL );
851 }
852 #endif
853 
local_completion(const char * text,int state)854 char* local_completion(const char* text, int state)
855 {
856     return((char*)NULL );  //matches will be NULL: readline will use local completion
857 }
858 
pushvalidoption(vector<string> * validOptions,const char * beginopt)859 void pushvalidoption(vector<string>  *validOptions, const char *beginopt)
860 {
861 #ifdef _WIN32
862     if (validoptionforreadline(beginopt))
863     {
864         validOptions->push_back(beginopt);
865     }
866     else
867     {
868         wstring input;
869         stringtolocalw(beginopt,&input);
870         wstring output = escapereadlinebreakers(input.c_str());
871 
872         string soutput;
873         localwtostring(&output,&soutput);
874         validOptions->push_back(soutput.c_str());
875     }
876 #else
877     validOptions->push_back(beginopt);
878 #endif
879 }
880 
changedir(const string & where)881 void changedir(const string& where)
882 {
883 #ifdef _WIN32
884     wstring wwhere;
885     stringtolocalw(where.c_str(), &wwhere);
886     wwhere.append(L"\\");
887     int r = SetCurrentDirectoryW((LPCWSTR)wwhere.data());
888     if (!r)
889     {
890         cerr << "Error at SetCurrentDirectoryW before local completion to " << where << ". errno: " << ERRNO << endl;
891     }
892 #else
893     chdir(where.c_str());
894 #endif
895 }
896 
897 
898 #ifndef NO_READLINE
remote_completion(const char * text,int state)899 char* remote_completion(const char* text, int state)
900 {
901     char *saved_line = strdup(getCurrentLine().c_str());
902 
903     static vector<string> validOptions;
904     if (state == 0)
905     {
906         validOptions.clear();
907         string completioncommand("completionshell ");
908         completioncommand+=saved_line;
909 
910         OUTSTRING s;
911         OUTSTRINGSTREAM oss(s);
912 
913         comms->executeCommand(completioncommand, readresponse, oss);
914 
915         string outputcommand;
916 
917 #ifdef _WIN32
918         #ifdef __MINGW32__
919             wstring soss=oss.str();
920             localwtostring(&soss,&outputcommand);
921         #else
922             localwtostring(&oss.str(),&outputcommand);
923         #endif
924 #else
925          outputcommand = oss.str();
926 #endif
927 
928         if (outputcommand == "MEGACMD_USE_LOCAL_COMPLETION")
929         {
930             free(saved_line);
931             return local_completion(text,state); //fallback to local path completion
932         }
933 
934         if (outputcommand.find("MEGACMD_USE_LOCAL_COMPLETION") == 0)
935         {
936             string where = outputcommand.substr(strlen("MEGACMD_USE_LOCAL_COMPLETION"));
937             changedir(where);
938             free(saved_line);
939             return local_completion(text,state); //fallback to local path completion
940         }
941 
942         char *ptr = (char *)outputcommand.c_str();
943 
944         char *beginopt = ptr;
945         while (*ptr)
946         {
947             if (*ptr == 0x1F)
948             {
949                 *ptr = '\0';
950                 if (strcmp(beginopt," ")) //the server will give a " " for empty_completion (no matches)
951                 {
952                     pushvalidoption(&validOptions,beginopt);
953                 }
954 
955                 beginopt=ptr+1;
956             }
957             ptr++;
958         }
959         if (*beginopt && strcmp(beginopt," "))
960         {
961             pushvalidoption(&validOptions,beginopt);
962         }
963     }
964 
965     free(saved_line);
966     saved_line = NULL;
967 
968     return generic_completion(text, state, validOptions);
969 }
970 
getCompletionMatches(const char * text,int start,int end)971 static char** getCompletionMatches(const char * text, int start, int end)
972 {
973     rl_filename_quoting_desired = 1;
974 
975     char **matches;
976 
977     matches = (char**)NULL;
978 
979     matches = rl_completion_matches((char*)text, remote_completion);
980 
981     return( matches );
982 }
983 
printHistory()984 void printHistory()
985 {
986     int length = history_length;
987     int offset = 1;
988     int rest = length;
989     while (rest >= 10)
990     {
991         offset++;
992         rest = rest / 10;
993     }
994 
995     for (int i = 0; i < length; i++)
996     {
997         history_set_pos(i);
998         OUTSTREAM << setw(offset) << i << "  " << current_history()->line << endl;
999     }
1000 }
1001 
1002 #ifdef _WIN32
1003 
1004 /**
1005  * @brief getcharacterreadlineUTF16support
1006  * while this works, somehow arrows and other readline stuff is disabled using this one.
1007  * @param stream
1008  * @return
1009  */
getcharacterreadlineUTF16support(FILE * stream)1010 int getcharacterreadlineUTF16support (FILE *stream)
1011 {
1012     int result;
1013     char b[10];
1014     memset(b,0,10);
1015 
1016     while (1)
1017     {
1018         int oldmode = _setmode(_fileno(stream), _O_U16TEXT);
1019 
1020         result = read (fileno (stream), &b, 10);
1021         _setmode(_fileno(stream), oldmode);
1022 
1023         if (result == 0)
1024         {
1025             return (EOF);
1026         }
1027 
1028         // convert the UTF16 string to widechar
1029         size_t wbuffer_size;
1030 #ifdef _TRUNCATE
1031         mbstowcs_s(&wbuffer_size, NULL, 0, b, _TRUNCATE);
1032 #else
1033         wbuffer_size=10;
1034 #endif
1035         wchar_t *wbuffer = new wchar_t[wbuffer_size];
1036 
1037 #ifdef _TRUNCATE
1038         mbstowcs_s(&wbuffer_size, wbuffer, wbuffer_size, b, _TRUNCATE);
1039 #else
1040         mbstowcs(wbuffer, b, wbuffer_size);
1041 #endif
1042 
1043         // convert the UTF16 widechar to UTF8 string
1044         string receivedutf8;
1045         utf16ToUtf8(wbuffer, wbuffer_size,&receivedutf8);
1046 
1047         if (strlen(receivedutf8.c_str()) > 1) //multi byte utf8 sequence: place the UTF8 characters into rl buffer one by one
1048         {
1049             for (unsigned int i=0;i< strlen(receivedutf8.c_str());i++)
1050             {
1051                 rl_line_buffer[rl_end++] = receivedutf8.c_str()[i];
1052                 rl_point=rl_end;
1053             }
1054             rl_line_buffer[rl_end] = '\0';
1055 
1056             return 0;
1057         }
1058 
1059         if (result =! 0)
1060         {
1061             return (b[0]);
1062         }
1063 
1064         /* If zero characters are returned, then the file that we are
1065      reading from is empty!  Return EOF in that case. */
1066         if (result == 0)
1067         {
1068             return (EOF);
1069         }
1070     }
1071 }
1072 #endif
1073 
wait_for_input(int readline_fd)1074 void wait_for_input(int readline_fd)
1075 {
1076     fd_set fds;
1077 
1078     FD_ZERO(&fds);
1079     FD_SET(readline_fd, &fds);
1080 
1081     int rc = select(FD_SETSIZE, &fds, NULL, NULL, NULL);
1082     if (rc < 0)
1083     {
1084         if (ERRNO != EINTR)  //syscall
1085         {
1086 #ifdef _WIN32
1087          if (ERRNO != WSAENOTSOCK) // it enters here since it is not a socket. Alt: Use WaitForMultipleObjectsEx
1088 #endif
1089                 cerr << "Error at select at wait_for_input errno: " << ERRNO << endl;
1090             return;
1091         }
1092     }
1093 }
1094 #else
1095 
1096 
remote_completion(string linetocomplete)1097 vector<autocomplete::ACState::Completion> remote_completion(string linetocomplete)
1098 {
1099     using namespace autocomplete;
1100     vector<ACState::Completion> result;
1101 
1102     // normalize any partially or intermediately quoted strings, eg.  `put c:\Program" Fi` or `/My" Documents/"`
1103     ACState acs = prepACState(linetocomplete, linetocomplete.size(), console->getAutocompleteStyle());
1104     string refactoredline;
1105     for (auto& s : acs.words)
1106     {
1107         refactoredline += (refactoredline.empty() ? "" : " ") + s.getQuoted();
1108     }
1109 
1110     OUTSTRING s;
1111     OUTSTRINGSTREAM oss(s);
1112     comms->executeCommand(string("completionshell ") + refactoredline, readresponse, oss);
1113 
1114     string outputcommand;
1115     auto ossstr=oss.str();
1116     localwtostring(&ossstr, &outputcommand);
1117 
1118     ACState::quoted_word completionword = acs.words.size() ? acs.words[acs.words.size() - 1] : string();
1119 
1120     if (outputcommand.find("MEGACMD_USE_LOCAL_COMPLETION") == 0)
1121     {
1122         string where;
1123         bool folders = false;
1124         if (outputcommand.find("MEGACMD_USE_LOCAL_COMPLETIONFOLDERS") == 0)
1125         {
1126             where = outputcommand.substr(strlen("MEGACMD_USE_LOCAL_COMPLETIONFOLDERS"));
1127             folders = true;
1128         }
1129         else
1130         {
1131             where = outputcommand.substr(strlen("MEGACMD_USE_LOCAL_COMPLETION"));
1132         }
1133         changedir(where);
1134 
1135         if (acs.words.size())
1136         {
1137             string l = completionword.getQuoted();
1138             CompletionState cs = autoComplete(l, l.size(), folders ? localFSFolder() : localFSPath(), console->getAutocompleteStyle());
1139             result.swap(cs.completions);
1140         }
1141         return result;
1142     }
1143     else
1144     {
1145         char *ptr = (char *)outputcommand.c_str();
1146 
1147         char *beginopt = ptr;
1148         while (*ptr)
1149         {
1150             if (*ptr == 0x1F)
1151             {
1152                 *ptr = '\0';
1153                 if (strcmp(beginopt, " ")) //the server will give a " " for empty_completion (no matches)
1154                 {
1155                     result.push_back(autocomplete::ACState::Completion(beginopt, false));
1156                 }
1157 
1158                 beginopt = ptr + 1;
1159             }
1160             ptr++;
1161         }
1162         if (*beginopt && strcmp(beginopt, " "))
1163         {
1164             result.push_back(autocomplete::ACState::Completion(beginopt, false));
1165         }
1166 
1167         if (result.size() == 1 && result[0].s == completionword.s)
1168         {
1169             result.clear();  // for parameters it returns the same string when there are no matches
1170         }
1171         return result;
1172     }
1173 }
1174 
exec_clear(autocomplete::ACState & s)1175 void exec_clear(autocomplete::ACState& s)
1176 {
1177     console->clearScreen();
1178 }
1179 
exec_history(autocomplete::ACState & s)1180 void exec_history(autocomplete::ACState& s)
1181 {
1182     console->outputHistory();
1183 }
1184 
exec_dos_unix(autocomplete::ACState & s)1185 void exec_dos_unix(autocomplete::ACState& s)
1186 {
1187     if (s.words.size() < 2)
1188     {
1189         OUTSTREAM << "autocomplete style: " << (console->getAutocompleteStyle() ? "unix" : "dos") << endl;
1190     }
1191     else
1192     {
1193         console->setAutocompleteStyle(s.words[1].s == "unix");
1194     }
1195 }
1196 
exec_codepage(autocomplete::ACState & s)1197 void exec_codepage(autocomplete::ACState& s)
1198 {
1199     if (s.words.size() == 1)
1200     {
1201         UINT cp1, cp2;
1202         console->getShellCodepages(cp1, cp2);
1203         cout << "Current codepage is " << cp1;
1204         if (cp2 != cp1)
1205         {
1206             cout << " with failover to codepage " << cp2 << " for any absent glyphs";
1207         }
1208         cout << endl;
1209         for (int i = 32; i < 256; ++i)
1210         {
1211             string theCharUtf8 = WinConsole::toUtf8String(WinConsole::toUtf16String(string(1, (char)i), cp1));
1212             cout << "  dec/" << i << " hex/" << hex << i << dec << ": '" << theCharUtf8 << "'";
1213             if (i % 4 == 3)
1214             {
1215                 cout << endl;
1216             }
1217         }
1218     }
1219     else if (s.words.size() == 2 && atoi(s.words[1].s.c_str()) != 0)
1220     {
1221         if (!console->setShellConsole(atoi(s.words[1].s.c_str()), atoi(s.words[1].s.c_str())))
1222         {
1223             cout << "Code page change failed - unicode selected" << endl;
1224         }
1225     }
1226     else if (s.words.size() == 3 && atoi(s.words[1].s.c_str()) != 0 && atoi(s.words[2].s.c_str()) != 0)
1227     {
1228         if (!console->setShellConsole(atoi(s.words[1].s.c_str()), atoi(s.words[2].s.c_str())))
1229         {
1230             cout << "Code page change failed - unicode selected" << endl;
1231         }
1232     }
1233     else
1234     {
1235         cout << "      codepage [N [N]]" << endl;
1236     }
1237 }
1238 
1239 
1240 autocomplete::ACN autocompleteSyntax;
1241 
buildAutocompleteSyntax()1242 autocomplete::ACN buildAutocompleteSyntax()
1243 {
1244     using namespace autocomplete;
1245     std::unique_ptr<Either> p(new Either("      "));
1246 
1247     p->Add(exec_clear,      sequence(text("clear")));
1248     p->Add(exec_codepage,   sequence(text("codepage"), opt(sequence(wholenumber(65001), opt(wholenumber(65001))))));
1249     p->Add(exec_dos_unix,   sequence(text("autocomplete"), opt(either(text("unix"), text("dos")))));
1250     p->Add(exec_history,    sequence(text("history")));
1251 
1252     return autocompleteSyntax = std::move(p);
1253 }
1254 
printHistory()1255 void printHistory()
1256 {
1257     console->outputHistory();
1258 }
1259 #endif
1260 
isserverloggedin()1261 bool isserverloggedin()
1262 {
1263     if (comms->executeCommand(("loggedin")) == MCMD_NOTLOGGEDIN )
1264     {
1265         return false;
1266     }
1267     return true;
1268 }
1269 
1270 
process_line(const char * line)1271 void process_line(const char * line)
1272 {
1273     string refactoredline;
1274 
1275     switch (prompt)
1276     {
1277         case AREYOUSURE:
1278             //this is currently never used
1279             if (!strcasecmp(line,"yes") || !strcasecmp(line,"y"))
1280             {
1281                 comms->setResponseConfirmation(true);
1282                 setprompt(COMMAND);
1283             }
1284             else if (!strcasecmp(line,"no") || !strcasecmp(line,"n"))
1285             {
1286                 comms->setResponseConfirmation(false);
1287                 setprompt(COMMAND);
1288             }
1289             else
1290             {
1291                 //Do nth, ask again
1292                 OUTSTREAM << "Please enter: [y]es/[n]o: " << flush;
1293             }
1294             break;
1295         case LOGINPASSWORD:
1296         {
1297             if (!strlen(line))
1298             {
1299                 break;
1300             }
1301             if (confirminglink)
1302             {
1303                 string confirmcommand("confirm ");
1304                 confirmcommand+=linktoconfirm;
1305                 confirmcommand+=" " ;
1306                 confirmcommand+=loginname;
1307                 confirmcommand+=" \"";
1308                 confirmcommand+=line;
1309                 confirmcommand+="\"" ;
1310                 OUTSTREAM << endl;
1311                 comms->executeCommand(confirmcommand.c_str(), readresponse);
1312             }
1313             else if (confirmingcancellink)
1314             {
1315                 string confirmcommand("confirmcancel ");
1316                 confirmcommand+=linktoconfirm;
1317                 confirmcommand+=" \"";
1318                 confirmcommand+=line;
1319                 confirmcommand+="\"" ;
1320                 OUTSTREAM << endl;
1321                 comms->executeCommand(confirmcommand.c_str(), readresponse);
1322             }
1323             else
1324             {
1325                 string logincommand("login -v ");
1326                 if (clientID.size())
1327                 {
1328                     logincommand += "--clientID=";
1329                     logincommand+=clientID;
1330                     logincommand+=" ";
1331                 }
1332                 logincommand+=loginname;
1333                 logincommand+=" \"" ;
1334                 logincommand+=line;
1335                 logincommand+="\"" ;
1336                 OUTSTREAM << endl;
1337                 comms->executeCommand(logincommand.c_str(), readresponse);
1338             }
1339             confirminglink = false;
1340             confirmingcancellink = false;
1341 
1342             setprompt(COMMAND);
1343             break;
1344         }
1345         case NEWPASSWORD:
1346         {
1347             if (!strlen(line))
1348             {
1349                 break;
1350             }
1351             newpasswd = line;
1352             OUTSTREAM << endl;
1353             setprompt(PASSWORDCONFIRM);
1354             break;
1355         }
1356         case PASSWORDCONFIRM:
1357         {
1358             if (!strlen(line))
1359             {
1360                 break;
1361             }
1362             if (line != newpasswd)
1363             {
1364                 OUTSTREAM << endl << "New passwords differ, please try again" << endl;
1365             }
1366             else
1367             {
1368                 OUTSTREAM << endl;
1369 
1370                 if (signingup)
1371                 {
1372                     signupline += " \"";
1373                     signupline += newpasswd;
1374                     signupline += "\"";
1375                     comms->executeCommand(signupline.c_str(), readresponse);
1376 
1377                     signingup = false;
1378                 }
1379                 else
1380                 {
1381                     string changepasscommand(passwdline);
1382                     passwdline = " ";
1383                     changepasscommand+=" " ;
1384                     if (oldpasswd.size())
1385                     {
1386                         changepasscommand+="\"" ;
1387                         changepasscommand+=oldpasswd;
1388                         changepasscommand+="\"" ;
1389                     }
1390                     changepasscommand+=" \"" ;
1391                     changepasscommand+=newpasswd;
1392                     changepasscommand+="\"" ;
1393                     comms->executeCommand(changepasscommand.c_str(), readresponse);
1394                 }
1395             }
1396 
1397             setprompt(COMMAND);
1398             break;
1399         }
1400         case COMMAND:
1401         {
1402 
1403 #ifdef NO_READLINE
1404             // if local command and syntax is satisfied, execute it
1405             string consoleOutput;
1406             if (autocomplete::autoExec(line, string::npos, autocompleteSyntax, false, consoleOutput, false))
1407             {
1408                 COUT << consoleOutput << flush;
1409                 return;
1410             }
1411 
1412             // normalize any partially or intermediately quoted strings, eg.  `put c:\Program" Fi` or get `/My" Documents/"`
1413             autocomplete::ACState acs = autocomplete::prepACState(line, strlen(line), console->getAutocompleteStyle());
1414             for (auto& s : acs.words)
1415             {
1416                 refactoredline += (refactoredline.empty() ? "" : " ") + s.getQuoted();
1417             }
1418             line = refactoredline.c_str();
1419 #endif
1420 
1421             vector<string> words = getlistOfWords((char *)line);
1422 
1423             string clientWidth = "--client-width=";
1424             clientWidth+= SSTR(getNumberOfCols(80));
1425 
1426             words.insert(words.begin()+1, clientWidth);
1427 
1428             string scommandtoexec(words[0]);
1429             scommandtoexec+=" ";
1430             scommandtoexec+=clientWidth;
1431             scommandtoexec+=" ";
1432 
1433             if (strlen(line)>(words[0].size()+1))
1434             {
1435                 scommandtoexec+=line+words[0].size()+1;
1436             }
1437 
1438             const char *commandtoexec = scommandtoexec.c_str();
1439 
1440             bool helprequested = false;
1441             for (unsigned int i = 1; i< words.size(); i++)
1442             {
1443                 if (words[i]== "--help") helprequested = true;
1444             }
1445             if (words.size())
1446             {
1447                 if ( words[0] == "exit" || words[0] == "quit")
1448                 {
1449                     if (find(words.begin(), words.end(), "--only-shell") == words.end())
1450                     {
1451                         comms->executeCommand(commandtoexec, readresponse);
1452                     }
1453 
1454                     if (find(words.begin(), words.end(), "--help") == words.end()
1455                             && find(words.begin(), words.end(), "--only-server") == words.end() )
1456                     {
1457                         doExit = true;
1458                     }
1459                 }
1460 #if defined(_WIN32) || defined(__APPLE__)
1461                 else if (words[0] == "update")
1462                 {
1463                     MegaCmdShellCommunications::updating = true;
1464                     int ret = comms->executeCommand(commandtoexec, readresponse);
1465                     if (ret == MCMD_REQRESTART)
1466                     {
1467                         OUTSTREAM << "MEGAcmd has been updated ... this shell will be restarted before proceding...." << endl;
1468                         doExit = true;
1469                         doReboot = true;
1470                     }
1471                     else if (ret != MCMD_INVALIDSTATE && words.size() == 1)
1472                     {
1473                         MegaCmdShellCommunications::updating = false;
1474                     }
1475                 }
1476 #endif
1477                 else if (words[0] == "history")
1478                 {
1479                     if (helprequested)
1480                     {
1481                         OUTSTREAM << " Prints commands history" << endl;
1482                     }
1483                     else
1484                     {
1485                         printHistory();
1486                     }
1487                 }
1488 #if defined(_WIN32) && !defined(NO_READLINE)
1489                 else if (!helprequested && words[0] == "unicode" && words.size() == 1)
1490                 {
1491                     rl_getc_function=(rl_getc_function==&getcharacterreadlineUTF16support)?rl_getc:&getcharacterreadlineUTF16support;
1492                     OUTSTREAM << "Unicode shell input " << ((rl_getc_function==&getcharacterreadlineUTF16support)?"ENABLED":"DISABLED") << endl;
1493                     return;
1494                 }
1495 #endif
1496                 else if (!helprequested && words[0] == "passwd")
1497                 {
1498                     if (isserverloggedin())
1499                     {
1500                         passwdline = commandtoexec;
1501                         discardOptionsAndFlags(&words);
1502                         if (words.size() == 1)
1503                         {
1504                             setprompt(NEWPASSWORD);
1505                         }
1506                         else
1507                         {
1508                             comms->executeCommand(commandtoexec, readresponse);
1509                         }
1510                     }
1511                     else
1512                     {
1513                         cerr << "Not logged in." << endl;
1514                     }
1515 
1516                     return;
1517                 }
1518                 else if (!helprequested && words[0] == "login")
1519                 {
1520                     if (!isserverloggedin())
1521                     {
1522                         discardOptionsAndFlags(&words);
1523 
1524                         if ( (words.size() == 2 || ( words.size() == 3 && !words[2].size() ) )
1525                                 && (words[1].find("@") != string::npos))
1526                         {
1527                             loginname = words[1];
1528                             setprompt(LOGINPASSWORD);
1529                         }
1530                         else
1531                         {
1532                             string s = commandtoexec;
1533                             if (clientID.size())
1534                             {
1535                                 s = "login --clientID=";
1536                                 s+=clientID;
1537                                 s.append(string(commandtoexec).substr(5));
1538                             }
1539                             comms->executeCommand(s, readresponse);
1540                         }
1541                     }
1542                     else
1543                     {
1544                         cerr << "Already logged in. Please log out first." << endl;
1545                     }
1546                     return;
1547                 }
1548                 else if (!helprequested && words[0] == "signup")
1549                 {
1550                     if (!isserverloggedin())
1551                     {
1552                         signupline = commandtoexec;
1553                         discardOptionsAndFlags(&words);
1554 
1555                         if (words.size() == 2)
1556                         {
1557                             loginname = words[1];
1558                             signingup = true;
1559                             setprompt(NEWPASSWORD);
1560                         }
1561                         else
1562                         {
1563                             comms->executeCommand(commandtoexec, readresponse);
1564                         }
1565                     }
1566                     else
1567                     {
1568                         cerr << "Please loggout first." << endl;
1569                     }
1570                     return;
1571                 }
1572                 else if (!helprequested && words[0] == "confirm")
1573                 {
1574                     discardOptionsAndFlags(&words);
1575 
1576                     if (words.size() == 3)
1577                     {
1578                         linktoconfirm = words[1];
1579                         loginname = words[2];
1580                         confirminglink = true;
1581                         setprompt(LOGINPASSWORD);
1582                     }
1583                     else
1584                     {
1585                         comms->executeCommand(commandtoexec, readresponse);
1586                     }
1587                 }
1588                 else if (!helprequested && words[0] == "confirmcancel")
1589                 {
1590                     discardOptionsAndFlags(&words);
1591 
1592                     if (words.size() == 2)
1593                     {
1594                         linktoconfirm = words[1];
1595                         confirmingcancellink = true;
1596                         setprompt(LOGINPASSWORD);
1597                     }
1598                     else
1599                     {
1600                         comms->executeCommand(commandtoexec, readresponse);
1601                     }
1602                     return;
1603                 }
1604                 else if (!helprequested && words[0] == "clear")
1605                 {
1606 #ifdef _WIN32
1607                     HANDLE hStdOut;
1608                     CONSOLE_SCREEN_BUFFER_INFO csbi;
1609                     DWORD count;
1610 
1611                     hStdOut = GetStdHandle( STD_OUTPUT_HANDLE );
1612                     if (hStdOut == INVALID_HANDLE_VALUE) return;
1613 
1614                     /* Get the number of cells in the current buffer */
1615                     if (!GetConsoleScreenBufferInfo( hStdOut, &csbi )) return;
1616                     /* Fill the entire buffer with spaces */
1617                     if (!FillConsoleOutputCharacter( hStdOut, (TCHAR) ' ', csbi.dwSize.X *csbi.dwSize.Y, { 0, 0 }, &count ))
1618                     {
1619                         return;
1620                     }
1621                     /* Fill the entire buffer with the current colors and attributes */
1622                     if (!FillConsoleOutputAttribute(hStdOut, csbi.wAttributes, csbi.dwSize.X *csbi.dwSize.Y, { 0, 0 }, &count))
1623                     {
1624                         return;
1625                     }
1626                     /* Move the cursor home */
1627                     SetConsoleCursorPosition( hStdOut, { 0, 0 } );
1628 #elif __linux__
1629                     printf("\033[H\033[J");
1630 #else
1631                     rl_clear_screen(0,0);
1632 #endif
1633                     return;
1634                 }
1635                 else if ( (words[0] == "transfers"))
1636                 {
1637                     string toexec;
1638 
1639                     if (!strstr (commandtoexec,"path-display-size"))
1640                     {
1641                         unsigned int width = getNumberOfCols(75);
1642                         int pathSize = int((width-46)/2);
1643 
1644                         toexec+=words[0];
1645                         toexec+=" --path-display-size=";
1646                         toexec+=SSTR(pathSize);
1647                         toexec+=" ";
1648                         if (strlen(commandtoexec)>(words[0].size()+1))
1649                         {
1650                             toexec+=commandtoexec+words[0].size()+1;
1651                         }
1652                     }
1653                     else
1654                     {
1655                         toexec+=commandtoexec;
1656                     }
1657 
1658                     comms->executeCommand(toexec.c_str(), readresponse);
1659                 }
1660                 else if ( (words[0] == "du"))
1661                 {
1662                     string toexec;
1663 
1664                     if (!strstr (commandtoexec,"path-display-size"))
1665                     {
1666                         unsigned int width = getNumberOfCols(75);
1667                         int pathSize = int(width-13);
1668                         if (strstr(commandtoexec, "--versions"))
1669                         {
1670                             pathSize -= 11;
1671                         }
1672 
1673                         toexec+=words[0];
1674                         toexec+=" --path-display-size=";
1675                         toexec+=SSTR(pathSize);
1676                         toexec+=" ";
1677                         if (strlen(commandtoexec)>(words[0].size()+1))
1678                         {
1679                             toexec+=commandtoexec+words[0].size()+1;
1680                         }
1681                     }
1682                     else
1683                     {
1684                         toexec+=commandtoexec;
1685                     }
1686 
1687                     comms->executeCommand(toexec.c_str(), readresponse);
1688                 }
1689                 else if (words[0] == "sync")
1690                 {
1691                     string toexec;
1692 
1693                     if (!strstr (commandtoexec,"path-display-size"))
1694                     {
1695                         unsigned int width = getNumberOfCols(75);
1696                         int pathSize = int((width-46)/2);
1697 
1698                         toexec+="sync --path-display-size=";
1699                         toexec+=SSTR(pathSize);
1700                         toexec+=" ";
1701                         if (strlen(commandtoexec)>strlen("sync "))
1702                         {
1703                             toexec+=commandtoexec+strlen("sync ");
1704                         }
1705                     }
1706                     else
1707                     {
1708                         toexec+=commandtoexec;
1709                     }
1710 
1711                     comms->executeCommand(toexec.c_str(), readresponse);
1712                 }
1713                 else if (words[0] == "mediainfo")
1714                 {
1715                     string toexec;
1716 
1717                     if (!strstr (commandtoexec,"path-display-size"))
1718                     {
1719                         unsigned int width = getNumberOfCols(75);
1720                         int pathSize = int(width - 28);
1721 
1722                         toexec+=words[0];
1723                         toexec+=" --path-display-size=";
1724                         toexec+=SSTR(pathSize);
1725                         toexec+=" ";
1726                         if (strlen(commandtoexec)>(words[0].size()+1))
1727                         {
1728                             toexec+=commandtoexec+words[0].size()+1;
1729                         }
1730                     }
1731                     else
1732                     {
1733                         toexec+=commandtoexec;
1734                     }
1735 
1736                     comms->executeCommand(toexec.c_str(), readresponse);
1737                 }
1738                 else if (words[0] == "backup")
1739                 {
1740                     string toexec;
1741 
1742                     if (!strstr (commandtoexec,"path-display-size"))
1743                     {
1744                         unsigned int width = getNumberOfCols(75);
1745                         int pathSize = int((width-21)/2);
1746 
1747                         toexec+="backup --path-display-size=";
1748                         toexec+=SSTR(pathSize);
1749                         toexec+=" ";
1750                         if (strlen(commandtoexec)>strlen("backup "))
1751                         {
1752                             toexec+=commandtoexec+strlen("backup ");
1753                         }
1754                     }
1755                     else
1756                     {
1757                         toexec+=commandtoexec;
1758                     }
1759 
1760                     comms->executeCommand(toexec.c_str(), readresponse);
1761                 }
1762                 else
1763                 {
1764                     if ( words[0] == "get" || words[0] == "put" || words[0] == "reload")
1765                     {
1766                         string s = commandtoexec;
1767                         if (clientID.size())
1768                         {
1769                             string sline = commandtoexec;
1770                             size_t pspace = sline.find_first_of(" ");
1771                             s="";
1772                             s=sline.substr(0,pspace);
1773                             s += " --clientID=";
1774                             s+=clientID;
1775                             if (pspace!=string::npos)
1776                             {
1777                                 s+=sline.substr(pspace);
1778                             }
1779                             words.push_back(s);
1780                         }
1781                         comms->executeCommand(s, readresponse);
1782 #ifdef _WIN32
1783                         Sleep(200); // give a brief while to print progress ended
1784 #endif
1785                     }
1786                     else
1787                     {
1788                         // execute user command
1789                         comms->executeCommand(commandtoexec, readresponse);
1790                     }
1791                 }
1792             }
1793             else
1794             {
1795                 cerr << "failed to interprete input commandtoexec: " << commandtoexec << endl;
1796             }
1797             break;
1798         }
1799     }
1800 
1801 }
1802 
1803 // main loop
1804 #ifndef NO_READLINE
readloop()1805 void readloop()
1806 {
1807     time_t lasttimeretrycons = 0;
1808 
1809     char *saved_line = NULL;
1810     int saved_point = 0;
1811 
1812     rl_save_prompt();
1813 
1814     int readline_fd = -1;
1815 
1816     readline_fd = fileno(rl_instream);
1817 
1818     procesingline = true;
1819     comms->registerForStateChanges(true, statechangehandle);
1820 
1821 
1822     //now we can relay on having a prompt received if the server is running
1823     {
1824         std::unique_lock<std::mutex> lk(promptLogReceivedMutex);
1825         if (!promtpLogReceivedBool)
1826         {
1827             if (promtpLogReceivedCV.wait_for(lk, std::chrono::seconds(2*RESUME_SESSION_TIMEOUT)) == std::cv_status::timeout)
1828             {
1829                 std::cerr << "Server seems irresponsive" << endl;
1830             }
1831         }
1832     }
1833 
1834 
1835     procesingline = false;
1836     promptreinstalledwhenprocessingline = false;
1837 
1838 #if defined(_WIN32) && defined(USE_PORT_COMMS)
1839     // due to a failure in reconnecting to the socket, if the server was initiated in while registeringForStateChanges
1840     // in windows we would not be yet connected. we need to manually try to register again.
1841     if (comms->registerAgainRequired)
1842     {
1843         comms->registerForStateChanges(true, statechangehandle);
1844     }
1845     //give it a while to communicate the state
1846     sleepMilliSeconds(1);
1847 #endif
1848 
1849     for (;; )
1850     {
1851         if (prompt == COMMAND)
1852         {
1853             mutexPrompt.lock();
1854             if (requirepromptinstall)
1855             {
1856                 install_rl_handler(*dynamicprompt ? dynamicprompt : prompts[COMMAND], false);
1857 
1858                 // display prompt
1859                 if (saved_line)
1860                 {
1861                     rl_replace_line(saved_line, 0);
1862                     free(saved_line);
1863                     saved_line = NULL;
1864                 }
1865 
1866                 rl_point = saved_point;
1867                 rl_redisplay();
1868             }
1869             mutexPrompt.unlock();
1870         }
1871 
1872 
1873 
1874         // command editing loop - exits when a line is submitted
1875         for (;; )
1876         {
1877             if (prompt == COMMAND || prompt == AREYOUSURE)
1878             {
1879                 procesingline = false;
1880                 promptreinstalledwhenprocessingline = false;
1881 
1882                 wait_for_input(readline_fd);
1883 
1884                 std::lock_guard<std::mutex> g(mutexPrompt);
1885 
1886                 rl_callback_read_char(); //this calls store_line if last char was enter
1887 
1888                 time_t tnow = time(NULL);
1889                 if ( (tnow - lasttimeretrycons) > 5 && !doExit && !MegaCmdShellCommunications::updating)
1890                 {
1891                     char * sl = rl_copy_text(0, rl_end);
1892                     if (string("quit").find(sl) != 0 && string("exit").find(sl) != 0)
1893                     {
1894                         comms->executeCommand("retrycons");
1895                         lasttimeretrycons = tnow;
1896                     }
1897                     free(sl);
1898                 }
1899 
1900                 rl_resize_terminal(); // to always adjust to new screen sizes
1901 
1902                 if (doExit)
1903                 {
1904                     if (saved_line != NULL)
1905                         free(saved_line);
1906                     saved_line = NULL;
1907                     return;
1908                 }
1909             }
1910             else
1911             {
1912                 console_readpwchar(pw_buf, sizeof pw_buf, &pw_buf_pos, &line);
1913             }
1914 
1915             if (line)
1916             {
1917                 break;
1918             }
1919 
1920         }
1921 
1922         cleanLastMessage();// clean last message that avoids broadcasts repetitions
1923 
1924         mutexPrompt.lock();
1925         // save line
1926         saved_point = rl_point;
1927         if (saved_line != NULL)
1928             free(saved_line);
1929         saved_line = rl_copy_text(0, rl_end);
1930 
1931         // remove prompt
1932         rl_save_prompt();
1933         rl_replace_line("", 0);
1934         rl_redisplay();
1935 
1936         mutexPrompt.unlock();
1937         if (line)
1938         {
1939             if (strlen(line))
1940             {
1941                 alreadyFinished = false;
1942                 percentDowloaded = 0.0;
1943 
1944                 handlerOverridenByExternalThread = false;
1945                 process_line(line);
1946 
1947                 {
1948                     //after processing the line, we want to reinstall the handler (except if during the process, or due to it,
1949                     // the handler is reinstalled by e.g: a change in prompt)
1950                     std::lock_guard<std::mutex> lkrlhandler(handlerInstallerMutex);
1951                     if (!handlerOverridenByExternalThread)
1952                     {
1953                         requirepromptinstall = true;
1954                     }
1955                 }
1956 
1957                 if (comms->registerAgainRequired)
1958                 {
1959                     // register again for state changes
1960                      comms->registerForStateChanges(true, statechangehandle);
1961                      comms->registerAgainRequired = false;
1962                 }
1963 
1964                 // sleep, so that in case there was a changeprompt waiting, gets executed before relooping
1965                 // this is not 100% guaranteed to happen
1966                 sleepSeconds(0);
1967             }
1968             free(line);
1969             line = NULL;
1970         }
1971         if (doExit)
1972         {
1973             if (saved_line != NULL)
1974                 free(saved_line);
1975             saved_line = NULL;
1976             return;
1977         }
1978     }
1979 }
1980 #else  // NO_READLINE
readloop()1981 void readloop()
1982 {
1983     time_t lasttimeretrycons = 0;
1984 
1985     comms->registerForStateChanges(true, statechangehandle);
1986 
1987     //give it a while to communicate the state
1988     sleepMilliSeconds(700);
1989 
1990 #if defined(_WIN32) && defined(USE_PORT_COMMS)
1991     // due to a failure in reconnecting to the socket, if the server was initiated in while registeringForStateChanges
1992     // in windows we would not be yet connected. we need to manually try to register again.
1993     if (comms->registerAgainRequired)
1994     {
1995         comms->registerForStateChanges(true, statechangehandle);
1996     }
1997     //give it a while to communicate the state
1998     sleepMilliSeconds(1);
1999 #endif
2000 
2001     for (;; )
2002     {
2003         if (prompt == COMMAND)
2004         {
2005             console->updateInputPrompt(*dynamicprompt ? dynamicprompt : prompts[COMMAND]);
2006         }
2007 
2008         // command editing loop - exits when a line is submitted
2009         for (;; )
2010         {
2011             line = console->checkForCompletedInputLine();
2012 
2013 
2014             if (line)
2015             {
2016                 break;
2017             }
2018             else
2019             {
2020                 time_t tnow = time(NULL);
2021                 if ((tnow - lasttimeretrycons) > 5 && !doExit && !MegaCmdShellCommunications::updating)
2022                 {
2023                     if (wstring(L"quit").find(console->getInputLineToCursor()) != 0 &&
2024                          wstring(L"exit").find(console->getInputLineToCursor()) != 0   )
2025                     {
2026                         comms->executeCommand("retrycons");
2027                         lasttimeretrycons = tnow;
2028                     }
2029                 }
2030 
2031                 if (doExit)
2032                 {
2033                     return;
2034                 }
2035             }
2036         }
2037 
2038         cleanLastMessage();// clean last message that avoids broadcasts repetitions
2039 
2040         if (line)
2041         {
2042             if (strlen(line))
2043             {
2044                 alreadyFinished = false;
2045                 percentDowloaded = 0.0;
2046 //                mutexPrompt.lock();
2047                 process_line(line);
2048                 requirepromptinstall = true;
2049 //                mutexPrompt.unlock();
2050 
2051                 if (comms->registerAgainRequired)
2052                 {
2053                     // register again for state changes
2054                     comms->registerForStateChanges(true, statechangehandle);
2055                     comms->registerAgainRequired = false;
2056                 }
2057 
2058                 // sleep, so that in case there was a changeprompt waiting, gets executed before relooping
2059                 // this is not 100% guaranteed to happen
2060                 sleepSeconds(0);
2061             }
2062             free(line);
2063             line = NULL;
2064         }
2065         if (doExit)
2066         {
2067             return;
2068         }
2069     }
2070 }
2071 #endif
2072 
2073 class NullBuffer : public std::streambuf
2074 {
2075 public:
overflow(int c)2076     int overflow(int c)
2077     {
2078         return c;
2079     }
2080 };
2081 
printWelcomeMsg(unsigned int width)2082 void printWelcomeMsg(unsigned int width)
2083 {
2084     if (!width)
2085     {
2086         width = getNumberOfCols(75);
2087     }
2088 
2089     COUT << endl;
2090     COUT << ".";
2091     for (unsigned int i = 0; i < width; i++)
2092         COUT << "=" ;
2093     COUT << ".";
2094     COUT << endl;
2095     printCenteredLine(" __  __ _____ ____    _                      _ ",width);
2096     printCenteredLine("|  \\/  | ___|/ ___|  / \\   ___ _ __ ___   __| |",width);
2097     printCenteredLine("| |\\/| | \\  / |  _  / _ \\ / __| '_ ` _ \\ / _` |",width);
2098     printCenteredLine("| |  | | /__\\ |_| |/ ___ \\ (__| | | | | | (_| |",width);
2099     printCenteredLine("|_|  |_|____|\\____/_/   \\_\\___|_| |_| |_|\\__,_|",width);
2100 
2101     COUT << "|";
2102     for (unsigned int i = 0; i < width; i++)
2103         COUT << " " ;
2104     COUT << "|";
2105     COUT << endl;
2106     printCenteredLine("Welcome to MEGAcmd! A Command Line Interactive and Scriptable",width);
2107     printCenteredLine("Application to interact with your MEGA account.",width);
2108     printCenteredLine("Please write to support@mega.nz if you find any issue or",width);
2109     printCenteredLine("have any suggestion concerning its functionalities.",width);
2110     printCenteredLine("Enter \"help --non-interactive\" to learn how to use MEGAcmd with scripts.",width);
2111     printCenteredLine("Enter \"help\" for basic info and a list of available commands.",width);
2112 
2113 #if defined(_WIN32) && defined(NO_READLINE)
2114     printCenteredLine("Unicode support in the console is improved, see \"help --unicode\"", width);
2115 #elif defined(_WIN32)
2116     printCenteredLine("Enter \"help --unicode\" for info regarding non-ASCII support.",width);
2117 #endif
2118 
2119     COUT << "`";
2120     for (unsigned int i = 0; i < width; i++)
2121         COUT << "=" ;
2122     COUT << "´";
2123     COUT << endl;
2124 
2125 }
2126 
quote_detector(char * line,int index)2127 int quote_detector(char *line, int index)
2128 {
2129     return (
2130         index > 0 &&
2131         line[index - 1] == '\\' &&
2132         !quote_detector(line, index - 1)
2133     );
2134 }
2135 
runningInBackground()2136 bool runningInBackground()
2137 {
2138 #ifndef _WIN32
2139     pid_t fg = tcgetpgrp(STDIN_FILENO);
2140     if(fg == -1) {
2141         // Piped:
2142         return false;
2143     }  else if (fg == getpgrp()) {
2144         // foreground
2145         return false;
2146     } else {
2147         // background
2148         return true;
2149     }
2150 #endif
2151     return false;
2152 }
2153 
2154 #ifdef _WIN32
mycompletefunct(char ** c,int num_matches,int max_length)2155 void mycompletefunct(char **c, int num_matches, int max_length)
2156 {
2157     int rows = 1, cols = 80;
2158 
2159 #if defined( RL_ISSTATE ) && defined( RL_STATE_INITIALIZED )
2160 
2161             if (RL_ISSTATE(RL_STATE_INITIALIZED))
2162             {
2163                 rl_resize_terminal();
2164                 rl_get_screen_size(&rows, &cols);
2165             }
2166 #endif
2167 
2168 
2169     // max_length is not trustworthy
2170     for (int i=1; i <= num_matches; i++) //contrary to what the documentation says, num_matches is not the size of c (but num_matches+1), current text is preappended in c[0]
2171     {
2172         max_length = max(max_length,(int)strlen(c[i]));
2173     }
2174 
2175     OUTSTREAM << endl;
2176 
2177     int nelements_per_col = max(1,(cols-1)/(max_length+1));
2178     for (int i=1; i <= num_matches; i++) //contrary to what the documentation says, num_matches is not the size of c (but num_matches+1), current text is preappended in c[0]
2179     {
2180         string option = c[i];
2181 
2182         MegaCmdShellCommunications::megaCmdStdoutputing.lock();
2183         OUTSTREAM << setw(min(cols-1,max_length+1)) << left;
2184         int oldmode = _setmode(_fileno(stdout), _O_U16TEXT);
2185         OUTSTREAM << c[i];
2186         _setmode(_fileno(stdout), oldmode);
2187         MegaCmdShellCommunications::megaCmdStdoutputing.unlock();
2188 
2189         if ( (i%nelements_per_col == 0) && (i != num_matches))
2190         {
2191             OUTSTREAM << endl;
2192         }
2193     }
2194     OUTSTREAM << endl;
2195 }
2196 #endif
2197 
2198 #ifndef NO_READLINE
readresponse(const char * question)2199 std::string readresponse(const char* question)
2200 {
2201     string response;
2202     auto responseRaw = readline(question);
2203     if (responseRaw)
2204     {
2205         response = responseRaw;
2206     }
2207     rl_set_prompt("");
2208     rl_replace_line("", 0);
2209 
2210     rl_callback_handler_remove(); //To fix broken readline (e.g: upper key wouldnt work)
2211 
2212     return response;
2213 }
2214 #else
readresponse(const char * question)2215 std::string readresponse(const char* question)
2216 {
2217     COUT << question << flush;
2218     console->updateInputPrompt(question);
2219     for (;;)
2220     {
2221         if (char* line = console->checkForCompletedInputLine())
2222         {
2223             console->updateInputPrompt("");
2224             string response(line);
2225             free(line);
2226             return response;
2227         }
2228         else
2229         {
2230             sleepMilliSeconds(200);
2231         }
2232     }
2233 }
2234 #endif
2235 
2236 } //end namespace
2237 
2238 using namespace megacmd;
2239 
main(int argc,char * argv[])2240 int main(int argc, char* argv[])
2241 {
2242 
2243 #if defined(_WIN32) && !defined(NO_READLINE)
2244     // Set Environment's default locale
2245     setlocale(LC_ALL, "en-US");
2246     rl_completion_display_matches_hook = mycompletefunct;
2247 #endif
2248 
2249     // intialize the comms object
2250 #if defined(_WIN32) && !defined(USE_PORT_COMMS)
2251     comms = new MegaCmdShellCommunicationsNamedPipes();
2252 #else
2253     comms = new MegaCmdShellCommunications();
2254 #endif
2255 
2256 #ifndef NO_READLINE
2257     rl_attempted_completion_function = getCompletionMatches;
2258     rl_completer_quote_characters = "\"'";
2259     rl_filename_quote_characters  = " ";
2260     rl_completer_word_break_characters = (char *)" ";
2261 
2262     rl_char_is_quoted_p = &quote_detector;
2263 
2264     if (!runningInBackground())
2265     {
2266         rl_initialize(); // initializes readline,
2267         // so that we can use rl_message or rl_resize_terminal safely before ever
2268         // prompting anything.
2269     }
2270 #endif
2271 
2272 #if defined(_WIN32) && defined(NO_READLINE)
2273     console = new CONSOLE_CLASS;
2274     console->setAutocompleteSyntax(buildAutocompleteSyntax());
2275     console->setAutocompleteFunction(remote_completion);
2276     console->setShellConsole(CP_UTF8, GetConsoleOutputCP());
2277     console->blockingConsolePeek = true;
2278 #endif
2279 
2280 #ifdef _WIN32
2281     // in windows, rl_resize_terminal fails to resize before first prompt appears, we take the width from elsewhere
2282     CONSOLE_SCREEN_BUFFER_INFO csbi;
2283     int columns;
2284     GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi);
2285     columns = csbi.srWindow.Right - csbi.srWindow.Left - 2;
2286     printWelcomeMsg(columns);
2287 #else
2288     sleepMilliSeconds(200); // this gives a little while so that the console is ready and rl_resize_terminal works fine
2289     printWelcomeMsg();
2290 #endif
2291 
2292     readloop();
2293 
2294 #ifndef NO_READLINE
2295     clear_history();
2296     if (!doReboot)
2297     {
2298         rl_callback_handler_remove(); //To avoid having the terminal messed up (requiring a "reset")
2299     }
2300 #endif
2301     delete comms;
2302 
2303     if (doReboot)
2304     {
2305 #ifdef _WIN32
2306         sleepSeconds(5); // Give a while for server to restart
2307         LPWSTR szPathExecQuoted = GetCommandLineW();
2308         wstring wspathexec = wstring(szPathExecQuoted);
2309 
2310         if (wspathexec.at(0) == '"')
2311         {
2312             wspathexec = wspathexec.substr(1);
2313         }
2314         while (wspathexec.size() && ( wspathexec.at(wspathexec.size()-1) == '"' || wspathexec.at(wspathexec.size()-1) == ' ' ))
2315         {
2316             wspathexec = wspathexec.substr(0,wspathexec.size()-1);
2317         }
2318         LPWSTR szPathExec = (LPWSTR) wspathexec.c_str();
2319 
2320         STARTUPINFO si;
2321         PROCESS_INFORMATION pi;
2322         ZeroMemory( &si, sizeof(si) );
2323         ZeroMemory( &pi, sizeof(pi) );
2324         si.cb = sizeof(si);
2325 
2326         if (!CreateProcess( szPathExec,szPathExec,NULL,NULL,TRUE,
2327                             CREATE_NEW_CONSOLE,
2328                             NULL,NULL,
2329                             &si,&pi) )
2330         {
2331             COUT << "Unable to execute: " << szPathExec << " errno = : " << ERRNO << endl;
2332             sleepSeconds(5);
2333         }
2334 #elif defined(__linux__)
2335         system("reset -I");
2336         string executable = argv[0];
2337         if (executable.find("/") != 0)
2338         {
2339             executable.insert(0, getCurrentExecPath()+"/");
2340         }
2341         execv(executable.c_str(), argv);
2342 #else
2343         system("reset -I");
2344         execv(argv[0], argv);
2345 #endif
2346 
2347     }
2348 }
2349