/*********************************************************
 * Copyright (C) 2014-2021 VMware, Inc. All rights reserved.
 *
 * This program 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 version 2.1 and no 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 Lesser GNU General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA.
 *
 *********************************************************/

/**
 * @file diskInfo.c
 *
 *      Get disk information.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "vm_assert.h"
#include "debug.h"
#include "guestInfoInt.h"
#include "str.h"
#include "strutil.h"
#include "posix.h"
#include "file.h"
#include "util.h"
#include "wiper.h"

/*
 * TODO: A general reorganization of or removal of diskInfo.c is needed
 *
 * Presumably diskInfo.c is meant to contain routines common to both Windows
 * and *nix guests; but that has not been the case.  Up until the addition
 * of OS disk device mapping, this source file contained only two functions.
 *
 *  GuestInfo_FreeDiskInfo() -    4 line function called for Windows and *nix
 *                                to free the partitionList used to collect
 *                                disk information in the guest.
 *  GuestInfoGetDiskInfoWiper() - Function only called from diskInfoPosix.c
 *                                to use the disk wiper routines to query
 *                                "known" file systems on a *nix guest.
 *
 * As a result, the Windows guestInfo plugin has included the unreferenced
 * GuestInfoGetDiskInfoWiper() function in releases.
 *
 * As of this change, diskInfoWin32.c has its own copy of
 * GuestInfo_FreeDiskInfo() as that function has changed for *nix disk
 * info.  Only non-Windows tools builds will include diskInfo.c.
 *
 * As device-based disc mapping is implemented for FreeBSD, MacOS and
 * Solaris, diskInfo.c could be folded into diskInfoPosix.c.  Disk device
 * lookup could be separate modules based on OS type or implemented
 * with conditional compilation.  Both methods have been used elsewhere
 * in tools.
 *
 * PR 2350224 has been filed to track this TODO item.
 */

#if defined (__linux__)
#ifndef PATH_MAX
   # define PATH_MAX 1024
#endif

#define LINUX_SYS_CLASS_BLOCK_DIR "/sys/class/block"
#define LINUX_SYS_DEV_BLOCK_DIR "/sys/dev/block"
#define LINUX_PROC_SELF_MOUNTINFO "/proc/self/mountinfo"

#define PCI_IDE         0x010100
#define PCI_SATA_AHCI_1 0x010601

#define PCI_SUBCLASS    0xFFFF00
#endif

#define COMP_STATIC_REGEX(gregex, mypattern, gerr, errorout)        \
   if (gregex == NULL) {                                            \
      gregex = g_regex_new(mypattern, 0, 0, &gerr);                 \
      if (gregex == NULL) {                                         \
         g_warning("%s: bad regex pattern \"" mypattern "\" (%s);"  \
                   " failing with INVALID_ARG\n",  __FUNCTION__,    \
                   gerr != NULL ? gerr->message : "");              \
         goto errorout;                                             \
      }                                                             \
   }



/*
 ******************************************************************************
 * GuestInfo_FreeDiskInfo --                                             */ /**
 *
 * @brief Frees memory allocated by GuestInfoGetDiskInfo.
 *
 * @param[in] di    DiskInfo container.
 *
 ******************************************************************************
 */

void
GuestInfo_FreeDiskInfo(GuestDiskInfoInt *di)
{
   if (di) {
      int indx;

      for (indx = 0; indx < di->numEntries; indx++) {
         free(di->partitionList[indx].diskDevNames);
      }
      free(di->partitionList);
      free(di);
   }
}


/*
 * Private library functions.
 */

#if defined (__linux__)

/*
 ******************************************************************************
 * GuestInfoAddDeviceName --                                             */ /**
 *
 * Add the disk device name into the array of anticipated devices for
 * the specified filesystem.
 *
 * @param[in]     devName    The device name being added.  May be an empty
 *                           string.
 * @param[in/out] partEntry  Pointer to the PartitionEntryInt structure to
 *                           receive the disk device names.
 * @param[in]     devNum     The number of the DiskDevName to be updated.
 *                           The diskDevNames is numbered  1, 2, ... as opposed
 *                           to array indexes which start at 0.
 *
 ******************************************************************************
 */

static void
GuestInfoAddDeviceName(char *devName,
                       PartitionEntryInt *partEntry,
                       int devNum)
{
   ASSERT(devName);
   ASSERT(partEntry);

   /* Add the device name to the device name array at the specified slot. */
   Str_ToLower(devName);
   if (devNum > partEntry->diskDevCnt) {
      partEntry->diskDevCnt = devNum;
      partEntry->diskDevNames = Util_SafeRealloc(partEntry->diskDevNames,
                                                 devNum *
                                                 sizeof *partEntry->diskDevNames);
   }
   Str_Strncpy(partEntry->diskDevNames[devNum - 1],
               sizeof *partEntry->diskDevNames,
               devName, strlen(devName));

   if (devName[0] == '\0') {
      g_debug("Empty disk device name in slot %d\n", devNum);
   }
}


/*
 ******************************************************************************
 * GuestInfoCheckPci0 --                                                */ /**
 *
 * Check if this PCI device path does not contain a "label" file and happens
 * to be PCI bus 0.
 *
 * @param[in]  pciDevPath  Path of the PCI device of interest.
 * @param[in]  unit        Disk unit or device number (previously determined).
 * @param[out] devName     Address of the buffer to receive device name.
 * @param[in]  devMaxLen   Maximum length of the device buffer available.
 *
 * @return True if this disk device is on PCI bus 0 and the devName has
 *         been filled in as scsi0:unit.
 *
 ******************************************************************************
 */

static Bool
GuestInfoCheckPci0(const char *pciDevPath,
                   const char *unit,
                   char *devName,
                   size_t devMaxLen)
{
   char *realPath = NULL;
   Bool result = FALSE;

   if((realPath = Posix_RealPath(pciDevPath)) == NULL) {
      goto exit;
   }
   if (StrUtil_EndsWith(realPath, "/0000:00:10.0")) {
      Str_Snprintf (devName, devMaxLen, "scsi0:%s", unit);
      result = TRUE;
   }
   free(realPath);
exit:
   return result;
}


/*
 ******************************************************************************
 * GuestInfoGetPCIName --                                                */ /**
 *
 * Extract the controller class and controller number from the "label" of
 * the specified PCI device.  Combine these with the previously determined
 * device or unit number.  The device name will be constructed in the format of
 * <class> <controller> : <unit> such as scsi0:0 or sata0:0.  The devName
 * will be left "blank" if unable to process the contents of the "label" file.
 *
 * @param[in]  pciDevPath  Path of the PCI device of interest.
 * @param[in]  unit        Disk unit or device number (previously determined).
 * @param[out] devName     Address of the buffer to receive device name.
 * @param[in]  devMaxLen   Maximum length of the device buffer available.
 *
 ******************************************************************************
 */

static void
GuestInfoGetPCIName(const char *pciDevPath,
                    const char *unit,
                    char *devName,
                    size_t devMaxLen)
{
   char labelPath[PATH_MAX];
   FILE *labelFile;
   char buffer[25];
   char *cPtr;

   Str_Snprintf(labelPath, PATH_MAX, "%s/%s", pciDevPath, "label");

   if ((labelFile = fopen(labelPath, "r")) == NULL) {
      /*
       * Need to check if this might be the LSI Logic Parallel or BusLogic
       * SCSI controller on PCI bus 0, in which case "label" is not expected.
       */
      if (errno != ENOENT ||
          !GuestInfoCheckPci0(pciDevPath, unit, devName, devMaxLen)) {
         g_debug("%s: unable to open \"label\" file for device %s.\n",
                 __FUNCTION__, pciDevPath);
      }
      return;
   }
   if (fgets(buffer, sizeof buffer, labelFile) == NULL) {
      g_debug("%s: unable to read \"label\" file for device %s.\n",
              __FUNCTION__, pciDevPath);
      goto exit;
   }

   /*
    * The "label" contents should already be in the form of SCSIn or satan.
    * A '\0' is stored after the last character in the buffer by fgets().
    * Check if the last character read is a new line and strip if found. */
   cPtr = &buffer[strlen(buffer) -1];
   if (*cPtr == '\n') {
      *cPtr = '\0';
   }
   Str_Snprintf (devName, devMaxLen, "%s:%s", buffer, unit);

exit:
   fclose(labelFile);
}


/*
 ******************************************************************************
 * GuestInfoGetIdeSataDev --                                             */ /**
 *
 * Determine the IDE controller or the SATA device number of the specified disk
 * device.
 *
 * @param[in]  tgtHostPath  Path of the PCI device of interest.
 * @param[in]  pciDevPath   Path of the PCI device of interest.
 *
 * @return   The unit number of the specified disk device.  A return value
 *           of -1 indicates that the unit number could not be determined.
 *
 ******************************************************************************
 */

static int
GuestInfoGetIdeSataDev(const char *tgtHostPath,
                       const char *pciDevPath)
{
   char *realPath = NULL;
   char **fileNameList = NULL;
   static GRegex *regexHostPath = NULL;
   static GRegex *regexHost = NULL;
   static GRegex *regexAtaPath = NULL;
   static GRegex *regexAta = NULL;
   GRegex *regexAtaOrHost;
   GError *gErr = NULL;
   GMatchInfo *matchInfo = NULL;
   char *charHost = NULL;
   int result = -1;
   int numFiles = 0;
   int number = 0;
   int fileNum;
   int host;

   /*
    * Depending on the Linux kernel version, the IDE device or SATA
    * controller notation may begin with "host" or "ata".  More recent
    * Linux releases are using "ata".
    */
   COMP_STATIC_REGEX(regexAtaPath, "^.*/ata(\\d+)$", gErr, exit)
   realPath = Posix_RealPath(tgtHostPath);
   if (g_regex_match(regexAtaPath, realPath, 0, &matchInfo)) {
      COMP_STATIC_REGEX(regexAta, "^ata(\\d+)$", gErr, exit)
      regexAtaOrHost = regexAta;

   } else {
      g_match_info_free(matchInfo);
      COMP_STATIC_REGEX(regexHostPath, "^.*/host(\\d+)$", gErr, exit)
      if (g_regex_match(regexHostPath, realPath, 0, &matchInfo)) {
         COMP_STATIC_REGEX(regexHost, "^host(\\d+)$", gErr, exit)
         regexAtaOrHost = regexHost;

      } else {
         g_debug("%s: Unable to locate IDE/SATA \"ata\" or \"host\" node "
                 "directory.\n", __FUNCTION__);
         goto exit;
      }
   }

   charHost = g_match_info_fetch(matchInfo, 1);
   if (sscanf(charHost, "%d", &host) != 1) {
      g_debug("%s: Unable to read host number.\n", __FUNCTION__);
      goto exit;
   }

   numFiles = File_ListDirectory(pciDevPath, &fileNameList);
   if (numFiles < 0) {
      g_debug("%s: Unable to list files in \"%s\" directory.\n",
              __FUNCTION__, pciDevPath);
   }
   for (fileNum = 0; fileNum < numFiles; fileNum++) {
      int currHost;

      g_match_info_free(matchInfo);
      if (g_regex_match(regexAtaOrHost, fileNameList[fileNum], 0, &matchInfo)) {
         g_free(charHost);
         charHost = g_match_info_fetch(matchInfo, 1);
         if (sscanf(charHost, "%d", &currHost) != 1) {
            g_debug("%s: Unable to read current host number.\n",
                    __FUNCTION__);
            goto exit;
         }
         if (currHost < host) {
            number++;
         }
      }
   }
   result = number;

exit:
   g_match_info_free(matchInfo);
   g_free(charHost);
   g_clear_error(&gErr);
   if (fileNameList != NULL) {
      Util_FreeStringList(fileNameList, numFiles);
   }
   free(realPath);
   return result;
}


/*
 ******************************************************************************
 * GuestInfoGetDevClass --                                               */ /**
 *
 * Locate and extract the value from the "class" file of the specified disk
 * device.  This file is typically located in the directory provided by the
 * parameter "pciDevPath".
 *
 * An IDE device layout change that occurred between 2.x and 3.x Linux kernels
 * may require a check in the parent directory.  If that is necessary, the
 * "pciDevPath" and "devPath" will be updated to reflect that difference.
 *
 * @param[in/out]  pciDevPath  Path to the IDE, SCSI or SAS disk device of
 *                             interest; expecting "class" file.
 * @param[in/out]  devPath     Associated directory which will need to be
 *                             adjusted if pciDevPath is altered.
 *
 * @return     The disk device class value.
 *
 ******************************************************************************
 */

static unsigned int
GuestInfoGetDevClass(char *pciDevPath,
                     char *devPath)
{
   char devClassPath[PATH_MAX];
   FILE *devClass = NULL;
   unsigned int classValue = 0;

   ASSERT(pciDevPath);
   Str_Snprintf(devClassPath, PATH_MAX, "%s/%s", pciDevPath, "class");
   if (!File_Exists(devClassPath)) {
      /* Check if "class" exists in the parent directory. */
      Str_Snprintf(devClassPath, PATH_MAX, "%s/../%s", pciDevPath, "class");
      if (File_Exists(devClassPath)) {
         /* "class" found in parent directory; probably IDE/SATA device.*/
         Str_Strcat(pciDevPath, "/..", PATH_MAX);
         Str_Strcat(devPath, "/..", PATH_MAX);
      } else {
         g_debug("%s: Unable to locate device 'class' file.\n", __FUNCTION__);
         goto exit;
      }
   }

   devClass = fopen((const char *)devClassPath, "r");
   if (devClass == NULL) {
      g_debug("%s: Error opening device 'class' file.\n", __FUNCTION__);
      goto exit;
   }
   if (fscanf(devClass, "%x", &classValue) != 1) {
      classValue = 0;
      g_debug("%s: Unable to read expected hex class setting.\n", __FUNCTION__);
   }
   fclose(devClass);

exit:
   return classValue;
}


/*
 ******************************************************************************
 * GuestInfoCheckSASDevice --                                            */ /**
 *
 * Check if the referenced disk device is a SAS device and if so, recalulate
 * the device (unit) number and update the paths as needed to continue
 * processing a SAS device.
 *
 * @param[in/out] pciDevPath  Path of the disk device to be checked.
 * @param[out]    tgtHostPath Address of the "host" path to be updated if this
 *                            is a SAS device.
 * @param[out]    unit        Pointer to the address of the unit buffer to be
 *                            updated if this is a SAS device.
 *
 ******************************************************************************
 */

static void
GuestInfoCheckSASDevice(char *pciDevPath,
                        char *tgtHostPath,
                        char **unit)
{
   char sas_portPath[PATH_MAX];
   char **fileNameList = NULL;
   static GRegex *regexSas = NULL;
   GError *gErr = NULL;
   GMatchInfo *matchInfo = NULL;
   int numFiles = 0;
   int fileNum;

   Str_Snprintf(sas_portPath, PATH_MAX, "%s/%s", pciDevPath, "sas_port");
   if (!File_IsDirectory(sas_portPath)) {
      return;
   }
   g_debug("%s: located a \"sas_port\" directory - %s.\n", __FUNCTION__,
           sas_portPath);

   /* Expecting to find a new "unit" number; scribble over old value.. */
   **unit = '?';
   COMP_STATIC_REGEX(regexSas, "^phy-\\d+:(\\d+)$", gErr, exit)

   numFiles = File_ListDirectory(pciDevPath, &fileNameList);
   if (numFiles < 0) {
      g_debug("%s: Unable to list files in \"%s\" directory.\n", __FUNCTION__,
              pciDevPath);
   }
   for (fileNum = 0; fileNum < numFiles; fileNum++) {
      g_match_info_free(matchInfo);
      if (g_regex_match(regexSas, fileNameList[fileNum], 0, &matchInfo)) {
         free(*unit);     /* free previous "unit" string */
         *unit = g_match_info_fetch(matchInfo, 1);
         break;
      }
   }

   /*
    * Adjust the tgtHostPath and pciDevPath for continued processing of
    * a SAS disk device.
    */
   Str_Snprintf(tgtHostPath, PATH_MAX, "%s/..", pciDevPath);
   Str_Snprintf(pciDevPath, PATH_MAX, "%s/..", tgtHostPath);

exit:
   g_match_info_free(matchInfo);
   g_clear_error(&gErr);
   if (fileNameList != NULL) {
      Util_FreeStringList(fileNameList, numFiles);
   }
}


/*
 ******************************************************************************
 * GuestInfoNvmeDevice --                                                */ /**
 *
 * Extract the NVMe disk unit number for the specified disk device.  In
 * Linux kernels 3.x and later, the NVME device is contained in the "nsid"
 * file in the sysfs filesystem.  On earlier kernels such as 2.6, the NVME
 * device number is the last number group in the node name.
 *
 * NVMe devices are number 1 and up; the "physical" disk unit numbering
 * starts at zero.
 *
 *  @param[in]    devPath    Path of the "device" file for this NVMe device.
 *                           The "nsid" file, if it exists, will be in the
 *                           same directory.
 * @param[out]    pciDevPath Path to be updated with the directory containing
 *                           the device "label" file.   This path will also
 *                           contain the device "class" file.
 * @param[out]    unit       Pointer to receive the address of the disk device
 *                           number dereived from the contents of the "nsid"
 *                           file or from the node name in 2.6 Linux kernels.
 *
 * @return TRUE if the NVMe disk device number has been determined and the
 *         device number is passed back as a character string through "unit".
 *         The "pciDevPath will be updated (replaced) with the directory which
 *         should contain the device "label" file.
 *
 ******************************************************************************
 */

static Bool
GuestInfoNvmeDevice(const char *devPath,
                    char *pciDevPath,
                    char **unit)
{
   char nsidPath[PATH_MAX];
   FILE *devNsid = NULL;
   int dirPathLen;
   int nsid;

   ASSERT(devPath);
   ASSERT(pciDevPath);
   dirPathLen = strrchr(devPath, '/') - devPath;
   Str_Strncpy(nsidPath, sizeof nsidPath, devPath, dirPathLen);
   Str_Strcat(nsidPath, "/nsid", PATH_MAX);
   if (File_Exists(nsidPath)) {
      devNsid = fopen((const char *)nsidPath, "r");
      if (devNsid == NULL) {
         g_debug("%s: Error opening NVMe device \"nsid\" file.\n",
                 __FUNCTION__);
         return FALSE;
      }
      if (fscanf(devNsid, "%d", &nsid) != 1) {
         g_debug("%s: Unable to read the nsid device number.\n", __FUNCTION__);
         fclose(devNsid);
         return FALSE;
      }
      fclose(devNsid);
   } else {
      static GRegex *regexNvmeNode = NULL;
      GError *gErr = NULL;
      GMatchInfo *matchInfo = NULL;
      char *nsidStr = NULL;
      char * realPath;
      Bool match_result = FALSE;

      /*
       * Earlier NVMe kernel implementation; no "nsid" file available.
       * The nsid equivalent can be derived from the last number in the
       * directory containing the "device" symbolic link.  Need real path
       * name.
       */
      Str_Strncpy(nsidPath, sizeof nsidPath, devPath, dirPathLen);
      realPath = Posix_RealPath(nsidPath);

      /* Check for NVMe device. */
      COMP_STATIC_REGEX(regexNvmeNode, "^.*/nvme\\d+n(\\d+)$", gErr, finished)

      if (!g_regex_match(regexNvmeNode, realPath, 0, &matchInfo)) {
         goto finished;
      }
      nsidStr = g_match_info_fetch(matchInfo, 1);
      nsid = atoi(nsidStr);
      match_result = TRUE;

finished:
      /* Some clean-up for this block before possibly returning FALSE. */
      g_match_info_free(matchInfo);
      g_clear_error(&gErr);
      g_free(nsidStr);
      free(realPath);

      if (!match_result) {
         return FALSE;
      }
   }

   /* NVMe device numbers start at 1; hardware device numbers start at zero. */
   *unit = g_strdup_printf("%d", nsid - 1);

   /* Set the pciDevPath to the directory containing the "label" file, */
   Str_Snprintf(pciDevPath, PATH_MAX, "%s/%s", devPath, "../..");
   return TRUE;
}


/*
 ******************************************************************************
 * GuestInfoLinuxBlockDevice --                                          */ /**
 *
 * Determine if this is a block device and if so add the disk device name
 * to the specified PartitionEntryInt structure.  If the startDevPath
 * represents a full disk, i.e. no partition table on the disk, the
 * "device" file will be in the starting directory.  For filesystems
 * created on a disk partition, the "device" file will be in the parent node.
 *
 * @param[in]     startPath  Starting path to begin the search for the "device"
 *                           file.
 * @param[in/out] partEntry  Pointer to the PartitionEntryInt structure to
 *                           receive the disk device names.
 * @param[in]     devNum     The number of the DiskDevName to be updated.
 *                           The diskDevNames is numbered  1, 2, ... as opposed
 *                           to array indexes which start at 0.
 * @return TRUE if the device name for the disk has been determined.
 *
 ******************************************************************************
 */

static Bool
GuestInfoLinuxBlockDevice(const char *startPath,
                          PartitionEntryInt *partEntry,
                          int devNum)
{
   char devPath[PATH_MAX];
   char pciDevPath[PATH_MAX];
   char *realPath = NULL;
   static GRegex *regex = NULL;
   static GRegex *regexNvme = NULL;
   GError *gErr = NULL;
   GMatchInfo *matchInfo = NULL;
   char *unit = NULL;
   unsigned int devClass;
   char devName[DISK_DEVICE_NAME_SIZE];
   Bool devNameSet;

   ASSERT(startPath);
   ASSERT(partEntry);
   ASSERT(devNum > 0);
   devName[0] = '\0';   /* Empty string for the device name until determined. */
   g_debug("%s: looking up device for file system on \"%s\"\n", __FUNCTION__,
           startPath);

   /* Check for "device" file in the starting path provided. */
   Str_Snprintf(devPath, PATH_MAX,  "%s/device", startPath);
   if (!File_Exists(devPath)) {
      /*
       * If working with a filesystem on a disk partition, we will need
       * to check in the parent node.
       */
      Str_Snprintf(devPath, PATH_MAX,  "%s/../device", startPath);
      if (!File_Exists(devPath)) {
         goto finished;
      }
   }

   realPath = Posix_RealPath(devPath);
   COMP_STATIC_REGEX(regex, "^.*/\\d+:\\d+:(\\d+):\\d+$", gErr, finished)

   if (g_regex_match(regex, realPath, 0, &matchInfo)) {
      unit = g_match_info_fetch(matchInfo, 1);

      Str_Strcat(devPath, "/../..", PATH_MAX);
      Str_Snprintf(pciDevPath, PATH_MAX, "%s/%s", devPath, "..");
      /*
       * Check if this is a SAS device.  The contents of "unit", "devPath" and
       * "pciDevPath" will be altered if a SAS device is detected..
       */
      GuestInfoCheckSASDevice(pciDevPath, devPath, &unit);

      /* Getting the disk device class. */
      devClass = GuestInfoGetDevClass(pciDevPath, devPath);

      /*
       * IDE and SATA devices need different handling.
       */
      if ((devClass & PCI_SUBCLASS) == PCI_IDE || devClass == PCI_SATA_AHCI_1) {
         int cnt;

         cnt = GuestInfoGetIdeSataDev(devPath, pciDevPath);
         if (cnt < 0) {
            g_debug("%s: ERROR, unable to determine IDE controller or SATA "
                    "device.\n", __FUNCTION__);
            goto finished;
         }
         if ((devClass & PCI_SUBCLASS) == PCI_IDE) {
            /* IDE - full device representation can be constructed. */
            Str_Snprintf(devName, sizeof devName, "ide%d:%s", cnt, unit);
         } else {
            /* SATA - The "host cnt" obtained becomes the "unit" number. */
            g_free(unit);
            unit = g_strdup_printf("%d", cnt);
         }
      }
   } else {
      /* Check for NVMe device. */
      COMP_STATIC_REGEX(regexNvme, "^.*/nvme\\d+$", gErr, finished)

      g_match_info_free(matchInfo);
      if (!g_regex_match(regexNvme, realPath, 0, &matchInfo)) {
         g_debug("%s: block disk device pattern not found\n", __FUNCTION__);
         goto finished;
      }
      if (!GuestInfoNvmeDevice(devPath, pciDevPath, &unit)) {
         g_debug("%s: NVMe disk device could not be determined.\n",
                 __FUNCTION__);
         goto finished;
      }
   }

   /* At this point only IDE disks would have a completed device name. */
   if (devName[0] == '\0') {
      /* Access the PCI device "label" file for the controller name. */
      GuestInfoGetPCIName(pciDevPath, unit, devName, sizeof devName);
   }

finished:
   /*
    * Add the device name, whether found or not, to the device list for
    * this mounted file system.  After processing the single partition of
    * a file system mounted on a block device or all the subordinate devices of
    * an LVM, the presence of a zero-length name indicates not all devices
    * have been correctly determined.
    */
   GuestInfoAddDeviceName(devName, partEntry, devNum);

   g_match_info_free(matchInfo);
   g_clear_error(&gErr);
   g_free(unit);
   free(realPath);
   devNameSet = devName[0] != '\0';
   g_debug("%s: Filesystem of interest found on device \"%s\"\n",
           __FUNCTION__, devNameSet ? devName : "** unknown **");
   return devNameSet;
}


/*
 ******************************************************************************
 * GuestInfoIsLinuxLvmDevice --                                          */ /**
 *
 * Determine if the fsName is a Linux LVM and if so, determine the disk
 * device or devices associated with this LVM based filesystem.  If for
 * any reason the full set of LVM subordinates cannot be determined, an
 * incomplete list of disk device names will not be provided.
 *
 * @param[in]     fsName     Name of the block device or LVM mapper name of
 *                           the filesystem of interest.
 * @param[in/out] partEntry  Pointer to the PartitionEntryInt structure to
 *                           receive the disk device names.
 * @return TRUE if the "slaves" directory has been located.  It does not
 *         indicate that LVM processing was successful, only that this
 *         does appear to be an LVM filesystem.  If the disk device names
 *         for all subordinates cannot be located, partial disk mapping is not
 *         reported.
 *
 ******************************************************************************
 */

static Bool
GuestInfoIsLinuxLvmDevice(const char *fsName,
                          PartitionEntryInt *partEntry)
{
   char *realPath;
   char **fileNameList = NULL;
   int numFiles = 0;
   int devIndx;
   char subordinatesPath[PATH_MAX];
   char devPath[PATH_MAX];

   /*
    * If a logical volume, the fsName will be a symbolic link to the
    * /dev/dm-<n>.  Use the "dm-<n>" name to access the logical volume
    * entry in the sysfs/device manager (/sys/class/block).
    */
   if ((realPath = Posix_RealPath(fsName)) == NULL) {
      return FALSE;
   }
   Str_Snprintf(subordinatesPath, PATH_MAX, "%s/%s/slaves", LINUX_SYS_CLASS_BLOCK_DIR,
                strrchr(realPath, '/') + 1);
   free(realPath);
   if (!File_IsDirectory(subordinatesPath)) {
      return FALSE;
   }
   numFiles = File_ListDirectory(subordinatesPath, &fileNameList);
   if (numFiles == 0) {
      /* An empty "slaves" directory happens at a disk device node; this
       * certainly is not an LVM.
       */
      return FALSE;
   }
   if (numFiles < 0) {
      g_debug("%s: Unable to list entries in \"%s\" directory.\n", __FUNCTION__,
              subordinatesPath);
      return TRUE;
   }

   /* Create a device name entry for each subordinate device found. */
   partEntry->diskDevCnt = numFiles;
   partEntry->diskDevNames = Util_SafeRealloc(partEntry->diskDevNames,
                                              numFiles *
                                              sizeof *partEntry->diskDevNames);

   for (devIndx = 0; devIndx < numFiles; devIndx++) {
      /*
       * Each subordinate device will be based on the disk or disk partition of
       * a virtual disk device.  Start the block device search from the
       * "slaves" path.
       */
      Str_Snprintf(devPath, PATH_MAX, "%s/%s", subordinatesPath,
                   fileNameList[devIndx]);
      GuestInfoLinuxBlockDevice(devPath, partEntry, devIndx + 1);
   }

   if (fileNameList != NULL) {
      Util_FreeStringList(fileNameList, numFiles);
   }
   return TRUE;
}


/*
 ******************************************************************************
 * GuestInfoCheckDevRoot --
 *
 * Disks mounted on /dev/root are setup at boot time before the OS enumerates
 * the PCI devices in /sys/class/block.  The device for /dev/root does not
 * appear there.   Instead the PCI device info needs to be accessed through
 * /sys/dev/block and the major and minor device number.  That can be extracted
 * from the contents of /proc/self/mountinfo.
 *
 * The format of the lines in /proc/self/mountinfo can vary between Linux
 * versions.
 *
 *    Field
 *    -----
 *      3     The major and minor number of the device in "m:n" format.
 *   9 or 10  The fsName for this specific entry.
 *
 * @param[in]  fsName       Name of the filesystem device - i.e. /dev/root
 * @param[in]  pathLen      Maximum size including terminating NUL character
 *                          for the starting search path.
 * @param[out] blockDevPath Starting path to begin the search for the "device"
 *                          file if the major and minor number can be found.
 * @return TRUE if the device major and minor numbers have been located and
 *         blockDevPath has been filled in.
 *
 ******************************************************************************
 */

static Bool
GuestInfoCheckDevRoot(const char *fsName,
                      int pathLen,
                      char *blockDevPath)
{
   FILE *mountinfo;
   char buffer[BUFSIZ];
   char pattern[128];

   if ((mountinfo = fopen(LINUX_PROC_SELF_MOUNTINFO, "r")) == NULL) {
      g_debug("%s: unable to open \"" LINUX_PROC_SELF_MOUNTINFO
              "\": (%d) %s\n", __FUNCTION__, errno, strerror(errno));
      return FALSE;
   }
   snprintf(pattern, sizeof pattern, " %s ", fsName);
   /* Locate the "/dev/root" entry in /proc/self/mountinfo. */
   while ((fgets(buffer, sizeof buffer, mountinfo)) != NULL) {
      if (strstr(buffer, pattern) != NULL) {
         char *savedPtr = NULL;
         const char *majMinNo;

         (void) strtok_r(buffer, " ", &savedPtr);
         (void) strtok_r(NULL, " ", &savedPtr);
         majMinNo = strtok_r(NULL, " ", &savedPtr);
         Str_Snprintf(blockDevPath, pathLen, "%s/%s", LINUX_SYS_DEV_BLOCK_DIR,
                      majMinNo);
         fclose(mountinfo);
         return TRUE;
      }
   }
   fclose(mountinfo);
   return FALSE;
}

#endif /* __linux__ */

/*
 ******************************************************************************
 * GuestInfoGetDiskDevice --                                             */ /**
 *
 * Determine the OS disk device for the block device or the disk devices of
 * the logical volume mapper name provided.
 *
 * @param[in]     fsName     Name of the block device or LVM mapper name of
 *                           the filesystem of interest.
 * @param[in/out] partEntry  Pointer to the PartitionEntryInt structure to
 *                           receive the disk device names.
 *
 * Currently only processing disks on a Linux guest.
 *
 * TODO: Other available controllers and their importance on vSphere hosted
 *       guests were discussed in review board posting:
 *          https://reviewboard.eng.vmware.com/r/1520060/
 *       PR 2356195 has been filed to track these issues post 11.0.0 FC.
 ******************************************************************************
 */

static void
GuestInfoGetDiskDevice(const char *fsName,
                       PartitionEntryInt *partEntry)
{
#if defined (__linux__)
   int indx;
#endif /* __linux__ */

   ASSERT(fsName);
   ASSERT(partEntry);
   g_debug("%s: looking up device(s) for file system on \"%s\".\n",
           __FUNCTION__, fsName);

#if defined (__linux__)
   /*
    * Determine if this is a filesystem on a block device or on a logical
    * volume (such as /dev/mapper/...).
    */

   /* Check first for an LVM filesystem. */
   if (!GuestInfoIsLinuxLvmDevice(fsName, partEntry)) {

      /* Not an LVM; check if a basic block device. */
      const char *baseDevName = strrchr(fsName, '/');

      /*
       * ZFS pools are not currently handled and may consist of a simple file
       * name.  Neither situation can currently be handled in the PCI device
       * lookup; avoid at this time.
       */
      if (baseDevName != NULL && strcmp(partEntry->fsType, "zfs") != 0) {
         char blockDevPath[PATH_MAX];

         /*
          * Have a single disk device associated with this mount point.  The
          * majority of these will be handled by the basic Linux block device
          * lookup.
          */
         Str_Snprintf(blockDevPath, sizeof blockDevPath,  "%s/%s",
                      LINUX_SYS_CLASS_BLOCK_DIR, baseDevName + 1);
         if (!GuestInfoLinuxBlockDevice(blockDevPath, partEntry, 1)) {
            /*
             * No device name was located.  This may be a pseudo device
             * such as Photon's /dev/root.  Try a lookup based on the device
             * major and minor number.
             */
            if (GuestInfoCheckDevRoot(fsName, sizeof blockDevPath,
                                      blockDevPath)) {
               GuestInfoLinuxBlockDevice(blockDevPath, partEntry, 1);
            }
         }
      }
   }

   /*
    * Check that all expected devices have been found.  If not, reset the
    * device count to zero so that partial disk mapping will not happen.
    */
   for (indx = 0; indx < partEntry->diskDevCnt; indx++) {
      if (partEntry->diskDevNames[indx][0] == '\0') {
         g_debug("%s: Missing disk device name; VMDK mapping unavailable "
                 "for \"%s\", fsName: \"%s\"\n", __FUNCTION__,
                 partEntry->name, fsName);
         partEntry->diskDevCnt = 0;
         free(partEntry->diskDevNames);
         partEntry->diskDevNames = NULL;
         break;
      }
   }
#endif /* __linux__ */

   g_debug("%s: found %d devices(s) for file system on \"%s\".\n",
           __FUNCTION__, partEntry->diskDevCnt, fsName);
}


/*
 ******************************************************************************
 * GuestInfoGetDiskInfoWiper --                                          */ /**
 *
 * Uses wiper library to enumerate fixed volumes and lookup utilization data.
 *
 * @return Pointer to a GuestDiskInfoInt structure on success or NULL on failure.
 *         Caller should free returned pointer with GuestInfo_FreeDiskInfo.
 *
 ******************************************************************************
 */

GuestDiskInfoInt *
GuestInfoGetDiskInfoWiper(Bool includeReserved,  // IN
                          Bool reportDevices)    // IN
{
   WiperPartition_List pl;
   DblLnkLst_Links *curr;
   unsigned int partCount = 0;
   uint64 freeBytes = 0;
   uint64 totalBytes = 0;
   size_t partNameSize = 0;
   Bool success = FALSE;
   GuestDiskInfoInt *di;

   /* Get partition list. */
   if (!WiperPartition_Open(&pl, FALSE)) {
      g_warning("GetDiskInfo: ERROR: could not get partition list\n");
      return FALSE;
   }

   di = Util_SafeCalloc(1, sizeof *di);
   partNameSize = sizeof (di->partitionList)[0].name;

   DblLnkLst_ForEach(curr, &pl.link) {
      WiperPartition *part = DblLnkLst_Container(curr, WiperPartition, link);

      if (part->type != PARTITION_UNSUPPORTED) {
         PartitionEntryInt *newPartitionList;
         PartitionEntryInt *partEntry;
         unsigned char *error;
         if (includeReserved) {
            error = WiperSinglePartition_GetSpace(part, NULL,
                                                  &freeBytes, &totalBytes);
         } else {
            error = WiperSinglePartition_GetSpace(part, &freeBytes,
                                                  NULL, &totalBytes);
         }
         if (strlen(error)) {
            g_warning("GetDiskInfo: ERROR: could not get space info for "
                      "partition %s: %s\n", part->mountPoint, error);
            goto out;
         }

         if (strlen(part->mountPoint) + 1 > partNameSize) {
            g_debug("GetDiskInfo: Partition name '%s' too large, truncating\n",
                    part->mountPoint);
         }

         newPartitionList = Util_SafeRealloc(di->partitionList,
                                             (partCount + 1) *
                                             sizeof *di->partitionList);

         partEntry = &newPartitionList[partCount++];
         Str_Strncpy(partEntry->name, partNameSize,
                     part->mountPoint, partNameSize - 1);
         partEntry->freeBytes = freeBytes;
         partEntry->totalBytes = totalBytes;
         Str_Strncpy(partEntry->fsType, sizeof (di->partitionList)[0].fsType,
                     part->fsType, strlen(part->fsType));

         /* Start with an empty set of disk device names. */
         partEntry->diskDevCnt = 0;
         partEntry->diskDevNames = NULL;

         if (reportDevices) {
            GuestInfoGetDiskDevice(part->fsName, partEntry);
         }

         di->partitionList = newPartitionList;
         g_debug("%s added partition #%d %s type %d fstype %s (mount point %s) "
                 "free %"FMT64"u total %"FMT64"u\n",
                 __FUNCTION__, partCount, partEntry->name, part->type,
                 partEntry->fsType, part->fsName,
                 partEntry->freeBytes, partEntry->totalBytes);
      } else {
         g_debug("%s ignoring unsupported partition %s %s\n",
                 __FUNCTION__, part->mountPoint,
                 part->comment ? part->comment : "");
      }
   }

   di->numEntries = partCount;
   success = TRUE;

out:
   if (!success) {
      GuestInfo_FreeDiskInfo(di);
      di = NULL;
   }
   WiperPartition_Close(&pl);
   return di;
}
