Exemplo n.º 1
0
static gboolean
handle_mdraid_create (StoragedManager         *_object,
                      GDBusMethodInvocation   *invocation,
                      const gchar *const      *arg_blocks,
                      const gchar             *arg_level,
                      const gchar             *arg_name,
                      guint64                  arg_chunk,
                      GVariant                *arg_options)
{
  StoragedLinuxManager *manager = STORAGED_LINUX_MANAGER (_object);
  StoragedObject *array_object = NULL;
  uid_t caller_uid;
  GError *error = NULL;
  const gchar *message;
  const gchar *action_id;
  guint num_devices = 0;
  GList *blocks = NULL;
  GList *l;
  guint n;
  gchar *escaped_name = NULL;
  GString *str = NULL;
  gint status;
  gchar *error_message = NULL;
  gchar *raid_device_file = NULL;
  struct stat statbuf;
  dev_t raid_device_num;

  error = NULL;
  if (!storaged_daemon_util_get_caller_uid_sync (manager->daemon, invocation, NULL /* GCancellable */, &caller_uid, NULL, NULL, &error))
    {
      g_dbus_method_invocation_return_gerror (invocation, error);
      g_clear_error (&error);
      goto out;
    }

  /* Translators: Shown in authentication dialog when the user
   * attempts to start a RAID Array.
   */
  /* TODO: variables */
  message = N_("Authentication is required to create a RAID array");
  action_id = "org.storaged.Storaged.manage-md-raid";
  if (!storaged_daemon_util_check_authorization_sync (manager->daemon,
                                                      NULL,
                                                      action_id,
                                                      arg_options,
                                                      message,
                                                      invocation))
    goto out;

  /* validate level */
  for (n = 0; raid_level_whitelist[n] != NULL; n++)
    {
      if (g_strcmp0 (raid_level_whitelist[n], arg_level) == 0)
        break;
    }
  if (raid_level_whitelist[n] == NULL)
    {
      g_dbus_method_invocation_return_error (invocation, STORAGED_ERROR, STORAGED_ERROR_FAILED,
                                             "Unsupported RAID level %s", arg_level);
      goto out;
    }

  /* validate chunk (TODO: check that it's a power of 2) */
  if ((arg_chunk & 0x0fff) != 0)
    {
      g_dbus_method_invocation_return_error (invocation, STORAGED_ERROR, STORAGED_ERROR_FAILED,
                                             "Chunk %" G_GUINT64_FORMAT " is not a multiple of 4KiB", arg_chunk);
      goto out;
    }

  /* validate name */
  if (g_strcmp0 (arg_level, "raid1") == 0 && arg_chunk != 0)
    {
      g_dbus_method_invocation_return_error (invocation, STORAGED_ERROR, STORAGED_ERROR_FAILED,
                                             "Chunk must be zero for level 'raid1'");
      goto out;
    }

  /* validate name */
  if (strlen (arg_name) > 32)
    {
      g_dbus_method_invocation_return_error (invocation, STORAGED_ERROR, STORAGED_ERROR_FAILED,
                                             "Name is invalid");
      goto out;
    }

  num_devices = g_strv_length ((gchar **) arg_blocks);

  /* validate number of devices */
  if (num_devices < 2)
    {
      g_dbus_method_invocation_return_error (invocation, STORAGED_ERROR, STORAGED_ERROR_FAILED,
                                             "Must have at least two devices");
      goto out;
    }

  /* Collect and validate block objects
   *
   * Also, check we can open the block devices at the same time - this
   * is to avoid start deleting half the block devices while the other
   * half is already in use.
   */
  for (n = 0; arg_blocks != NULL && arg_blocks[n] != NULL; n++)
    {
      StoragedObject *object = NULL;
      StoragedBlock *block = NULL;
      gchar *device_file = NULL;
      int fd;

      object = storaged_daemon_find_object (manager->daemon, arg_blocks[n]);
      if (object == NULL)
        {
          g_dbus_method_invocation_return_error (invocation,
                                                 STORAGED_ERROR,
                                                 STORAGED_ERROR_FAILED,
                                                 "Invalid object path %s at index %u",
                                                 arg_blocks[n], n);
          goto out;
        }

      block = storaged_object_get_block (object);
      if (block == NULL)
        {
          g_dbus_method_invocation_return_error (invocation,
                                                 STORAGED_ERROR,
                                                 STORAGED_ERROR_FAILED,
                                                 "Object path %s for index %u is not a block device",
                                                 arg_blocks[n], n);
          goto out;
        }

      device_file = storaged_block_dup_device (block);
      fd = open (device_file, O_RDWR | O_EXCL);
      if (fd < 0)
        {
          g_dbus_method_invocation_return_error (invocation,
                                                 STORAGED_ERROR,
                                                 STORAGED_ERROR_FAILED,
                                                 "Error opening device %s: %m",
                                                 device_file);
          g_free (device_file);
          goto out;
        }
      close (fd);
      g_free (device_file);

      blocks = g_list_prepend (blocks, block); /* adopts ownership */
      g_object_unref (object);
    }
  blocks = g_list_reverse (blocks);

  /* wipe existing devices */
  for (l = blocks; l != NULL; l = l->next)
    {
      StoragedBlock *block = STORAGED_BLOCK (l->data);
      StoragedObject *object_for_block;
      gchar *escaped_device;
      object_for_block = storaged_daemon_util_dup_object (block, &error);
      if (object_for_block == NULL)
        {
          g_dbus_method_invocation_return_gerror (invocation, error);
          g_clear_error (&error);
          goto out;
        }
      escaped_device = storaged_daemon_util_escape (storaged_block_get_device (block));
      if (!storaged_daemon_launch_spawned_job_sync (manager->daemon,
                                                  object_for_block,
                                                  "format-erase", caller_uid,
                                                  NULL, /* cancellable */
                                                  0,    /* uid_t run_as_uid */
                                                  0,    /* uid_t run_as_euid */
                                                  &status,
                                                  &error_message,
                                                  NULL, /* input_string */
                                                  "wipefs -a \"%s\"",
                                                  escaped_device))
        {
          g_dbus_method_invocation_return_error (invocation,
                                                 STORAGED_ERROR,
                                                 STORAGED_ERROR_FAILED,
                                                 "Error wiping device %s to be used in a RAID array: %s",
                                                 storaged_block_get_device (block),
                                                 error_message);
          g_free (error_message);
          g_object_unref (object_for_block);
          g_free (escaped_device);
          goto out;
        }
      g_object_unref (object_for_block);
      g_free (escaped_device);
    }

  /* Create the array... */
  escaped_name = storaged_daemon_util_escape (arg_name);
  str = g_string_new ("mdadm");
  raid_device_file = storaged_daemon_util_get_free_mdraid_device ();
  if (raid_device_file == NULL)
    {
      g_dbus_method_invocation_return_error (invocation, STORAGED_ERROR, STORAGED_ERROR_FAILED,
                                             "Unable to find free MD device");
      goto out;
    }
  g_string_append_printf (str, " --create %s", raid_device_file);
  g_string_append_printf (str, " --run");
  if (arg_chunk > 0)
    g_string_append_printf (str, " --chunk %" G_GUINT64_FORMAT, (guint64) (arg_chunk / 1024LL));
  g_string_append_printf (str, " --level %s", arg_level);
  if (strlen (arg_name) > 0)
    g_string_append_printf (str, " --name \"%s\"", escaped_name);
  g_string_append_printf (str, " --raid-devices %u", num_devices);
  for (l = blocks; l != NULL; l = l->next)
    {
      StoragedBlock *block = STORAGED_BLOCK (l->data);
      gchar *escaped_device;
      escaped_device = storaged_daemon_util_escape (storaged_block_get_device (block));
      g_string_append_printf (str, " \"%s\"", escaped_device);
      g_free (escaped_device);
    }

  if (!storaged_daemon_launch_spawned_job_sync (manager->daemon,
                                                NULL,
                                                "mdraid-create", caller_uid,
                                                NULL, /* cancellable */
                                                0,    /* uid_t run_as_uid */
                                                0,    /* uid_t run_as_euid */
                                                &status,
                                                &error_message,
                                                NULL, /* input_string */
                                                "%s",
                                                str->str))
    {
      g_dbus_method_invocation_return_error (invocation,
                                             STORAGED_ERROR,
                                             STORAGED_ERROR_FAILED,
                                             "Error creating RAID array: %s",
                                             error_message);
      g_free (error_message);
      goto out;
    }

  /* ... then, sit and wait for raid array object to show up */
  array_object = storaged_daemon_wait_for_object_sync (manager->daemon,
                                                       wait_for_array_object,
                                                       raid_device_file,
                                                       NULL,
                                                       10, /* timeout_seconds */
                                                       &error);
  if (array_object == NULL)
    {
      g_prefix_error (&error,
                      "Error waiting for array object after creating %s",
                      raid_device_file);
      g_dbus_method_invocation_take_error (invocation, error);
      goto out;
    }

  if (stat (raid_device_file, &statbuf) != 0)
    {
      g_dbus_method_invocation_return_error (invocation,
                                             STORAGED_ERROR,
                                             STORAGED_ERROR_FAILED,
                                             "Error calling stat(2) on %s: %m",
                                             raid_device_file);
      goto out;
    }
  if (!S_ISBLK (statbuf.st_mode))
    {
      g_dbus_method_invocation_return_error (invocation,
                                             STORAGED_ERROR,
                                             STORAGED_ERROR_FAILED,
                                             "Device file %s is not a block device",
                                             raid_device_file);
      goto out;
    }
  raid_device_num = statbuf.st_rdev;

  /* update the mdraid file */
  storaged_state_add_mdraid (storaged_daemon_get_state (manager->daemon),
                             raid_device_num,
                             caller_uid);

  /* ... wipe the created RAID array */
  if (!storaged_daemon_launch_spawned_job_sync (manager->daemon,
                                                array_object,
                                                "format-erase", caller_uid,
                                                NULL, /* cancellable */
                                                0,    /* uid_t run_as_uid */
                                                0,    /* uid_t run_as_euid */
                                                &status,
                                                &error_message,
                                                NULL, /* input_string */
                                                "wipefs -a %s",
                                                raid_device_file))
    {
      g_dbus_method_invocation_return_error (invocation,
                                             STORAGED_ERROR,
                                             STORAGED_ERROR_FAILED,
                                             "Error wiping raid device %s: %s",
                                             raid_device_file,
                                             error_message);
      goto out;
    }

  /* ... finally trigger uevents on the members - we want this so the
   * udev database is updated for them with e.g. ID_FS_TYPE. Ideally
   * mdadm(8) or whatever thing is writing out the RAID metadata would
   * ensure this, but that's not how things currently work :-/
   */
  for (l = blocks; l != NULL; l = l->next)
    {
      StoragedBlock *block = STORAGED_BLOCK (l->data);
      StoragedObject *object_for_block;
      object_for_block = storaged_daemon_util_dup_object (block, &error);
      if (object_for_block == NULL)
        {
          g_dbus_method_invocation_return_gerror (invocation, error);
          g_clear_error (&error);
          goto out;
        }
      storaged_linux_block_object_trigger_uevent (STORAGED_LINUX_BLOCK_OBJECT (object_for_block));
      g_object_unref (object_for_block);
    }

  /* ... and, we're done! */
  storaged_manager_complete_mdraid_create (_object,
                                           invocation,
                                           g_dbus_object_get_object_path (G_DBUS_OBJECT (array_object)));

 out:
  g_free (raid_device_file);
  if (str != NULL)
    g_string_free (str, TRUE);
  g_list_free_full (blocks, g_object_unref);
  g_free (escaped_name);
  g_clear_object (&array_object);

  return TRUE; /* returning TRUE means that we handled the method invocation */
}
static StoragedObject *
storaged_linux_partition_table_handle_create_partition (StoragedPartitionTable   *table,
                                                        GDBusMethodInvocation    *invocation,
                                                        guint64                   offset,
                                                        guint64                   size,
                                                        const gchar              *type,
                                                        const gchar              *name,
                                                        GVariant                 *options)
{
  const gchar *action_id = NULL;
  const gchar *message = NULL;
  StoragedBlock *block = NULL;
  StoragedObject *object = NULL;
  StoragedDaemon *daemon = NULL;
  gchar *error_message = NULL;
  gchar *escaped_device = NULL;
  gchar *command_line = NULL;
  WaitForPartitionData *wait_data = NULL;
  StoragedObject *partition_object = NULL;
  StoragedBlock *partition_block = NULL;
  gchar *escaped_partition_device = NULL;
  const gchar *table_type;
  uid_t caller_uid;
  gid_t caller_gid;
  gboolean do_wipe = TRUE;
  GError *error;

  error = NULL;
  object = storaged_daemon_util_dup_object (table, &error);
  if (object == NULL)
    {
      g_dbus_method_invocation_take_error (invocation, error);
      goto out;
    }

  daemon = storaged_linux_block_object_get_daemon (STORAGED_LINUX_BLOCK_OBJECT (object));
  block = storaged_object_get_block (object);
  if (block == NULL)
    {
      g_dbus_method_invocation_return_error (invocation, STORAGED_ERROR, STORAGED_ERROR_FAILED,
                                             "Partition table object is not a block device");
      goto out;
    }

  error = NULL;
  if (!storaged_daemon_util_get_caller_uid_sync (daemon,
                                                 invocation,
                                                 NULL /* GCancellable */,
                                                 &caller_uid,
                                                 &caller_gid,
                                                 NULL,
                                                 &error))
    {
      g_dbus_method_invocation_return_gerror (invocation, error);
      g_error_free (error);
      goto out;
    }

  action_id = "org.storaged.Storaged.modify-device";
  /* Translators: Shown in authentication dialog when the user
   * requests creating a new partition.
   *
   * Do not translate $(drive), it's a placeholder and
   * will be replaced by the name of the drive/device in question
   */
  message = N_("Authentication is required to create a partition on $(drive)");
  if (!storaged_daemon_util_setup_by_user (daemon, object, caller_uid))
    {
      if (storaged_block_get_hint_system (block))
        {
          action_id = "org.storaged.Storaged.modify-device-system";
        }
      else if (!storaged_daemon_util_on_user_seat (daemon, object, caller_uid))
        {
          action_id = "org.storaged.Storaged.modify-device-other-seat";
        }
    }

  if (!storaged_daemon_util_check_authorization_sync (daemon,
                                                    object,
                                                    action_id,
                                                    options,
                                                    message,
                                                    invocation))
    goto out;

  escaped_device = storaged_daemon_util_escape_and_quote (storaged_block_get_device (block));

  table_type = storaged_partition_table_get_type_ (table);
  wait_data = g_new0 (WaitForPartitionData, 1);
  if (g_strcmp0 (table_type, "dos") == 0)
    {
      guint64 start_mib;
      guint64 end_bytes;
      guint64 max_end_bytes;
      const gchar *part_type;
      char *endp;
      gint type_as_int;
      gboolean is_logical = FALSE;

      max_end_bytes = storaged_block_get_size (block);

      if (strlen (name) > 0)
        {
          g_dbus_method_invocation_return_error (invocation, STORAGED_ERROR, STORAGED_ERROR_FAILED,
                                                 "MBR partition table does not support names");
          goto out;
        }

      /* Determine whether we are creating a primary, extended or logical partition */
      type_as_int = strtol (type, &endp, 0);
      if (type[0] != '\0' && *endp == '\0' &&
          (type_as_int == 0x05 || type_as_int == 0x0f || type_as_int == 0x85))
        {
          part_type = "extended";
          do_wipe = FALSE;  // wiping an extended partition destroys it
          if (have_partition_in_range (table, object, offset, offset + size, FALSE))
            {
              g_dbus_method_invocation_return_error (invocation, STORAGED_ERROR, STORAGED_ERROR_FAILED,
                                                     "Requested range is already occupied by a partition");
              goto out;
            }
        }
      else
        {
          if (have_partition_in_range (table, object, offset, offset + size, FALSE))
            {
              if (have_partition_in_range (table, object, offset, offset + size, TRUE))
                {
                  g_dbus_method_invocation_return_error (invocation, STORAGED_ERROR, STORAGED_ERROR_FAILED,
                                                         "Requested range is already occupied by a partition");
                  goto out;
                }
              else
                {
                  StoragedPartition *container = find_container_partition (table, object,
                                                                           offset, offset + size);
                  g_assert (container != NULL);
                  is_logical = TRUE;
                  part_type = "logical ext2";
                  max_end_bytes = (storaged_partition_get_offset(container)
                                   + storaged_partition_get_size(container));
                }
            }
          else
            {
              part_type = "primary ext2";
            }
        }

      /* Ensure we _start_ at MiB granularity since that ensures optimal IO...
       * Also round up size to nearest multiple of 512
       */
      start_mib = offset / MIB_SIZE + 1L;
      end_bytes = start_mib * MIB_SIZE + ((size + 511L) & (~511L));

      /* Now reduce size until we are not
       *
       *  - overlapping neighboring partitions; or
       *  - exceeding the end of the disk
       */
      while (end_bytes > start_mib * MIB_SIZE && (have_partition_in_range (table,
                                                                           object,
                                                                           start_mib * MIB_SIZE,
                                                                           end_bytes, is_logical) ||
                                                  end_bytes > max_end_bytes))
        {
          /* TODO: if end_bytes is sufficiently big this could be *a lot* of loop iterations
           *       and thus a potential DoS attack...
           */
          end_bytes -= 512L;
        }
      wait_data->pos_to_wait_for = (start_mib*MIB_SIZE + end_bytes) / 2L;
      wait_data->ignore_container = is_logical;

      command_line = g_strdup_printf ("parted --align optimal --script %s "
                                      "\"mkpart %s %" G_GUINT64_FORMAT "MiB %" G_GUINT64_FORMAT "b\"",
                                      escaped_device,
                                      part_type,
                                      start_mib,
                                      end_bytes - 1); /* end_bytes is *INCLUSIVE* (!) */
    }
  else if (g_strcmp0 (table_type, "gpt") == 0)
    {
      guint64 start_mib;
      guint64 end_bytes;
      gchar *escaped_name;
      gchar *escaped_escaped_name;

      /* GPT is easy, no extended/logical crap */
      if (have_partition_in_range (table, object, offset, offset + size, FALSE))
        {
          g_dbus_method_invocation_return_error (invocation, STORAGED_ERROR, STORAGED_ERROR_FAILED,
                                                 "Requested range is already occupied by a partition");
          goto out;
        }

      /* bah, parted(8) is broken with empty names (it sets the name to 'ext2' in that case)
       * TODO: file bug
       */
      if (strlen (name) == 0)
        {
          name = " ";
        }

      escaped_name = storaged_daemon_util_escape (name);
      escaped_escaped_name = storaged_daemon_util_escape (escaped_name);

      /* Ensure we _start_ at MiB granularity since that ensures optimal IO...
       * Also round up size to nearest multiple of 512
       */
      start_mib = offset / MIB_SIZE + 1L;
      end_bytes = start_mib * MIB_SIZE + ((size + 511L) & (~511L));

      /* Now reduce size until we are not
       *
       *  - overlapping neighboring partitions; or
       *  - exceeding the end of the disk (note: the 33 LBAs is the Secondary GPT)
       */
      while (end_bytes > start_mib * MIB_SIZE && (have_partition_in_range (table,
                                                                           object,
                                                                           start_mib * MIB_SIZE,
                                                                           end_bytes, FALSE) ||
                                                  (end_bytes > storaged_block_get_size (block) - 33*512)))
        {
          /* TODO: if end_bytes is sufficiently big this could be *a lot* of loop iterations
           *       and thus a potential DoS attack...
           */
          end_bytes -= 512L;
        }
      wait_data->pos_to_wait_for = (start_mib*MIB_SIZE + end_bytes) / 2L;
      command_line = g_strdup_printf ("parted --align optimal --script %s "
                                      "\"mkpart \\\"%s\\\" ext2 %" G_GUINT64_FORMAT "MiB %" G_GUINT64_FORMAT "b\"",
                                      escaped_device,
                                      escaped_escaped_name,
                                      start_mib,
                                      end_bytes - 1); /* end_bytes is *INCLUSIVE* (!) */
      g_free (escaped_escaped_name);
      g_free (escaped_name);
    }
  else
    {
      g_dbus_method_invocation_return_error (invocation, STORAGED_ERROR, STORAGED_ERROR_FAILED,
                                             "Don't know how to create partitions this partition table of type `%s'",
                                             table_type);
      goto out;
    }

  if (!storaged_daemon_launch_spawned_job_sync (daemon,
                                              object,
                                              "partition-create", caller_uid,
                                              NULL, /* GCancellable */
                                              0,    /* uid_t run_as_uid */
                                              0,    /* uid_t run_as_euid */
                                              NULL, /* gint *out_status */
                                              &error_message,
                                              NULL,  /* input_string */
                                              "%s",
                                              command_line))
    {
      g_dbus_method_invocation_return_error (invocation,
                                             STORAGED_ERROR,
                                             STORAGED_ERROR_FAILED,
                                             "Error creating partition on %s: %s",
                                             storaged_block_get_device (block),
                                             error_message);
      goto out;
    }
  /* this is sometimes needed because parted(8) does not generate the uevent itself */
  storaged_linux_block_object_trigger_uevent (STORAGED_LINUX_BLOCK_OBJECT (object));

  /* sit and wait for the partition to show up */
  g_warn_if_fail (wait_data->pos_to_wait_for > 0);
  wait_data->partition_table_object = object;
  error = NULL;
  partition_object = storaged_daemon_wait_for_object_sync (daemon,
                                                           wait_for_partition,
                                                           wait_data,
                                                           NULL,
                                                           30,
                                                           &error);
  if (partition_object == NULL)
    {
      g_prefix_error (&error, "Error waiting for partition to appear: ");
      g_dbus_method_invocation_take_error (invocation, error);
      goto out;
    }
  partition_block = storaged_object_get_block (partition_object);
  if (partition_block == NULL)
    {
      g_dbus_method_invocation_return_error (invocation, STORAGED_ERROR, STORAGED_ERROR_FAILED,
                                             "Partition object is not a block device");
      g_clear_object (&partition_object);
      goto out;
    }
  escaped_partition_device = storaged_daemon_util_escape_and_quote (storaged_block_get_device (partition_block));

  /* TODO: set partition type */

  /* wipe the newly created partition if wanted */
  if (do_wipe)
    {
      if (!storaged_daemon_launch_spawned_job_sync (daemon,
                                                    partition_object,
                                                    "partition-create", caller_uid,
                                                    NULL, /* GCancellable */
                                                    0,    /* uid_t run_as_uid */
                                                    0,    /* uid_t run_as_euid */
                                                    NULL, /* gint *out_status */
                                                    &error_message,
                                                    NULL,  /* input_string */
                                                    "wipefs -a %s",
                                                    escaped_partition_device))
        {
          g_dbus_method_invocation_return_error (invocation,
                                                 STORAGED_ERROR,
                                                 STORAGED_ERROR_FAILED,
                                                 "Error wiping newly created partition %s: %s",
                                                 storaged_block_get_device (partition_block),
                                                 error_message);
          g_clear_object (&partition_object);
          goto out;
        }
    }

  /* this is sometimes needed because parted(8) does not generate the uevent itself */
  storaged_linux_block_object_trigger_uevent (STORAGED_LINUX_BLOCK_OBJECT (partition_object));

 out:
  g_free (escaped_partition_device);
  g_free (wait_data);
  g_clear_object (&partition_block);
  g_free (command_line);
  g_free (escaped_device);
  g_free (error_message);
  g_clear_object (&object);
  g_clear_object (&block);
  return partition_object;
}