X-Git-Url: https://git.sur5r.net/?a=blobdiff_plain;ds=sidebyside;f=bacula%2Fsrc%2Flib%2Futil.c;h=82f8ee3cbf3af16893b1d4978df9d76db417d05d;hb=5495c4c46f7a97e8423044ba65e2fde4919155da;hp=8f91db6c67a44a3e369169963c2503ac57d7c123;hpb=9d8607727598a4ece6597153c42c11d9e4483033;p=bacula%2Fbacula diff --git a/bacula/src/lib/util.c b/bacula/src/lib/util.c index 8f91db6c67..82f8ee3cbf 100644 --- a/bacula/src/lib/util.c +++ b/bacula/src/lib/util.c @@ -1,29 +1,36 @@ /* - * util.c miscellaneous utility subroutines for Bacula - * - * Kern Sibbald, MM - * - * Version $Id$ - */ + Bacula® - The Network Backup Solution -/* - Copyright (C) 2000, 2001, 2002 Kern Sibbald and John Walker + Copyright (C) 2000-2009 Free Software Foundation Europe e.V. - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License, or (at your option) any later version. + The main author of Bacula is Kern Sibbald, with contributions from + many others, a complete list can be found in the file AUTHORS. + This program is Free Software; you can redistribute it and/or + modify it under the terms of version two of the GNU General Public + License as published by the Free Software Foundation and included + in the file LICENSE. - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public - License along with this program; if not, write to the Free - Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, - MA 02111-1307, USA. + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. + Bacula® is a registered trademark of Kern Sibbald. + The licensor of Bacula is the Free Software Foundation Europe + (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich, + Switzerland, email:ftf@fsfeurope.org. +*/ +/* + * util.c miscellaneous utility subroutines for Bacula + * + * Kern Sibbald, MM + * + * Version $Id$ */ #include "bacula.h" @@ -36,21 +43,21 @@ */ /* Return true of buffer has all zero bytes */ -int is_buf_zero(char *buf, int len) +bool is_buf_zero(char *buf, int len) { uint64_t *ip; char *p; int i, len64, done, rem; if (buf[0] != 0) { - return 0; + return false; } ip = (uint64_t *)buf; /* Optimize by checking uint64_t for zero */ len64 = len / sizeof(uint64_t); for (i=0; i < len64; i++) { if (ip[i] != 0) { - return 0; + return false; } } done = len64 * sizeof(uint64_t); /* bytes already checked */ @@ -58,10 +65,10 @@ int is_buf_zero(char *buf, int len) rem = len - done; for (i = 0; i < rem; i++) { if (p[i] != 0) { - return 0; + return false; } } - return 1; + return true; } @@ -69,13 +76,14 @@ int is_buf_zero(char *buf, int len) void lcase(char *str) { while (*str) { - if (B_ISUPPER(*str)) - *str = tolower((int)(*str)); + if (B_ISUPPER(*str)) { + *str = tolower((int)(*str)); + } str++; } } -/* Convert spaces to non-space character. +/* Convert spaces to non-space character. * This makes scanf of fields containing spaces easier. */ void @@ -83,11 +91,26 @@ bash_spaces(char *str) { while (*str) { if (*str == ' ') - *str = 0x1; + *str = 0x1; + str++; + } +} + +/* Convert spaces to non-space character. + * This makes scanf of fields containing spaces easier. + */ +void +bash_spaces(POOL_MEM &pm) +{ + char *str = pm.c_str(); + while (*str) { + if (*str == ' ') + *str = 0x1; str++; } } + /* Convert non-space characters (0x1) back into spaces */ void unbash_spaces(char *str) @@ -99,47 +122,49 @@ unbash_spaces(char *str) } } +/* Convert non-space characters (0x1) back into spaces */ +void +unbash_spaces(POOL_MEM &pm) +{ + char *str = pm.c_str(); + while (*str) { + if (*str == 0x1) + *str = ' '; + str++; + } +} -char *encode_time(time_t time, char *buf) +char *encode_time(utime_t utime, char *buf) { struct tm tm; int n = 0; + time_t time = utime; + +#if defined(HAVE_WIN32) + /* + * Avoid a seg fault in Microsoft's CRT localtime_r(), + * which incorrectly references a NULL returned from gmtime() if + * time is negative before or after the timezone adjustment. + */ + struct tm *gtm; + + if ((gtm = gmtime(&time)) == NULL) { + return buf; + } + + if (gtm->tm_year == 1970 && gtm->tm_mon == 1 && gtm->tm_mday < 3) { + return buf; + } +#endif if (localtime_r(&time, &tm)) { n = sprintf(buf, "%04d-%02d-%02d %02d:%02d:%02d", - tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, - tm.tm_hour, tm.tm_min, tm.tm_sec); + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec); } return buf+n; } -/* - * Concatenate a string (str) onto a pool memory buffer pm - * Returns: length of concatenated string - */ -int pm_strcat(POOLMEM **pm, char *str) -{ - int pmlen = strlen(*pm); - int len = strlen(str) + 1; - - *pm = check_pool_memory_size(*pm, pmlen + len); - memcpy(*pm+pmlen, str, len); - return pmlen + len - 1; -} - - -/* - * Copy a string (str) into a pool memory buffer pm - * Returns: length of string copied - */ -int pm_strcpy(POOLMEM **pm, char *str) -{ - int len = strlen(str) + 1; - - *pm = check_pool_memory_size(*pm, len); - memcpy(*pm, str, len); - return len - 1; -} /* @@ -147,7 +172,7 @@ int pm_strcpy(POOLMEM **pm, char *str) */ void jobstatus_to_ascii(int JobStatus, char *msg, int maxlen) { - char *jobstat; + const char *jobstat; char buf[100]; switch (JobStatus) { @@ -170,6 +195,9 @@ void jobstatus_to_ascii(int JobStatus, char *msg, int maxlen) case JS_Error: jobstat = _("Non-fatal error"); break; + case JS_Warnings: + jobstat = _("OK -- with warnings"); + break; case JS_Canceled: jobstat = _("Canceled"); break; @@ -206,10 +234,22 @@ void jobstatus_to_ascii(int JobStatus, char *msg, int maxlen) case JS_WaitPriority: jobstat = _("Waiting on Priority"); break; + case JS_DataCommitting: + jobstat = _("SD committing Data"); + break; + case JS_DataDespooling: + jobstat = _("SD despooling Data"); + break; + case JS_AttrDespooling: + jobstat = _("SD despooling Attributes"); + break; + case JS_AttrInserting: + jobstat = _("Dir inserting Attributes"); + break; default: if (JobStatus == 0) { - buf[0] = 0; + buf[0] = 0; } else { bsnprintf(buf, sizeof(buf), _("Unknown Job termination status=%d"), JobStatus); } @@ -219,17 +259,70 @@ void jobstatus_to_ascii(int JobStatus, char *msg, int maxlen) bstrncpy(msg, jobstat, maxlen); } +/* + * Convert a JobStatus code into a human readable form - gui version + */ +void jobstatus_to_ascii_gui(int JobStatus, char *msg, int maxlen) +{ + const char *cnv = NULL; + switch (JobStatus) { + case JS_Terminated: + cnv = _("Completed successfully"); + break; + case JS_Warnings: + cnv = _("Completed with warnings"); + break; + case JS_ErrorTerminated: + cnv = _("Terminated with errors"); + break; + case JS_FatalError: + cnv = _("Fatal error"); + break; + case JS_Created: + cnv = _("Created, not yet running"); + break; + case JS_Canceled: + cnv = _("Canceled by user"); + break; + case JS_Differences: + cnv = _("Verify found differences"); + break; + case JS_WaitFD: + cnv = _("Waiting for File daemon"); + break; + case JS_WaitSD: + cnv = _("Waiting for Storage daemon"); + break; + case JS_WaitPriority: + cnv = _("Waiting for higher priority jobs"); + break; + case JS_AttrInserting: + cnv = _("Batch inserting file records"); + break; + }; + + if (cnv) { + bstrncpy(msg, cnv, maxlen); + } else { + jobstatus_to_ascii(JobStatus, msg, maxlen); + } +} + + /* * Convert Job Termination Status into a string */ -char *job_status_to_str(int stat) +const char *job_status_to_str(int stat) { - char *str; + const char *str; switch (stat) { case JS_Terminated: str = _("OK"); break; + case JS_Warnings: + str = _("OK -- with warnings"); + break; case JS_ErrorTerminated: case JS_Error: str = _("Error"); @@ -254,36 +347,60 @@ char *job_status_to_str(int stat) /* * Convert Job Type into a string */ -char *job_type_to_str(int type) +const char *job_type_to_str(int type) { - char *str; + const char *str = NULL; switch (type) { case JT_BACKUP: str = _("Backup"); break; + case JT_MIGRATED_JOB: + str = _("Migrated Job"); + break; case JT_VERIFY: str = _("Verify"); break; case JT_RESTORE: str = _("Restore"); break; + case JT_CONSOLE: + str = _("Console"); + break; + case JT_SYSTEM: + str = _("System or Console"); + break; case JT_ADMIN: str = _("Admin"); break; - default: - str = _("Unknown Type"); + case JT_ARCHIVE: + str = _("Archive"); + break; + case JT_JOB_COPY: + str = _("Job Copy"); + break; + case JT_COPY: + str = _("Copy"); + break; + case JT_MIGRATE: + str = _("Migrate"); + break; + case JT_SCAN: + str = _("Scan"); break; } + if (!str) { + str = _("Unknown Type"); + } return str; } /* * Convert Job Level into a string */ -char *job_level_to_str(int level) +const char *job_level_to_str(int level) { - char *str; + const char *str; switch (level) { case L_BASE: @@ -315,6 +432,9 @@ char *job_level_to_str(int level) case L_VERIFY_DATA: str = _("Verify Data"); break; + case L_VIRTUAL_FULL: + str = _("Virtual Full"); + break; case L_NONE: str = " "; break; @@ -325,6 +445,33 @@ char *job_level_to_str(int level) return str; } +const char *volume_status_to_str(const char *status) +{ + int pos; + const char *vs[] = { + NT_("Append"), _("Append"), + NT_("Archive"), _("Archive"), + NT_("Disabled"), _("Disabled"), + NT_("Full"), _("Full"), + NT_("Used"), _("Used"), + NT_("Cleaning"), _("Cleaning"), + NT_("Purged"), _("Purged"), + NT_("Recycle"), _("Recycle"), + NT_("Read-Only"), _("Read-Only"), + NT_("Error"), _("Error"), + NULL, NULL}; + + if (status) { + for (pos = 0 ; vs[pos] ; pos += 2) { + if ( !strcmp(vs[pos],status) ) { + return vs[pos+1]; + } + } + } + + return _("Invalid volume status"); +} + /*********************************************************************** * Encode the mode bits into a 10 character string like LS does @@ -332,7 +479,7 @@ char *job_level_to_str(int level) char *encode_mode(mode_t mode, char *buf) { - char *cp = buf; + char *cp = buf; *cp++ = S_ISDIR(mode) ? 'd' : S_ISBLK(mode) ? 'b' : S_ISCHR(mode) ? 'c' : S_ISLNK(mode) ? 'l' : S_ISFIFO(mode) ? 'f' : S_ISSOCK(mode) ? 's' : '-'; @@ -355,7 +502,18 @@ char *encode_mode(mode_t mode, char *buf) return cp; } +#if defined(HAVE_WIN32) +int do_shell_expansion(char *name, int name_len) +{ + char *src = bstrdup(name); + + ExpandEnvironmentStrings(src, name, name_len); + + free(src); + return 1; +} +#else int do_shell_expansion(char *name, int name_len) { static char meta[] = "~\\$[]*?`'<>\""; @@ -364,14 +522,14 @@ int do_shell_expansion(char *name, int name_len) POOLMEM *cmd; BPIPE *bpipe; char line[MAXSTRING]; - char *shellcmd; + const char *shellcmd; /* Check if any meta characters are present */ len = strlen(meta); for (i = 0; i < len; i++) { if (strchr(name, meta[i])) { - found = true; - break; + found = true; + break; } } if (found) { @@ -385,93 +543,152 @@ int do_shell_expansion(char *name, int name_len) pm_strcat(&cmd, name); pm_strcat(&cmd, "\""); Dmsg1(400, "Send: %s\n", cmd); - bpipe = open_bpipe(cmd, 0, "r"); - *line = 0; - fgets(line, sizeof(line), bpipe->rfd); - strip_trailing_junk(line); - stat = close_bpipe(bpipe); - Dmsg2(400, "stat=%d got: %s\n", stat, line); + if ((bpipe = open_bpipe(cmd, 0, "r"))) { + *line = 0; + fgets(line, sizeof(line), bpipe->rfd); + strip_trailing_junk(line); + stat = close_bpipe(bpipe); + Dmsg2(400, "stat=%d got: %s\n", stat, line); + } else { + stat = 1; /* error */ + } free_pool_memory(cmd); if (stat == 0) { - bstrncpy(name, line, name_len); + bstrncpy(name, line, name_len); } } return 1; } +#endif -/* MAKESESSIONKEY -- Generate session key with optional start - key. If mode is TRUE, the key will be - translated to a string, otherwise it is - returned as 16 binary bytes. +/* MAKESESSIONKEY -- Generate session key with optional start + key. If mode is TRUE, the key will be + translated to a string, otherwise it is + returned as 16 binary bytes. from SpeakFreely by John Walker */ void make_session_key(char *key, char *seed, int mode) { - int j, k; - struct MD5Context md5c; - unsigned char md5key[16], md5key1[16]; - char s[1024]; - - s[0] = 0; - if (seed != NULL) { - strcat(s, seed); - } + int j, k; + struct MD5Context md5c; + unsigned char md5key[16], md5key1[16]; + char s[1024]; - /* The following creates a seed for the session key generator - based on a collection of volatile and environment-specific - information unlikely to be vulnerable (as a whole) to an - exhaustive search attack. If one of these items isn't - available on your machine, replace it with something - equivalent or, if you like, just delete it. */ - - sprintf(s + strlen(s), "%lu", (unsigned long) getpid()); - sprintf(s + strlen(s), "%lu", (unsigned long) getppid()); - getcwd(s + strlen(s), 256); - sprintf(s + strlen(s), "%lu", (unsigned long) clock()); - sprintf(s + strlen(s), "%lu", (unsigned long) time(NULL)); -#ifdef Solaris - sysinfo(SI_HW_SERIAL,s + strlen(s), 12); +#define ss sizeof(s) + + s[0] = 0; + if (seed != NULL) { + bstrncat(s, seed, sizeof(s)); + } + + /* The following creates a seed for the session key generator + based on a collection of volatile and environment-specific + information unlikely to be vulnerable (as a whole) to an + exhaustive search attack. If one of these items isn't + available on your machine, replace it with something + equivalent or, if you like, just delete it. */ + +#if defined(HAVE_WIN32) + { + LARGE_INTEGER li; + DWORD length; + FILETIME ft; + char *p; + + p = s; + bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)GetCurrentProcessId()); + (void)getcwd(s + strlen(s), 256); + bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)GetTickCount()); + QueryPerformanceCounter(&li); + bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)li.LowPart); + GetSystemTimeAsFileTime(&ft); + bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)ft.dwLowDateTime); + bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)ft.dwHighDateTime); + length = 256; + GetComputerName(s + strlen(s), &length); + length = 256; + GetUserName(s + strlen(s), &length); + } +#else + bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)getpid()); + bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)getppid()); + (void)getcwd(s + strlen(s), 256); + bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)clock()); + bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)time(NULL)); +#if defined(Solaris) + sysinfo(SI_HW_SERIAL,s + strlen(s), 12); #endif -#ifdef HAVE_GETHOSTID - sprintf(s + strlen(s), "%lu", (unsigned long) gethostid()); +#if defined(HAVE_GETHOSTID) + bsnprintf(s + strlen(s), ss, "%lu", (uint32_t) gethostid()); #endif -#ifdef HAVE_GETDOMAINNAME - getdomainname(s + strlen(s), 256); + gethostname(s + strlen(s), 256); + bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)getuid()); + bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)getgid()); #endif - gethostname(s + strlen(s), 256); - sprintf(s + strlen(s), "%u", (unsigned)getuid()); - sprintf(s + strlen(s), "%u", (unsigned)getgid()); - MD5Init(&md5c); - MD5Update(&md5c, (unsigned char *)s, strlen(s)); - MD5Final(md5key, &md5c); - sprintf(s + strlen(s), "%lu", (unsigned long) ((time(NULL) + 65121) ^ 0x375F)); - MD5Init(&md5c); - MD5Update(&md5c, (unsigned char *)s, strlen(s)); - MD5Final(md5key1, &md5c); + MD5Init(&md5c); + MD5Update(&md5c, (uint8_t *)s, strlen(s)); + MD5Final(md5key, &md5c); + bsnprintf(s + strlen(s), ss, "%lu", (uint32_t)((time(NULL) + 65121) ^ 0x375F)); + MD5Init(&md5c); + MD5Update(&md5c, (uint8_t *)s, strlen(s)); + MD5Final(md5key1, &md5c); #define nextrand (md5key[j] ^ md5key1[j]) - if (mode) { - for (j = k = 0; j < 16; j++) { - unsigned char rb = nextrand; + if (mode) { + for (j = k = 0; j < 16; j++) { + unsigned char rb = nextrand; #define Rad16(x) ((x) + 'A') - key[k++] = Rad16((rb >> 4) & 0xF); - key[k++] = Rad16(rb & 0xF); + key[k++] = Rad16((rb >> 4) & 0xF); + key[k++] = Rad16(rb & 0xF); #undef Rad16 - if (j & 1) { - key[k++] = '-'; - } - } - key[--k] = 0; - } else { - for (j = 0; j < 16; j++) { - key[j] = nextrand; - } + if (j & 1) { + key[k++] = '-'; + } } + key[--k] = 0; + } else { + for (j = 0; j < 16; j++) { + key[j] = nextrand; + } + } } #undef nextrand +void encode_session_key(char *encode, char *session, char *key, int maxlen) +{ + int i; + for (i=0; (i < maxlen-1) && session[i]; i++) { + if (session[i] == '-') { + encode[i] = '-'; + } else { + encode[i] = ((session[i] - 'A' + key[i]) & 0xF) + 'A'; + } + } + encode[i] = 0; + Dmsg3(000, "Session=%s key=%s encode=%s\n", session, key, encode); +} + +void decode_session_key(char *decode, char *session, char *key, int maxlen) +{ + int i, x; + + for (i=0; (i < maxlen-1) && session[i]; i++) { + if (session[i] == '-') { + decode[i] = '-'; + } else { + x = (session[i] - 'A' - key[i]) & 0xF; + if (x < 0) { + x += 16; + } + decode[i] = x + 'A'; + } + } + decode[i] = 0; + Dmsg3(000, "Session=%s key=%s decode=%s\n", session, key, decode); +} + /* @@ -481,88 +698,134 @@ void make_session_key(char *key, char *seed, int mode) * %d = Director's name * %e = Job Exit code * %i = JobId - * %j = Unique Job name + * %j = Unique Job id * %l = job level * %n = Unadorned Job name + * %s = Since time * %t = Job type (Backup, ...) * %r = Recipients * %v = Volume name * * omsg = edited output message * imsg = input string containing edit codes (%x) - * to = recepients list + * to = recepients list * */ -POOLMEM *edit_job_codes(JCR *jcr, char *omsg, char *imsg, char *to) +POOLMEM *edit_job_codes(JCR *jcr, char *omsg, char *imsg, const char *to, job_code_callback_t callback) { - char *p, *str; + char *p, *q; + const char *str; char add[20]; char name[MAX_NAME_LENGTH]; + int i; *omsg = 0; Dmsg1(200, "edit_job_codes: %s\n", imsg); for (p=imsg; *p; p++) { if (*p == '%') { - switch (*++p) { + switch (*++p) { case '%': str = "%"; - break; + break; case 'c': - str = jcr->client_name; - if (!str) { - str = ""; - } - break; + if (jcr) { + str = jcr->client_name; + } else { + str = _("*none*"); + } + break; case 'd': str = my_name; /* Director's name */ - break; + break; case 'e': - str = job_status_to_str(jcr->JobStatus); - break; + if (jcr) { + str = job_status_to_str(jcr->JobStatus); + } else { + str = _("*none*"); + } + break; case 'i': - bsnprintf(add, sizeof(add), "%d", jcr->JobId); - str = add; - break; + if (jcr) { + bsnprintf(add, sizeof(add), "%d", jcr->JobId); + str = add; + } else { + str = _("*none*"); + } + break; case 'j': /* Job name */ - str = jcr->Job; - break; + if (jcr) { + str = jcr->Job; + } else { + str = _("*none*"); + } + break; case 'l': - str = job_level_to_str(jcr->JobLevel); - break; + if (jcr) { + str = job_level_to_str(jcr->get_JobLevel()); + } else { + str = _("*none*"); + } + break; case 'n': - bstrncpy(name, jcr->Job, sizeof(name)); - /* There are three periods after the Job name */ - for (int i=0; i<3; i++) { - if ((str=strrchr(name, '.')) != NULL) { - *str = 0; - } - } - str = name; - break; + if (jcr) { + bstrncpy(name, jcr->Job, sizeof(name)); + /* There are three periods after the Job name */ + for (i=0; i<3; i++) { + if ((q=strrchr(name, '.')) != NULL) { + *q = 0; + } + } + str = name; + } else { + str = _("*none*"); + } + break; case 'r': - str = to; - break; + str = to; + break; + case 's': /* since time */ + if (jcr && jcr->stime) { + str = jcr->stime; + } else { + str = _("*none*"); + } + break; case 't': - str = job_type_to_str(jcr->JobType); - break; + if (jcr) { + str = job_type_to_str(jcr->get_JobType()); + } else { + str = _("*none*"); + } + break; case 'v': - if (jcr->VolumeName && jcr->VolumeName[0]) { - str = jcr->VolumeName; - } else { - str = ""; - } - break; - default: - add[0] = '%'; - add[1] = *p; - add[2] = 0; - str = add; - break; - } + if (jcr) { + if (jcr->VolumeName && jcr->VolumeName[0]) { + str = jcr->VolumeName; + } else { + str = ""; + } + } else { + str = _("*none*"); + } + break; + default: + str = NULL; + if (callback != NULL) { + str = callback(jcr, p); + } + + if (!str) { + add[0] = '%'; + add[1] = *p; + add[2] = 0; + str = add; + } + break; + } } else { - add[0] = *p; - add[1] = 0; - str = add; + add[0] = *p; + add[1] = 0; + str = add; } Dmsg1(1200, "add_str %s\n", str); pm_strcat(&omsg, str); @@ -573,18 +836,30 @@ POOLMEM *edit_job_codes(JCR *jcr, char *omsg, char *imsg, char *to) void set_working_directory(char *wd) { - struct stat stat_buf; + struct stat stat_buf; if (wd == NULL) { Emsg0(M_ERROR_TERM, 0, _("Working directory not defined. Cannot continue.\n")); } if (stat(wd, &stat_buf) != 0) { Emsg1(M_ERROR_TERM, 0, _("Working Directory: \"%s\" not found. Cannot continue.\n"), - wd); + wd); } if (!S_ISDIR(stat_buf.st_mode)) { Emsg1(M_ERROR_TERM, 0, _("Working Directory: \"%s\" is not a directory. Cannot continue.\n"), - wd); + wd); + } + working_directory = wd; /* set global */ +} + +const char *last_path_separator(const char *str) +{ + if (*str != '\0') { + for (const char *p = &str[strlen(str) - 1]; p >= str; p--) { + if (IsPathSeparator(*p)) { + return p; + } + } } - working_directory = wd; /* set global */ + return NULL; }