| 1 | // ========================================================= // |
|---|
| 2 | // // |
|---|
| 3 | // File : xcmd.cxx // |
|---|
| 4 | // Purpose : support to run commands in (x)terminal // |
|---|
| 5 | // // |
|---|
| 6 | // Coded by Ralf Westram (coder@reallysoft.de) in Sep 25 // |
|---|
| 7 | // http://www.arb-home.de/ // |
|---|
| 8 | // // |
|---|
| 9 | // ========================================================= // |
|---|
| 10 | |
|---|
| 11 | #include "xcmd.hxx" |
|---|
| 12 | #include "dbserver.hxx" |
|---|
| 13 | |
|---|
| 14 | #include <arbdb.h> |
|---|
| 15 | #include <arb_strbuf.h> |
|---|
| 16 | #include <arb_sleep.h> |
|---|
| 17 | #include <aw_msg.hxx> |
|---|
| 18 | |
|---|
| 19 | using namespace std; |
|---|
| 20 | |
|---|
| 21 | // --------------------------- |
|---|
| 22 | // external commands |
|---|
| 23 | |
|---|
| 24 | #define SYNC_STATE_STARTED "started" |
|---|
| 25 | #define SYNC_STATE_FINISHED "finished" |
|---|
| 26 | |
|---|
| 27 | static GB_ERROR wait_for_sync__servingDB(GBDATA *gb_main, const char *sync_id) { |
|---|
| 28 | bool sync_reached = false; |
|---|
| 29 | GB_ERROR error = NULp; |
|---|
| 30 | |
|---|
| 31 | ARB_timeout show_wait(2, SEC); |
|---|
| 32 | ARB_timeout show_serve_error(500, MS); |
|---|
| 33 | |
|---|
| 34 | while (!sync_reached && !error) { |
|---|
| 35 | GB_ERROR serve_error = serve_db_while_GUI_is_blocked(gb_main); |
|---|
| 36 | if (serve_error) { |
|---|
| 37 | if (show_serve_error.passed()) { |
|---|
| 38 | // Note: this error is unexpected and might indicate a severe problem (deadlock, broken design, ...) |
|---|
| 39 | fprintf(stderr, "Error serving DB (while waiting for sync '%s'): %s\n", sync_id, serve_error); |
|---|
| 40 | show_serve_error.restart(1.7); |
|---|
| 41 | } |
|---|
| 42 | } |
|---|
| 43 | |
|---|
| 44 | GB_transaction ta(gb_main); |
|---|
| 45 | GB_CSTR value = GB_read_sync_value(gb_main, sync_id); |
|---|
| 46 | if (value) { |
|---|
| 47 | if (strcmp(value, SYNC_STATE_FINISHED) == 0) { |
|---|
| 48 | fprintf(stderr, "reached expected sync on ID '%s': current value is '%s'\n", sync_id, value); |
|---|
| 49 | sync_reached = true; |
|---|
| 50 | } |
|---|
| 51 | else if (strcmp(value, SYNC_STATE_STARTED) != 0) { |
|---|
| 52 | error = GBS_global_string("sync-ID '%s' has unexpected value '%s'", sync_id, value); |
|---|
| 53 | } |
|---|
| 54 | else { |
|---|
| 55 | if (show_wait.passed()) { |
|---|
| 56 | fprintf(stderr, "waiting for sync on ID '%s': current value is '%s'\n", sync_id, value); |
|---|
| 57 | show_wait.restart(1.7); |
|---|
| 58 | } |
|---|
| 59 | } |
|---|
| 60 | } |
|---|
| 61 | else { |
|---|
| 62 | error = GB_await_error(); |
|---|
| 63 | } |
|---|
| 64 | } |
|---|
| 65 | |
|---|
| 66 | return error; |
|---|
| 67 | } |
|---|
| 68 | |
|---|
| 69 | GB_ERROR ARB_system(const char *cmd, XCmdType boundExectype) { |
|---|
| 70 | XCMD_TYPE exectype = boundExectype.get_type(); |
|---|
| 71 | GBDATA *gb_main = boundExectype.get_gb_main(); |
|---|
| 72 | |
|---|
| 73 | // runs a command in an xterm |
|---|
| 74 | |
|---|
| 75 | bool background = exectype & _XCMD__ASYNC; // true -> run asynchronous |
|---|
| 76 | bool requires_DB_access = exectype & _XCMD__ACCESS_DB; // true -> assume shell process requires DB access |
|---|
| 77 | bool always_wait_key = exectype & _XCMD__WAITKEY; // true -> always wait for keypress (otherwise only wait if 'cmd' fails) |
|---|
| 78 | bool hidden = exectype & _XCMD__HIDDEN; // true -> do not use terminal window |
|---|
| 79 | char *sync_via_ID = NULp; |
|---|
| 80 | |
|---|
| 81 | if (!background && requires_DB_access) { |
|---|
| 82 | if (GB_is_server(gb_main)) { |
|---|
| 83 | static int sync_counter = 0; |
|---|
| 84 | sync_via_ID = GBS_global_string_copy("xcmd%i", ++sync_counter); |
|---|
| 85 | |
|---|
| 86 | // run shell command asynchronously (synchronisation will be done via database) |
|---|
| 87 | background = true; |
|---|
| 88 | |
|---|
| 89 | fprintf(stderr, "synchronise command using ID '%s'. command=%s\n", sync_via_ID, cmd); |
|---|
| 90 | } |
|---|
| 91 | // else: we are a client -> database access is possible from synchronous shell command |
|---|
| 92 | // (Note: this might fail, if the client itself currently blocks the server) |
|---|
| 93 | } |
|---|
| 94 | |
|---|
| 95 | const int BUFSIZE = 4096; |
|---|
| 96 | GBS_strstruct system_call(BUFSIZE); |
|---|
| 97 | |
|---|
| 98 | system_call.put('('); |
|---|
| 99 | if (!hidden) system_call.cat(GB_getenvARB_XCMD()); |
|---|
| 100 | |
|---|
| 101 | { |
|---|
| 102 | GBS_strstruct bash_command(BUFSIZE); |
|---|
| 103 | |
|---|
| 104 | GB_CSTR ldlibpath = GB_getenv("LD_LIBRARY_PATH"); // due to SIP ldlibpath is always NULL under osx |
|---|
| 105 | // (should not matter because all binaries are linked statically) |
|---|
| 106 | |
|---|
| 107 | if (ldlibpath) { |
|---|
| 108 | bash_command.cat("LD_LIBRARY_PATH="); |
|---|
| 109 | { |
|---|
| 110 | char *dquoted_library_path = GBK_doublequote(ldlibpath); |
|---|
| 111 | bash_command.cat(dquoted_library_path); |
|---|
| 112 | free(dquoted_library_path); |
|---|
| 113 | } |
|---|
| 114 | bash_command.cat(";export LD_LIBRARY_PATH;"); |
|---|
| 115 | } |
|---|
| 116 | #if !defined(DARWIN) |
|---|
| 117 | else { |
|---|
| 118 | // under Linux it normally is a problem if LD_LIBRARY_PATH is undefined |
|---|
| 119 | // -> warn in terminal to provide a hint: |
|---|
| 120 | bash_command.cat("echo 'Warning: LD_LIBRARY_PATH is undefined';"); |
|---|
| 121 | } |
|---|
| 122 | #endif |
|---|
| 123 | |
|---|
| 124 | bash_command.cat(" ("); |
|---|
| 125 | bash_command.cat(cmd); |
|---|
| 126 | |
|---|
| 127 | { |
|---|
| 128 | char *sync_command = sync_via_ID ? GBS_global_string_copy("arb_sync --write %s " SYNC_STATE_FINISHED, sync_via_ID) : NULp; |
|---|
| 129 | const char *wait_commands = hidden ? "sleep 1" : "echo; echo 'Press ENTER to close this window'; read a"; |
|---|
| 130 | |
|---|
| 131 | if (always_wait_key) { |
|---|
| 132 | bash_command.cat("; [ $? -ne 0 ] && echo \"Command terminated with failure (exit code $?)\""); // display failure |
|---|
| 133 | bash_command.cat("; "); |
|---|
| 134 | } |
|---|
| 135 | else { |
|---|
| 136 | bash_command.cat("; xc=$?"); // store exitcode of command |
|---|
| 137 | if (sync_command) { |
|---|
| 138 | bash_command.cat("; "); |
|---|
| 139 | bash_command.cat(sync_command); // sync arb macro execution |
|---|
| 140 | } |
|---|
| 141 | bash_command.cat("; [ $xc -ne 0 ] && echo \"Command terminated with failure (exit code $xc)\""); // display failure |
|---|
| 142 | bash_command.cat("; exit $xc"); // restore exitcode of command |
|---|
| 143 | bash_command.cat(") || ("); |
|---|
| 144 | } |
|---|
| 145 | bash_command.cat(wait_commands); |
|---|
| 146 | if (always_wait_key && sync_command) { |
|---|
| 147 | bash_command.cat("; "); |
|---|
| 148 | bash_command.cat(sync_command); // sync arb macro execution |
|---|
| 149 | } |
|---|
| 150 | bash_command.put(')'); |
|---|
| 151 | |
|---|
| 152 | free(sync_command); |
|---|
| 153 | } |
|---|
| 154 | |
|---|
| 155 | char *squoted_bash_command = GBK_singlequote(bash_command.get_data()); |
|---|
| 156 | system_call.cat(" bash -c "); |
|---|
| 157 | system_call.cat(squoted_bash_command); |
|---|
| 158 | free(squoted_bash_command); |
|---|
| 159 | } |
|---|
| 160 | system_call.cat(" )"); |
|---|
| 161 | if (background) system_call.cat(" &"); |
|---|
| 162 | |
|---|
| 163 | GB_ERROR error = NULp; |
|---|
| 164 | if (sync_via_ID) { |
|---|
| 165 | GB_transaction ta(gb_main); |
|---|
| 166 | error = GB_write_sync_value(gb_main, sync_via_ID, SYNC_STATE_STARTED); |
|---|
| 167 | } |
|---|
| 168 | |
|---|
| 169 | if (!error) { |
|---|
| 170 | error = GBK_system(system_call.get_data()); |
|---|
| 171 | // if error occurs here (or before) -> sync value will never change |
|---|
| 172 | } |
|---|
| 173 | |
|---|
| 174 | if (sync_via_ID && !error) { |
|---|
| 175 | error = wait_for_sync__servingDB(gb_main, sync_via_ID); |
|---|
| 176 | } |
|---|
| 177 | |
|---|
| 178 | freenull(sync_via_ID); |
|---|
| 179 | |
|---|
| 180 | return error; |
|---|
| 181 | } |
|---|
| 182 | |
|---|
| 183 | void ARB_system_in_console_cb(AW_window*, const char *command, const XCmdType *exectype) { |
|---|
| 184 | // like ARB_system, but usable as WindowCallback + displays errors as message. |
|---|
| 185 | aw_message_if(ARB_system(command, *exectype)); |
|---|
| 186 | } |
|---|
| 187 | |
|---|