/* $Id: VBoxServiceAutoMount.cpp $ */
/** @file
 * VBoxService - Auto-mounting for Shared Folders, only Linux & Solaris atm.
 */

/*
 * Copyright (C) 2010-2020 Oracle Corporation
 *
 * This file is part of VirtualBox Open Source Edition (OSE), as
 * available from http://www.virtualbox.org. This file is free software;
 * you can redistribute it and/or modify it under the terms of the GNU
 * General Public License (GPL) as published by the Free Software
 * Foundation, in version 2 as it comes in the "COPYING" file of the
 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
 */


/** @page pg_vgsvc_automount VBoxService - Shared Folder Automounter
 *
 * The Shared Folder Automounter subservice mounts shared folders upon request
 * from the host.
 *
 * This retrieves shared folder automount requests from Main via the VMMDev.
 * The current implemention only does this once, for some inexplicable reason,
 * so the run-time addition of automounted shared folders are not heeded.
 *
 * This subservice is only used on linux and solaris.  On Windows the current
 * thinking is this is better of done from VBoxTray, some one argue that for
 * drive letter assigned shared folders it would be better to do some magic here
 * (obviously not involving NDAddConnection).
 *
 */


/*********************************************************************************************************************************
*   Header Files                                                                                                                 *
*********************************************************************************************************************************/
#include <iprt/assert.h>
#include <iprt/ctype.h>
#include <iprt/dir.h>
#include <iprt/mem.h>
#include <iprt/path.h>
#include <iprt/semaphore.h>
#include <iprt/sort.h>
#include <iprt/string.h>
#include <VBox/err.h>
#include <VBox/VBoxGuestLib.h>
#include <VBox/shflsvc.h>
#include "VBoxServiceInternal.h"
#include "VBoxServiceUtils.h"

#ifdef RT_OS_WINDOWS
#elif defined(RT_OS_OS2)
# define INCL_DOSFILEMGR
# define INCL_ERRORS
# define OS2EMX_PLAIN_CHAR
# include <os2emx.h>
#else
# include <errno.h>
# include <grp.h>
# include <sys/mount.h>
# ifdef RT_OS_SOLARIS
#  include <sys/mntent.h>
#  include <sys/mnttab.h>
#  include <sys/vfs.h>
RT_C_DECLS_BEGIN /* Only needed for old code.*/
#  include "../../linux/sharedfolders/vbsfmount.h"
RT_C_DECLS_END
# elif defined(RT_OS_LINUX)
#  include <mntent.h>
#  include <paths.h>
RT_C_DECLS_BEGIN
#  include "../../linux/sharedfolders/vbsfmount.h"
RT_C_DECLS_END
# else
#  error "Port me!"
# endif
# include <unistd.h>
#endif



/*********************************************************************************************************************************
*   Defined Constants And Macros                                                                                                 *
*********************************************************************************************************************************/
/** @def VBOXSERVICE_AUTOMOUNT_DEFAULT_DIR
 * Default mount directory (unix only).
 */
#ifndef VBOXSERVICE_AUTOMOUNT_DEFAULT_DIR
# define VBOXSERVICE_AUTOMOUNT_DEFAULT_DIR      "/media"
#endif

/** @def VBOXSERVICE_AUTOMOUNT_DEFAULT_PREFIX
 * Default mount prefix (unix only).
 */
#ifndef VBOXSERVICE_AUTOMOUNT_DEFAULT_PREFIX
# define VBOXSERVICE_AUTOMOUNT_DEFAULT_PREFIX   "sf_"
#endif

#ifndef _PATH_MOUNTED
# ifdef RT_OS_SOLARIS
#  define _PATH_MOUNTED                          "/etc/mnttab"
# else
#  define _PATH_MOUNTED                          "/etc/mtab"
# endif
#endif

/** @def VBOXSERVICE_AUTOMOUNT_MIQF
 * The drive letter / path mount point flag.  */
#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
# define VBOXSERVICE_AUTOMOUNT_MIQF             SHFL_MIQF_DRIVE_LETTER
#else
# define VBOXSERVICE_AUTOMOUNT_MIQF             SHFL_MIQF_PATH
#endif


/*********************************************************************************************************************************
*   Structures and Typedefs                                                                                                      *
*********************************************************************************************************************************/
/**
 * Automounter mount table entry.
 *
 * This holds the information returned by SHFL_FN_QUERY_MAP_INFO and
 * additional mount state info.  We only keep entries for mounted mappings.
 */
typedef struct VBSVCAUTOMOUNTERENTRY
{
    /** The root ID. */
    uint32_t     idRoot;
    /** The root ID version. */
    uint32_t     uRootIdVersion;
    /** Map info flags, SHFL_MIF_XXX. */
    uint64_t     fFlags;
    /** The shared folder (mapping) name. */
    char        *pszName;
    /** The configured mount point, NULL if none. */
    char        *pszMountPoint;
    /** The actual mount point, NULL if not mount.  */
    char        *pszActualMountPoint;
} VBSVCAUTOMOUNTERENTRY;
/** Pointer to an automounter entry.   */
typedef VBSVCAUTOMOUNTERENTRY *PVBSVCAUTOMOUNTERENTRY;

/** Automounter mount table. */
typedef struct VBSVCAUTOMOUNTERTABLE
{
    /** Current number of entries in the array. */
    uint32_t                cEntries;
    /** Max number of entries the array can hold w/o growing it. */
    uint32_t                cAllocated;
    /** Pointer to an array of entry pointers. */
    PVBSVCAUTOMOUNTERENTRY   *papEntries;
} VBSVCAUTOMOUNTERTABLE;
/** Pointer to an automounter mount table.   */
typedef  VBSVCAUTOMOUNTERTABLE *PVBSVCAUTOMOUNTERTABLE;


/*********************************************************************************************************************************
*   Global Variables                                                                                                             *
*********************************************************************************************************************************/
/** The semaphore we're blocking on. */
static RTSEMEVENTMULTI  g_hAutoMountEvent = NIL_RTSEMEVENTMULTI;
/** The Shared Folders service client ID. */
static uint32_t         g_idClientSharedFolders = 0;
/** Set if we can wait on changes to the mappings. */
static bool             g_fHostSupportsWaitAndInfoQuery = false;

#ifdef RT_OS_OS2
/** The attachment tag we use to identify attchments that belongs to us. */
static char const       g_szTag[] = "VBoxAutomounter";
#elif defined(RT_OS_LINUX)
/** Tag option value that lets us identify mounts that belongs to us. */
static char const       g_szTag[] = "VBoxAutomounter";
#elif defined(RT_OS_SOLARIS)
/** Dummy mount option that lets us identify mounts that belongs to us. */
static char const       g_szTag[] = "VBoxAutomounter";
#endif



/**
 * @interface_method_impl{VBOXSERVICE,pfnInit}
 */
static DECLCALLBACK(int) vbsvcAutomounterInit(void)
{
    VGSvcVerbose(3, "vbsvcAutomounterInit\n");

    int rc = RTSemEventMultiCreate(&g_hAutoMountEvent);
    AssertRCReturn(rc, rc);

    rc = VbglR3SharedFolderConnect(&g_idClientSharedFolders);
    if (RT_SUCCESS(rc))
    {
        VGSvcVerbose(3, "vbsvcAutomounterInit: Service Client ID: %#x\n", g_idClientSharedFolders);
        g_fHostSupportsWaitAndInfoQuery = RT_SUCCESS(VbglR3SharedFolderCancelMappingsChangesWaits(g_idClientSharedFolders));
    }
    else
    {
        /* If the service was not found, we disable this service without
           causing VBoxService to fail. */
        if (rc == VERR_HGCM_SERVICE_NOT_FOUND) /* Host service is not available. */
        {
            VGSvcVerbose(0, "vbsvcAutomounterInit: Shared Folders service is not available\n");
            rc = VERR_SERVICE_DISABLED;
        }
        else
            VGSvcError("Control: Failed to connect to the Shared Folders service! Error: %Rrc\n", rc);
        RTSemEventMultiDestroy(g_hAutoMountEvent);
        g_hAutoMountEvent = NIL_RTSEMEVENTMULTI;
    }

    return rc;
}


#if defined(RT_OS_SOLARIS) || defined(RT_OS_LINUX) /* The old code: */

/**
 * @todo Integrate into RTFsQueryMountpoint()?
 */
static bool vbsvcAutoMountShareIsMountedOld(const char *pszShare, char *pszMountPoint, size_t cbMountPoint)
{
    AssertPtrReturn(pszShare, false);
    AssertPtrReturn(pszMountPoint, false);
    AssertReturn(cbMountPoint, false);

    bool fMounted = false;

# if defined(RT_OS_SOLARIS)
    /** @todo What to do if we have a relative path in mtab instead
     *       of an absolute one ("temp" vs. "/media/temp")?
     * procfs contains the full path but not the actual share name ...
     * FILE *pFh = setmntent("/proc/mounts", "r+t"); */
    FILE *pFh = fopen(_PATH_MOUNTED, "r");
    if (!pFh)
        VGSvcError("vbsvcAutoMountShareIsMountedOld: Could not open mount tab '%s'!\n", _PATH_MOUNTED);
    else
    {
        mnttab mntTab;
        while ((getmntent(pFh, &mntTab)))
        {
            if (!RTStrICmp(mntTab.mnt_special, pszShare))
            {
                fMounted = RTStrPrintf(pszMountPoint, cbMountPoint, "%s", mntTab.mnt_mountp)
                         ? true : false;
                break;
            }
        }
        fclose(pFh);
    }
# elif defined(RT_OS_LINUX)
    FILE *pFh = setmntent(_PATH_MOUNTED, "r+t"); /** @todo r=bird: why open it for writing? (the '+') */
    if (pFh == NULL)
        VGSvcError("vbsvcAutoMountShareIsMountedOld: Could not open mount tab '%s'!\n", _PATH_MOUNTED);
    else
    {
        mntent *pMntEnt;
        while ((pMntEnt = getmntent(pFh)))
        {
            if (!RTStrICmp(pMntEnt->mnt_fsname, pszShare))
            {
                fMounted = RTStrPrintf(pszMountPoint, cbMountPoint, "%s", pMntEnt->mnt_dir)
                         ? true : false;
                break;
            }
        }
        endmntent(pFh);
    }
# else
#  error "PORTME!"
# endif

    VGSvcVerbose(4, "vbsvcAutoMountShareIsMountedOld: Share '%s' at mount point '%s' = %s\n",
                       pszShare, fMounted ? pszMountPoint : "<None>", fMounted ? "Yes" : "No");
    return fMounted;
}


/**
 * Unmounts a shared folder.
 *
 * @returns VBox status code
 * @param   pszMountPoint   The shared folder mount point.
 */
static int vbsvcAutoMountUnmountOld(const char *pszMountPoint)
{
    AssertPtrReturn(pszMountPoint, VERR_INVALID_PARAMETER);

    int rc = VINF_SUCCESS;
    uint8_t uTries = 0;
    int r;
    while (uTries++ < 3)
    {
        r = umount(pszMountPoint);
        if (r == 0)
            break;
/** @todo r=bird: Why do sleep 5 seconds after the final retry?
 *  May also be a good idea to check for EINVAL or other signs that someone
 *  else have already unmounted the share. */
        RTThreadSleep(5000); /* Wait a while ... */
    }
    if (r == -1)  /** @todo r=bird: RTThreadSleep set errno.  */
        rc = RTErrConvertFromErrno(errno);
    return rc;
}


/**
 * Prepares a mount point (create it, set group and mode).
 *
 * @returns VBox status code
 * @param   pszMountPoint   The mount point.
 * @param   pszShareName    Unused.
 * @param   pOpts           For getting the group ID.
 */
static int vbsvcAutoMountPrepareMountPointOld(const char *pszMountPoint, const char *pszShareName, vbsf_mount_opts *pOpts)
{
    AssertPtrReturn(pOpts, VERR_INVALID_PARAMETER);
    AssertPtrReturn(pszMountPoint, VERR_INVALID_PARAMETER);
    AssertPtrReturn(pszShareName, VERR_INVALID_PARAMETER);

    RTFMODE fMode = RTFS_UNIX_IRWXU | RTFS_UNIX_IRWXG; /* Owner (=root) and the group (=vboxsf) have full access. */
    int rc = RTDirCreateFullPath(pszMountPoint, fMode);
    if (RT_SUCCESS(rc))
    {
        rc = RTPathSetOwnerEx(pszMountPoint, NIL_RTUID /* Owner, unchanged */, pOpts->gid, RTPATH_F_ON_LINK);
        if (RT_SUCCESS(rc))
        {
            rc = RTPathSetMode(pszMountPoint, fMode);
            if (RT_FAILURE(rc))
            {
                if (rc == VERR_WRITE_PROTECT)
                {
                    VGSvcVerbose(3, "vbsvcAutoMountPrepareMountPointOld: Mount directory '%s' already is used/mounted\n",
                                 pszMountPoint);
                    rc = VINF_SUCCESS;
                }
                else
                    VGSvcError("vbsvcAutoMountPrepareMountPointOld: Could not set mode %RTfmode for mount directory '%s', rc = %Rrc\n",
                               fMode, pszMountPoint, rc);
            }
        }
        else
            VGSvcError("vbsvcAutoMountPrepareMountPointOld: Could not set permissions for mount directory '%s', rc = %Rrc\n",
                       pszMountPoint, rc);
    }
    else
        VGSvcError("vbsvcAutoMountPrepareMountPointOld: Could not create mount directory '%s' with mode %RTfmode, rc = %Rrc\n",
                   pszMountPoint, fMode, rc);
    return rc;
}


/**
 * Mounts a shared folder.
 *
 * @returns VBox status code reflecting unmount and mount point preparation
 *          results, but not actual mounting
 *
 * @param   pszShareName    The shared folder name.
 * @param   pszMountPoint   The mount point.
 */
static int vbsvcAutoMountSharedFolderOld(const char *pszShareName, const char *pszMountPoint)
{
    /*
     * Linux and solaris share the same mount structure.
     */
    struct group *grp_vboxsf = getgrnam("vboxsf");
    if (!grp_vboxsf)
    {
        VGSvcError("vbsvcAutoMountWorker: Group 'vboxsf' does not exist\n");
        return VINF_SUCCESS;
    }

    struct vbsf_mount_opts Opts =
    {
        -1,                    /* ttl */
        -1,                    /* msDirCacheTTL */
        -1,                    /* msInodeTTL */
        0,                     /* cMaxIoPages */
        0,                     /* cbDirBuf */
        kVbsfCacheMode_Default,
        0,                     /* uid */
        (int)grp_vboxsf->gr_gid, /* gid */
        0770,                  /* dmode, owner and group "vboxsf" have full access */
        0770,                  /* fmode, owner and group "vboxsf" have full access */
        0,                     /* dmask */
        0,                     /* fmask */
        0,                     /* ronly */
        0,                     /* sloppy */
        0,                     /* noexec */
        0,                     /* nodev */
        0,                     /* nosuid */
        0,                     /* remount */
        "\0",                  /* nls_name */
        NULL,                  /* convertcp */
    };

    int rc = vbsvcAutoMountPrepareMountPointOld(pszMountPoint, pszShareName, &Opts);
    if (RT_SUCCESS(rc))
    {
# ifdef RT_OS_SOLARIS
        int fFlags = 0;
        if (Opts.ronly)
            fFlags |= MS_RDONLY;
        char szOptBuf[MAX_MNTOPT_STR] = { '\0', };
        RTStrPrintf(szOptBuf, sizeof(szOptBuf), "uid=%d,gid=%d,dmode=%0o,fmode=%0o,dmask=%0o,fmask=%0o",
                    Opts.uid, Opts.gid, Opts.dmode, Opts.fmode, Opts.dmask, Opts.fmask);
        int r = mount(pszShareName,
                      pszMountPoint,
                      fFlags | MS_OPTIONSTR,
                      "vboxfs",
                      NULL,                     /* char *dataptr */
                      0,                        /* int datalen */
                      szOptBuf,
                      sizeof(szOptBuf));
        if (r == 0)
            VGSvcVerbose(0, "vbsvcAutoMountWorker: Shared folder '%s' was mounted to '%s'\n", pszShareName, pszMountPoint);
        else if (errno != EBUSY) /* Share is already mounted? Then skip error msg. */
            VGSvcError("vbsvcAutoMountWorker: Could not mount shared folder '%s' to '%s', error = %s\n",
                       pszShareName, pszMountPoint, strerror(errno));

# else /* RT_OS_LINUX */
        unsigned long fFlags = MS_NODEV;

        /*const char *szOptions = { "rw" }; - ??? */
        struct vbsf_mount_info_new mntinf;
        RT_ZERO(mntinf);

        mntinf.nullchar     = '\0';
        mntinf.signature[0] = VBSF_MOUNT_SIGNATURE_BYTE_0;
        mntinf.signature[1] = VBSF_MOUNT_SIGNATURE_BYTE_1;
        mntinf.signature[2] = VBSF_MOUNT_SIGNATURE_BYTE_2;
        mntinf.length       = sizeof(mntinf);

        mntinf.uid   = Opts.uid;
        mntinf.gid   = Opts.gid;
        mntinf.ttl   = Opts.ttl;
        mntinf.dmode = Opts.dmode;
        mntinf.fmode = Opts.fmode;
        mntinf.dmask = Opts.dmask;
        mntinf.fmask = Opts.fmask;
        mntinf.cMaxIoPages = Opts.cMaxIoPages;
        mntinf.szTag[0] = '\0';

        strcpy(mntinf.name, pszShareName);
        strcpy(mntinf.nls_name, "\0");

        int r = mount(pszShareName,
                      pszMountPoint,
                      "vboxsf",
                      fFlags,
                      &mntinf);
        if (r == 0)
        {
            VGSvcVerbose(0, "vbsvcAutoMountWorker: Shared folder '%s' was mounted to '%s'\n", pszShareName, pszMountPoint);

            r = vbsfmount_complete(pszShareName, pszMountPoint, fFlags, &Opts);
            switch (r)
            {
                case 0: /* Success. */
                    errno = 0; /* Clear all errors/warnings. */
                    break;

                case 1:
                    VGSvcError("vbsvcAutoMountWorker: Could not update mount table (failed to create memstream): %s\n",
                               strerror(errno));
                    break;

                case 2:
                    VGSvcError("vbsvcAutoMountWorker: Could not open mount table for update: %s\n", strerror(errno));
                    break;

                case 3:
                    /* VGSvcError("vbsvcAutoMountWorker: Could not add an entry to the mount table: %s\n", strerror(errno)); */
                    errno = 0;
                    break;

                default:
                    VGSvcError("vbsvcAutoMountWorker: Unknown error while completing mount operation: %d\n", r);
                    break;
            }
        }
        else /* r == -1, we got some error in errno.  */
        {
            if (errno == EPROTO)
            {
                VGSvcVerbose(3, "vbsvcAutoMountWorker: Messed up share name, re-trying ...\n");

                /** @todo r=bird: What on earth is going on here?????  Why can't you
                 *        strcpy(mntinf.name, pszShareName) to fix it again? */

                /* Sometimes the mount utility messes up the share name.  Try to
                 * un-mangle it again. */
                char szCWD[RTPATH_MAX];
                size_t cchCWD;
                if (!getcwd(szCWD, sizeof(szCWD)))
                {
                    VGSvcError("vbsvcAutoMountWorker: Failed to get the current working directory\n");
                    szCWD[0] = '\0';
                }
                cchCWD = strlen(szCWD);
                if (!strncmp(pszMountPoint, szCWD, cchCWD))
                {
                    while (pszMountPoint[cchCWD] == '/')
                        ++cchCWD;
                    /* We checked before that we have enough space */
                    strcpy(mntinf.name, pszMountPoint + cchCWD);
                }
                r = mount(mntinf.name, pszMountPoint, "vboxsf", fFlags, &mntinf);
            }
            if (r == -1) /* Was there some error from one of the tries above? */
            {
                switch (errno)
                {
                    /* If we get EINVAL here, the system already has mounted the Shared Folder to another
                     * mount point. */
                    case EINVAL:
                        VGSvcVerbose(0, "vbsvcAutoMountWorker: Shared folder '%s' already is mounted!\n", pszShareName);
                        /* Ignore this error! */
                        break;
                    case EBUSY:
                        /* Ignore these errors! */
                        break;

                    default:
                        VGSvcError("vbsvcAutoMountWorker: Could not mount shared folder '%s' to '%s': %s (%d)\n",
                                         pszShareName, pszMountPoint, strerror(errno), errno);
                        rc = RTErrConvertFromErrno(errno);
                        break;
                }
            }
        }
# endif
    }
    VGSvcVerbose(3, "vbsvcAutoMountWorker: Mounting returned with rc=%Rrc\n", rc);
    return rc;
}


/**
 * Processes shared folder mappings retrieved from the host.
 *
 * @returns VBox status code.
 * @param   paMappings      The mappings.
 * @param   cMappings       The number of mappings.
 * @param   pszMountDir     The mount directory.
 * @param   pszSharePrefix  The share prefix.
 * @param   uClientID       The shared folder service (HGCM) client ID.
 */
static int vbsvcAutoMountProcessMappingsOld(PCVBGLR3SHAREDFOLDERMAPPING paMappings, uint32_t cMappings,
                                            const char *pszMountDir, const char *pszSharePrefix, uint32_t uClientID)
{
    if (cMappings == 0)
        return VINF_SUCCESS;
    AssertPtrReturn(paMappings, VERR_INVALID_PARAMETER);
    AssertPtrReturn(pszMountDir, VERR_INVALID_PARAMETER);
    AssertPtrReturn(pszSharePrefix, VERR_INVALID_PARAMETER);
    AssertReturn(uClientID > 0, VERR_INVALID_PARAMETER);

    /** @todo r=bird: Why is this loop schitzoid about status codes? It quits if
     * RTPathJoin fails (i.e. if the user specifies a very long name), but happily
     * continues if RTStrAPrintf failes (mem alloc).
     *
     * It also happily continues if the 'vboxsf' group is missing, which is a waste
     * of effort... In fact, retrieving the group ID could probably be done up
     * front, outside the loop. */
    int rc = VINF_SUCCESS;
    for (uint32_t i = 0; i < cMappings && RT_SUCCESS(rc); i++)
    {
        char *pszShareName = NULL;
        rc = VbglR3SharedFolderGetName(uClientID, paMappings[i].u32Root, &pszShareName);
        if (   RT_SUCCESS(rc)
            && *pszShareName)
        {
            VGSvcVerbose(3, "vbsvcAutoMountWorker: Connecting share %u (%s) ...\n", i+1, pszShareName);

            /** @todo r=bird: why do you copy things twice here and waste heap space?
             * szMountPoint has a fixed size.
             * @code
             * char szMountPoint[RTPATH_MAX];
             * rc = RTPathJoin(szMountPoint, sizeof(szMountPoint), pszMountDir, *pszSharePrefix ? pszSharePrefix : pszShareName);
             * if (RT_SUCCESS(rc) && *pszSharePrefix)
             *     rc = RTStrCat(szMountPoint, sizeof(szMountPoint), pszShareName);
             * @endcode */
            char *pszShareNameFull = NULL;
            if (RTStrAPrintf(&pszShareNameFull, "%s%s", pszSharePrefix, pszShareName) > 0)
            {
                char szMountPoint[RTPATH_MAX];
                rc = RTPathJoin(szMountPoint, sizeof(szMountPoint), pszMountDir, pszShareNameFull);
                if (RT_SUCCESS(rc))
                {
                    VGSvcVerbose(4, "vbsvcAutoMountWorker: Processing mount point '%s'\n", szMountPoint);

                    /*
                     * Already mounted?
                     */
                    /** @todo r-bird: this does not take into account that a shared folder could
                     *        be mounted twice... We're really just interested in whether the
                     *        folder is mounted on 'szMountPoint', no where else... */
                    bool fSkip = false;
                    char szAlreadyMountedOn[RTPATH_MAX];
                    if (vbsvcAutoMountShareIsMountedOld(pszShareName, szAlreadyMountedOn, sizeof(szAlreadyMountedOn)))
                    {
                        /* Do if it not mounted to our desired mount point */
                        if (RTStrICmp(szMountPoint, szAlreadyMountedOn))
                        {
                            VGSvcVerbose(3, "vbsvcAutoMountWorker: Shared folder '%s' already mounted on '%s', unmounting ...\n",
                                         pszShareName, szAlreadyMountedOn);
                            rc = vbsvcAutoMountUnmountOld(szAlreadyMountedOn);
                            if (RT_SUCCESS(rc))
                                fSkip = false;
                            else
                                VGSvcError("vbsvcAutoMountWorker: Failed to unmount '%s', %s (%d)! (rc=%Rrc)\n",
                                           szAlreadyMountedOn, strerror(errno), errno, rc); /** @todo errno isn't reliable at this point */
                        }
                        if (fSkip)
                            VGSvcVerbose(3, "vbsvcAutoMountWorker: Shared folder '%s' already mounted on '%s', skipping\n",
                                         pszShareName, szAlreadyMountedOn);
                    }
                    if (!fSkip)
                    {
                        /*
                         * Mount it.
                         */
                        rc = vbsvcAutoMountSharedFolderOld(pszShareName, szMountPoint);
                    }
                }
                else
                    VGSvcError("vbsvcAutoMountWorker: Unable to join mount point/prefix/shrae, rc = %Rrc\n", rc);
                RTStrFree(pszShareNameFull);
            }
            else
                VGSvcError("vbsvcAutoMountWorker: Unable to allocate full share name\n");
            RTStrFree(pszShareName);
        }
        else
            VGSvcError("vbsvcAutoMountWorker: Error while getting the shared folder name for root node = %u, rc = %Rrc\n",
                             paMappings[i].u32Root, rc);
    } /* for cMappings. */
    return rc;
}

#endif /* defined(RT_OS_SOLARIS) || defined(RT_OS_LINUX) - the old code*/


/**
 * Service worker function for old host.
 *
 * This only mount stuff on startup.
 *
 * @returns VBox status code.
 * @param   pfShutdown          Shutdown indicator.
 */
static int vbsvcAutoMountWorkerOld(bool volatile *pfShutdown)
{
#if defined(RT_OS_SOLARIS) || defined(RT_OS_LINUX)
    /*
     * We only do a single pass here.
     */
    uint32_t cMappings;
    PVBGLR3SHAREDFOLDERMAPPING paMappings;
    int rc = VbglR3SharedFolderGetMappings(g_idClientSharedFolders, true /* Only process auto-mounted folders */,
                                           &paMappings, &cMappings);
    if (   RT_SUCCESS(rc)
        && cMappings)
    {
        char *pszMountDir;
        rc = VbglR3SharedFolderGetMountDir(&pszMountDir);
        if (rc == VERR_NOT_FOUND)
            rc = RTStrDupEx(&pszMountDir, VBOXSERVICE_AUTOMOUNT_DEFAULT_DIR);
        if (RT_SUCCESS(rc))
        {
            VGSvcVerbose(3, "vbsvcAutoMountWorker: Shared folder mount dir set to '%s'\n", pszMountDir);

            char *pszSharePrefix;
            rc = VbglR3SharedFolderGetMountPrefix(&pszSharePrefix);
            if (RT_SUCCESS(rc))
            {
                VGSvcVerbose(3, "vbsvcAutoMountWorker: Shared folder mount prefix set to '%s'\n", pszSharePrefix);
# ifdef USE_VIRTUAL_SHARES
                /* Check for a fixed/virtual auto-mount share. */
                if (VbglR3SharedFolderExists(g_idClientSharedFolders, "vbsfAutoMount"))
                    VGSvcVerbose(3, "vbsvcAutoMountWorker: Host supports auto-mount root\n");
                else
                {
# endif
                    VGSvcVerbose(3, "vbsvcAutoMountWorker: Got %u shared folder mappings\n", cMappings);
                    rc = vbsvcAutoMountProcessMappingsOld(paMappings, cMappings, pszMountDir, pszSharePrefix,
                                                          g_idClientSharedFolders);
# ifdef USE_VIRTUAL_SHARES
                }
# endif
                RTStrFree(pszSharePrefix);
            } /* Mount share prefix. */
            else
                VGSvcError("vbsvcAutoMountWorker: Error while getting the shared folder mount prefix, rc = %Rrc\n", rc);
            RTStrFree(pszMountDir);
        }
        else
            VGSvcError("vbsvcAutoMountWorker: Error while getting the shared folder directory, rc = %Rrc\n", rc);
        VbglR3SharedFolderFreeMappings(paMappings);
    }
    else if (RT_FAILURE(rc))
        VGSvcError("vbsvcAutoMountWorker: Error while getting the shared folder mappings, rc = %Rrc\n", rc);
    else
        VGSvcVerbose(3, "vbsvcAutoMountWorker: No shared folder mappings found\n");

#else
    int rc = VINF_SUCCESS;
#endif /* defined(RT_OS_SOLARIS) || defined(RT_OS_LINUX) */


    /*
     * Wait on shutdown (this used to be a silly RTThreadSleep(500) loop).
     */
    while (!*pfShutdown)
    {
        rc = RTSemEventMultiWait(g_hAutoMountEvent, RT_MS_1MIN);
        if (rc != VERR_TIMEOUT)
            break;
    }

    VGSvcVerbose(3, "vbsvcAutoMountWorkerOld: Finished with rc=%Rrc\n", rc);
    return rc;
}

#if !defined(RT_OS_WINDOWS) && !defined(RT_OS_OS2)
/**
 * Assembles the mount directory and prefix into @a pszDst.
 *
 * Will fall back on defaults if we have trouble with the configuration from the
 * host.  This ASSUMES that @a cbDst is rather large and won't cause trouble
 * with the default.
 *
 * @returns IPRT status code.
 * @param   pszDst          Where to return the prefix.
 * @param   cbDst           The size of the prefix buffer.
 */
static int vbsvcAutomounterQueryMountDirAndPrefix(char *pszDst, size_t cbDst)
{
    /*
     * Query the config first.
     */
    /* Mount directory: */
    const char *pszDir = VBOXSERVICE_AUTOMOUNT_DEFAULT_DIR;
    char       *pszCfgDir;
    int rc = VbglR3SharedFolderGetMountDir(&pszCfgDir);
    if (RT_SUCCESS(rc))
    {
        if (*pszCfgDir == '/')
            pszDir = pszCfgDir;
    }
    else
        pszCfgDir = NULL;

    /* Prefix: */
    const char *pszPrefix = VBOXSERVICE_AUTOMOUNT_DEFAULT_PREFIX;
    char *pszCfgPrefix;
    rc = VbglR3SharedFolderGetMountPrefix(&pszCfgPrefix);
    if (RT_SUCCESS(rc))
    {
        if (   strchr(pszCfgPrefix, '/')  == NULL
            && strchr(pszCfgPrefix, '\\') == NULL
            && strcmp(pszCfgPrefix, "..") != 0)
            pszPrefix = pszCfgPrefix;
    }
    else
        pszCfgPrefix = NULL;

    /*
     * Try combine the two.
     */
    rc = RTPathAbs(pszDir, pszDst, cbDst);
    if (RT_SUCCESS(rc))
    {
        if (*pszPrefix)
        {
            rc = RTPathAppend(pszDst, cbDst, pszPrefix);
            if (RT_FAILURE(rc))
                VGSvcError("vbsvcAutomounterQueryMountDirAndPrefix: RTPathAppend(%s,,%s) -> %Rrc\n", pszDst, pszPrefix, rc);
        }
        else
        {
            rc = RTPathEnsureTrailingSeparator(pszDst, cbDst);
            if (RT_FAILURE(rc))
                VGSvcError("vbsvcAutomounterQueryMountDirAndPrefix: RTPathEnsureTrailingSeparator(%s) -> %Rrc\n", pszDst, rc);
        }
    }
    else
        VGSvcError("vbsvcAutomounterQueryMountDirAndPrefix: RTPathAbs(%s) -> %Rrc\n", pszDir, rc);


    /*
     * Return the default dir + prefix if the above failed.
     */
    if (RT_FAILURE(rc))
    {
        rc = RTStrCopy(pszDst, cbDst, VBOXSERVICE_AUTOMOUNT_DEFAULT_DIR "/" VBOXSERVICE_AUTOMOUNT_DEFAULT_PREFIX);
        AssertRC(rc);
    }

    RTStrFree(pszCfgDir);
    RTStrFree(pszCfgPrefix);
    return rc;
}
#endif /* !RT_OS_WINDOW && !RT_OS_OS2 */


/**
 * @callback_method_impl{FNRTSORTCMP, For sorting mount table by root ID. }
 */
static DECLCALLBACK(int) vbsvcAutomounterCompareEntry(void const *pvElement1, void const *pvElement2, void *pvUser)
{
    RT_NOREF_PV(pvUser);
    PVBSVCAUTOMOUNTERENTRY pEntry1 = (PVBSVCAUTOMOUNTERENTRY)pvElement1;
    PVBSVCAUTOMOUNTERENTRY pEntry2 = (PVBSVCAUTOMOUNTERENTRY)pvElement2;
    return pEntry1->idRoot < pEntry2->idRoot ? -1
         : pEntry1->idRoot > pEntry2->idRoot ? 1 : 0;
}


/**
 * Worker for vbsvcAutomounterPopulateTable for adding discovered entries.
 *
 * This is puts dummies in for missing values, depending on
 * vbsvcAutomounterPopulateTable to query them later.
 *
 * @returns VINF_SUCCESS or VERR_NO_MEMORY;
 * @param   pMountTable     The mount table to add an entry to.
 * @param   pszName         The shared folder name.
 * @param   pszMountPoint   The mount point.
 */
static int vbsvcAutomounterAddEntry(PVBSVCAUTOMOUNTERTABLE pMountTable, const char *pszName, const char *pszMountPoint)
{
    VGSvcVerbose(2, "vbsvcAutomounterAddEntry: %s -> %s\n", pszMountPoint, pszName);
    PVBSVCAUTOMOUNTERENTRY pEntry = (PVBSVCAUTOMOUNTERENTRY)RTMemAlloc(sizeof(*pEntry));
    pEntry->idRoot              = UINT32_MAX;
    pEntry->uRootIdVersion      = UINT32_MAX;
    pEntry->fFlags              = UINT64_MAX;
    pEntry->pszName             = RTStrDup(pszName);
    pEntry->pszMountPoint       = NULL;
    pEntry->pszActualMountPoint = RTStrDup(pszMountPoint);
    if (pEntry->pszName && pEntry->pszActualMountPoint)
    {
        if (pMountTable->cEntries + 1 <= pMountTable->cAllocated)
        {
            pMountTable->papEntries[pMountTable->cEntries++] = pEntry;
            return VINF_SUCCESS;
        }

        void *pvNew = RTMemRealloc(pMountTable->papEntries, (pMountTable->cAllocated + 8) * sizeof(pMountTable->papEntries[0]));
        if (pvNew)
        {
            pMountTable->cAllocated += 8;
            pMountTable->papEntries = (PVBSVCAUTOMOUNTERENTRY *)pvNew;

            pMountTable->papEntries[pMountTable->cEntries++] = pEntry;
            return VINF_SUCCESS;
        }
    }
    RTMemFree(pEntry->pszActualMountPoint);
    RTMemFree(pEntry->pszName);
    RTMemFree(pEntry);
    return VERR_NO_MEMORY;
}


/**
 * Populates the mount table as best we can with existing automount entries.
 *
 * @returns VINF_SUCCESS or VERR_NO_MEMORY;
 * @param   pMountTable     The mount table (empty).
 */
static int vbsvcAutomounterPopulateTable(PVBSVCAUTOMOUNTERTABLE pMountTable)
{
    int rc;

#ifdef RT_OS_WINDOWS
    /*
     * Loop thru the drive letters and check out each of them using QueryDosDeviceW.
     */
    static const char s_szDevicePath[] = "\\Device\\VBoxMiniRdr\\;";
    for (char chDrive = 'Z'; chDrive >= 'A'; chDrive--)
    {
        RTUTF16 const wszMountPoint[4] = { chDrive, ':', '\0', '\0' };
        RTUTF16       wszTargetPath[RTPATH_MAX];
        DWORD const   cwcResult = QueryDosDeviceW(wszMountPoint, wszTargetPath, RT_ELEMENTS(wszTargetPath));
        if (   cwcResult > sizeof(s_szDevicePath)
            && RTUtf16NICmpAscii(wszTargetPath, RT_STR_TUPLE(s_szDevicePath)) == 0)
        {
            PCRTUTF16 pwsz = &wszTargetPath[RT_ELEMENTS(s_szDevicePath) - 1];
            Assert(pwsz[-1] == ';');
            if (   (pwsz[0] & ~(RTUTF16)0x20) == chDrive
                && pwsz[1] == ':'
                && pwsz[2] == '\\')
            {
                /* For now we'll just use the special capitalization of the
                   "server" name to identify it as our work.  We could check
                   if the symlink is from \Global?? or \??, but that trick does
                   work for older OS versions (<= XP) or when running the
                   service manually for testing/wathever purposes. */
                /** @todo Modify the windows shared folder driver to allow tagging drives.*/
                if (RTUtf16NCmpAscii(&pwsz[3], RT_STR_TUPLE("VBoxSvr\\")) == 0)
                {
                    pwsz += 3 + 8;
                    if (*pwsz != '\\' && *pwsz)
                    {
                        /* The shared folder name should follow immediately after the server prefix. */
                        char *pszMountedName = NULL;
                        rc = RTUtf16ToUtf8(pwsz, &pszMountedName);
                        if (RT_SUCCESS(rc))
                        {
                            char const szMountPoint[4] = { chDrive, ':', '\0', '\0' };
                            rc = vbsvcAutomounterAddEntry(pMountTable, pszMountedName, szMountPoint);
                            RTStrFree(pszMountedName);
                        }
                        if (RT_FAILURE(rc))
                            return rc;
                    }
                    else
                        VGSvcVerbose(2, "vbsvcAutomounterPopulateTable: Malformed, not ours: %ls -> %ls\n",
                                     wszMountPoint, wszTargetPath);
                }
                else
                    VGSvcVerbose(3, "vbsvcAutomounterPopulateTable: Not ours: %ls -> %ls\n", wszMountPoint, wszTargetPath);
            }
        }
    }

#elif defined(RT_OS_OS2)
    /*
     * Just loop thru the drive letters and check the attachment of each.
     */
    for (char chDrive = 'Z'; chDrive >= 'A'; chDrive--)
    {
        char const szMountPoint[4] = { chDrive, ':', '\0', '\0' };
        union
        {
            FSQBUFFER2  FsQueryBuf;
            char        achPadding[1024];
        } uBuf;
        RT_ZERO(uBuf);
        ULONG  cbBuf = sizeof(uBuf) - 2;
        APIRET rcOs2 = DosQueryFSAttach(szMountPoint, 0, FSAIL_QUERYNAME, &uBuf.FsQueryBuf, &cbBuf);
        if (rcOs2 == NO_ERROR)
        {
            const char *pszFsdName = (const char *)&uBuf.FsQueryBuf.szName[uBuf.FsQueryBuf.cbName + 1];
            if (   uBuf.FsQueryBuf.iType == FSAT_REMOTEDRV
                && RTStrICmpAscii(pszFsdName, "VBOXSF") == 0)
            {
                const char *pszMountedName = (const char *)&pszFsdName[uBuf.FsQueryBuf.cbFSDName + 1];
                const char *pszTag = pszMountedName + strlen(pszMountedName) + 1; /* (Safe. Always two trailing zero bytes, see above.) */
                if (strcmp(pszTag, g_szTag) == 0)
                {
                    rc = vbsvcAutomounterAddEntry(pMountTable, pszMountedName, szMountPoint);
                    if (RT_FAILURE(rc))
                        return rc;
                }
            }
        }
    }

#elif defined(RT_OS_LINUX)
    /*
     * Scan the mount table file for the mount point and then match file system
     * and device/share.  We identify our mounts by mount path + prefix for now,
     * but later we may use the same approach as on solaris.
     */
    FILE *pFile = setmntent("/proc/mounts", "r");
    int iErrMounts = errno;
    if (!pFile)
        pFile = setmntent("/etc/mtab", "r");
    if (pFile)
    {
        rc = VWRN_NOT_FOUND;
        struct mntent *pEntry;
        while ((pEntry = getmntent(pFile)) != NULL)
            if (strcmp(pEntry->mnt_type, "vboxsf") == 0)
                if (strstr(pEntry->mnt_opts, g_szTag) != NULL)
                {
                    rc = vbsvcAutomounterAddEntry(pMountTable, pEntry->mnt_fsname, pEntry->mnt_dir);
                    if (RT_FAILURE(rc))
                    {
                        endmntent(pFile);
                        return rc;
                    }
                }
        endmntent(pFile);
    }
    else
        VGSvcError("vbsvcAutomounterQueryMountPoint: Could not open mount tab '%s' (errno=%d) or '/proc/mounts' (errno=%d)\n",
                   _PATH_MOUNTED, errno, iErrMounts);

#elif defined(RT_OS_SOLARIS)
    /*
     * Look thru the system mount table and inspect the vboxsf mounts.
     */
    FILE *pFile = fopen(_PATH_MOUNTED, "r");
    if (pFile)
    {
        rc = VINF_SUCCESS;
        struct mnttab Entry;
        while (getmntent(pFile, &Entry) == 0)
            if (strcmp(Entry.mnt_fstype, "vboxfs") == 0)
            {
                /* Look for the dummy automounter option. */
                if (   Entry.mnt_mntopts != NULL
                    && strstr(Entry.mnt_mntopts, g_szTag) != NULL)
                {
                    rc = vbsvcAutomounterAddEntry(pMountTable, Entry.mnt_special, Entry.mnt_mountp);
                    if (RT_FAILURE(rc))
                    {
                        fclose(pFile);
                        return rc;
                    }
                }
            }
        fclose(pFile);
    }
    else
        VGSvcError("vbsvcAutomounterQueryMountPoint: Could not open mount tab '%s' (errno=%d)\n", _PATH_MOUNTED, errno);

#else
# error "PORTME!"
#endif

    /*
     * Try reconcile the detected folders with data from the host.
     */
    uint32_t                    cMappings = 0;
    PVBGLR3SHAREDFOLDERMAPPING  paMappings = NULL;
    rc = VbglR3SharedFolderGetMappings(g_idClientSharedFolders, true /*fAutoMountOnly*/, &paMappings, &cMappings);
    if (RT_SUCCESS(rc))
    {
        for (uint32_t i = 0; i < cMappings && RT_SUCCESS(rc); i++)
        {
            uint32_t const idRootSrc = paMappings[i].u32Root;

            uint32_t uRootIdVer = UINT32_MAX;
            uint64_t fFlags     = 0;
            char    *pszName    = NULL;
            char    *pszMntPt   = NULL;
            int rc2 = VbglR3SharedFolderQueryFolderInfo(g_idClientSharedFolders, idRootSrc,  VBOXSERVICE_AUTOMOUNT_MIQF,
                                                        &pszName, &pszMntPt, &fFlags, &uRootIdVer);
            if (RT_SUCCESS(rc2))
            {
                uint32_t iPrevHit = UINT32_MAX;
                for (uint32_t iTable = 0; iTable < pMountTable->cEntries; iTable++)
                {
                    PVBSVCAUTOMOUNTERENTRY pEntry = pMountTable->papEntries[iTable];
                    if (RTStrICmp(pEntry->pszName, pszName) == 0)
                    {
                        VGSvcVerbose(2, "vbsvcAutomounterPopulateTable: Identified %s -> %s: idRoot=%u ver=%u fFlags=%#x AutoMntPt=%s\n",
                                     pEntry->pszActualMountPoint, pEntry->pszName, idRootSrc, uRootIdVer, fFlags, pszMntPt);
                        pEntry->fFlags         = fFlags;
                        pEntry->idRoot         = idRootSrc;
                        pEntry->uRootIdVersion = uRootIdVer;
                        RTStrFree(pEntry->pszMountPoint);
                        pEntry->pszMountPoint = RTStrDup(pszMntPt);
                        if (!pEntry->pszMountPoint)
                        {
                            rc = VERR_NO_MEMORY;
                            break;
                        }

                        /* If multiple mappings of the same folder, pick the first or the one
                           with matching mount point. */
                        if (iPrevHit == UINT32_MAX)
                            iPrevHit = iTable;
                        else if (RTPathCompare(pszMntPt, pEntry->pszActualMountPoint) == 0)
                        {
                            if (iPrevHit != UINT32_MAX)
                                pMountTable->papEntries[iPrevHit]->uRootIdVersion -= 1;
                            iPrevHit = iTable;
                        }
                        else
                            pEntry->uRootIdVersion -= 1;
                    }
                }

                RTStrFree(pszName);
                RTStrFree(pszMntPt);
            }
            else
                VGSvcError("vbsvcAutomounterPopulateTable: VbglR3SharedFolderQueryFolderInfo(%u) failed: %Rrc\n", idRootSrc, rc2);
        }

        VbglR3SharedFolderFreeMappings(paMappings);

        /*
         * Sort the table by root ID.
         */
        if (pMountTable->cEntries > 1)
            RTSortApvShell((void **)pMountTable->papEntries, pMountTable->cEntries, vbsvcAutomounterCompareEntry, NULL);

        for (uint32_t iTable = 0; iTable < pMountTable->cEntries; iTable++)
        {
            PVBSVCAUTOMOUNTERENTRY pEntry = pMountTable->papEntries[iTable];
            if (pMountTable->papEntries[iTable]->idRoot != UINT32_MAX)
                VGSvcVerbose(1, "vbsvcAutomounterPopulateTable: #%u: %s -> %s idRoot=%u ver=%u fFlags=%#x AutoMntPt=%s\n",
                             iTable, pEntry->pszActualMountPoint, pEntry->pszName, pEntry->idRoot, pEntry->uRootIdVersion,
                             pEntry->fFlags, pEntry->pszMountPoint);
            else
                VGSvcVerbose(1, "vbsvcAutomounterPopulateTable: #%u: %s -> %s - not identified!\n",
                             iTable, pEntry->pszActualMountPoint, pEntry->pszName);
        }
    }
    else
        VGSvcError("vbsvcAutomounterPopulateTable: VbglR3SharedFolderGetMappings failed: %Rrc\n", rc);
    return rc;
}


/**
 * Checks whether the shared folder @a pszName is mounted on @a pszMountPoint.
 *
 * @returns Exactly one of the following IPRT status codes;
 * @retval  VINF_SUCCESS if mounted
 * @retval  VWRN_NOT_FOUND if nothing is mounted at @a pszMountPoint.
 * @retval  VERR_RESOURCE_BUSY if a different shared folder is mounted there.
 * @retval  VERR_ACCESS_DENIED if a non-shared folder file system is mounted
 *          there.
 *
 * @param   pszMountPoint   The mount point to check.
 * @param   pszName         The name of the shared folder (mapping).
 */
static int vbsvcAutomounterQueryMountPoint(const char *pszMountPoint, const char *pszName)
{
    VGSvcVerbose(4, "vbsvcAutomounterQueryMountPoint: pszMountPoint=%s pszName=%s\n", pszMountPoint, pszName);

#ifdef RT_OS_WINDOWS
    /*
     * We could've used RTFsQueryType here but would then have to
     * calling RTFsQueryLabel for the share name hint, ending up
     * doing the same work twice.  We could also use QueryDosDeviceW,
     * but output is less clear...
     */
    PRTUTF16 pwszMountPoint = NULL;
    int rc = RTStrToUtf16(pszMountPoint, &pwszMountPoint);
    if (RT_SUCCESS(rc))
    {
        DWORD   uSerial = 0;
        DWORD   cchCompMax = 0;
        DWORD   fFlags = 0;
        RTUTF16 wszLabel[512];
        RTUTF16 wszFileSystem[256];
        RT_ZERO(wszLabel);
        RT_ZERO(wszFileSystem);
        if (GetVolumeInformationW(pwszMountPoint, wszLabel, RT_ELEMENTS(wszLabel) - 1, &uSerial, &cchCompMax, &fFlags,
                                  wszFileSystem, RT_ELEMENTS(wszFileSystem) - 1))
        {
            if (RTUtf16ICmpAscii(wszFileSystem, "VBoxSharedFolderFS") == 0)
            {
                char *pszLabel = NULL;
                rc = RTUtf16ToUtf8(wszLabel, &pszLabel);
                if (RT_SUCCESS(rc))
                {
                    const char *pszMountedName = pszLabel;
                    if (RTStrStartsWith(pszMountedName, "VBOX_"))
                        pszMountedName += sizeof("VBOX_") - 1;
                    if (RTStrICmp(pszMountedName, pszName) == 0)
                    {
                        VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s'.\n",
                                     pszName, pszMountPoint);
                        rc = VINF_SUCCESS;
                    }
                    else
                    {
                        VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s', not '%s'...\n",
                                     pszMountedName, pszMountPoint, pszName);
                        rc = VERR_RESOURCE_BUSY;
                    }
                    RTStrFree(pszLabel);
                }
                else
                {
                    VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: RTUtf16ToUtf8(%ls,) failed: %Rrc\n", wszLabel, rc);
                    rc = VERR_RESOURCE_BUSY;
                }
            }
            else
            {
                VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found a '%ls' with label '%ls' mount at '%s', not '%s'...\n",
                             wszFileSystem, wszLabel, pszMountPoint, pszName);
                rc = VERR_ACCESS_DENIED;
            }
        }
        else
        {
            rc = GetLastError();
            if (rc != ERROR_PATH_NOT_FOUND || g_cVerbosity >= 4)
                VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: GetVolumeInformationW('%ls',,,,) failed: %u\n", pwszMountPoint, rc);
            if (rc == ERROR_PATH_NOT_FOUND)
                rc = VWRN_NOT_FOUND;
            else if (   RT_C_IS_ALPHA(pszMountPoint[0])
                     && pszMountPoint[1] == ':'
                     && (   pszMountPoint[2] == '\0'
                         || (RTPATH_IS_SLASH(pszMountPoint[2]) && pszMountPoint[3] == '\0')))
            {
                /* See whether QueryDosDeviceW thinks its a malfunctioning shared folder or
                   something else (it doesn't access the file system).  We've seen
                   VERR_NET_HOST_NOT_FOUND here for shared folders that was removed on the
                   host side.

                   Note! This duplicates code from vbsvcAutomounterPopulateTable. */
                rc = VERR_ACCESS_DENIED;
                static const char s_szDevicePath[] = "\\Device\\VBoxMiniRdr\\;";
                wszFileSystem[0] = pwszMountPoint[0];
                wszFileSystem[1] = pwszMountPoint[1];
                wszFileSystem[2] = '\0';
                DWORD const cwcResult = QueryDosDeviceW(wszFileSystem, wszLabel, RT_ELEMENTS(wszLabel));
                if (   cwcResult > sizeof(s_szDevicePath)
                    && RTUtf16NICmpAscii(wszLabel, RT_STR_TUPLE(s_szDevicePath)) == 0)
                {
                    PCRTUTF16 pwsz = &wszLabel[RT_ELEMENTS(s_szDevicePath) - 1];
                    Assert(pwsz[-1] == ';');
                    if (   (pwsz[0] & ~(RTUTF16)0x20) == (wszFileSystem[0] & ~(RTUTF16)0x20)
                        && pwsz[1] == ':'
                        && pwsz[2] == '\\')
                    {
                        if (RTUtf16NICmpAscii(&pwsz[3], RT_STR_TUPLE("VBoxSvr\\")) == 0)
                        {
                            pwsz += 3 + 8;
                            char *pszMountedName = NULL;
                            rc = RTUtf16ToUtf8(pwsz, &pszMountedName);
                            if (RT_SUCCESS(rc))
                            {
                                if (RTStrICmp(pszMountedName, pszName) == 0)
                                {
                                    rc = VINF_SUCCESS;
                                    VGSvcVerbose(2, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s' (using QueryDosDeviceW).\n",
                                                 pszName, pszMountPoint);
                                }
                                else
                                {
                                    VGSvcVerbose(2, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s' (using QueryDosDeviceW), not '%s'...\n",
                                                 pszMountedName, pszMountPoint, pszName);
                                    rc = VERR_RESOURCE_BUSY;
                                }
                                RTStrFree(pszMountedName);
                            }
                            else
                            {
                                VGSvcVerbose(2, "vbsvcAutomounterQueryMountPoint: RTUtf16ToUtf8 failed: %Rrc\n", rc);
                                AssertRC(rc);
                                rc = VERR_RESOURCE_BUSY;
                            }
                        }
                    }
                }
            }
            else
                rc = VERR_ACCESS_DENIED;
        }
        RTUtf16Free(pwszMountPoint);
    }
    else
    {
        VGSvcError("vbsvcAutomounterQueryMountPoint: RTStrToUtf16(%s,) -> %Rrc\n", pszMountPoint, rc);
        rc = VWRN_NOT_FOUND;
    }
    return rc;

#elif defined(RT_OS_OS2)
    /*
     * Query file system attachment info for the given drive letter.
     */
    union
    {
        FSQBUFFER2  FsQueryBuf;
        char        achPadding[512];
    } uBuf;
    RT_ZERO(uBuf);

    ULONG cbBuf = sizeof(uBuf);
    APIRET rcOs2 = DosQueryFSAttach(pszMountPoint, 0, FSAIL_QUERYNAME, &uBuf.FsQueryBuf, &cbBuf);
    int rc;
    if (rcOs2 == NO_ERROR)
    {
        const char *pszFsdName = (const char *)&uBuf.FsQueryBuf.szName[uBuf.FsQueryBuf.cbName + 1];
        if (   uBuf.FsQueryBuf.iType == FSAT_REMOTEDRV
            && RTStrICmpAscii(pszFsdName, "VBOXSF") == 0)
        {
            const char *pszMountedName = (const char *)&pszFsdName[uBuf.FsQueryBuf.cbFSDName + 1];
            if (RTStrICmp(pszMountedName, pszName) == 0)
            {
                VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s'.\n",
                             pszName, pszMountPoint);
                rc = VINF_SUCCESS;
            }
            else
            {
                VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s', not '%s'...\n",
                             pszMountedName, pszMountPoint, pszName);
                rc = VERR_RESOURCE_BUSY;
            }
        }
        else
        {
            VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found a '%s' type %u mount at '%s', not '%s'...\n",
                         pszFsdName, uBuf.FsQueryBuf.iType, pszMountPoint, pszName);
            rc = VERR_ACCESS_DENIED;
        }
    }
    else
    {
        rc = VWRN_NOT_FOUND;
        VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: DosQueryFSAttach(%s) -> %u\n", pszMountPoint, rcOs2);
        AssertMsgStmt(rcOs2 != ERROR_BUFFER_OVERFLOW && rcOs2 != ERROR_INVALID_PARAMETER,
                      ("%s -> %u\n", pszMountPoint, rcOs2), rc = VERR_ACCESS_DENIED);
    }
    return rc;

#elif defined(RT_OS_LINUX)
    /*
     * Scan one of the mount table file for the mount point and then
     * match file system and device/share.
     */
    FILE *pFile = setmntent("/proc/mounts", "r");
    int rc = errno;
    if (!pFile)
        pFile = setmntent(_PATH_MOUNTED, "r");
    if (pFile)
    {
        rc = VWRN_NOT_FOUND;
        struct mntent *pEntry;
        while ((pEntry = getmntent(pFile)) != NULL)
            if (RTPathCompare(pEntry->mnt_dir, pszMountPoint) == 0)
            {
                if (strcmp(pEntry->mnt_type, "vboxsf") == 0)
                {
                    if (RTStrICmp(pEntry->mnt_fsname, pszName) == 0)
                    {
                        VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s'.\n",
                                     pszName, pszMountPoint);
                        rc = VINF_SUCCESS;
                    }
                    else
                    {
                        VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s', not '%s'...\n",
                                     pEntry->mnt_fsname, pszMountPoint, pszName);
                        rc = VERR_RESOURCE_BUSY;
                    }
                }
                else
                {
                    VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found a '%s' mount of '%s' at '%s', not '%s'...\n",
                                 pEntry->mnt_type, pEntry->mnt_fsname, pszMountPoint, pszName);
                    rc = VERR_ACCESS_DENIED;
                }
                /* We continue searching in case of stacked mounts, we want the last one. */
            }
        endmntent(pFile);
    }
    else
    {
        VGSvcError("vbsvcAutomounterQueryMountPoint: Could not open mount tab '/proc/mounts' (errno=%d) or '%s' (errno=%d)\n",
                   rc, _PATH_MOUNTED, errno);
        rc = VERR_ACCESS_DENIED;
    }
    return rc;

#elif defined(RT_OS_SOLARIS)
    /*
     * Similar to linux.
     */
    int rc;
    FILE *pFile = fopen(_PATH_MOUNTED, "r");
    if (pFile)
    {
        rc = VWRN_NOT_FOUND;
        struct mnttab Entry;
        while (getmntent(pFile, &Entry) == 0)
            if (RTPathCompare(Entry.mnt_mountp, pszMountPoint) == 0)
            {
                if (strcmp(Entry.mnt_fstype, "vboxfs") == 0)
                {
                    if (RTStrICmp(Entry.mnt_special, pszName) == 0)
                    {
                        VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s'.\n",
                                     pszName, pszMountPoint);
                        rc = VINF_SUCCESS;
                    }
                    else
                    {
                        VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s', not '%s'...\n",
                                     Entry.mnt_special, pszMountPoint, pszName);
                        rc = VERR_RESOURCE_BUSY;
                    }
                }
                else
                {
                    VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found a '%s' mount of '%s' at '%s', not '%s'...\n",
                                 Entry.mnt_fstype, Entry.mnt_special, pszMountPoint, pszName);
                    rc = VERR_ACCESS_DENIED;
                }
                /* We continue searching in case of stacked mounts, we want the last one. */
            }
        fclose(pFile);
    }
    else
    {
        VGSvcError("vbsvcAutomounterQueryMountPoint: Could not open mount tab '%s' (errno=%d)\n", _PATH_MOUNTED, errno);
        rc = VERR_ACCESS_DENIED;
    }
    return rc;
#else
# error "PORTME"
#endif
}


/**
 * Worker for vbsvcAutomounterMountNewEntry that does the OS mounting.
 *
 * @returns IPRT status code.
 * @param   pEntry      The entry to try mount.
 */
static int vbsvcAutomounterMountIt(PVBSVCAUTOMOUNTERENTRY pEntry)
{
    VGSvcVerbose(3, "vbsvcAutomounterMountIt: Trying to mount '%s' (idRoot=%#x) on '%s'...\n",
                 pEntry->pszName, pEntry->idRoot, pEntry->pszActualMountPoint);
#ifdef RT_OS_WINDOWS
    /*
     * Attach the shared folder using WNetAddConnection2W.
     *
     * According to google we should get a drive symlink in \\GLOBAL?? when
     * we are running under the system account.  Otherwise it will a session
     * local link (\\??).
     */
    Assert(RT_C_IS_UPPER(pEntry->pszActualMountPoint[0]) && pEntry->pszActualMountPoint[1] == ':' && pEntry->pszActualMountPoint[2] == '\0');
    RTUTF16 wszDrive[4] = { pEntry->pszActualMountPoint[0], ':', '\0', '\0' };

    RTUTF16 wszPrefixedName[RTPATH_MAX];
    int rc = RTUtf16CopyAscii(wszPrefixedName, RT_ELEMENTS(wszPrefixedName), "\\\\VBoxSvr\\");
    AssertRC(rc);

    size_t const offName = RTUtf16Len(wszPrefixedName);
    PRTUTF16 pwszName = &wszPrefixedName[offName];
    rc = RTStrToUtf16Ex(pEntry->pszName, RTSTR_MAX, &pwszName, sizeof(wszPrefixedName) - offName, NULL);
    if (RT_FAILURE(rc))
    {
        VGSvcError("vbsvcAutomounterMountIt: RTStrToUtf16Ex failed on '%s': %Rrc\n", pEntry->pszName, rc);
        return rc;
    }

    NETRESOURCEW NetRsrc;
    RT_ZERO(NetRsrc);
    NetRsrc.dwType          = RESOURCETYPE_DISK;
    NetRsrc.lpLocalName     = wszDrive;
    NetRsrc.lpRemoteName    = wszPrefixedName;
    NetRsrc.lpProvider      = L"VirtualBox Shared Folders"; /* Only try our provider. */
    NetRsrc.lpComment       = pwszName;

    DWORD dwErr = WNetAddConnection2W(&NetRsrc, NULL /*pwszPassword*/, NULL /*pwszUserName*/, 0 /*dwFlags*/);
    if (dwErr == NO_ERROR)
    {
        VGSvcVerbose(0, "vbsvcAutomounterMountIt: Successfully mounted '%s' on '%s'\n",
                     pEntry->pszName, pEntry->pszActualMountPoint);
        return VINF_SUCCESS;
    }
    VGSvcError("vbsvcAutomounterMountIt: Failed to attach '%s' to '%s': %u\n",
               pEntry->pszName, pEntry->pszActualMountPoint, rc);
    return VERR_OPEN_FAILED;

#elif defined(RT_OS_OS2)
    /*
     * It's a rather simple affair on OS/2.
     *
     * In order to be able to detect our mounts we add a 2nd string after
     * the folder name that tags the attachment.  The IFS will remember this
     * and return it when DosQueryFSAttach is called.
     *
     * Note! Kernel currently accepts limited 7-bit ASCII names.  We could
     *       change that to UTF-8 if we like as that means no extra string
     *       encoding conversion fun here.
     */
    char    szzNameAndTag[256];
    size_t  cchName = strlen(pEntry->pszName);
    if (cchName + 1 + sizeof(g_szTag) <= sizeof(szzNameAndTag))
    {
        memcpy(szzNameAndTag, pEntry->pszName, cchName);
        szzNameAndTag[cchName] = '\0';
        memcpy(&szzNameAndTag[cchName + 1], g_szTag, sizeof(g_szTag));

        APIRET rc = DosFSAttach(pEntry->pszActualMountPoint, "VBOXSF", szzNameAndTag, cchName + 1 + sizeof(g_szTag), FS_ATTACH);
        if (rc == NO_ERROR)
        {
            VGSvcVerbose(0, "vbsvcAutomounterMountIt: Successfully mounted '%s' on '%s'\n",
                         pEntry->pszName, pEntry->pszActualMountPoint);
            return VINF_SUCCESS;
        }
        VGSvcError("vbsvcAutomounterMountIt: DosFSAttach failed to attach '%s' to '%s': %u\n",
                   pEntry->pszName, pEntry->pszActualMountPoint, rc);
    }
    else
        VGSvcError("vbsvcAutomounterMountIt: Share name for attach to '%s' is too long: %u chars - '%s'\n",
                   pEntry->pszActualMountPoint, cchName, pEntry->pszName);
    return VERR_OPEN_FAILED;

#else
    /*
     * Common work for unix-like systems: Get group, make sure mount directory exist.
     */
    int rc = RTDirCreateFullPath(pEntry->pszActualMountPoint,
                                 RTFS_UNIX_IRWXU | RTFS_UNIX_IXGRP | RTFS_UNIX_IRGRP | RTFS_UNIX_IXOTH | RTFS_UNIX_IROTH);
    if (RT_FAILURE(rc))
    {
        VGSvcError("vbsvcAutomounterMountIt: Failed to create mount path '%s' for share '%s': %Rrc\n",
                   pEntry->pszActualMountPoint, pEntry->pszName, rc);
        return rc;
    }

    gid_t gidMount;
    struct group *grp_vboxsf = getgrnam("vboxsf");
    if (grp_vboxsf)
        gidMount = grp_vboxsf->gr_gid;
    else
    {
        VGSvcError("vbsvcAutomounterMountIt: Group 'vboxsf' does not exist\n");
        gidMount = 0;
    }

#  if defined(RT_OS_LINUX)
    /*
     * Linux a bit more work...
     */
    struct vbsf_mount_info_new MntInfo;
    RT_ZERO(MntInfo);
    struct vbsf_mount_opts MntOpts;
    RT_ZERO(MntOpts);
    MntInfo.nullchar     = '\0';
    MntInfo.signature[0] = VBSF_MOUNT_SIGNATURE_BYTE_0;
    MntInfo.signature[1] = VBSF_MOUNT_SIGNATURE_BYTE_1;
    MntInfo.signature[2] = VBSF_MOUNT_SIGNATURE_BYTE_2;
    MntInfo.length       = sizeof(MntInfo);
    MntInfo.ttl          = MntOpts.ttl              = -1 /*default*/;
    MntInfo.msDirCacheTTL= MntOpts.msDirCacheTTL    = -1 /*default*/;
    MntInfo.msInodeTTL   = MntOpts.msInodeTTL       = -1 /*default*/;
    MntInfo.cMaxIoPages  = MntOpts.cMaxIoPages      = 0 /*default*/;
    MntInfo.cbDirBuf     = MntOpts.cbDirBuf         = 0 /*default*/;
    MntInfo.enmCacheMode = MntOpts.enmCacheMode     = kVbsfCacheMode_Default;
    MntInfo.uid          = MntOpts.uid   = 0;
    MntInfo.gid          = MntOpts.gid   = gidMount;
    MntInfo.dmode        = MntOpts.dmode = 0770;
    MntInfo.fmode        = MntOpts.fmode = 0770;
    MntInfo.dmask        = MntOpts.dmask = 0000;
    MntInfo.fmask        = MntOpts.fmask = 0000;
    memcpy(MntInfo.szTag, g_szTag, sizeof(g_szTag)); AssertCompile(sizeof(MntInfo.szTag) >= sizeof(g_szTag));
    rc = RTStrCopy(MntInfo.name, sizeof(MntInfo.name), pEntry->pszName);
    if (RT_FAILURE(rc))
    {
        VGSvcError("vbsvcAutomounterMountIt: Share name '%s' is too long for the MntInfo.name field!\n", pEntry->pszName);
        return rc;
    }

    errno = 0;
    unsigned long fFlags = MS_NODEV;
    rc = mount(pEntry->pszName, pEntry->pszActualMountPoint, "vboxsf", fFlags, &MntInfo);
    if (rc == 0)
    {
        VGSvcVerbose(0, "vbsvcAutomounterMountIt: Successfully mounted '%s' on '%s'\n",
                     pEntry->pszName, pEntry->pszActualMountPoint);

        errno = 0;
        rc = vbsfmount_complete(pEntry->pszName, pEntry->pszActualMountPoint, fFlags, &MntOpts);
        if (rc != 0) /* Ignorable. /etc/mtab is probably a link to /proc/mounts. */
            VGSvcVerbose(1, "vbsvcAutomounterMountIt: vbsfmount_complete failed: %s (%d/%d)\n",
                         rc == 1 ? "open_memstream" : rc == 2 ? "setmntent" : rc == 3 ? "addmntent" : "unknown", rc, errno);
        return VINF_SUCCESS;
    }
    else if (errno == EINVAL)
        VGSvcError("vbsvcAutomounterMountIt: Failed to mount '%s' on '%s' because it is probably mounted elsewhere arleady! (%d,%d)\n",
                   pEntry->pszName, pEntry->pszActualMountPoint, rc, errno);
    else
        VGSvcError("vbsvcAutomounterMountIt: Failed to mount '%s' on '%s': %s (%d,%d)\n",
                   pEntry->pszName, pEntry->pszActualMountPoint, strerror(errno), rc, errno);
    return VERR_WRITE_ERROR;

#  elif defined(RT_OS_SOLARIS)
    /*
     * Solaris is rather simple compared to linux.
     *
     * The ',VBoxService=auto' option (g_szTag) is ignored by the kernel but helps
     * us identify our own mounts on restart.  See vbsvcAutomounterPopulateTable().
     *
     * Note! Must pass MAX_MNTOPT_STR rather than cchOpts to mount, as it may fail
     *       with EOVERFLOW in vfs_buildoptionstr() during domount() otherwise.
     */
    char szOpts[MAX_MNTOPT_STR] = { '\0', };
    ssize_t cchOpts = RTStrPrintf2(szOpts, sizeof(szOpts),
                                   "uid=0,gid=%d,dmode=0770,fmode=0770,dmask=0000,fmask=0000,tag=%s", gidMount, g_szTag);
    if (cchOpts <= 0)
    {
        VGSvcError("vbsvcAutomounterMountIt: szOpts overflow! %zd\n", cchOpts);
        return VERR_BUFFER_OVERFLOW;
    }

    rc = mount(pEntry->pszName, pEntry->pszActualMountPoint, MS_OPTIONSTR, "vboxfs",
               NULL /*dataptr*/, 0 /* datalen */, szOpts, MAX_MNTOPT_STR);
    if (rc == 0)
    {
        VGSvcVerbose(0, "vbsvcAutomounterMountIt: Successfully mounted '%s' on '%s'\n",
                     pEntry->pszName, pEntry->pszActualMountPoint);
        return VINF_SUCCESS;
    }

    rc = errno;
    VGSvcError("vbsvcAutomounterMountIt: mount failed for '%s' on '%s' (szOpts=%s): %s (%d)\n",
               pEntry->pszName, pEntry->pszActualMountPoint, szOpts, strerror(rc), rc);
    return VERR_OPEN_FAILED;

# else
#  error "PORTME!"
# endif
#endif
}


/**
 * Attempts to mount the given shared folder, adding it to the mount table on
 * success.
 *
 * @returns iTable + 1 on success, iTable on failure.
 * @param   pTable          The mount table.
 * @param   iTable          The mount table index at which to add the mount.
 * @param   pszName         The name of the shared folder mapping.
 * @param   pszMntPt        The mount point (hint) specified by the host.
 * @param   fFlags          The shared folder flags, SHFL_MIF_XXX.
 * @param   idRoot          The root ID.
 * @param   uRootIdVersion  The root ID version.
 * @param   fAutoMntPt      Whether to try automatically assign a mount point if
 *                          pszMntPt doesn't work out.  This is set in pass \#3.
 */
static uint32_t vbsvcAutomounterMountNewEntry(PVBSVCAUTOMOUNTERTABLE pTable, uint32_t iTable,
                                              const char *pszName, const char *pszMntPt, uint64_t fFlags,
                                              uint32_t idRoot, uint32_t uRootIdVersion, bool fAutoMntPt)
{
    VGSvcVerbose(3, "vbsvcAutomounterMountNewEntry: #%u: '%s' at '%s'%s\n",
                 iTable, pszName, pszMntPt, fAutoMntPt ? " auto-assign" : "");

    /*
     * First we need to figure out the actual mount point.
     */
    char szActualMountPoint[RTPATH_MAX];

#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS)
    /*
     * Drive letter based.  We only care about the first two characters
     * and ignore the rest (see further down).
     */
    char chNextLetter = 'Z';
    if (RT_C_IS_ALPHA(pszMntPt[0]) && pszMntPt[1] == ':')
        szActualMountPoint[0] = RT_C_TO_UPPER(pszMntPt[0]);
    else if (!fAutoMntPt)
        return iTable;
    else
        szActualMountPoint[0] = chNextLetter--;
    szActualMountPoint[1] = ':';
    szActualMountPoint[2] = '\0';

    int rc;
    for (;;)
    {
        rc = vbsvcAutomounterQueryMountPoint(szActualMountPoint, pszName);
        if (rc == VWRN_NOT_FOUND)
            break;

        /* next */
        if (chNextLetter == 'A' || !fAutoMntPt)
            return iTable;
        szActualMountPoint[0] = chNextLetter--;
    }

#else
    /*
     * Path based #1: Host specified mount point.
     */

    /* Skip DOS drive letter if there is a UNIX mount point path following it: */
    if (   pszMntPt[0] != '/'
        && pszMntPt[0] != '\0'
        && pszMntPt[1] == ':'
        && pszMntPt[2] == '/')
        pszMntPt += 2;

    /* Try specified mount point if it starts with a UNIX slash: */
    int rc = VERR_ACCESS_DENIED;
    if (*pszMntPt == '/')
    {
        rc = RTPathAbs(pszMntPt, szActualMountPoint, sizeof(szActualMountPoint));
        if (RT_SUCCESS(rc))
        {
            static const char * const s_apszBlacklist[] =
            { "/", "/dev", "/bin", "/sbin", "/lib", "/etc", "/var", "/tmp", "/usr", "/usr/bin", "/usr/sbin", "/usr/lib" };
            for (size_t i = 0; i < RT_ELEMENTS(s_apszBlacklist); i++)
                if (strcmp(szActualMountPoint, s_apszBlacklist[i]) == 0)
                {
                    rc = VERR_ACCESS_DENIED;
                    break;
                }
            if (RT_SUCCESS(rc))
                rc = vbsvcAutomounterQueryMountPoint(szActualMountPoint, pszName);
        }
    }
    if (rc != VWRN_NOT_FOUND)
    {
        if (!fAutoMntPt)
            return iTable;

        /*
         * Path based #2: Mount dir + prefix + share.
         */
        rc = vbsvcAutomounterQueryMountDirAndPrefix(szActualMountPoint, sizeof(szActualMountPoint));
        if (RT_SUCCESS(rc))
        {
            /* Append a sanitized share name: */
            size_t const offShare = strlen(szActualMountPoint);
            size_t offDst = offShare;
            size_t offSrc = 0;
            for (;;)
            {
                char ch = pszName[offSrc++];
                if (ch == ' ' || ch == '/' || ch == '\\' || ch == ':' || ch == '$')
                    ch = '_';
                else if (!ch)
                    break;
                else if (ch < 0x20 || ch == 0x7f)
                    continue;
                if (offDst < sizeof(szActualMountPoint) - 1)
                    szActualMountPoint[offDst++] = ch;
            }
            szActualMountPoint[offDst] = '\0';
            if (offDst > offShare)
            {
                rc = vbsvcAutomounterQueryMountPoint(szActualMountPoint, pszName);
                if (rc != VWRN_NOT_FOUND)
                {
                    /*
                     * Path based #3: Mount dir + prefix + share + _ + number.
                     */
                    if (offDst + 2 >= sizeof(szActualMountPoint))
                        return iTable;

                    szActualMountPoint[offDst++] = '_';
                    for (uint32_t iTry = 1; iTry < 10 && rc != VWRN_NOT_FOUND; iTry++)
                    {
                        szActualMountPoint[offDst] = '0' + iTry;
                        szActualMountPoint[offDst + 1] = '\0';
                        rc = vbsvcAutomounterQueryMountPoint(szActualMountPoint, pszName);
                    }
                    if (rc != VWRN_NOT_FOUND)
                       return iTable;
                }
            }
            else
                VGSvcError("vbsvcAutomounterMountNewEntry: Bad share name: %.*Rhxs", strlen(pszName), pszName);
        }
        else
            VGSvcError("vbsvcAutomounterMountNewEntry: Failed to construct basic auto mount point for '%s'", pszName);
    }
#endif

    /*
     * Prepare a table entry and ensure space in the table..
     */
    if (pTable->cEntries + 1 > pTable->cAllocated)
    {
        void *pvEntries = RTMemRealloc(pTable->papEntries, sizeof(pTable->papEntries[0]) * (pTable->cAllocated + 8));
        if (!pvEntries)
        {
            VGSvcError("vbsvcAutomounterMountNewEntry: Out of memory for growing table (size %u)\n", pTable->cAllocated);
            return iTable;
        }
        pTable->cAllocated += 8;
        pTable->papEntries = (PVBSVCAUTOMOUNTERENTRY *)pvEntries;
    }

    PVBSVCAUTOMOUNTERENTRY pEntry = (PVBSVCAUTOMOUNTERENTRY)RTMemAlloc(sizeof(*pEntry));
    if (pEntry)
    {
        pEntry->idRoot              = idRoot;
        pEntry->uRootIdVersion      = uRootIdVersion;
        pEntry->fFlags              = fFlags;
        pEntry->pszName             = RTStrDup(pszName);
        pEntry->pszMountPoint       = RTStrDup(pszMntPt);
        pEntry->pszActualMountPoint = RTStrDup(szActualMountPoint);
        if (pEntry->pszName && pEntry->pszMountPoint && pEntry->pszActualMountPoint)
        {
            /*
             * Now try mount it.
             */
            rc = vbsvcAutomounterMountIt(pEntry);
            if (RT_SUCCESS(rc))
            {
                uint32_t cToMove = pTable->cEntries - iTable;
                if (cToMove > 0)
                    memmove(&pTable->papEntries[iTable + 1], &pTable->papEntries[iTable], cToMove * sizeof(pTable->papEntries[0]));
                pTable->papEntries[iTable] = pEntry;
                pTable->cEntries++;
                return iTable + 1;
            }
        }
        else
            VGSvcError("vbsvcAutomounterMountNewEntry: Out of memory for table entry!\n");
        RTMemFree(pEntry->pszActualMountPoint);
        RTMemFree(pEntry->pszMountPoint);
        RTMemFree(pEntry->pszName);
        RTMemFree(pEntry);
    }
    else
        VGSvcError("vbsvcAutomounterMountNewEntry: Out of memory for table entry!\n");
    return iTable;
}



/**
 * Does the actual unmounting.
 *
 * @returns Exactly one of the following IPRT status codes;
 * @retval  VINF_SUCCESS if successfully umounted or nothing was mounted there.
 * @retval  VERR_TRY_AGAIN if the shared folder is busy.
 * @retval  VERR_RESOURCE_BUSY if a different shared folder is mounted there.
 * @retval  VERR_ACCESS_DENIED if a non-shared folder file system is mounted
 *          there.
 *
 * @param   pszMountPoint       The mount point.
 * @param   pszName             The shared folder (mapping) name.
 */
static int vbsvcAutomounterUnmount(const char *pszMountPoint, const char *pszName)
{
    /*
     * Retry for 5 seconds in a hope that busy mounts will quiet down.
     */
    for (unsigned iTry = 0; ; iTry++)
    {
        /*
         * Check what's mounted there before we start umounting stuff.
         */
        int rc = vbsvcAutomounterQueryMountPoint(pszMountPoint, pszName);
        if (rc == VINF_SUCCESS)
        { /* pszName is mounted there */ }
        else if (rc == VWRN_NOT_FOUND) /* nothing mounted there */
            return VINF_SUCCESS;
        else
        {
            Assert(rc == VERR_RESOURCE_BUSY || rc == VERR_ACCESS_DENIED);
            return VERR_RESOURCE_BUSY;
        }

        /*
         * Do host specific unmounting.
         */
#ifdef RT_OS_WINDOWS
        Assert(RT_C_IS_UPPER(pszMountPoint[0]) && pszMountPoint[1] == ':' && pszMountPoint[2] == '\0');
        RTUTF16 const wszDrive[4] = { pszMountPoint[0], ':', '\0', '\0' };
        DWORD dwErr = WNetCancelConnection2W(wszDrive, 0 /*dwFlags*/, FALSE /*fForce*/);
        if (dwErr == NO_ERROR)
            return VINF_SUCCESS;
        VGSvcVerbose(2, "vbsvcAutomounterUnmount: WNetCancelConnection2W returns %u for '%s' ('%s')\n", dwErr, pszMountPoint, pszName);
        if (dwErr == ERROR_NOT_CONNECTED)
            return VINF_SUCCESS;

#elif defined(RT_OS_OS2)
        APIRET rcOs2 = DosFSAttach(pszMountPoint, "VBOXSF", NULL, 0, FS_DETACH);
        if (rcOs2 == NO_ERROR)
            return VINF_SUCCESS;
        VGSvcVerbose(2, "vbsvcAutomounterUnmount: DosFSAttach failed on '%s' ('%s'): %u\n", pszMountPoint, pszName, rcOs2);
        if (rcOs2 == ERROR_INVALID_FSD_NAME)
            return VERR_ACCESS_DENIED;
        if (   rcOs2 == ERROR_INVALID_DRIVE
            || rcOs2 == ERROR_INVALID_PATH)
            return VERR_TRY_AGAIN;

#else
        int rc2 = umount(pszMountPoint);
        if (rc2 == 0)
        {
            /* Remove the mount directory if not directly under the root dir. */
            RTPATHPARSED Parsed;
            RT_ZERO(Parsed);
            RTPathParse(pszMountPoint, &Parsed, sizeof(Parsed), RTPATH_STR_F_STYLE_HOST);
            if (Parsed.cComps >= 3)
                RTDirRemove(pszMountPoint);

            return VINF_SUCCESS;
        }
        rc2 = errno;
        VGSvcVerbose(2, "vbsvcAutomounterUnmount: umount failed on '%s' ('%s'): %d\n", pszMountPoint, pszName, rc2);
        if (rc2 != EBUSY && rc2 != EAGAIN)
            return VERR_ACCESS_DENIED;
#endif

        /*
         * Check what's mounted there before we start delaying.
         */
        RTThreadSleep(8); /* fudge */
        rc = vbsvcAutomounterQueryMountPoint(pszMountPoint, pszName);
        if (rc == VINF_SUCCESS)
        { /* pszName is mounted there */ }
        else if (rc == VWRN_NOT_FOUND) /* nothing mounted there */
            return VINF_SUCCESS;
        else
        {
            Assert(rc == VERR_RESOURCE_BUSY || rc == VERR_ACCESS_DENIED);
            return VERR_RESOURCE_BUSY;
        }

        if (iTry >= 5)
            return VERR_TRY_AGAIN;
        RTThreadSleep(1000);
    }
}


/**
 * Unmounts a mount table entry and evicts it from the table if successful.
 *
 * @returns The next iTable (same value on success, +1 on failure).
 * @param   pTable              The mount table.
 * @param   iTable              The table entry.
 * @param   pszReason           Why we're here.
 */
static uint32_t vbsvcAutomounterUnmountEntry(PVBSVCAUTOMOUNTERTABLE pTable, uint32_t iTable, const char *pszReason)
{
    Assert(iTable < pTable->cEntries);
    PVBSVCAUTOMOUNTERENTRY pEntry = pTable->papEntries[iTable];
    VGSvcVerbose(2, "vbsvcAutomounterUnmountEntry: #%u: '%s' at '%s' (reason: %s)\n",
                 iTable, pEntry->pszName, pEntry->pszActualMountPoint, pszReason);

    /*
     * Do we need to umount the entry?  Return if unmount fails and we .
     */
    if (pEntry->pszActualMountPoint)
    {
        int rc = vbsvcAutomounterUnmount(pEntry->pszActualMountPoint, pEntry->pszName);
        if (rc == VERR_TRY_AGAIN)
        {
            VGSvcVerbose(1, "vbsvcAutomounterUnmountEntry: Keeping '%s' -> '%s' (VERR_TRY_AGAIN)\n",
                         pEntry->pszActualMountPoint, pEntry->pszName);
            return iTable + 1;
        }
    }

    /*
     * Remove the entry by shifting up the ones after it.
     */
    pTable->cEntries -= 1;
    uint32_t cAfter = pTable->cEntries - iTable;
    if (cAfter)
        memmove(&pTable->papEntries[iTable], &pTable->papEntries[iTable + 1], cAfter * sizeof(pTable->papEntries[0]));
    pTable->papEntries[pTable->cEntries] = NULL;

    RTStrFree(pEntry->pszActualMountPoint);
    pEntry->pszActualMountPoint = NULL;
    RTStrFree(pEntry->pszMountPoint);
    pEntry->pszMountPoint = NULL;
    RTStrFree(pEntry->pszName);
    pEntry->pszName = NULL;
    RTMemFree(pEntry);

    return iTable;
}


/**
 * @callback_method_impl{FNRTSORTCMP, For sorting the mappings by ID. }
 */
static DECLCALLBACK(int) vbsvcSharedFolderMappingCompare(void const *pvElement1, void const *pvElement2, void *pvUser)
{
    RT_NOREF_PV(pvUser);
    PVBGLR3SHAREDFOLDERMAPPING pMapping1 = (PVBGLR3SHAREDFOLDERMAPPING)pvElement1;
    PVBGLR3SHAREDFOLDERMAPPING pMapping2 = (PVBGLR3SHAREDFOLDERMAPPING)pvElement2;
    return pMapping1->u32Root < pMapping2->u32Root ? -1 : pMapping1->u32Root != pMapping2->u32Root ? 1 : 0;
}


/**
 * Refreshes the mount table.
 *
 * @returns true if we've processed the current config, false if we failed to
 *          query the mappings.
 * @param   pTable          The mount table to refresh.
 */
static bool vbsvcAutomounterRefreshTable(PVBSVCAUTOMOUNTERTABLE pTable)
{
    /*
     * Query the root IDs of all auto-mountable shared folder mappings.
     */
    uint32_t                    cMappings = 0;
    PVBGLR3SHAREDFOLDERMAPPING  paMappings = NULL;
    int rc = VbglR3SharedFolderGetMappings(g_idClientSharedFolders, true /*fAutoMountOnly*/, &paMappings, &cMappings);
    if (RT_FAILURE(rc))
    {
        VGSvcError("vbsvcAutomounterRefreshTable: VbglR3SharedFolderGetMappings failed: %Rrc\n", rc);
        return false;
    }

    /*
     * Walk the table and the mappings in parallel, so we have to make sure
     * they are both sorted by root ID.
     */
    if (cMappings > 1)
        RTSortShell(paMappings, cMappings, sizeof(paMappings[0]), vbsvcSharedFolderMappingCompare, NULL);

    /*
     * Pass #1: Do all the umounting.
     *
     * By doing the umount pass separately from the mount pass, we can
     * better handle changing involving the same mount points (switching
     * mount points between two shares, new share on same mount point but
     * with lower root ID, ++).
     */
    uint32_t iTable = 0;
    for (uint32_t iSrc = 0; iSrc < cMappings; iSrc++)
    {
        /*
         * Unmount table entries up to idRootSrc.
         */
        uint32_t const idRootSrc = paMappings[iSrc].u32Root;
        while (   iTable < pTable->cEntries
               && pTable->papEntries[iTable]->idRoot < idRootSrc)
            iTable = vbsvcAutomounterUnmountEntry(pTable, iTable, "dropped");

        /*
         * If the paMappings entry and the mount table entry has the same
         * root ID, umount if anything has changed or if we cannot query
         * the mapping data.
         */
        if (iTable < pTable->cEntries)
        {
            PVBSVCAUTOMOUNTERENTRY pEntry = pTable->papEntries[iTable];
            if (pEntry->idRoot == idRootSrc)
            {
                uint32_t uRootIdVer = UINT32_MAX;
                uint64_t fFlags     = 0;
                char    *pszName    = NULL;
                char    *pszMntPt   = NULL;
                rc = VbglR3SharedFolderQueryFolderInfo(g_idClientSharedFolders, idRootSrc, VBOXSERVICE_AUTOMOUNT_MIQF,
                                                       &pszName, &pszMntPt, &fFlags, &uRootIdVer);
                if (RT_FAILURE(rc))
                    iTable = vbsvcAutomounterUnmountEntry(pTable, iTable, "VbglR3SharedFolderQueryFolderInfo failed");
                else if (pEntry->uRootIdVersion != uRootIdVer)
                    iTable = vbsvcAutomounterUnmountEntry(pTable, iTable, "root ID version changed");
                else if (RTPathCompare(pEntry->pszMountPoint, pszMntPt) != 0)
                    iTable = vbsvcAutomounterUnmountEntry(pTable, iTable, "mount point changed");
                else if (RTStrICmp(pEntry->pszName, pszName) != 0)
                    iTable = vbsvcAutomounterUnmountEntry(pTable, iTable, "name changed");
                else
                {
                    VGSvcVerbose(3, "vbsvcAutomounterRefreshTable: Unchanged: %s -> %s\n", pEntry->pszMountPoint, pEntry->pszName);
                    iTable++;
                }
                if (RT_SUCCESS(rc))
                {
                    RTStrFree(pszName);
                    RTStrFree(pszMntPt);
                }
            }
        }
    }

    while (iTable < pTable->cEntries)
        iTable = vbsvcAutomounterUnmountEntry(pTable, iTable, "dropped (tail)");

    VGSvcVerbose(4, "vbsvcAutomounterRefreshTable: %u entries in mount table after pass #1.\n", pTable->cEntries);

    /*
     * Pass #2: Try mount new folders that has mount points assigned.
     * Pass #3: Try mount new folders not mounted in pass #2.
     */
    for (uint32_t iPass = 2; iPass <= 3; iPass++)
    {
        iTable = 0;
        for (uint32_t iSrc = 0; iSrc < cMappings; iSrc++)
        {
            uint32_t const idRootSrc = paMappings[iSrc].u32Root;

            /*
             * Skip tabel entries we couldn't umount in pass #1.
             */
            while (   iTable < pTable->cEntries
                   && pTable->papEntries[iTable]->idRoot < idRootSrc)
            {
                VGSvcVerbose(4, "vbsvcAutomounterRefreshTable: %u/#%u/%#u: Skipping idRoot=%u %s\n",
                             iPass, iSrc, iTable, pTable->papEntries[iTable]->idRoot, pTable->papEntries[iTable]->pszName);
                iTable++;
            }

            /*
             * New share?
             */
            if (   iTable >= pTable->cEntries
                || pTable->papEntries[iTable]->idRoot != idRootSrc)
            {
                uint32_t uRootIdVer = UINT32_MAX;
                uint64_t fFlags     = 0;
                char    *pszName    = NULL;
                char    *pszMntPt   = NULL;
                rc = VbglR3SharedFolderQueryFolderInfo(g_idClientSharedFolders, idRootSrc, VBOXSERVICE_AUTOMOUNT_MIQF,
                                                       &pszName, &pszMntPt, &fFlags, &uRootIdVer);
                if (RT_SUCCESS(rc))
                {
                    VGSvcVerbose(4, "vbsvcAutomounterRefreshTable: %u/#%u/%#u: Mounting idRoot=%u/%u %s\n", iPass, iSrc, iTable,
                                 idRootSrc, iTable >= pTable->cEntries ? UINT32_MAX : pTable->papEntries[iTable]->idRoot, pszName);
                    iTable = vbsvcAutomounterMountNewEntry(pTable, iTable, pszName, pszMntPt, fFlags,
                                                           idRootSrc, uRootIdVer, iPass == 3);

                    RTStrFree(pszName);
                    RTStrFree(pszMntPt);
                }
                else
                    VGSvcVerbose(1, "vbsvcAutomounterRefreshTable: VbglR3SharedFolderQueryFolderInfo failed: %Rrc\n", rc);
            }
            else
                VGSvcVerbose(4, "vbsvcAutomounterRefreshTable: %u/#%u/%#u: idRootSrc=%u vs idRoot=%u %s\n", iPass, iSrc,
                             iTable, idRootSrc, pTable->papEntries[iTable]->idRoot, pTable->papEntries[iTable]->pszName);
        }
    }

    VbglR3SharedFolderFreeMappings(paMappings);
    return true;
}


/**
 * @interface_method_impl{VBOXSERVICE,pfnWorker}
 */
static DECLCALLBACK(int) vbsvcAutomounterWorker(bool volatile *pfShutdown)
{
    /*
     * Tell the control thread that it can continue spawning services.
     */
    RTThreadUserSignal(RTThreadSelf());

    /* Divert old hosts to original auto-mount code. */
    if (!g_fHostSupportsWaitAndInfoQuery)
        return vbsvcAutoMountWorkerOld(pfShutdown);

    /*
     * Initialize the state in case we're restarted...
     */
    VBSVCAUTOMOUNTERTABLE MountTable  = { 0, 0, NULL };
    int rc = vbsvcAutomounterPopulateTable(&MountTable);
    if (RT_FAILURE(rc))
    {
        VGSvcError("vbsvcAutomounterWorker: vbsvcAutomounterPopulateTable failed (%Rrc), quitting!\n", rc);
        return rc;
    }

    /*
     * Work loop.
     */
    uint32_t uConfigVer    = UINT32_MAX;
    uint32_t uNewVersion   = 0;
    bool     fForceRefresh = true;
    while (!*pfShutdown)
    {
        /*
         * Update the mounts.
         */
        if (   uConfigVer != uNewVersion
            || fForceRefresh)
        {
            fForceRefresh = !vbsvcAutomounterRefreshTable(&MountTable);
            uConfigVer    = uNewVersion;
        }

        /*
         * Wait for more to do.
         */
        if (!*pfShutdown)
        {
            uNewVersion = uConfigVer - 1;
            VGSvcVerbose(2, "vbsvcAutomounterWorker: Waiting with uConfigVer=%u\n", uConfigVer);
            rc = VbglR3SharedFolderWaitForMappingsChanges(g_idClientSharedFolders, uConfigVer, &uNewVersion);
            VGSvcVerbose(2, "vbsvcAutomounterWorker: Woke up with uNewVersion=%u and rc=%Rrc\n", uNewVersion, rc);

            /* Delay a little before doing a table refresh so the GUI can finish
               all its updates.  Delay a little longer on non-shutdown failure to
               avoid eating too many CPU cycles if something goes wrong here... */
            if (!*pfShutdown)
                RTSemEventMultiWait(g_hAutoMountEvent, RT_SUCCESS(rc) ? 256 : 1000);
        }
    }

    /*
     * Destroy the mount table.
     */
    while (MountTable.cEntries-- > 0)
        RTMemFree(MountTable.papEntries[MountTable.cEntries]);
    MountTable.papEntries = NULL;

    VGSvcVerbose(3, "vbsvcAutomounterWorker: Finished\n");
    return VINF_SUCCESS;
}


/**
 * @interface_method_impl{VBOXSERVICE,pfnStop}
 */
static DECLCALLBACK(void) vbsvcAutomounterStop(void)
{
    RTSemEventMultiSignal(g_hAutoMountEvent);
    if (g_fHostSupportsWaitAndInfoQuery)
        VbglR3SharedFolderCancelMappingsChangesWaits(g_idClientSharedFolders);
}


/**
 * @interface_method_impl{VBOXSERVICE,pfnTerm}
 */
static DECLCALLBACK(void) vbsvcAutomounterTerm(void)
{
    VGSvcVerbose(3, "vbsvcAutoMountTerm\n");

    if (g_fHostSupportsWaitAndInfoQuery)
        VbglR3SharedFolderCancelMappingsChangesWaits(g_idClientSharedFolders);

    VbglR3SharedFolderDisconnect(g_idClientSharedFolders);
    g_idClientSharedFolders = 0;

    if (g_hAutoMountEvent != NIL_RTSEMEVENTMULTI)
    {
        RTSemEventMultiDestroy(g_hAutoMountEvent);
        g_hAutoMountEvent = NIL_RTSEMEVENTMULTI;
    }
}


/**
 * The 'automount' service description.
 */
VBOXSERVICE g_AutoMount =
{
    /* pszName. */
    "automount",
    /* pszDescription. */
    "Automounter for Shared Folders",
    /* pszUsage. */
    NULL,
    /* pszOptions. */
    NULL,
    /* methods */
    VGSvcDefaultPreInit,
    VGSvcDefaultOption,
    vbsvcAutomounterInit,
    vbsvcAutomounterWorker,
    vbsvcAutomounterStop,
    vbsvcAutomounterTerm
};

