void mu_msg_crypto_verify_part (GMimeMultipartSigned *sig, MuMsgOptions opts, GError **err) { /* the signature status */ MuMsgPartSigStatusReport *report; GMimeCryptoContext *ctx; GMimeSignatureList *sigs; g_return_if_fail (GMIME_IS_MULTIPART_SIGNED(sig)); ctx = get_crypto_context (opts, NULL, NULL, err); if (!ctx) { mu_util_g_set_error (err, MU_ERROR_CRYPTO, "failed to get crypto context"); return; } sigs = g_mime_multipart_signed_verify (sig, ctx, err); g_object_unref (ctx); if (!sigs) { if (err && !*err) mu_util_g_set_error (err, MU_ERROR_CRYPTO, "verification failed"); return; } report = get_status_report (sigs); g_mime_signature_list_clear (sigs); /* tag this part with the signature status check */ tag_with_sig_status(G_OBJECT(sig), report); }
MuMsgPartSigStatusReport* mu_msg_crypto_verify_part (GMimeMultipartSigned *sig, MuMsgOptions opts, GError **err) { MuMsgPartSigStatusReport *report; GMimeCryptoContext *ctx; GMimeSignatureList *sigs; g_return_val_if_fail (GMIME_IS_MULTIPART_SIGNED(sig), NULL); ctx = get_crypto_context (opts, NULL, NULL, err); if (!ctx) { mu_util_g_set_error (err, MU_ERROR_CRYPTO, "failed to get crypto context"); return NULL; } sigs = g_mime_multipart_signed_verify (sig, ctx, err); g_object_unref (ctx); if (!sigs) { if (err && !*err) mu_util_g_set_error (err, MU_ERROR_CRYPTO, "verification failed"); return NULL; } report = get_status_report (sigs); g_mime_signature_list_clear (sigs); return report; }
GMimeObject* /* this is declared in mu-msg-priv.h */ mu_msg_crypto_decrypt_part (GMimeMultipartEncrypted *enc, MuMsgOptions opts, MuMsgPartPasswordFunc func, gpointer user_data, GError **err) { GMimeObject *dec; GMimeCryptoContext *ctx; g_return_val_if_fail (GMIME_IS_MULTIPART_ENCRYPTED(enc), NULL); ctx = get_crypto_context (opts, func, user_data, err); if (!ctx) { mu_util_g_set_error (err, MU_ERROR_CRYPTO, "failed to get crypto context"); return NULL; } dec = g_mime_multipart_encrypted_decrypt (enc, ctx, NULL, err); g_object_unref (ctx); if (!dec) { if (err && !*err) mu_util_g_set_error (err, MU_ERROR_CRYPTO, "decryption failed"); return NULL; } return dec; }
static MuError index_and_cleanup (MuIndex *index, const char *path, GError **err) { MuError rv; MuIndexStats stats, stats2; mu_index_stats_clear (&stats); rv = mu_index_run (index, path, FALSE, &stats, index_msg_cb, NULL, NULL); if (rv != MU_OK && rv != MU_STOP) { mu_util_g_set_error (err, MU_ERROR_INTERNAL, "indexing failed"); return rv; } mu_index_stats_clear (&stats2); rv = mu_index_cleanup (index, &stats2, NULL, NULL, err); if (rv != MU_OK && rv != MU_STOP) { mu_util_g_set_error (err, MU_ERROR_INTERNAL, "cleanup failed"); return rv; } print_expr ("(:info index :status complete " ":processed %u :updated %u :cleaned-up %u)", stats._processed, stats._updated, stats2._cleaned_up); return rv; }
static gboolean parse_cmd (int *argcp, char ***argvp, GError **err) { MU_CONFIG.cmd = MU_CONFIG_CMD_NONE; MU_CONFIG.cmdstr = NULL; if (*argcp < 2) /* no command found at all */ return TRUE; else if ((**argvp)[1] == '-') /* if the first param starts with '-', there is no * command, just some option (like --version, --help * etc.)*/ return TRUE; MU_CONFIG.cmdstr = g_strdup ((*argvp)[1]); MU_CONFIG.cmd = cmd_from_string (MU_CONFIG.cmdstr); #ifndef BUILD_GUILE if (MU_CONFIG.cmd == MU_CONFIG_CMD_SCRIPT) { mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS, "command 'script' not supported"); return FALSE; } #endif /*!BUILD_GUILE*/ if (MU_CONFIG.cmdstr && MU_CONFIG.cmdstr[0] != '-' && MU_CONFIG.cmd == MU_CONFIG_CMD_UNKNOWN) { mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS, "unknown command '%s'", MU_CONFIG.cmdstr); return FALSE; } return TRUE; }
/* NOTE: this assumes there is only _one_ docid (message) for the * particular message id */ static unsigned get_docid_from_msgid (MuQuery *query, const char *str, GError **err) { gchar *querystr; unsigned docid; MuMsgIter *iter; querystr = g_strdup_printf ("msgid:%s", str); iter = mu_query_run (query, querystr, FALSE, MU_MSG_FIELD_ID_NONE, FALSE, 1, err); g_free (querystr); docid = MU_STORE_INVALID_DOCID; if (!iter || mu_msg_iter_is_done (iter)) mu_util_g_set_error (err, MU_ERROR_NO_MATCHES, "could not find message %s", str); else { MuMsg *msg; msg = mu_msg_iter_get_msg_floating (iter); if (!mu_msg_is_readable(msg)) { mu_util_g_set_error (err, MU_ERROR_FILE_CANNOT_READ, "'%s' is not readable", mu_msg_get_path(msg)); } else docid = mu_msg_iter_get_docid (iter); mu_msg_iter_destroy (iter); } return docid; }
/* we need do to determine the * /home/foo/Maildir/bar * from the /bar * that we got */ char* get_target_mdir (MuMsg *msg, const char *target_maildir, GError **err) { char *rootmaildir, *rv; const char *maildir; gboolean not_top_level; /* maildir is the maildir stored in the message, e.g. '/foo' */ maildir = mu_msg_get_maildir(msg); if (!maildir) { mu_util_g_set_error (err, MU_ERROR_GMIME, "message without maildir"); return NULL; } /* the 'rootmaildir' is the filesystem path from root to * maildir, ie. /home/user/Maildir/foo */ rootmaildir = mu_maildir_get_maildir_from_path (mu_msg_get_path(msg)); if (!rootmaildir) { mu_util_g_set_error (err, MU_ERROR_GMIME, "cannot determine maildir"); return NULL; } /* we do a sanity check: verify that that maildir is a suffix of * rootmaildir;*/ not_top_level = TRUE; if (!g_str_has_suffix (rootmaildir, maildir) && /* special case for the top-level '/' maildir, and * remember not_top_level */ (not_top_level = (g_strcmp0 (maildir, "/") != 0))) { g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_FILE, "path is '%s', but maildir is '%s' ('%s')", rootmaildir, mu_msg_get_maildir(msg), mu_msg_get_path (msg)); g_free (rootmaildir); return NULL; } /* if we're not at the top-level, remove the final '/' from * the rootmaildir */ if (not_top_level) rootmaildir[strlen(rootmaildir) - strlen (mu_msg_get_maildir(msg))] = '\0'; rv = g_strconcat (rootmaildir, target_maildir, NULL); g_free (rootmaildir); return rv; }
static inline void check_decrypt_result(GMimeMultipartEncrypted *part, GMimeDecryptResult *res, GError **err) { GMimeSignatureList *sigs; MuMsgPartSigStatusReport *report; if (res) { /* Check if the decrypted part had any embed signatures */ sigs = res->signatures; if (sigs) { report = get_status_report (sigs); g_mime_signature_list_clear (sigs); /* tag this part with the signature status check */ tag_with_sig_status(G_OBJECT(part), report); } else { if (err && !*err) mu_util_g_set_error (err, MU_ERROR_CRYPTO, "verification failed"); } g_object_unref (res); } }
static GMimeCryptoContext* get_gpg_crypto_context (MuMsgOptions opts, GError **err) { GMimeCryptoContext *cctx; char *gpg; cctx = NULL; if (!(gpg = get_gpg (err))) return NULL; cctx = g_mime_gpg_context_new ( (GMimePasswordRequestFunc)password_requester, gpg); g_free (gpg); if (!cctx) { mu_util_g_set_error (err, MU_ERROR, "failed to get GPG crypto context"); return NULL; } /* always try to use the agent */ g_mime_gpg_context_set_use_agent (GMIME_GPG_CONTEXT(cctx), TRUE); g_mime_gpg_context_set_auto_key_retrieve (GMIME_GPG_CONTEXT(cctx), opts & MU_MSG_OPTION_AUTO_RETRIEVE ? TRUE:FALSE); return cctx; }
static const char* get_string_from_args (GSList *args, const char *param, gboolean optional, GError **err) { size_t param_len; param_len = strlen (param); while (args) { const char *arg; arg = (const char*)args->data; /* do we have this param */ if (arg && g_str_has_prefix (arg, param) && arg[param_len] == ':') return (const char*) arg + param_len + 1; args = g_slist_next (args); } if (!optional) mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS, "parameter '%s' not found", param); return NULL; }
gchar* mu_msg_part_get_path (MuMsg *msg, MuMsgOptions opts, const char* targetdir, unsigned index, GError **err) { char *fname, *filepath; GMimeObject* mobj; g_return_val_if_fail (msg, NULL); if (!mu_msg_load_msg_file (msg, NULL)) return NULL; mobj = get_mime_object_at_index (msg, opts, index); if (!mobj){ mu_util_g_set_error (err, MU_ERROR_GMIME, "cannot find part %u", index); return NULL; } fname = mime_part_get_filename (mobj, index, TRUE); filepath = g_build_path (G_DIR_SEPARATOR_S, targetdir ? targetdir : "", fname, NULL); g_free (fname); return filepath; }
gchar* mu_msg_part_get_cache_path (MuMsg *msg, MuMsgOptions opts, guint partid, GError **err) { char *dirname, *filepath; const char* path; g_return_val_if_fail (msg, NULL); if (!mu_msg_load_msg_file (msg, NULL)) return NULL; path = mu_msg_get_path (msg); /* g_compute_checksum_for_string may be better, but requires * rel. new glib (2.16) */ dirname = g_strdup_printf ("%s%c%x%c%u", mu_util_cache_dir(), G_DIR_SEPARATOR, g_str_hash (path), G_DIR_SEPARATOR, partid); if (!mu_util_create_dir_maybe (dirname, 0700, FALSE)) { mu_util_g_set_error (err, MU_ERROR_FILE, "failed to create dir %s", dirname); g_free (dirname); return NULL; } filepath = mu_msg_part_get_path (msg, opts, dirname, partid, err); g_free (dirname); return filepath; }
static GMimeCryptoContext* get_gpg_crypto_context (MuMsgOptions opts, GError **err) { GMimeCryptoContext *cctx; const char *prog; cctx = NULL; prog = g_getenv ("MU_GPG_PATH"); if (prog) cctx = g_mime_gpg_context_new ( (GMimePasswordRequestFunc)password_requester, prog); else { char *path; path = g_find_program_in_path ("gpg"); if (path) cctx = g_mime_gpg_context_new ( password_requester, path); g_free (path); } if (!cctx) { mu_util_g_set_error (err, MU_ERROR, "failed to get GPG crypto context"); return NULL; } /* always try to use the agent */ g_mime_gpg_context_set_use_agent (GMIME_GPG_CONTEXT(cctx), TRUE); g_mime_gpg_context_set_auto_key_retrieve (GMIME_GPG_CONTEXT(cctx), opts & MU_MSG_OPTION_AUTO_RETRIEVE ? TRUE:FALSE); return cctx; }
MuError mu_cmd_extract (MuConfig *opts, GError **err) { int rv; g_return_val_if_fail (opts, MU_ERROR_INTERNAL); g_return_val_if_fail (opts->cmd == MU_CONFIG_CMD_EXTRACT, MU_ERROR_INTERNAL); if (!check_params (opts)) { g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_IN_PARAMETERS, "error in parameters"); return MU_ERROR_IN_PARAMETERS; } if (!opts->params[2] && !opts->parts && !opts->save_attachments && !opts->save_all) rv = show_parts (opts->params[1], opts, err); /* show, don't save */ else { rv = mu_util_check_dir(opts->targetdir, FALSE, TRUE); if (!rv) mu_util_g_set_error (err, MU_ERROR_FILE_CANNOT_WRITE, "target '%s' is not a writable directory", opts->targetdir); else rv = save_parts (opts->params[1], opts->params[2], opts); /* save */ } return rv ? MU_OK : MU_ERROR; }
gboolean mu_script_guile_run (MuScriptInfo *msi, const char *muhome, const char **args, GError **err) { char *mainargs, *expr; char *argv[] = { "guile", "-l", NULL, "-c", NULL, NULL }; g_return_val_if_fail (msi, FALSE); g_return_val_if_fail (muhome, FALSE); if (access (mu_script_info_path (msi), R_OK) != 0) { mu_util_g_set_error (err, MU_ERROR_FILE_CANNOT_READ, strerror(errno)); return FALSE; } argv[2] = (char*)mu_script_info_path (msi); mainargs = mu_str_quoted_from_strv (args); expr = g_strdup_printf ( "(main '(\"%s\" \"--muhome=%s\" %s))", mu_script_info_name (msi), muhome, mainargs ? mainargs : ""); g_free (mainargs); argv[4] = expr; scm_boot_guile (5, argv, guile_shell, NULL); /* never reached but let's be correct(TM)*/ g_free (expr); return TRUE; }
/* get a *list* of all messages with the given message id */ static GSList* get_docids_from_msgids (MuQuery *query, const char *str, GError **err) { gchar *querystr; MuMsgIter *iter; GSList *lst; querystr = g_strdup_printf ("msgid:%s", str); iter = mu_query_run (query, querystr, FALSE, MU_MSG_FIELD_ID_NONE, FALSE,-1 /*unlimited*/, err); g_free (querystr); if (!iter || mu_msg_iter_is_done (iter)) { mu_util_g_set_error (err, MU_ERROR_NO_MATCHES, "could not find message %s", str); return NULL; } lst = NULL; do { lst = g_slist_prepend (lst, GSIZE_TO_POINTER(mu_msg_iter_get_docid (iter))); } while (mu_msg_iter_next (iter)); mu_msg_iter_destroy (iter); return lst; }
/* parse the find parameters, and return the values as out params */ static MuError get_find_params (GSList *args, gboolean *threads, MuMsgFieldId *sortfield, gboolean *reverse, int *maxnum, GError **err) { const char *maxnumstr, *sortfieldstr; /* maximum number of results */ maxnumstr = get_string_from_args (args, "maxnum", TRUE, NULL); *maxnum = maxnumstr ? atoi (maxnumstr) : 0; /* whether to show threads or not */ *threads = get_bool_from_args (args, "threads", TRUE, NULL); *reverse = get_bool_from_args (args, "reverse", TRUE, NULL); /* field to sort by */ sortfieldstr = get_string_from_args (args, "sortfield", TRUE, NULL); if (sortfieldstr) { *sortfield = mu_msg_field_id_from_name (sortfieldstr, FALSE); /* note: shortcuts are not allowed here */ if (*sortfield == MU_MSG_FIELD_ID_NONE) { mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS, "not a valid sort field: '%s'", sortfield); return MU_G_ERROR_CODE(err); } } else *sortfield = MU_MSG_FIELD_ID_DATE; return MU_OK; }
/* 'remove' removes the message with either docid: or msgid:, sends a * (:remove ...) message when it succeeds */ static MuError cmd_remove (ServerContext *ctx, GSList *args, GError **err) { unsigned docid; const char *path; docid = determine_docid (ctx->query, args, err); if (docid == MU_STORE_INVALID_DOCID) { print_and_clear_g_error (err); return MU_OK; } path = get_path_from_docid (ctx->store, docid, err); if (!path) { print_and_clear_g_error (err); return MU_OK; } if (unlink (path) != 0) { mu_util_g_set_error (err, MU_ERROR_FILE_CANNOT_UNLINK, "%s", strerror (errno)); print_and_clear_g_error (err); return MU_OK; } if (!mu_store_remove_path (ctx->store, path)) { print_error (MU_ERROR_XAPIAN_REMOVE_FAILED, "failed to remove from database"); return MU_OK; } print_expr ("(:remove %u)", docid); return MU_OK; }
static MuError move_msgid (MuStore *store, unsigned docid, const char* flagstr, GError **err) { MuMsg *msg; MuError rv; MuFlags flags; rv = MU_ERROR; msg = mu_store_get_msg (store, docid, err); if (!msg) goto leave; flags = flagstr ? get_flags (mu_msg_get_path(msg), flagstr) : mu_msg_get_flags (msg); if (flags == MU_FLAG_INVALID) { mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS, "invalid flags"); goto leave; } rv = do_move (store, docid, msg, NULL, flags, err); leave: if (msg) mu_msg_unref (msg); if (rv != MU_OK) print_and_clear_g_error (err); return rv; }
static gboolean password_requester (GMimeCryptoContext *ctx, const char *user_id, const char* prompt_ctx, gboolean reprompt, GMimeStream *response, GError **err) { CallbackData *cbdata; gchar *password; ssize_t written; cbdata = g_object_get_data (G_OBJECT(ctx), CALLBACK_DATA); if (!cbdata || !cbdata->pw_func) return FALSE; password = cbdata->pw_func (user_id, prompt_ctx, reprompt, cbdata->user_data); if (!password) { mu_util_g_set_error (err, MU_ERROR_CRYPTO, "failed to get password"); return FALSE; } written = g_mime_stream_write_string (response, password); if (written != -1) written = g_mime_stream_write_string (response, "\n"); if (written == -1) mu_util_g_set_error (err, MU_ERROR_CRYPTO, "writing password to mime stream failed"); /* it seems that GMime tries to flush the fd; however, this * does not work for pipes/sockets, causing getting a password * to fail. * * I have reported this, and it has been fixed now: * * http://git.gnome.org/browse/gmime/commit/ * ?id=bda4834d3d9a1fbefb6d97edfef2bc1da9357f58 * * however, it may take a while before everybody has this * version of GMime (ie. version > 2.6.10) * */ memset (password, 0, strlen(password)); g_free (password); return written != -1 ? TRUE : FALSE; }
/* never reached but let's be correct(TM)*/ g_free (expr); return TRUE; } #else /*!BUILD_GUILE*/ gboolean mu_script_guile_run (MuScriptInfo *msi, const char *muhome, const char **args, GError **err) { mu_util_g_set_error (err, MU_ERROR_INTERNAL, "this mu does not have guile support"); return FALSE; }
GMimeObject* /* this is declared in mu-msg-priv.h */ mu_msg_crypto_decrypt_part (GMimeMultipartEncrypted *enc, MuMsgOptions opts, MuMsgPartPasswordFunc func, gpointer user_data, GError **err) { GMimeObject *dec; GMimeCryptoContext *ctx; GMimeDecryptResult *res; g_return_val_if_fail (GMIME_IS_MULTIPART_ENCRYPTED(enc), NULL); ctx = get_crypto_context (opts, func, user_data, err); if (!ctx) { mu_util_g_set_error (err, MU_ERROR_CRYPTO, "failed to get crypto context"); return NULL; } /* at the time of writing, there is a small leak in * g_mime_multipart_encrypted_decrypt; I've notified its * author and it has been fixed 2012-09-12: * http://git.gnome.org/browse/gmime/commit/ * ?id=1bacd43b50d91bd03a4ae1dc9f46f5783dee61b1 * (or GMime > 2.6.10) * */ res = NULL; dec = g_mime_multipart_encrypted_decrypt (enc, ctx, &res, err); g_object_unref (ctx); /* we don't use the 3rd param 'res' * (GMimeDecryptResult), * but we must unref it. */ if (res) g_object_unref (res); if (!dec) { if (err && !*err) mu_util_g_set_error (err, MU_ERROR_CRYPTO, "decryption failed"); return NULL; } return dec; }
static gboolean query_params_valid (MuConfig *opts, GError **err) { const gchar *xpath; if (!opts->params[1]) { mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS, "missing query"); return FALSE; } xpath = mu_runtime_path (MU_RUNTIME_PATH_XAPIANDB); if (mu_util_check_dir (xpath, TRUE, FALSE)) return TRUE; mu_util_g_set_error (err, MU_ERROR_FILE_CANNOT_READ, "'%s' is not a readable Xapian directory", xpath); return FALSE; }
static gboolean check_params (MuConfig *opts, GError **err) { if (!opts->params||!opts->params[0]) {/* no command? */ show_usage (); mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS, "error in parameters"); return FALSE; } return TRUE; }
static gboolean check_params (MuConfig *opts, GError **err) { if (!mu_util_supports (MU_FEATURE_GUILE | MU_FEATURE_GNUPLOT)) { mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS, "the 'script' command is not available " "in this version of mu"); return FALSE; } return TRUE; }
static const char* get_string_from_args (GHashTable *args, const char *param, gboolean optional, GError **err) { const char *str; str = g_hash_table_lookup (args, param); if (!str && !optional) mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS, "parameter '%s' not found", param); return str; }
MuError mu_cmd_add (MuStore *store, MuConfig *opts, GError **err) { gboolean allok; int i; g_return_val_if_fail (store, MU_ERROR_INTERNAL); g_return_val_if_fail (opts, MU_ERROR_INTERNAL); g_return_val_if_fail (opts->cmd == MU_CONFIG_CMD_ADD, MU_ERROR_INTERNAL); /* note: params[0] will be 'add' */ if (!opts->params[0] || !opts->params[1]) { g_print ("usage: mu add <file> [<files>]\n"); mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS, "missing source and/or target"); return MU_ERROR_IN_PARAMETERS; } for (i = 1, allok = TRUE; opts->params[i]; ++i) { const char* src; src = opts->params[i]; if (!check_file_okay (src, TRUE) || mu_store_add_path (store, src, NULL, err) == MU_STORE_INVALID_DOCID) { MU_WRITE_LOG ("failed to add %s", src); allok = FALSE; } } if (!allok) { mu_util_g_set_error (err, MU_ERROR_XAPIAN_STORE_FAILED, "store failed for some message(s)"); return MU_ERROR_XAPIAN_STORE_FAILED; } return MU_OK; }
static gboolean prepare_links (MuConfig *opts, GError **err) { /* note, mu_maildir_mkdir simply ignores whatever part of the * mail dir already exists */ if (!mu_maildir_mkdir (opts->linksdir, 0700, TRUE, err)) { mu_util_g_set_error (err, MU_ERROR_FILE_CANNOT_MKDIR, "error creating %s", opts->linksdir); return FALSE; } if (opts->clearlinks && !mu_maildir_clear_links (opts->linksdir, err)) { mu_util_g_set_error (err, MU_ERROR_FILE, "error clearing links under %s", opts->linksdir); return FALSE; } return TRUE; }
static gboolean view_params_valid (MuConfig *opts, GError **err) { /* note: params[0] will be 'view' */ if (!opts->params[0] || !opts->params[1]) { mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS, "error in parameters"); return FALSE; } switch (opts->format) { case MU_CONFIG_FORMAT_PLAIN: case MU_CONFIG_FORMAT_SEXP: break; default: mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS, "invalid output format"); return FALSE; } return TRUE; }
static gboolean format_params_valid (MuConfig *opts, GError **err) { switch (opts->format) { case MU_CONFIG_FORMAT_EXEC: break; case MU_CONFIG_FORMAT_PLAIN: case MU_CONFIG_FORMAT_SEXP: case MU_CONFIG_FORMAT_LINKS: case MU_CONFIG_FORMAT_XML: case MU_CONFIG_FORMAT_XQUERY: case MU_CONFIG_FORMAT_MQUERY: if (opts->exec) { mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS, "--exec and --format cannot be combined"); return FALSE; } break; default: mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS, "invalid output format %s", opts->formatstr ? opts->formatstr : "<none>"); return FALSE; } if (opts->format == MU_CONFIG_FORMAT_LINKS && !opts->linksdir) { mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS, "missing --linksdir argument"); return FALSE; } if (opts->linksdir && opts->format != MU_CONFIG_FORMAT_LINKS) { mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS, "--linksdir is only valid with --format=links"); return FALSE; } return TRUE; }