/* libmondo-fork.c $Id: libmondo-fork.c 2316 2009-08-18 12:34:29Z bruno $ - subroutines for handling forking/pthreads/etc. */ #include "my-stuff.h" #include "mr_mem.h" #include "mondostructures.h" #include "libmondo-fork.h" #include "libmondo-string-EXT.h" #include "libmondo-gui-EXT.h" #include "libmondo-files-EXT.h" #include "libmondo-tools-EXT.h" #include "lib-common-externs.h" /*@unused@*/ //static char cvsid[] = "$Id: libmondo-fork.c 2316 2009-08-18 12:34:29Z bruno $"; extern t_bkptype g_backup_media_type; extern bool g_text_mode; extern char *MONDO_LOGFILE; /* Reference to global bkpinfo */ extern struct s_bkpinfo *bkpinfo; pid_t g_buffer_pid = 0; /** * Call a program and retrieve its last line of output. * @param call The program to run. * @return The last line of its output. * @note The returned value points to static storage that will be overwritten with each call. */ char *call_program_and_get_last_line_of_output(char *call) { /*@ buffers ***************************************************** */ static char result[512]; char *tmp; /*@ pointers **************************************************** */ FILE *fin; /*@ initialize data ********************************************* */ malloc_string(tmp); result[0] = '\0'; tmp[0] = '\0'; /*@******************************************************************** */ assert_string_is_neither_NULL_nor_zerolength(call); if ((fin = popen(call, "r"))) { for (fgets(tmp, MAX_STR_LEN, fin); !feof(fin); fgets(tmp, MAX_STR_LEN, fin)) { if (strlen(tmp) > 1) { strcpy(result, tmp); } } paranoid_pclose(fin); } else { log_OS_error("Unable to popen call"); } strip_spaces(result); paranoid_free(tmp); return (result); } #define MONDO_POPMSG "Your PC will not retract the CD tray automatically. Please call mondoarchive with the -m (manual CD tray) flag." /** * Call mkisofs to create an ISO image. * @param bkpinfo The backup information structure. Fields used: * - @c bkpinfo->manual_cd_tray * - @c bkpinfo->backup_media_type * - @c bkpinfo->please_dont_eject_when_restoring * @param basic_call The call to mkisofs. May contain tokens that will be resolved to actual data. The tokens are: * - @c _ISO_ will become the ISO file (@p isofile) * - @c _CD#_ becomes the CD number (@p cd_no) * - @c _ERR_ becomes the logfile (@p g_logfile) * @param isofile Replaces @c _ISO_ in @p basic_call. Should probably be the ISO image to create (-o parameter to mkisofs). * @param cd_no Replaces @c _CD#_ in @p basic_call. Should probably be the CD number. * @param logstub Unused. * @param what_i_am_doing The action taking place (e.g. "Making ISO #1"). Used as the title of the progress dialog. * @return Exit code of @c mkisofs (0 is success, anything else indicates failure). * @bug @p logstub is unused. */ int eval_call_to_make_ISO(char *basic_call, char *isofile, int cd_no, char *logstub, char *what_i_am_doing) { /*@ int's *** */ int retval = 0; /*@ buffers *** */ char *midway_call = NULL; char *ultimate_call = NULL; char *tmp = NULL; char *incoming, *old_stderr; char *cd_number_str = NULL; char *command = NULL; char *p; char *tmp1 = NULL; char *tmp2 = NULL; /*@*********** End Variables ***************************************/ log_msg(3, "Starting"); assert(bkpinfo != NULL); assert_string_is_neither_NULL_nor_zerolength(isofile); assert_string_is_neither_NULL_nor_zerolength(logstub); malloc_string(incoming); malloc_string(old_stderr); incoming[0] = '\0'; old_stderr[0] = '\0'; if (bkpinfo->nfs_user) { mr_asprintf(&tmp1, "su - %s -c \"%s\"", bkpinfo->nfs_user, basic_call); } else { mr_asprintf(&tmp1, "%s", basic_call); } mr_asprintf(&cd_number_str, "%d", cd_no); log_msg(4, "basic call = '%s'", tmp1); midway_call = resolve_naff_tokens(tmp1, isofile, "_ISO_"); mr_free(tmp1); log_msg(4, "midway_call = '%s'", midway_call); tmp = resolve_naff_tokens(midway_call, cd_number_str, "_CD#_"); mr_free(cd_number_str); mr_free(midway_call); log_msg(4, "tmp = '%s'", tmp); ultimate_call = resolve_naff_tokens(tmp, MONDO_LOGFILE, "_ERR_"); mr_free(tmp); log_msg(4, "ultimate call = '%s'", ultimate_call); mr_asprintf(&command, "%s >> %s", ultimate_call, MONDO_LOGFILE); mr_free(ultimate_call); log_to_screen ("Please be patient. Do not be alarmed by on-screen inactivity."); log_msg(4, "Calling open_evalcall_form() with what_i_am_doing='%s'", what_i_am_doing); if (bkpinfo->manual_cd_tray) { mr_asprintf(&tmp2, "%s", command); p = strstr(tmp2, "2>>"); if (p) { *p++ = ' '; *p++ = ' '; *p++ = ' '; while (*p == ' ') { p++; } for (; (*p != ' ') && (*p != '\0'); p++) { *p = ' '; } } mr_free(command); command = tmp2; #ifndef _XWIN if (!g_text_mode) { newtSuspend(); } #endif log_msg(1, "command = '%s'", command); retval += system(command); if (!g_text_mode) { newtResume(); } if (retval) { popup_and_OK("mkisofs and/or cdrecord returned an error. CD was not created"); } } /* if text mode then do the above & RETURN; if not text mode, do this... */ else { log_msg(3, "command = '%s'", command); retval = run_external_binary_with_percentage_indicator_NEW(what_i_am_doing, command); } mr_free(command); paranoid_free(incoming); paranoid_free(old_stderr); return (retval); } /** * Call copy of data to create an USB image. * @param bkpinfo The backup information structure. Fields used: * - @c bkpinfo->backup_media_type * @return Exit code of @c copy (0 is success, anything else indicates failure). */ int eval_call_to_make_USB(char *command, char *what_i_am_doing) { /*@ int's *** */ int retval = 0; /*@*********** End Variables ***************************************/ log_msg(3, "Starting"); assert(bkpinfo != NULL); log_to_screen ("Please be patient. Do not be alarmed by on-screen inactivity."); log_msg(4, "Calling open_evalcall_form() with what_i_am_doing='%s'", what_i_am_doing); if (!g_text_mode) { newtSuspend(); } log_msg(1, "command = '%s'", command); if (!g_text_mode) { retval = run_external_binary_with_percentage_indicator_NEW (what_i_am_doing, command); } else { retval += system(command); } if (!g_text_mode) { newtResume(); } return (retval); } /** * Run a program and log its output (stdout and stderr) to the logfile. * @param program The program to run. Passed to the shell, so you can use pipes etc. * @param debug_level If @p g_loglevel is higher than this, do not log the output. * @return The exit code of @p program (depends on the command, but 0 almost always indicates success). */ int run_program_and_log_output(char *program, int debug_level) { /*@ buffer ****************************************************** */ char *callstr = NULL; char incoming[MAX_STR_LEN * 2]; char *tmp1 = NULL; /*@ int ********************************************************* */ int res; int i; bool log_if_failure = FALSE; bool log_if_success = FALSE; /*@ pointers *************************************************** */ FILE *fin; char *p; /*@ end vars *************************************************** */ assert(program != NULL); if (!program[0]) { log_msg(2, "Warning - asked to run zerolength program"); return (1); } if (debug_level <= g_loglevel) { log_if_success = TRUE; log_if_failure = TRUE; } mr_asprintf(&callstr, "%s > %s/mondo-run-prog-thing.tmp 2> %s/mondo-run-prog-thing.err", program, bkpinfo->tmpdir, bkpinfo->tmpdir); while ((p = strchr(callstr, '\r'))) { *p = ' '; } while ((p = strchr(callstr, '\n'))) { *p = ' '; } /* single '=' is intentional */ res = system(callstr); if (((res == 0) && log_if_success) || ((res != 0) && log_if_failure)) { log_msg(0, "running: %s", callstr); log_msg(0, "--------------------------------start of output-----------------------------"); } mr_free(callstr); mr_asprintf(&callstr, "cat %s/mondo-run-prog-thing.err >> %s/mondo-run-prog-thing.tmp 2> /dev/null", bkpinfo->tmpdir, bkpinfo->tmpdir); if (log_if_failure && system(callstr)) { log_OS_error("Command failed"); } mr_free(callstr); mr_asprintf(&tmp1, "%s/mondo-run-prog-thing.err", bkpinfo->tmpdir); unlink(tmp1); mr_free(tmp1); mr_asprintf(&tmp1, "%s/mondo-run-prog-thing.tmp", bkpinfo->tmpdir); fin = fopen(tmp1, "r"); if (fin) { for (fgets(incoming, MAX_STR_LEN, fin); !feof(fin); fgets(incoming, MAX_STR_LEN, fin)) { p = incoming; while (p && *p) { if ((p = strchr(p, '%'))) { memmove(p, p + 1, strlen(p) + 1); p += 2; } } strip_spaces(incoming); if ((res == 0 && log_if_success) || (res != 0 && log_if_failure)) { log_msg(0, incoming); } } paranoid_fclose(fin); } unlink(tmp1); mr_free(tmp1); if ((res == 0 && log_if_success) || (res != 0 && log_if_failure)) { log_msg(0, "--------------------------------end of output------------------------------"); if (res) { log_msg(0, "...ran with res=%d", res); } else { log_msg(0, "...ran just fine. :-)"); } } return (res); } /** * Run a program and log its output to the screen. * @param basic_call The program to run. * @param what_i_am_doing The title of the evalcall form. * @return The return value of the command (varies, but 0 almost always means success). * @see run_program_and_log_output * @see log_to_screen */ int run_program_and_log_to_screen(char *basic_call, char *what_i_am_doing) { /*@ int ******************************************************** */ int retval = 0; int res = 0; int i; /*@ pointers **************************************************** */ FILE *fin; /*@ buffers **************************************************** */ char *tmp = NULL; char *command = NULL; char *lockfile = NULL; char tmp1[MAX_STR_LEN *2]; /*@ end vars *************************************************** */ assert_string_is_neither_NULL_nor_zerolength(basic_call); mr_asprintf(&lockfile, "%s/mojo-jojo.bla.bla", bkpinfo->tmpdir); mr_asprintf(&command, "echo hi > %s ; %s >> %s 2>> %s; res=$?; sleep 1; rm -f %s; exit $res", lockfile, basic_call, MONDO_LOGFILE, MONDO_LOGFILE, lockfile); open_evalcall_form(what_i_am_doing); mr_asprintf(&tmp, "Executing %s", basic_call); log_msg(2, tmp); mr_free(tmp); if (!(fin = popen(command, "r"))) { log_OS_error("Unable to popen-in command"); mr_asprintf(&tmp, "Failed utterly to call '%s'", command); log_to_screen(tmp); mr_free(tmp); mr_free(command); mr_free(lockfile); return (1); } mr_free(command); if (!does_file_exist(lockfile)) { log_to_screen("Waiting for external binary to start"); for (i = 0; i < 60 && !does_file_exist(lockfile); sleep(1), i++) { log_msg(3, "Waiting for lockfile %s to exist", lockfile); } } #ifdef _XWIN /* This only can update when newline goes into the file, but it's *much* prettier/faster on Qt. */ while (does_file_exist(lockfile)) { while (!feof(fin)) { if (!fgets(tmp1, 512, fin)) break; log_to_screen(tmp1); } usleep(500000); } #else /* This works on Newt, and it gives quicker updates. */ for (; does_file_exist(lockfile); sleep(1)) { log_file_end_to_screen(MONDO_LOGFILE, ""); update_evalcall_form(1); } #endif /* Evaluate the status returned by pclose to get the exit code of the called program. */ errno = 0; res = pclose(fin); /* Log actual pclose errors. */ if (errno) log_msg(5, "pclose err: %d", errno); /* Check if we have a valid status. If we do, extract the called program's exit code. */ /* If we don't, highlight this fact by returning -1. */ if (WIFEXITED(res)) { retval = WEXITSTATUS(res); } else { retval = -1; } close_evalcall_form(); unlink(lockfile); mr_free(lockfile); return (retval); } /** * Apparently unused. @bug This has a purpose, but what? */ #define PIMP_START_SZ "STARTSTARTSTART9ff3kff9a82gv34r7fghbkaBBC2T231hc81h42vws8" #define PIMP_END_SZ "ENDENDEND0xBBC10xBBC2T231hc81h42vws89ff3kff9a82gv34r7fghbka" int copy_from_src_to_dest(FILE * f_orig, FILE * f_archived, char direction) { // if dir=='w' then copy from orig to archived // if dir=='r' then copy from archived to orig char *tmp = NULL; char *tmp1 = NULL; char *buf = NULL; char *filestr = NULL; long int bytes_to_be_read, bytes_read_in, bytes_written_out = 0, bufcap, subsliceno = 0; int retval = 0; FILE *fin; FILE *fout; FILE *ftmp; int tmpcap = 512; log_msg(5, "Opening."); bufcap = 256L * 1024L; if (!(buf = malloc(bufcap))) { fatal_error("Failed to malloc() buf"); } if (direction == 'w') { fin = f_orig; fout = f_archived; mr_asprintf(&tmp, "%-64s", PIMP_START_SZ); if (fwrite(tmp, 1, 64, fout) != 64) { mr_free(tmp); fatal_error("Can't write the introductory block"); } mr_free(tmp); while (1) { bytes_to_be_read = bytes_read_in = fread(buf, 1, bufcap, fin); if (bytes_read_in == 0) { break; } mr_asprintf(&tmp, "%-64ld", bytes_read_in); if (fwrite(tmp, 1, 64, fout) != 64) { mr_free(tmp); fatal_error("Cannot write introductory block"); } mr_free(tmp); log_msg(7, "subslice #%ld --- I have read %ld of %ld bytes in from f_orig", subsliceno, bytes_read_in, bytes_to_be_read); bytes_written_out += fwrite(buf, 1, bytes_read_in, fout); mr_asprintf(&tmp, "%-64ld", subsliceno); if (fwrite(tmp, 1, 64, fout) != 64) { mr_free(tmp); fatal_error("Cannot write post-thingy block"); } mr_free(tmp); log_msg(7, "Subslice #%d written OK", subsliceno); subsliceno++; } mr_asprintf(&tmp, "%-64ld", 0L); if (fwrite(tmp, 1, 64L, fout) != 64L) { mr_free(tmp); fatal_error("Cannot write final introductory block"); } mr_free(tmp); mr_asprintf(&tmp, "%-64s", PIMP_END_SZ); if (fwrite(tmp, 1, 64, fout) != 64) { mr_free(tmp); fatal_error("Can't write the final block"); } mr_free(tmp); } else { fin = f_archived; fout = f_orig; if (!(tmp1 = malloc(tmpcap))) { fatal_error("Failed to malloc() tmp"); } if (fread(tmp1, 1, 64L, fin) != 64L) { mr_free(tmp1); fatal_error("Cannot read the introductory block"); } log_msg(5, "tmp1 is %s", tmp1); if (!strstr(tmp1, PIMP_START_SZ)) { mr_free(tmp1); fatal_error("Can't find intro blk"); } if (fread(tmp1, 1, 64L, fin) != 64L) { mr_free(tmp1); fatal_error("Cannot read introductory blk"); } bytes_to_be_read = atol(tmp1); while (bytes_to_be_read > 0) { log_msg(7, "subslice#%ld, bytes=%ld", subsliceno, bytes_to_be_read); bytes_read_in = fread(buf, 1, bytes_to_be_read, fin); if (bytes_read_in != bytes_to_be_read) { mr_free(tmp1); fatal_error("Danger, WIll Robinson. Failed to read whole subvol from archives."); } bytes_written_out += fwrite(buf, 1, bytes_read_in, fout); if (fread(tmp1, 1, 64, fin) != 64) { mr_free(tmp1); fatal_error("Cannot read post-thingy block"); } if (atol(tmp1) != subsliceno) { log_msg(1, "Wanted subslice %ld but got %ld ('%s')", subsliceno, atol(tmp1), tmp1); } log_msg(7, "Subslice #%ld read OK", subsliceno); subsliceno++; if (fread(tmp1, 1, 64, fin) != 64) { mr_free(tmp1); fatal_error("Cannot read introductory block"); } bytes_to_be_read = atol(tmp1); } log_msg(1, "tmpA is %s", tmp1); if (!strstr(tmp1, PIMP_END_SZ)) { if (fread(tmp1, 1, 64, fin) != 64) { mr_free(tmp1); fatal_error("Can't read the final block"); } log_msg(5, "tmpB is %s", tmp1); if (!strstr(tmp1, PIMP_END_SZ)) { mr_asprintf(&filestr, "%s/out.leftover", bkpinfo->tmpdir); ftmp = fopen(filestr, "w"); mr_free(filestr); bytes_read_in = fread(tmp1, 1, 64, fin); log_msg(1, "bytes_read_in = %ld", bytes_read_in); fwrite(tmp1, 1, bytes_read_in, ftmp); fread(tmp1, 1, tmpcap, fin); log_msg(0, "tmp1 = '%s'", tmp1); fwrite(tmp1, 1, tmpcap, ftmp); fclose(ftmp); mr_free(tmp1); fatal_error("Missing terminating block"); } } mr_free(tmp1); } paranoid_free(buf); log_msg(3, "Successfully copied %ld bytes", bytes_written_out); return (retval); } /** * Feed @p input_device through ntfsclone to @p output_fname. * @param input_device The device to image. * @param output_fname The file to write. * @return 0 for success, nonzero for failure. */ int feed_into_ntfsprog(char *input_device, char *output_fname) { // BACKUP int res = -1; char*command = NULL; if (!does_file_exist(input_device)) { fatal_error("input device does not exist"); } if ( !find_home_of_exe("ntfsclone")) { fatal_error("ntfsclone not found"); } mr_asprintf(&command, "ntfsclone --force --save-image --overwrite %s %s", output_fname, input_device); res = run_program_and_log_output(command, 5); mr_free(command); unlink(output_fname); return (res); } void *run_prog_in_bkgd_then_exit(void *info) { char *sz_command; static int res = 4444; res = 999; sz_command = (char *) info; log_msg(4, "sz_command = '%s'", sz_command); res = system(sz_command); if (res > 256 && res != 4444) { res = res / 256; } log_msg(4, "child res = %d", res); sz_command[0] = '\0'; pthread_exit((void *) (&res)); } int run_external_binary_with_percentage_indicator_NEW(char *tt, char *cmd) { /*@ int *************************************************************** */ int res = 0; int percentage = 0; int maxpc = 100; int pcno = 0; int last_pcno = 0; int counter = 0; /*@ buffers *********************************************************** */ char *command = NULL; char *title; /*@ pointers ********************************************************** */ static int chldres = 0; int *pchild_result; pthread_t childthread; pchild_result = &chldres; assert_string_is_neither_NULL_nor_zerolength(cmd); assert_string_is_neither_NULL_nor_zerolength(tt); *pchild_result = 999; malloc_string(title); strcpy(title, tt); mr_asprintf(&command, "%s 2>> %s", cmd, MONDO_LOGFILE); log_msg(3, "command = '%s'", command); if ((res = pthread_create(&childthread, NULL, run_prog_in_bkgd_then_exit, (void *) command))) { fatal_error("Unable to create an archival thread"); } log_msg(8, "Parent running"); open_evalcall_form(title); for (sleep(1); command[0] != '\0'; sleep(1)) { pcno = grab_percentage_from_last_line_of_file(MONDO_LOGFILE); if (pcno <= 0 || pcno > 100) { log_msg(8, "Weird pc#"); continue; } percentage = pcno * 100 / maxpc; if (pcno <= 5 && last_pcno >= 40) { close_evalcall_form(); strcpy(title, "Verifying..."); open_evalcall_form(title); } if (counter++ >= 5) { counter = 0; log_file_end_to_screen(MONDO_LOGFILE, ""); } last_pcno = pcno; update_evalcall_form(percentage); } mr_free(command); log_file_end_to_screen(MONDO_LOGFILE, ""); close_evalcall_form(); pthread_join(childthread, (void *) (&pchild_result)); if (pchild_result) { res = *pchild_result; } else { res = 666; } log_msg(3, "Parent res = %d", res); paranoid_free(title); return (res); } /** * Feed @p input_fifo through ntfsclone (restore) to @p output_device. * @param input_fifo The ntfsclone file to read. * @param output_device Where to put the output. * @return The return value of ntfsclone (0 for success). */ int feed_outfrom_ntfsprog(char *output_device, char *input_fifo) { // RESTORE int res = -1; char *command = NULL; if ( !find_home_of_exe("ntfsclone")) { fatal_error("ntfsclone not found"); } mr_asprintf(&command, "ntfsclone --force --restore-image --overwrite %s %s", output_device, input_fifo); res = run_program_and_log_output(command, 5); mr_free(command); return (res); }