/* libguestfs
 * Copyright (C) 2010 Red Hat Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include <config.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "guestfs.h"
#include "guestfs-internal.h"
#include "guestfs-internal-actions.h"

/* List filesystems.
 *
 * The current implementation just uses guestfs_vfs_type and doesn't
 * try mounting anything, but we reserve the right in future to try
 * mounting filesystems.
 */

static void remove_from_list (char **list, const char *item);
static int check_with_vfs_type (guestfs_h *g, const char *dev, struct stringsbuf *sb);
static int is_mbr_partition_type_42 (guestfs_h *g, const char *partition);

char **
guestfs_impl_list_filesystems (guestfs_h *g)
{
  size_t i;
  DECLARE_STRINGSBUF (ret);

  const char *lvm2[] = { "lvm2", NULL };
  const int has_lvm2 = guestfs_feature_available (g, (char **) lvm2);
  const char *ldm[] = { "ldm", NULL };
  const int has_ldm = guestfs_feature_available (g, (char **) ldm);

  CLEANUP_FREE_STRING_LIST char **devices = NULL;
  CLEANUP_FREE_STRING_LIST char **partitions = NULL;
  CLEANUP_FREE_STRING_LIST char **mds = NULL;
  CLEANUP_FREE_STRING_LIST char **lvs = NULL;
  CLEANUP_FREE_STRING_LIST char **ldmvols = NULL;
  CLEANUP_FREE_STRING_LIST char **ldmparts = NULL;

  /* Look to see if any devices directly contain filesystems
   * (RHBZ#590167).  However vfs-type will fail to tell us anything
   * useful about devices which just contain partitions, so we also
   * get the list of partitions and exclude the corresponding devices
   * by using part-to-dev.
   */
  devices = guestfs_list_devices (g);
  if (devices == NULL) goto error;
  partitions = guestfs_list_partitions (g);
  if (partitions == NULL) goto error;
  mds = guestfs_list_md_devices (g);
  if (mds == NULL) goto error;

  for (i = 0; partitions[i] != NULL; ++i) {
    CLEANUP_FREE char *dev = guestfs_part_to_dev (g, partitions[i]);
    if (dev)
      remove_from_list (devices, dev);
  }

  /* Use vfs-type to check for filesystems on devices. */
  for (i = 0; devices[i] != NULL; ++i)
    if (check_with_vfs_type (g, devices[i], &ret) == -1)
      goto error;

  /* Use vfs-type to check for filesystems on partitions. */
  for (i = 0; partitions[i] != NULL; ++i) {
    if (has_ldm == 0 || ! is_mbr_partition_type_42 (g, partitions[i])) {
      if (check_with_vfs_type (g, partitions[i], &ret) == -1)
        goto error;
    }
  }

  /* Use vfs-type to check for filesystems on md devices. */
  for (i = 0; mds[i] != NULL; ++i)
    if (check_with_vfs_type (g, mds[i], &ret) == -1)
      goto error;

  if (has_lvm2 > 0) {
    /* Use vfs-type to check for filesystems on LVs. */
    lvs = guestfs_lvs (g);
    if (lvs == NULL) goto error;

    for (i = 0; lvs[i] != NULL; ++i)
      if (check_with_vfs_type (g, lvs[i], &ret) == -1)
        goto error;
  }

  if (has_ldm > 0) {
    /* Use vfs-type to check for filesystems on Windows dynamic disks. */
    ldmvols = guestfs_list_ldm_volumes (g);
    if (ldmvols == NULL) goto error;

    for (i = 0; ldmvols[i] != NULL; ++i)
      if (check_with_vfs_type (g, ldmvols[i], &ret) == -1)
        goto error;

    ldmparts = guestfs_list_ldm_partitions (g);
    if (ldmparts == NULL) goto error;

    for (i = 0; ldmparts[i] != NULL; ++i)
      if (check_with_vfs_type (g, ldmparts[i], &ret) == -1)
        goto error;
  }

  /* Finish off the list and return it. */
  guestfs_int_end_stringsbuf (g, &ret);
  return ret.argv;

 error:
  guestfs_int_free_stringsbuf (&ret);
  return NULL;
}

/* If 'item' occurs in 'list', remove and free it. */
static void
remove_from_list (char **list, const char *item)
{
  size_t i;

  for (i = 0; list[i] != NULL; ++i)
    if (STREQ (list[i], item)) {
      free (list[i]);
      for (; list[i+1] != NULL; ++i)
        list[i] = list[i+1];
      list[i] = NULL;
      return;
    }
}

/* Use vfs-type to look for a filesystem of some sort on 'dev'.
 * Apart from some types which we ignore, add the result to the
 * 'ret' string list.
 */
static int
check_with_vfs_type (guestfs_h *g, const char *device, struct stringsbuf *sb)
{
  const char *v;
  CLEANUP_FREE char *vfs_type = NULL;

  guestfs_push_error_handler (g, NULL, NULL);
  vfs_type = guestfs_vfs_type (g, device);
  guestfs_pop_error_handler (g);

  if (!vfs_type)
    v = "unknown";
  else if (STREQ (vfs_type, ""))
    v = "unknown";
  else if (STREQ (vfs_type, "btrfs")) {
    CLEANUP_FREE_BTRFSSUBVOLUME_LIST struct guestfs_btrfssubvolume_list *vols =
      guestfs_btrfs_subvolume_list (g, device);

    if (vols == NULL)
      return -1;

    const int64_t default_volume =
      guestfs_btrfs_subvolume_get_default (g, device);

    for (size_t i = 0; i < vols->len; i++) {
      struct guestfs_btrfssubvolume *this = &vols->val[i];

      /* Ignore the default subvolume.  We get it by simply mounting
       * the whole device of this btrfs filesystem.
       */
      if (this->btrfssubvolume_id == (uint64_t) default_volume)
        continue;

      guestfs_int_add_sprintf (g, sb,
			       "btrfsvol:%s/%s",
			       device, this->btrfssubvolume_path);
      guestfs_int_add_string (g, sb, "btrfs");
    }

    v = vfs_type;
  }
  else {
    /* Ignore all "*_member" strings.  In libblkid these are returned
     * for things which are members of some RAID or LVM set, most
     * importantly "LVM2_member" which is a PV.
     */
    const size_t n = strlen (vfs_type);
    if (n >= 7 && STREQ (&vfs_type[n-7], "_member"))
      return 0;

    /* Ignore LUKS-encrypted partitions.  These are also containers. */
    if (STREQ (vfs_type, "crypto_LUKS"))
      return 0;

    v = vfs_type;
  }

  guestfs_int_add_string (g, sb, device);
  guestfs_int_add_string (g, sb, v);

  return 0;
}

/* We should ignore partitions that have MBR type byte 0x42, because
 * these are members of a Windows dynamic disk group.  Trying to read
 * them will cause errors (RHBZ#887520).  Assuming that libguestfs was
 * compiled with ldm support, we'll get the filesystems on these later.
 */
static int
is_mbr_partition_type_42 (guestfs_h *g, const char *partition)
{
  CLEANUP_FREE char *device = NULL;
  int partnum;
  int mbr_id;
  int ret = 0;

  guestfs_push_error_handler (g, NULL, NULL);

  partnum = guestfs_part_to_partnum (g, partition);
  if (partnum == -1)
    goto out;

  device = guestfs_part_to_dev (g, partition);
  if (device == NULL)
    goto out;

  mbr_id = guestfs_part_get_mbr_id (g, device, partnum);

  ret = mbr_id == 0x42;

 out:
  guestfs_pop_error_handler (g);

  return ret;
}
