Index: /trunk/include/VBox/com/string.h
===================================================================
--- /trunk/include/VBox/com/string.h	(revision 42176)
+++ /trunk/include/VBox/com/string.h	(revision 42177)
@@ -5,5 +5,5 @@
 
 /*
- * Copyright (C) 2006-2011 Oracle Corporation
+ * Copyright (C) 2006-2012 Oracle Corporation
  *
  * This file is part of VirtualBox Open Source Edition (OSE), as
@@ -511,4 +511,9 @@
     }
 
+    Utf8Str(const char *a_pszSrc, size_t a_cchSrc)
+        : RTCString(a_pszSrc, a_cchSrc)
+    {
+    }
+
     /**
      * Constructs a new string given the format string and the list of the
Index: /trunk/src/VBox/Main/include/VirtualBoxImpl.h
===================================================================
--- /trunk/src/VBox/Main/include/VirtualBoxImpl.h	(revision 42176)
+++ /trunk/src/VBox/Main/include/VirtualBoxImpl.h	(revision 42177)
@@ -222,4 +222,5 @@
                         ComObjPtr<Machine> *machine = NULL);
 
+    HRESULT validateMachineGroup(const Utf8Str &aGroup);
     HRESULT convertMachineGroups(ComSafeArrayIn(IN_BSTR, aMachineGroups), StringsList *pllMachineGroups);
 
@@ -314,5 +315,5 @@
     HRESULT unregisterDHCPServer(DHCPServer *aDHCPServer,
                                  bool aSaveRegistry = true);
-    
+
     void decryptSettings();
     void decryptMediumSettings(Medium *pMedium);
Index: /trunk/src/VBox/Main/src-server/MachineImpl.cpp
===================================================================
--- /trunk/src/VBox/Main/src-server/MachineImpl.cpp	(revision 42176)
+++ /trunk/src/VBox/Main/src-server/MachineImpl.cpp	(revision 42177)
@@ -315,5 +315,5 @@
 
         mUserData->s.strName = strName;
-        
+
         mUserData->s.llGroups = llGroups;
 
@@ -8942,5 +8942,6 @@
     if (    mUserData->s.fNameSync
          && mUserData.isBackedUp()
-         && mUserData.backedUpData()->s.strName != mUserData->s.strName
+         && (   mUserData.backedUpData()->s.strName != mUserData->s.strName
+             || mUserData.backedUpData()->s.llGroups.front() != mUserData->s.llGroups.front())
        )
     {
@@ -8958,16 +8959,27 @@
             Utf8Str name = mUserData.backedUpData()->s.strName;
             Utf8Str newName = mUserData->s.strName;
+            Utf8Str group = mUserData.backedUpData()->s.llGroups.front();
+            if (group == "/")
+                group.setNull();
+            Utf8Str newGroup = mUserData->s.llGroups.front();
+            if (newGroup == "/")
+                newGroup.setNull();
 
             configFile = mData->m_strConfigFileFull;
 
-            /* first, rename the directory if it matches the machine name */
+            /* first, rename the directory if it matches the group and machine name */
+            Utf8Str groupPlusName = Utf8StrFmt("%s%c%s",
+                group.c_str(), RTPATH_DELIMITER, name.c_str());
+            Utf8Str newGroupPlusName = Utf8StrFmt("%s%c%s",
+                newGroup.c_str(), RTPATH_DELIMITER, newName.c_str());
             configDir = configFile;
             configDir.stripFilename();
             newConfigDir = configDir;
-            if (!strcmp(RTPathFilename(configDir.c_str()), name.c_str()))
+            if (   configDir.length() >= groupPlusName.length()
+                && configDir.substr(configDir.length() - groupPlusName.length(), groupPlusName.length()).equals(groupPlusName.c_str()))
             {
-                newConfigDir.stripFilename();
-                newConfigDir.append(RTPATH_DELIMITER);
-                newConfigDir.append(newName);
+                newConfigDir = newConfigDir.substr(0, configDir.length() - groupPlusName.length());
+                Utf8Str newConfigBaseDir(newConfigDir);
+                newConfigDir.append(newGroupPlusName);
                 /* new dir and old dir cannot be equal here because of 'if'
                  * above and because name != newName */
@@ -8977,4 +8989,12 @@
                     /* perform real rename only if the machine is not new */
                     vrc = RTPathRename(configDir.c_str(), newConfigDir.c_str(), 0);
+                    if (vrc == VERR_FILE_NOT_FOUND)
+                    {
+                        /* create the parent directory, then retry renaming */
+                        Utf8Str parent(newConfigDir);
+                        parent.stripFilename();
+                        (void)RTDirCreateFullPath(parent.c_str(), 0700);
+                        vrc = RTPathRename(configDir.c_str(), newConfigDir.c_str(), 0);
+                    }
                     if (RT_FAILURE(vrc))
                     {
@@ -8985,4 +9005,14 @@
                                       vrc);
                         break;
+                    }
+                    /* delete subdirectories which are no longer needed */
+                    Utf8Str dir(configDir);
+                    dir.stripFilename();
+                    while (dir != newConfigBaseDir && dir != ".")
+                    {
+                        vrc = RTDirRemove(dir.c_str());
+                        if (RT_FAILURE(vrc))
+                            break;
+                        dir.stripFilename();
                     }
                     dirRenamed = true;
Index: /trunk/src/VBox/Main/src-server/VirtualBoxImpl.cpp
===================================================================
--- /trunk/src/VBox/Main/src-server/VirtualBoxImpl.cpp	(revision 42176)
+++ /trunk/src/VBox/Main/src-server/VirtualBoxImpl.cpp	(revision 42177)
@@ -1375,6 +1375,4 @@
                                                 BSTR *aFilename)
 {
-    /// @todo implement aGroup
-    NOREF(aGroup);
     LogFlowThisFuncEnter();
     LogFlowThisFunc(("aName=\"%ls\",aBaseFolder=\"%ls\"\n", aName, aBaseFolder));
@@ -1386,7 +1384,14 @@
     if (FAILED(autoCaller.rc())) return autoCaller.rc();
 
+    Utf8Str strGroup(aGroup);
+    if (strGroup.isEmpty())
+        strGroup = "/";
+    HRESULT rc = validateMachineGroup(strGroup);
+    if (FAILED(rc))
+        return rc;
+
     /* Compose the settings file name using the following scheme:
      *
-     *     <base_folder>/<machine_name>/<machine_name>.xml
+     *     <base_folder><group>/<machine_name>/<machine_name>.xml
      *
      * If a non-null and non-empty base folder is specified, the default
@@ -1405,20 +1410,14 @@
     calculateFullPath(strBase, strBase);
 
-    Bstr bstrSettingsFile = BstrFmt("%s%c%s%c%s.vbox",
+    /* eliminate toplevel group to avoid // in the result */
+    if (strGroup == "/")
+        strGroup.setNull();
+    Bstr bstrSettingsFile = BstrFmt("%s%s%c%s%c%s.vbox",
                                     strBase.c_str(),
+                                    strGroup.c_str(),
                                     RTPATH_DELIMITER,
                                     strName.c_str(),
                                     RTPATH_DELIMITER,
                                     strName.c_str());
-
-#if 0  /* Try to get a unique name. */
-    for (unsigned i = 1; RTFileExists(bstrSettingsFile.c_str() && i < 100; ++i)
-        bstrSettingsFile = BstrFmt("%s%c%s%u%c%s%u.vbox",
-                                   strBase.c_str(),
-                                   RTPATH_DELIMITER,
-                                   strName.c_str(), i,
-                                   RTPATH_DELIMITER,
-                                   strName.c_str());
-#endif
 
     bstrSettingsFile.detachTo(aFilename);
@@ -3022,4 +3021,72 @@
 }
 
+static HRESULT validateMachineGroupHelper(const Utf8Str &aGroup)
+{
+    /* empty strings are invalid */
+    if (aGroup.isEmpty())
+        return E_INVALIDARG;
+    /* the toplevel group is valid */
+    if (aGroup == "/")
+        return S_OK;
+    /* any other strings of length 1 are invalid */
+    if (aGroup.length() == 1)
+        return E_INVALIDARG;
+    /* must start with a slash */
+    if (aGroup.c_str()[0] != '/')
+        return E_INVALIDARG;
+    /* must not end with a slash */
+    if (aGroup.c_str()[aGroup.length() - 1] == '/')
+        return E_INVALIDARG;
+    /* check the group components */
+    const char *pStr = aGroup.c_str() + 1;  /* first char is /, skip it */
+    while (pStr)
+    {
+        char *pSlash = RTStrStr(pStr, "/");
+        if (pSlash)
+        {
+            /* no empty components (or // sequences in other words) */
+            if (pSlash == pStr)
+                return E_INVALIDARG;
+            /* check if the machine name rules are violated, because that means
+             * the group components is too close to the limits. */
+            Utf8Str tmp((const char *)pStr, (size_t)(pSlash - pStr));
+            Utf8Str tmp2(tmp);
+            sanitiseMachineFilename(tmp);
+            if (tmp != tmp2)
+                return E_INVALIDARG;
+            pStr = pSlash + 1;
+        }
+        else
+        {
+            /* check if the machine name rules are violated, because that means
+             * the group components is too close to the limits. */
+            Utf8Str tmp(pStr);
+            Utf8Str tmp2(tmp);
+            sanitiseMachineFilename(tmp);
+            if (tmp != tmp2)
+                return E_INVALIDARG;
+            pStr = NULL;
+        }
+    }
+    return S_OK;
+}
+
+/**
+ * Validates a machine group.
+ *
+ * @param aMachineGroup     Machine group.
+ *
+ * @return S_OK or E_INVALIDARG
+ */
+HRESULT VirtualBox::validateMachineGroup(const Utf8Str &aGroup)
+{
+    HRESULT rc = validateMachineGroupHelper(aGroup);
+    if (FAILED(rc))
+        rc = setError(rc,
+                      tr("Invalid machine group '%s'"),
+                      aGroup.c_str());
+    return rc;
+}
+
 /**
  * Takes a list of machine groups, and sanitizes/validates it.
@@ -3030,5 +3097,4 @@
  * @return S_OK or E_INVALIDARG
  */
-
 HRESULT VirtualBox::convertMachineGroups(ComSafeArrayIn(IN_BSTR, aMachineGroups), StringsList *pllMachineGroups)
 {
@@ -3042,12 +3108,8 @@
             if (group.length() == 0)
                 group = "/";
-            /* must start with a slash */
-            if (group.c_str()[0] != '/')
-                return E_INVALIDARG;
-            /* must not end with a slash */
-            if (group.length() > 1 && group.c_str()[group.length() - 1] == '/')
-                return E_INVALIDARG;
-
-            /** @todo validate each component of the group hierarchy */
+
+            HRESULT rc = validateMachineGroup(group);
+            if (FAILED(rc))
+                return rc;
 
             /* no duplicates please */
