Merge branch 'upstream' into tizen
[platform/upstream/cryptsetup.git] / src / utils_tools.c
index 84329c6..47bcfe1 100644 (file)
@@ -1,14 +1,15 @@
 /*
  * cryptsetup - setup cryptographic volumes for dm-crypt
  *
- * Copyright (C) 2004, Christophe Saout <christophe@saout.de>
- * Copyright (C) 2004-2007, Clemens Fruhwirth <clemens@endorphin.org>
- * Copyright (C) 2009-2012, Red Hat, Inc. All rights reserved.
- * Copyright (C) 2009-2012, Milan Broz
+ * Copyright (C) 2004 Jana Saout <jana@saout.de>
+ * Copyright (C) 2004-2007 Clemens Fruhwirth <clemens@endorphin.org>
+ * Copyright (C) 2009-2020 Red Hat, Inc. All rights reserved.
+ * Copyright (C) 2009-2020 Milan Broz
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
- * version 2 as published by the Free Software Foundation.
+ * as published by the Free Software Foundation; either version 2
+ * 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
  */
 
 #include "cryptsetup.h"
+#include <math.h>
 #include <signal.h>
 
 int opt_verbose = 0;
 int opt_debug = 0;
+int opt_debug_json = 0;
 int opt_batch_mode = 0;
+int opt_progress_frequency = 0;
 
 /* interrupt handling */
 volatile int quit = 0;
@@ -73,29 +77,27 @@ void check_signal(int *r)
                *r = -EINTR;
 }
 
+#define LOG_MAX_LEN 4096
+
 __attribute__((format(printf, 5, 6)))
 void clogger(struct crypt_device *cd, int level, const char *file, int line,
             const char *format, ...)
 {
        va_list argp;
-       char *target = NULL;
+       char target[LOG_MAX_LEN + 2];
 
        va_start(argp, format);
 
-       if (vasprintf(&target, format, argp) > 0) {
-               if (level >= 0) {
-                       crypt_log(cd, level, target);
-#ifdef CRYPT_DEBUG
-               } else if (opt_debug)
-                       printf("# %s:%d %s\n", file ?: "?", line, target);
-#else
-               } else if (opt_debug)
-                       printf("# %s\n", target);
-#endif
+       if (vsnprintf(&target[0], LOG_MAX_LEN, format, argp) > 0) {
+               /* All verbose and error messages in tools end with EOL. */
+               if (level == CRYPT_LOG_VERBOSE || level == CRYPT_LOG_ERROR ||
+                   level == CRYPT_LOG_DEBUG || level == CRYPT_LOG_DEBUG_JSON)
+                       strncat(target, "\n", LOG_MAX_LEN);
+
+               crypt_log(cd, level, target);
        }
 
        va_end(argp);
-       free(target);
 }
 
 void tool_log(int level, const char *msg, void *usrptr __attribute__((unused)))
@@ -103,21 +105,19 @@ void tool_log(int level, const char *msg, void *usrptr __attribute__((unused)))
        switch(level) {
 
        case CRYPT_LOG_NORMAL:
-               fputs(msg, stdout);
+               fprintf(stdout, "%s", msg);
                break;
        case CRYPT_LOG_VERBOSE:
                if (opt_verbose)
-                       fputs(msg, stdout);
+                       fprintf(stdout, "%s", msg);
                break;
        case CRYPT_LOG_ERROR:
-               fputs(msg, stderr);
+               fprintf(stderr, "%s", msg);
                break;
+       case CRYPT_LOG_DEBUG_JSON:
        case CRYPT_LOG_DEBUG:
                if (opt_debug)
-                       printf("# %s\n", msg);
-               break;
-       default:
-               fprintf(stderr, "Internal error on logging class for msg: %s", msg);
+                       fprintf(stdout, "# %s", msg);
                break;
        }
 }
@@ -129,28 +129,33 @@ void quiet_log(int level, const char *msg, void *usrptr)
        tool_log(level, msg, usrptr);
 }
 
-int yesDialog(const char *msg, void *usrptr __attribute__((unused)))
+static int _dialog(const char *msg, void *usrptr, int default_answer)
 {
+       const char *fail_msg = (const char *)usrptr;
        char *answer = NULL;
        size_t size = 0;
-       int r = 1, block;
+       int r = default_answer, block;
 
        block = tools_signals_blocked();
        if (block)
                set_int_block(0);
 
-       if(isatty(STDIN_FILENO) && !opt_batch_mode) {
+       if (isatty(STDIN_FILENO) && !opt_batch_mode) {
                log_std("\nWARNING!\n========\n");
-               log_std("%s\n\nAre you sure? (Type uppercase yes): ", msg);
+               log_std("%s\n\nAre you sure? (Type 'yes' in capital letters): ", msg);
+               fflush(stdout);
                if(getline(&answer, &size, stdin) == -1) {
                        r = 0;
                        /* Aborted by signal */
                        if (!quit)
-                               log_err(_("Error reading response from terminal.\n"));
+                               log_err(_("Error reading response from terminal."));
                        else
                                log_dbg("Query interrupted on signal.");
-               } else if(strcmp(answer, "YES\n"))
-                       r = 0;
+               } else {
+                       r = !strcmp(answer, "YES\n");
+                       if (!r && fail_msg)
+                               log_err("%s", fail_msg);
+               }
        }
 
        if (block && !quit)
@@ -160,9 +165,19 @@ int yesDialog(const char *msg, void *usrptr __attribute__((unused)))
        return r;
 }
 
+int yesDialog(const char *msg, void *usrptr)
+{
+       return _dialog(msg, usrptr, 1);
+}
+
+int noDialog(const char *msg, void *usrptr)
+{
+       return _dialog(msg, usrptr, 0);
+}
+
 void show_status(int errcode)
 {
-       char error[256], *error_;
+       char *crypt_error;
 
        if(!opt_verbose)
                return;
@@ -172,21 +187,23 @@ void show_status(int errcode)
                return;
        }
 
-       crypt_get_error(error, sizeof(error));
-
-       if (!error[0]) {
-               error_ = strerror_r(-errcode, error, sizeof(error));
-               if (error_ != error) {
-                       strncpy(error, error_, sizeof(error));
-                       error[sizeof(error) - 1] = '\0';
-               }
-       }
-
-       log_err(_("Command failed with code %i"), -errcode);
-       if (*error)
-               log_err(": %s\n", error);
+       if (errcode < 0)
+               errcode = translate_errno(errcode);
+
+       if (errcode == 1)
+               crypt_error = _("wrong or missing parameters");
+       else if (errcode == 2)
+               crypt_error = _("no permission or bad passphrase");
+       else if (errcode == 3)
+               crypt_error = _("out of memory");
+       else if (errcode == 4)
+               crypt_error = _("wrong device or file specified");
+       else if (errcode == 5)
+               crypt_error = _("device already exists or device is busy");
        else
-               log_err(".\n");
+               crypt_error = _("unknown error");
+
+       log_std(_("Command failed with code %i (%s).\n"), -errcode, crypt_error);
 }
 
 const char *uuid_or_device(const char *spec)
@@ -200,7 +217,7 @@ const char *uuid_or_device(const char *spec)
                strcpy(device, "/dev/disk/by-uuid/");
                ptr = &device[strlen(device)];
                i = uuid_len;
-               while ((s = spec[i++]) && i < PATH_MAX) {
+               while ((s = spec[i++]) && i < (PATH_MAX - 13)) {
                        if (!isxdigit(s) && s != '-')
                                return spec; /* Bail it out */
                        if (isalpha(s))
@@ -220,7 +237,7 @@ __attribute__ ((noreturn)) void usage(poptContext popt_context,
 {
        poptPrintUsage(popt_context, stderr, 0);
        if (error)
-               log_err("%s: %s\n", more, error);
+               log_err("%s: %s", more, error);
        poptFreeContext(popt_context);
        exit(exitcode);
 }
@@ -256,3 +273,361 @@ int translate_errno(int r)
        }
        return r;
 }
+
+void tools_keyslot_msg(int keyslot, crypt_object_op op)
+{
+       if (keyslot < 0)
+               return;
+
+       if (op == CREATED)
+               log_verbose(_("Key slot %i created."), keyslot);
+       else if (op == UNLOCKED)
+               log_verbose(_("Key slot %i unlocked."), keyslot);
+       else if (op == REMOVED)
+               log_verbose(_("Key slot %i removed."), keyslot);
+}
+
+void tools_token_msg(int token, crypt_object_op op)
+{
+       if (token < 0)
+               return;
+
+       if (op == CREATED)
+               log_verbose(_("Token %i created."), token);
+       else if (op == REMOVED)
+               log_verbose(_("Token %i removed."), token);
+}
+
+/*
+ * Device size string parsing, suffixes:
+ * s|S - 512 bytes sectors
+ * k  |K  |m  |M  |g  |G  |t  |T   - 1024 base
+ * kiB|KiB|miB|MiB|giB|GiB|tiB|TiB - 1024 base
+ * kb |KB |mM |MB |gB |GB |tB |TB  - 1000 base
+ */
+int tools_string_to_size(struct crypt_device *cd, const char *s, uint64_t *size)
+{
+       char *endp = NULL;
+       size_t len;
+       uint64_t mult_base, mult, tmp;
+
+       *size = strtoull(s, &endp, 10);
+       if (!isdigit(s[0]) ||
+           (errno == ERANGE && *size == ULLONG_MAX) ||
+           (errno != 0 && *size == 0))
+               return -EINVAL;
+
+       if (!endp || !*endp)
+               return 0;
+
+       len = strlen(endp);
+       /* Allow "B" and "iB" suffixes */
+       if (len > 3 ||
+          (len == 3 && (endp[1] != 'i' || endp[2] != 'B')) ||
+          (len == 2 && endp[1] != 'B'))
+               return -EINVAL;
+
+       if (len == 1 || len == 3)
+               mult_base = 1024;
+       else
+               mult_base = 1000;
+
+       mult = 1;
+       switch (endp[0]) {
+       case 's':
+       case 'S': mult = 512;
+               break;
+       case 't':
+       case 'T': mult *= mult_base;
+                /* Fall through */
+       case 'g':
+       case 'G': mult *= mult_base;
+                /* Fall through */
+       case 'm':
+       case 'M': mult *= mult_base;
+                /* Fall through */
+       case 'k':
+       case 'K': mult *= mult_base;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       tmp = *size * mult;
+       if (*size && (tmp / *size) != mult) {
+               log_dbg("Device size overflow.");
+               return -EINVAL;
+       }
+
+       *size = tmp;
+       return 0;
+}
+
+/* Time progress helper */
+
+/* The difference in seconds between two times in "timeval" format. */
+static double time_diff(struct timeval *start, struct timeval *end)
+{
+       return (end->tv_sec - start->tv_sec)
+               + (end->tv_usec - start->tv_usec) / 1E6;
+}
+
+void tools_clear_line(void)
+{
+       if (opt_progress_frequency)
+               return;
+       /* vt100 code clear line */
+       log_std("\33[2K\r");
+}
+
+static void tools_time_progress(uint64_t device_size, uint64_t bytes, uint64_t *start_bytes,
+                        struct timeval *start_time, struct timeval *end_time)
+{
+       struct timeval now_time;
+       unsigned long long mbytes, eta;
+       double tdiff, uib, frequency;
+       int final = (bytes == device_size);
+       const char *eol, *ustr = "";
+
+       if (opt_batch_mode)
+               return;
+
+       gettimeofday(&now_time, NULL);
+       if (start_time->tv_sec == 0 && start_time->tv_usec == 0) {
+               *start_time = now_time;
+               *end_time = now_time;
+               *start_bytes = bytes;
+               return;
+       }
+
+       if (opt_progress_frequency) {
+               frequency = (double)opt_progress_frequency;
+               eol = "\n";
+       } else {
+               frequency = 0.5;
+               eol = "";
+       }
+
+       if (!final && time_diff(end_time, &now_time) < frequency)
+               return;
+
+       *end_time = now_time;
+
+       tdiff = time_diff(start_time, end_time);
+       if (!tdiff)
+               return;
+
+       mbytes = bytes  / 1024 / 1024;
+       uib = (double)(bytes - *start_bytes) / tdiff;
+
+       /* FIXME: calculate this from last minute only. */
+       eta = (unsigned long long)(device_size / uib - tdiff);
+
+       if (uib > 1073741824.0f) {
+               uib /= 1073741824.0f;
+               ustr = "Gi";
+       } else if (uib > 1048576.0f) {
+               uib /= 1048576.0f;
+               ustr = "Mi";
+       } else if (uib > 1024.0f) {
+               uib /= 1024.0f;
+               ustr = "Ki";
+       }
+
+       tools_clear_line();
+       if (final)
+               log_std("Finished, time %02llu:%02llu.%03llu, "
+                       "%4llu MiB written, speed %5.1f %sB/s\n",
+                       (unsigned long long)tdiff / 60,
+                       (unsigned long long)tdiff % 60,
+                       (unsigned long long)((tdiff - floor(tdiff)) * 1000.0),
+                       mbytes, uib, ustr);
+       else
+               log_std("Progress: %5.1f%%, ETA %02llu:%02llu, "
+                       "%4llu MiB written, speed %5.1f %sB/s%s",
+                       (double)bytes / device_size * 100,
+                       eta / 60, eta % 60, mbytes, uib, ustr, eol);
+       fflush(stdout);
+}
+
+int tools_wipe_progress(uint64_t size, uint64_t offset, void *usrptr)
+{
+       static struct timeval start_time = {}, end_time = {};
+       static uint64_t start_offset = 0;
+       int r = 0;
+
+       tools_time_progress(size, offset, &start_offset, &start_time, &end_time);
+
+       check_signal(&r);
+       if (r) {
+               tools_clear_line();
+               log_err(_("\nWipe interrupted."));
+       }
+
+       return r;
+}
+
+static void report_partition(const char *value, const char *device)
+{
+       if (opt_batch_mode)
+               log_dbg("Device %s already contains a '%s' partition signature.", device, value);
+       else
+               log_std(_("WARNING: Device %s already contains a '%s' partition signature.\n"), device, value);
+}
+
+static void report_superblock(const char *value, const char *device)
+{
+       if (opt_batch_mode)
+               log_dbg("Device %s already contains a '%s' superblock signature.", device, value);
+       else
+               log_std(_("WARNING: Device %s already contains a '%s' superblock signature.\n"), device, value);
+}
+
+int tools_detect_signatures(const char *device, int ignore_luks, size_t *count)
+{
+       int r;
+       size_t tmp_count;
+       struct blkid_handle *h;
+       blk_probe_status pr;
+
+       if (!count)
+               count = &tmp_count;
+
+       *count = 0;
+
+       if (!blk_supported()) {
+               log_dbg("Blkid support disabled.");
+               return 0;
+       }
+
+       if ((r = blk_init_by_path(&h, device))) {
+               log_err(_("Failed to initialize device signature probes."));
+               return -EINVAL;
+       }
+
+       blk_set_chains_for_full_print(h);
+
+       if (ignore_luks && blk_superblocks_filter_luks(h)) {
+               r = -EINVAL;
+               goto out;
+       }
+
+       while ((pr = blk_probe(h)) < PRB_EMPTY) {
+               if (blk_is_partition(h))
+                       report_partition(blk_get_partition_type(h), device);
+               else if (blk_is_superblock(h))
+                       report_superblock(blk_get_superblock_type(h), device);
+               else {
+                       log_dbg("Internal tools_detect_signatures() error.");
+                       r = -EINVAL;
+                       goto out;
+               }
+               (*count)++;
+       }
+
+       if (pr == PRB_FAIL)
+               r = -EINVAL;
+out:
+       blk_free(h);
+       return r;
+}
+
+int tools_wipe_all_signatures(const char *path)
+{
+       int fd, flags, r;
+       blk_probe_status pr;
+       struct stat st;
+       struct blkid_handle *h = NULL;
+
+       if (!blk_supported()) {
+               log_dbg("Blkid support disabled.");
+               return 0;
+       }
+
+       if (stat(path, &st)) {
+               log_err(_("Failed to stat device %s."), path);
+               return -EINVAL;
+       }
+
+       flags = O_RDWR;
+       if (S_ISBLK(st.st_mode))
+               flags |= O_EXCL;
+
+       /* better than opening regular file with O_EXCL (undefined) */
+       /* coverity[toctou] */
+       fd = open(path, flags);
+       if (fd < 0) {
+               if (errno == EBUSY)
+                       log_err(_("Device %s is in use. Can not proceed with format operation."), path);
+               else
+                       log_err(_("Failed to open file %s in read/write mode."), path);
+               return -EINVAL;
+       }
+
+       if ((r = blk_init_by_fd(&h, fd))) {
+               log_err(_("Failed to initialize device signature probes."));
+               r = -EINVAL;
+               goto out;
+       }
+
+       blk_set_chains_for_wipes(h);
+
+       while ((pr = blk_probe(h)) < PRB_EMPTY) {
+               if (blk_is_partition(h))
+                       log_verbose(_("Existing '%s' partition signature (offset: %" PRIi64 " bytes) on device %s will be wiped."),
+                                   blk_get_partition_type(h), blk_get_offset(h), path);
+               if (blk_is_superblock(h))
+                       log_verbose(_("Existing '%s' superblock signature (offset: %" PRIi64 " bytes) on device %s will be wiped."),
+                                   blk_get_superblock_type(h), blk_get_offset(h), path);
+               if (blk_do_wipe(h)) {
+                       log_err(_("Failed to wipe device signature."));
+                       r = -EINVAL;
+                       goto out;
+               }
+       }
+
+       if (pr != PRB_EMPTY) {
+               log_err(_("Failed to probe device %s for a signature."), path);
+               r = -EINVAL;
+       }
+out:
+       close(fd);
+       blk_free(h);
+       return r;
+}
+
+int tools_is_cipher_null(const char *cipher)
+{
+       if (!cipher)
+               return 0;
+
+       return !strcmp(cipher, "cipher_null") ? 1 : 0;
+}
+
+/*
+ * Keyfile - is standard input treated as a binary file (no EOL handling).
+ */
+int tools_is_stdin(const char *key_file)
+{
+       if (!key_file)
+               return 1;
+
+       return strcmp(key_file, "-") ? 0 : 1;
+}
+
+int tools_reencrypt_progress(uint64_t size, uint64_t offset, void *usrptr)
+{
+       static struct timeval start_time = {}, end_time = {};
+       static uint64_t start_offset = 0;
+       int r = 0;
+
+       tools_time_progress(size, offset, &start_offset, &start_time, &end_time);
+
+       check_signal(&r);
+       if (r) {
+               tools_clear_line();
+               log_err(_("\nReencryption interrupted."));
+       }
+
+       return r;
+}