/* ** Copyright (C) 2008-2021 Dirk-Jan C. Binnema ** ** 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 3 of the License, or ** (at your option) any later version. ** ** 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #include #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif /*_GNU_SOURCE*/ #include "mu-util.h" #ifdef HAVE_WORDEXP_H #include /* for shell-style globbing */ #endif /*HAVE_WORDEXP_H*/ #include #include #include /* for setlocale() */ #include #include #include #include #include #include #include #include #include static char* do_wordexp (const char *path) { #ifdef HAVE_WORDEXP_H wordexp_t wexp; char *dir; if (!path) { /* g_debug ("%s: path is empty", __func__); */ return NULL; } if (wordexp (path, &wexp, 0) != 0) { /* g_debug ("%s: expansion failed for %s", __func__, path); */ return NULL; } /* we just pick the first one */ dir = g_strdup (wexp.we_wordv[0]); /* strangely, below seems to lead to a crash on MacOS (BSD); so we have to allow for a tiny leak here on that platform... maybe instead of __APPLE__ it should be __BSD__? Hmmm., cannot reproduce that crash anymore, so commenting it out for now... */ /* #ifndef __APPLE__ */ wordfree (&wexp); /* #endif /\*__APPLE__*\/ */ return dir; # else /*!HAVE_WORDEXP_H*/ /* E.g. OpenBSD does not have wordexp.h, so we ignore it */ return path ? g_strdup (path) : NULL; #endif /*HAVE_WORDEXP_H*/ } /* note, the g_debugs are commented out because this function may be * called before the log handler is installed. */ char* mu_util_dir_expand (const char *path) { char *dir; char resolved[PATH_MAX + 1]; g_return_val_if_fail (path, NULL); dir = do_wordexp (path); if (!dir) return NULL; /* error */ /* don't try realpath if the dir does not exist */ if (access (dir, F_OK) != 0) return dir; /* now resolve any symlinks, .. etc. */ if (realpath (dir, resolved) == NULL) { /* g_debug ("%s: could not get realpath for '%s': %s", */ /* __func__, dir, g_strerror(errno)); */ g_free (dir); return NULL; } else g_free (dir); return g_strdup (resolved); } GQuark mu_util_error_quark (void) { static GQuark error_domain = 0; if (G_UNLIKELY(error_domain == 0)) error_domain = g_quark_from_static_string ("mu-error-quark"); return error_domain; } gboolean mu_util_check_dir (const gchar* path, gboolean readable, gboolean writeable) { int mode; struct stat statbuf; if (!path) return FALSE; mode = F_OK | (readable ? R_OK : 0) | (writeable ? W_OK : 0); if (access (path, mode) != 0) { /* g_debug ("Cannot access %s: %s", path, g_strerror (errno)); */ return FALSE; } if (stat (path, &statbuf) != 0) { /* g_debug ("Cannot stat %s: %s", path, g_strerror (errno)); */ return FALSE; } return S_ISDIR(statbuf.st_mode) ? TRUE: FALSE; } gchar* mu_util_guess_maildir (void) { const gchar *mdir1, *home; /* first, try MAILDIR */ mdir1 = g_getenv ("MAILDIR"); if (mdir1 && mu_util_check_dir (mdir1, TRUE, FALSE)) return g_strdup (mdir1); /* then, try /Maildir */ home = g_get_home_dir(); if (home) { char *mdir2; mdir2 = g_strdup_printf ("%s%cMaildir", home, G_DIR_SEPARATOR); if (mu_util_check_dir (mdir2, TRUE, FALSE)) return mdir2; g_free (mdir2); } /* nope; nothing found */ return NULL; } gboolean mu_util_create_dir_maybe (const gchar *path, mode_t mode, gboolean nowarn) { struct stat statbuf; g_return_val_if_fail (path, FALSE); /* if it exists, it must be a readable dir */ if (stat (path, &statbuf) == 0) { if ((!S_ISDIR(statbuf.st_mode)) || (access (path, W_OK|R_OK) != 0)) { if (!nowarn) g_warning ("not a read-writable" "directory: %s", path); return FALSE; } } if (g_mkdir_with_parents (path, mode) != 0) { if (!nowarn) g_warning ("failed to create %s: %s", path, g_strerror(errno)); return FALSE; } return TRUE; } gboolean mu_util_supports (MuFeature feature) { /* check for Guile support */ #ifndef BUILD_GUILE if (feature & MU_FEATURE_GUILE) return FALSE; #endif /*BUILD_GUILE*/ /* check for Gnuplot */ if (feature & MU_FEATURE_GNUPLOT) if (!mu_util_program_in_path ("gnuplot")) return FALSE; return TRUE; } gboolean mu_util_program_in_path (const char *prog) { gchar *path; g_return_val_if_fail (prog, FALSE); path = g_find_program_in_path (prog); g_free (path); return (path != NULL) ? TRUE : FALSE; } /* * Set the child to a group leader to avoid being killed when the * parent group is killed. */ static void maybe_setsid (G_GNUC_UNUSED gpointer user_data) { #if HAVE_SETSID setsid(); #endif /*HAVE_SETSID*/ } gboolean mu_util_play (const char *path, GError **err) { GFile *gf; gboolean rv, is_native; const gchar *argv[3]; const char *prog; g_return_val_if_fail (path, FALSE); gf = g_file_new_for_path(path); is_native = g_file_is_native(gf); g_object_unref(gf); if (!is_native) { mu_util_g_set_error (err, MU_ERROR_FILE_CANNOT_EXECUTE, "'%s' is not a native file", path); return FALSE; } prog = g_getenv ("MU_PLAY_PROGRAM"); if (!prog) { #ifdef __APPLE__ prog = "open"; #else prog = "xdg-open"; #endif /*!__APPLE__*/ } if (!mu_util_program_in_path (prog)) { mu_util_g_set_error (err, MU_ERROR_FILE_CANNOT_EXECUTE, "cannot find '%s' in path", prog); return FALSE; } argv[0] = prog; argv[1] = path; argv[2] = NULL; err = NULL; rv = g_spawn_async (NULL, (gchar**)&argv, NULL, G_SPAWN_SEARCH_PATH, maybe_setsid, NULL, NULL, err); return rv; } unsigned char mu_util_get_dtype (const char *path, gboolean use_lstat) { int res; struct stat statbuf; g_return_val_if_fail (path, DT_UNKNOWN); if (use_lstat) res = lstat (path, &statbuf); else res = stat (path, &statbuf); if (res != 0) { g_warning ("%sstat failed on %s: %s", use_lstat ? "l" : "", path, g_strerror(errno)); return DT_UNKNOWN; } /* we only care about dirs, regular files and links */ if (S_ISREG (statbuf.st_mode)) return DT_REG; else if (S_ISDIR (statbuf.st_mode)) return DT_DIR; else if (S_ISLNK (statbuf.st_mode)) return DT_LNK; return DT_UNKNOWN; } gboolean mu_util_locale_is_utf8 (void) { const gchar *dummy; static int is_utf8 = -1; if (G_UNLIKELY(is_utf8 == -1)) is_utf8 = g_get_charset(&dummy) ? 1 : 0; return is_utf8 ? TRUE : FALSE; } gboolean mu_util_fputs_encoded (const char *str, FILE *stream) { int rv; char *conv; g_return_val_if_fail (stream, FALSE); /* g_get_charset return TRUE when the locale is UTF8 */ if (mu_util_locale_is_utf8()) return fputs (str, stream) == EOF ? FALSE : TRUE; /* charset is _not_ utf8, so we need to convert it */ conv = NULL; if (g_utf8_validate (str, -1, NULL)) conv = g_locale_from_utf8 (str, -1, NULL, NULL, NULL); /* conversion failed; this happens because is some cases GMime may gives * us non-UTF-8 strings from e.g. wrongly encoded message-subjects; if * so, we escape the string */ conv = conv ? conv : g_strescape (str, "\n\t"); rv = conv ? fputs (conv, stream) : EOF; g_free (conv); return (rv == EOF) ? FALSE : TRUE; } gboolean mu_util_g_set_error (GError **err, MuError errcode, const char *frm, ...) { va_list ap; char *msg; /* don't bother with NULL errors, or errors already set */ if (!err || *err) return FALSE; msg = NULL; va_start (ap, frm); g_vasprintf (&msg, frm, ap); va_end (ap); g_set_error (err, MU_ERROR_DOMAIN, errcode, "%s", msg); g_free (msg); return FALSE; } __attribute__((format(printf, 2, 0))) static gboolean print_args (FILE *stream, const char *frm, va_list args) { gchar *str; gboolean rv; str = g_strdup_vprintf (frm, args); rv = mu_util_fputs_encoded (str, stream); g_free (str); return rv; } gboolean mu_util_print_encoded (const char *frm, ...) { va_list args; gboolean rv; g_return_val_if_fail (frm, FALSE); va_start (args, frm); rv = print_args (stdout, frm, args); va_end (args); return rv; } char* mu_str_summarize (const char* str, size_t max_lines) { char *summary; size_t nl_seen; unsigned i,j; gboolean last_was_blank; g_return_val_if_fail (str, NULL); g_return_val_if_fail (max_lines > 0, NULL); /* len for summary <= original len */ summary = g_new (gchar, strlen(str) + 1); /* copy the string up to max_lines lines, replace CR/LF/tab with * single space */ for (i = j = 0, nl_seen = 0, last_was_blank = TRUE; nl_seen < max_lines && str[i] != '\0'; ++i) { if (str[i] == '\n' || str[i] == '\r' || str[i] == '\t' || str[i] == ' ' ) { if (str[i] == '\n') ++nl_seen; /* no double-blanks or blank at end of str */ if (!last_was_blank && str[i+1] != '\0') summary[j++] = ' '; last_was_blank = TRUE; } else { summary[j++] = str[i]; last_was_blank = FALSE; } } summary[j] = '\0'; return summary; }