shell32: SHFileOperation improvements

Rolf Kalbermatter rolf.kalbermatter at citeng.com
Wed Oct 27 14:59:18 CDT 2004


This is the final and largest patch to SHFileOperation. It is not easily splittable
in smaller parts.

Changelog
  - dlls/shell32/shlfileop.c
    - Implements "rename on collision" and also creating the name mapping handle
      if desired by the caller.
    - Attempts have been made to simplify the SHFileOperation function (no goto
      labels, parameter checks in separate function, etc) eventhough it implements
      more functionality.
    - Last but not least it implements for an enormous number of cases the W2K or XP
      behaviour including correct return values as earlier Windows versions have sometimes
      horrible behaviour when tickled with strange parameters, leaving sometimes strange
      or simply stupid changes on disk after returning with an error. Even W2K, XP has
      still some corner cases where it behaves improper, but they are prevented in this
      implementation. 

License: X11/LGPL

Rolf Kalbermatter

Index: shlfileop.c
===================================================================
RCS file: /home/wine/wine/dlls/shell32/shlfileop.c,v
retrieving revision 1.47
diff -u -r1.47 shlfileop.c
--- shlfileop.c	22 Oct 2004 22:27:51 -0000	1.47
+++ shlfileop.c	27 Oct 2004 17:39:19 -0000
@@ -20,7 +20,6 @@
  * License along with this library; if not, write to the Free Software
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
-
 #include "config.h"
 #include "wine/port.h"
 
@@ -50,12 +49,17 @@
 #define IsAttribDir(x)  IsAttrib(x, FILE_ATTRIBUTE_DIRECTORY)
 #define IsDotDir(x)     ((x[0] == '.') && ((x[1] == 0) || ((x[1] == '.') && (x[2] == 0))))
 
-#define FO_MASK         0xF
+#define FO_MASK         0xFF
+#define FO_LevelShift   8
+
+#define FOI_NeverOverwrite 2
 
 static const WCHAR wWildcardFile[] = {'*',0};
 static const WCHAR wWildcardChars[] = {'*','?',0};
 static const WCHAR wBackslash[] = {'\\',0};
 
+static LPCSTR cFO_String [] = {"FO_????","FO_MOVE","FO_COPY","FO_DELETE","FO_RENAME"};
+
 static BOOL SHELL_DeleteDirectoryW(LPCWSTR path, BOOL bShowUI);
 static DWORD SHNotifyCreateDirectoryA(LPCSTR path, LPSECURITY_ATTRIBUTES sec);
 static DWORD SHNotifyCreateDirectoryW(LPCWSTR path, LPSECURITY_ATTRIBUTES sec);
@@ -419,6 +423,10 @@
  *
  * RETURNS
  *  ERORR_SUCCESS if successful
+ *
+ * NOTES
+ *  Contains the common part of FO_MOVE/FO_RENAME.
+ *  It seems not fully solvable with recursive calls.
  */
 static DWORD SHNotifyMoveFileW(LPCWSTR src, LPCWSTR dest)
 {
@@ -455,13 +463,13 @@
  * Copies a file. Also triggers a change notify if one exists.
  *
  * PARAMS
- *  src           [I]   path to source file to move
- *  dest          [I]   path to target file to move to
+ *  src           [I]   path to source file to copy
+ *  dest          [I]   path to target file to copy to
  *  bFailIfExists [I]   if TRUE, the target file will not be overwritten if
  *                      a file with this name already exists
  *
  * RETURNS
- *  ERROR_SUCCESS if successful
+ *  ERORR_SUCCESS if successful
  */
 static DWORD SHNotifyCopyFileW(LPCWSTR src, LPCWSTR dest, BOOL bFailIfExists)
 {
@@ -609,6 +617,11 @@
 	return ret;
 }
 
+static BOOL SetIfPointer(LPWSTR pToSlash, WCHAR x)
+{
+	if (pToSlash) *pToSlash = x;
+	return (BOOL)pToSlash;
+}
 
 /*************************************************************************
  * SHFindAttrW      [internal]
@@ -650,49 +663,68 @@
 
 /*************************************************************************
  *
- * SHFileStrICmp HelperFunction for SHFileOperationW
+ * SHFileICmpW HelperFunction for SHFileOperationW
  *
  */
-BOOL SHFileStrICmpW(LPWSTR p1, LPWSTR p2, LPWSTR p1End, LPWSTR p2End)
-{
-	WCHAR C1 = '\0';
-	WCHAR C2 = '\0';
-	int i_Temp = -1;
-	int i_len1 = lstrlenW(p1);
-	int i_len2 = lstrlenW(p2);
-
-	if (p1End && (&p1[i_len1] >= p1End) && ('\\' == p1End[0]))
-	{
-	  C1 = p1End[0];
-	  p1End[0] = '\0';
-	  i_len1 = lstrlenW(p1);
-	}
-	if (p2End)
-	{
-	  if ((&p2[i_len2] >= p2End) && ('\\' == p2End[0]))
+#define SFSC_DIFFERENT_DRIVE            0x0
+#define SFSC_IDENTIICAL                 0x1
+#define SFSC_SAME_DIR                   0x2
+#define SFSC_SOURCE_DIR_CONTAINS_TARGET 0x4
+#define SFSC_SOURCE_CONTAINS_TARGET     0x8
+#define SFSC_TARGET_CONTAINS_SOURCE    0x10
+#define SFSC_DIFFERENT_ROOT            0x20
+
+static DWORD SHFileICmpW(LPSHFILEOPSTRUCTW lpFileOp)
+{
+	DWORD i = -1;
+	DWORD i_Slash = 0;
+	DWORD retVal = SFSC_DIFFERENT_DRIVE;
+	WCHAR wFrom, wTo;
+	BOOL b;
+	LPCWSTR pFrom = lpFileOp->pFrom;
+	LPCWSTR pTo   = lpFileOp->pTo;
+	do
+	{
+	  i++;
+	  wFrom = pFrom[i];
+	  wTo = pTo[i];
+	  b = (wFrom && wTo);
+	  if (('\\' == wFrom) && ('\\' == wTo)) i_Slash = i;
+	} while (b && ((wFrom == wTo) || (toupperW(wFrom) == toupperW(wTo))));
+
+	if (2 <= i_Slash)
+	{
+	  if (!wFrom && !wTo)
+	    retVal = SFSC_IDENTIICAL;
+	  else if (!wFrom && ('\\' == wTo))
 	  {
-	    C2 = p2End[0];
-	    if (C2)
-	      p2End[0] = '\0';
+	    retVal = (pTo[i+1]) ? SFSC_SOURCE_CONTAINS_TARGET : SFSC_IDENTIICAL;
 	  }
-	}
-	else
-	{
-	  if ((i_len1 <= i_len2) && ('\\' == p2[i_len1]))
+	  else if (!wTo   && ('\\' == wFrom))
+	  {
+	    retVal = (pFrom[i+1]) ? SFSC_TARGET_CONTAINS_SOURCE : SFSC_IDENTIICAL /* this shoul be never done */;
+	  }
+	  else
 	  {
-	    C2 = p2[i_len1];
-	    if (C2)
-	      p2[i_len1] = '\0';
+	    pFrom = pFrom[i_Slash] ? StrChrW(&pFrom[i_Slash+1],'\\') : NULL;
+	    pTo   = pTo[i_Slash]   ? StrChrW(&pTo[i_Slash+1],'\\')   : NULL;
+	    if (pTo && !pTo[1])
+	      pTo = NULL;
+	    if ((wFrom != wTo) && !pFrom)
+	    {
+	      retVal = (pTo) ? SFSC_SOURCE_DIR_CONTAINS_TARGET : SFSC_SAME_DIR;
+	    }
+	    else if (wTo && pFrom)
+	      retVal = SFSC_DIFFERENT_ROOT;
+	    else
+	    {
+	      if (pFrom && pTo) retVal = SFSC_DIFFERENT_ROOT;
+	      else retVal = SFSC_DIFFERENT_ROOT;
+	    }
 	  }
 	}
-	i_len2 = lstrlenW(p2);
-	if (i_len1 == i_len2)
-	  i_Temp = lstrcmpiW(p1,p2);
-	if (C1)
-	  p1[i_len1] = C1;
-	if (C2)
-	  p2[i_len2] = C2;
-	return !(i_Temp);
+	TRACE("SHFileICmpW %s->%s,%ld", debugstr_w(lpFileOp->pFrom), debugstr_w(lpFileOp->pTo), retVal);
+	return retVal;
 }
 
 /*************************************************************************
@@ -700,7 +732,7 @@
  * SHFileStrCpyCat HelperFunction for SHFileOperationW
  *
  */
-LPWSTR SHFileStrCpyCatW(LPWSTR pTo, LPCWSTR pFrom, LPCWSTR pCatStr)
+static LPWSTR SHFileStrCpyCatW(LPWSTR pTo, LPCWSTR pFrom, LPCWSTR pCatStr)
 {
 	LPWSTR pToFile = NULL;
 	int  i_len;
@@ -711,10 +743,10 @@
 	  if (pCatStr)
 	  {
 	    i_len = lstrlenW(pTo);
-	    if ((i_len) && (pTo[--i_len] != '\\'))
+	    if ((i_len) && ('\\' != pTo[--i_len]))
 	      i_len++;
 	    pTo[i_len] = '\\';
-	    if (pCatStr[0] == '\\')
+	    if ('\\' == pCatStr[0])
 	      pCatStr++; \
 	    lstrcpyW(&pTo[i_len+1], pCatStr);
 	  }
@@ -731,12 +763,32 @@
  * Accepts two \0 delimited lists of the file names. Checks whether number of
  * files in both lists is the same, and checks also if source-name exists.
  */
-BOOL SHELL_FileNamesMatch(LPCWSTR pszFiles1, LPCWSTR pszFiles2, BOOL bOnlySrc)
+static BOOL SHELL_FileNamesMatch(LPCWSTR pszFiles1, LPCWSTR pszFiles2, BOOL bOnlySrc)
 {
+	LPWSTR pszTemp;
+
+	TRACE("%s %s %d\n", debugstr_w(pszFiles1), debugstr_w(pszFiles2), bOnlySrc);
+
 	while ((pszFiles1[0] != '\0') &&
 	       (bOnlySrc || (pszFiles2[0] != '\0')))
 	{
-	  if (NULL == StrPBrkW(pszFiles1, wWildcardChars))
+	  pszTemp = StrChrW(pszFiles1,'\\');
+	  /* root (without mask/name) is also not allowed as source, tested in W98 */
+	  if (!pszTemp || !pszTemp[1])
+	    return FALSE;
+	  pszTemp = StrPBrkW(pszFiles1, wWildcardChars);
+	  if (pszTemp)
+	  {
+	    WCHAR szMask [MAX_PATH];
+	    pszTemp = StrRChrW(pszFiles1, pszTemp, '\\');
+	    if (!pszTemp)
+	      return FALSE;
+	    lstrcpynW(szMask, pszFiles1, (pszTemp - pszFiles1) + 1);
+	    /* we will check the root of the mask as valid dir */
+	    if (!IsAttribDir(GetFileAttributesW(&szMask[0])))
+	      return FALSE;
+	  }
+	  else
 	  {
 	    if (INVALID_FILE_ATTRIBUTES == GetFileAttributesW(pszFiles1))
 	      return FALSE;
@@ -749,130 +801,210 @@
 }
 
 /*************************************************************************
- *
- * SHNameTranslate HelperFunction for SHFileOperationA
- *
- * Translates a list of 0 terminated ASCII strings into Unicode. If *wString
- * is NULL, only the necessary size of the string is determined and returned,
- * otherwise the ASCII strings are copied into it and the buffer is increased
- * to point to the location after the final 0 termination char.
+ * SHFileOperationCheck
  */
-DWORD SHNameTranslate(LPWSTR* wString, LPCWSTR* pWToFrom, BOOL more)
+static int SHFileOperationCheck(LPSHFILEOPSTRUCTW lpFileOp, long *func, long *level)
 {
-	DWORD size = 0, aSize = 0;
-	LPCSTR aString = (LPCSTR)*pWToFrom;
+	FILEOP_FLAGS OFl;
+	int retCode = NO_ERROR;
 
-	if (aString)
-	{
-	  do
+	if (!lpFileOp || !lpFileOp->pFrom)
+	  return ERROR_INVALID_PARAMETER;
+
+	*func = lpFileOp->wFunc & FO_MASK;
+	*level = lpFileOp->wFunc >> FO_LevelShift;
+
+	if ((*func < FO_MOVE) || (*func > FO_RENAME))
+	{
+	  *func = 0;
+	  return ERROR_INVALID_PARAMETER; /* nt40 and earlier returns NO_ERROR */
+	}
+
+	OFl = (FILEOP_FLAGS)lpFileOp->fFlags;
+	TRACE(" %s level=%ld nFileOp.fFlags=0x%x\n", cFO_String[*func], *level, OFl);
+
+	if (!*level)
+	{
+	  lpFileOp->hNameMappings = 0;
+	  lpFileOp->fAnyOperationsAborted = FALSE;
+	  TRACE("%s: flags (0x%04x) : %s%s%s%s%s%s%s%s%s%s%s%s%s \n", cFO_String[*func], OFl,
+	              OFl & FOF_MULTIDESTFILES ? "FOF_MULTIDESTFILES " : "",
+	              OFl & FOF_CONFIRMMOUSE ? "FOF_CONFIRMMOUSE " : "",
+	              OFl & FOF_SILENT ? "FOF_SILENT " : "",
+	              OFl & FOF_RENAMEONCOLLISION ? "FOF_RENAMEONCOLLISION " : "",
+	              OFl & FOF_NOCONFIRMATION ? "FOF_NOCONFIRMATION " : "",
+	              OFl & FOF_WANTMAPPINGHANDLE ? "FOF_WANTMAPPINGHANDLE " : "",
+	              OFl & FOF_ALLOWUNDO ? "FOF_ALLOWUNDO " : "",
+	              OFl & FOF_FILESONLY ? "FOF_FILESONLY " : "",
+	              OFl & FOF_SIMPLEPROGRESS ? "FOF_SIMPLEPROGRESS " : "",
+	              OFl & FOF_NOCONFIRMMKDIR ? "FOF_NOCONFIRMMKDIR " : "",
+	              OFl & FOF_NOERRORUI ? "FOF_NOERRORUI " : "",
+	              OFl & FOF_NOCOPYSECURITYATTRIBS ? "FOF_NOCOPYSECURITYATTRIBS" : "",
+	              OFl & 0xf000 ? "MORE-UNKNOWN-Flags" : "");
+
+	/*
+	 * Summary of flags:
+	 *
+	 * implemented flags:
+	 * FOF_FILESONLY, FOF_WANTMAPPINGHANDLE, FOF_NOCONFIRMATION,
+	 *   FOF_RENAMEONCOLLISION, FOF_MULTIDESTFILES
+	 *
+	 * unimplememented and ignored flags:
+	 * FOF_CONFIRMMOUSE, FOF_SILENT, FOF_NOCONFIRMMKDIR,
+	 *   FOF_SIMPLEPROGRESS, FOF_NOCOPYSECURITYATTRIBS
+	 *
+	 * unimplemented and break if set:
+	 * FOF_ALLOWUNDO
+	 */
+	  OFl &= (~(FOF_FILESONLY | FOF_WANTMAPPINGHANDLE | FOF_NOCONFIRMATION |
+	            FOF_RENAMEONCOLLISION | FOF_MULTIDESTFILES));
+	  OFl ^= (FOF_SILENT | FOF_NOCONFIRMMKDIR | FOF_NOERRORUI | FOF_NOCOPYSECURITYATTRIBS);
+	  OFl &= (~FOF_SIMPLEPROGRESS); /* ignored, only with FOF_SILENT */
+	  if (OFl)
 	  {
-	    size = lstrlenA(aString) + 1;
-	    aSize += size;
-	    aString += size;
-	  } while ((size != 1) && more);
-	  /* The two sizes might be different in the case of multibyte chars */
-	  size = MultiByteToWideChar(CP_ACP, 0, aString, aSize, *wString, 0);
-	  if (*wString) /* only in the second loop */
-	  {
-	    MultiByteToWideChar(CP_ACP, 0, (LPCSTR)*pWToFrom, aSize, *wString, size);
-	    *pWToFrom = *wString;
-	    *wString += size;
-	  }
+	    if (OFl & (~(FOF_CONFIRMMOUSE | FOF_SILENT | FOF_NOCONFIRMMKDIR | 
+	                 FOF_NOERRORUI | FOF_NOCOPYSECURITYATTRIBS)))
+	    {
+	      TRACE("%s lpFileOp->fFlags=0x%x not implemented, Aborted=TRUE, stub\n", cFO_String[*func], OFl);
+	      retCode = ERROR_INVALID_PARAMETER;
+	    }
+	    else
+	    {
+	      TRACE("%s lpFileOp->fFlags=0x%x not fully implemented, stub\n", cFO_String[*func], OFl);
+	    } /* endif */
+	  } /* endif */
 	}
-	return size;
+	return retCode;
 }
+
 /*************************************************************************
- * SHFileOperationA          [SHELL32.@]
  *
- * Function to copy, move, delete and create one or more files with optional
- * user prompts.
+ * SHCreateMappingElement internal HelperFunction for SHRenameOnCollision
  *
  * PARAMS
- *  lpFileOp   [I/O] pointer to a structure containing all the necessary information
+ *  pName      [I]   old path which had a conflict
+ *  size       [O]   number of characters in newly created pointer
  *
- * NOTES
- *  exported by name
+ * RETURNS
+ *  a valid pointer allocated with SHAlloc() to a string copy of the name
+ *  if successful or NULL otherwise
+ *
+ * NOTE
+ *  Windows 9x and possibly ME always return ANSI name mappings, while
+ *  all NT based shells always return Unicode name mappings even in
+ *  SHFileOperationA.
  */
-int WINAPI SHFileOperationA(LPSHFILEOPSTRUCTA lpFileOp)
+static LPVOID SHCreateMappingElement(LPCWSTR pName, PINT size)
 {
-	SHFILEOPSTRUCTW nFileOp = *((LPSHFILEOPSTRUCTW)lpFileOp);
-	int retCode = 0;
-	DWORD size;
-	LPWSTR ForFree = NULL, /* we change wString in SHNameTranslate and can't use it for freeing */
-	       wString = NULL; /* we change this in SHNameTranslate */
+	LPVOID ptr;
+	int sizeInByte = 0;
 
-	TRACE("\n");
-	if (FO_DELETE == (nFileOp.wFunc & FO_MASK))
-	  nFileOp.pTo = NULL; /* we need a NULL or a valid pointer for translation */
-	if (!(nFileOp.fFlags & FOF_SIMPLEPROGRESS))
-	  nFileOp.lpszProgressTitle = NULL; /* we need a NULL or a valid pointer for translation */
-	while (1) /* every loop calculate size, second translate also, if we have storage for this */
-	{
-	  size = SHNameTranslate(&wString, &nFileOp.lpszProgressTitle, FALSE); /* no loop */
-	  size += SHNameTranslate(&wString, &nFileOp.pFrom, TRUE); /* internal loop */
-	  size += SHNameTranslate(&wString, &nFileOp.pTo, TRUE); /* internal loop */
+	if (SHELL_OsIsUnicode())
+	{
+	  *size = lstrlenW(pName);
+	  sizeInByte = (*size + 1) * sizeof(WCHAR);
+	  ptr = SHAlloc(sizeInByte);
+	  if (ptr)
+	    memcpy(ptr, pName, sizeInByte);
+	}
+	else
+	{
+	  sizeInByte = WideCharToMultiByte(CP_ACP, 0, pName, sizeInByte, NULL, -1, NULL, NULL);
+	  *size = sizeInByte - 1;
+	  ptr = SHAlloc(sizeInByte);
+	  if (ptr)
+	    WideCharToMultiByte(CP_ACP, 0, pName, sizeInByte, ptr, sizeInByte, NULL, NULL);
+	}
+	return ptr;
+}
 
-	  if (ForFree)
+#define ERROR_SHELL_INTERNAL_FILE_NOT_FOUND 1026
+#define HIGH_ADR (LPWSTR)0xffffffff
+
+#define FlagMultiDestFiles(pFileOp)    ((pFileOp)->fFlags & FOF_MULTIDESTFILES)
+#define FlagRenameOnCollision(pFileOp) ((pFileOp)->fFlags & FOF_RENAMEONCOLLISION)
+#define FlagNoConfirmation(pFileOp)    ((pFileOp)->fFlags & FOF_NOCONFIRMATION)
+#define FlagAskOverwrite(pFileOp)      (!((pFileOp)->fFlags & (FOF_NOCONFIRMATION | FOF_RENAMEONCOLLISION)))
+#define FlagNotOverwrite(pFileOp)      (!FlagNoConfirmation(pFileOp) || FlagRenameOnCollision(pFileOp))
+
+static WCHAR wStrFormat[] = {' ','(','%','d',')',0};
+
+/*************************************************************************
+ *
+ * SHRenameOnCollision internal HelperFunction for SHFileOperationW
+ *
+ * Checks for existence of a path name and if it exists creates an alternative
+ * name.
+ *
+ * NOTE:
+ *  W98 has problems with some renames on collision, if the original target-name,
+ *  or the root of the target-name is equal to any former orginal target-name,
+ *  and if the root-dir of the target differs from the partiel root-dir of the source.
+ *  If we have different target-names or all target have the same root of their source,
+ *  there is no problem. Root of target can be shorter as the full root of source.
+ *  I think, that is a mapping-problem.
+ *  Move within the same or shorter root can be made from FO_RENAME, that works always
+ *  in W98, only move/copy to other roots has this problem for at least W98. W2K has this
+ *  problem properly solved.
+ */
+static DWORD SHRenameOnCollision(LPSHFILEOPSTRUCTW lpFileOp, LPWSTR pToFile, LPCWSTR pFromFile)
+{
+	/* todo ERROR_FILENAME_EXCED_RANGE ?? */
+	static WCHAR wCopy_x_of[40] = {0};
+	LPWSTR pszTemp = wStrFormat + 5;
+	DWORD ToAttr;
+	SHNAMEMAPPINGW shm;
+	WCHAR szNumber[16];
+	int number = 0;
+
+	while (ToAttr = SHFindAttrW(lpFileOp->pTo, FALSE),
+	       (INVALID_FILE_ATTRIBUTES != ToAttr) && FlagRenameOnCollision(lpFileOp))
+	{
+	  if (!number)
 	  {
-	    retCode = SHFileOperationW(&nFileOp);
-	    HeapFree(GetProcessHeap(), 0, ForFree); /* we can not use wString, it was changed */
-	    break;
+	    lstrcpyW(pToFile, pFromFile);
+	    shm.pszOldPath = SHCreateMappingElement(lpFileOp->pTo, &shm.cchOldPath);
+	    if (!*wCopy_x_of && !LoadStringW(shell32_hInstance, IDS_COPY_X_OF_TEXT, wCopy_x_of, sizeof(wCopy_x_of)-1))
+	      break; /* should never be */
 	  }
-	  else
+	  number++;
+	  if (1 < number)
 	  {
-	    wString = ForFree = HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR));
-	    if (ForFree) continue;
-	    retCode = ERROR_OUTOFMEMORY;
-	    nFileOp.fAnyOperationsAborted = TRUE;
-	    SetLastError(retCode);
-	    return retCode;
+	    pszTemp = szNumber;
+	    wsprintfW(szNumber, wStrFormat, number);
 	  }
+	  wsprintfW(pToFile, wCopy_x_of, pszTemp, pFromFile);
+	  pToFile[lstrlenW(pToFile) + 1] = 0;
+	} /* endwhile */
+
+	if (number)
+	{
+	  shm.pszNewPath = SHCreateMappingElement(lpFileOp->pTo, &shm.cchNewPath);
+	  if (!lpFileOp->hNameMappings)
+	    lpFileOp->hNameMappings = DSA_Create(sizeof(SHNAMEMAPPINGW), 1);
+	  DSA_InsertItem ((HDSA)lpFileOp->hNameMappings, DSA_APPEND, &shm);
 	}
-
-	lpFileOp->hNameMappings = nFileOp.hNameMappings;
-	lpFileOp->fAnyOperationsAborted = nFileOp.fAnyOperationsAborted;
-	return retCode;
-}
-
-static const char * debug_shfileops_flags( DWORD fFlags )
-{
-    return wine_dbg_sprintf( "%s%s%s%s%s%s%s%s%s%s%s%s%s",
-	fFlags & FOF_MULTIDESTFILES ? "FOF_MULTIDESTFILES " : "",
-	fFlags & FOF_CONFIRMMOUSE ? "FOF_CONFIRMMOUSE " : "",
-	fFlags & FOF_SILENT ? "FOF_SILENT " : "",
-	fFlags & FOF_RENAMEONCOLLISION ? "FOF_RENAMEONCOLLISION " : "",
-	fFlags & FOF_NOCONFIRMATION ? "FOF_NOCONFIRMATION " : "",
-	fFlags & FOF_WANTMAPPINGHANDLE ? "FOF_WANTMAPPINGHANDLE " : "",
-	fFlags & FOF_ALLOWUNDO ? "FOF_ALLOWUNDO " : "",
-	fFlags & FOF_FILESONLY ? "FOF_FILESONLY " : "",
-	fFlags & FOF_SIMPLEPROGRESS ? "FOF_SIMPLEPROGRESS " : "",
-	fFlags & FOF_NOCONFIRMMKDIR ? "FOF_NOCONFIRMMKDIR " : "",
-	fFlags & FOF_NOERRORUI ? "FOF_NOERRORUI " : "",
-	fFlags & FOF_NOCOPYSECURITYATTRIBS ? "FOF_NOCOPYSECURITYATTRIBS" : "",
-	fFlags & 0xf000 ? "MORE-UNKNOWN-Flags" : "");
+	return ToAttr;
 }
 
-static const char * debug_shfileops_action( DWORD op )
-{
-    LPCSTR cFO_Name [] = {"FO_????","FO_MOVE","FO_COPY","FO_DELETE","FO_RENAME"};
-    return wine_dbg_sprintf("%s", cFO_Name[ op ]);
-}
-
-#define ERROR_SHELL_INTERNAL_FILE_NOT_FOUND 1026
-#define HIGH_ADR (LPWSTR)0xffffffff
-
 /*************************************************************************
  * SHFileOperationW          [SHELL32.@]
  *
- * See SHFileOperationA
+ * Function to copy, move, delete and create one or more files with optional
+ * user prompts.
+ *
+ * PARAMS
+ *  lpFileOp   [I/O] pointer to a structure containing all the necessary information
+ *
+ * NOTES
+ *  exported by name
  */
 int WINAPI SHFileOperationW(LPSHFILEOPSTRUCTW lpFileOp)
 {
-	SHFILEOPSTRUCTW nFileOp = *(lpFileOp);
-
-	LPCWSTR pNextFrom = nFileOp.pFrom;
-	LPCWSTR pNextTo = nFileOp.pTo;
-	LPCWSTR pFrom = pNextFrom;
+	SHFILEOPSTRUCTW nFileOp;
+	LPCWSTR pNextFrom;
+	LPCWSTR pNextTo;
+	LPCWSTR pFrom;
 	LPCWSTR pTo = NULL;
 	HANDLE hFind = INVALID_HANDLE_VALUE;
 	WIN32_FIND_DATAW wfd;
@@ -880,475 +1012,566 @@
 	LPWSTR pTempTo = NULL;
 	LPWSTR pFromFile;
 	LPWSTR pToFile = NULL;
-	LPWSTR lpFileName;
-	int retCode = 0;
+	LPWSTR pToTailSlash; /* points also to ToMask, we know not what is this */
+	LPWSTR lpFileName; /* temporary for pFromFile, pToFile, pToTailSlash */
 	DWORD ToAttr;
 	DWORD ToPathAttr;
-	DWORD FromPathAttr;
-	FILEOP_FLAGS OFl = ((FILEOP_FLAGS)lpFileOp->fFlags & 0xfff);
-
-	BOOL b_Multi = (nFileOp.fFlags & FOF_MULTIDESTFILES);
+	DWORD FromAttr;
+	DWORD f_SameDrive = FALSE;
 
-	BOOL b_MultiTo = (FO_DELETE != (lpFileOp->wFunc & FO_MASK));
-	BOOL b_MultiPaired = (!b_MultiTo);
+	BOOL b_Multi;
+	BOOL b_MultiTo;
+	BOOL b_MultiPaired;
 	BOOL b_MultiFrom = FALSE;
-	BOOL not_overwrite;
-	BOOL ask_overwrite;
-	BOOL b_SameRoot;
-	BOOL b_SameTailName;
-	BOOL b_ToInvalidTail = FALSE;
-	BOOL b_ToValid; /* for W98-Bug for FO_MOVE with source and target in same rootdrive */
-	BOOL b_Mask;
-	BOOL b_ToTailSlash = FALSE;
-
-	long FuncSwitch = (nFileOp.wFunc & FO_MASK);
-	long level= nFileOp.wFunc>>4;
-
-	/*  default no error */
-	nFileOp.fAnyOperationsAborted = FALSE;
-
-	if ((FuncSwitch < FO_MOVE) || (FuncSwitch > FO_RENAME))
-	    goto shfileop_end; /* no valid FunctionCode */
-
-	if (level == 0)
-            TRACE("%s: flags (0x%04x) : %s\n",
-                debug_shfileops_action(FuncSwitch), nFileOp.fFlags,
-                debug_shfileops_flags(nFileOp.fFlags) );
-
-        /* establish when pTo is interpreted as the name of the destination file
-         * or the directory where the Fromfile should be copied to.
-         * This depends on:
-         * (1) pTo points to the name of an existing directory;
-         * (2) the flag FOF_MULTIDESTFILES is present;
-         * (3) whether pFrom point to multiple filenames.
-         *
-         * Some experiments:
-         *
-         * destisdir               1 1 1 1 0 0 0 0
-         * FOF_MULTIDESTFILES      1 1 0 0 1 1 0 0
-         * multiple from filenames 1 0 1 0 1 0 1 0
-         *                         ---------------
-         * copy files to dir       1 0 1 1 0 0 1 0
-         * create dir              0 0 0 0 0 0 1 0
-         */
-
-        /*
-         * Summary of flags:
-         *
-         * implemented flags:
-         * FOF_MULTIDESTFILES, FOF_NOCONFIRMATION, FOF_FILESONLY
-         *
-         * unimplememented and ignored flags:
-         * FOF_CONFIRMMOUSE, FOF_SILENT, FOF_NOCONFIRMMKDIR,
-         *       FOF_SIMPLEPROGRESS, FOF_NOCOPYSECURITYATTRIBS
-         *
-         * partially implemented, breaks if file exists:
-         * FOF_RENAMEONCOLLISION
-         *
-         * unimplemented and break if any other flag set:
-         * FOF_ALLOWUNDO, FOF_WANTMAPPINGHANDLE
-         */
-
-        TRACE("%s level=%ld nFileOp.fFlags=0x%x\n", 
-                debug_shfileops_action(FuncSwitch), level, lpFileOp->fFlags);
-
-        /*    OFl &= (-1 - (FOF_MULTIDESTFILES | FOF_FILESONLY)); */
-        /*    OFl ^= (FOF_SILENT | FOF_NOCONFIRMATION | FOF_SIMPLEPROGRESS | FOF_NOCONFIRMMKDIR); */
-        OFl &= (~(FOF_MULTIDESTFILES | FOF_NOCONFIRMATION | FOF_FILESONLY));  /* implemented */
-        OFl ^= (FOF_SILENT | FOF_NOCONFIRMMKDIR | FOF_NOERRORUI | FOF_NOCOPYSECURITYATTRIBS); /* ignored, if one */
-        OFl &= (~FOF_SIMPLEPROGRESS);                      /* ignored, only with FOF_SILENT */
-        if (OFl)
-        {
-	    if (OFl & (~(FOF_CONFIRMMOUSE | FOF_SILENT | FOF_RENAMEONCOLLISION |
-	                 FOF_NOCONFIRMMKDIR | FOF_NOERRORUI | FOF_NOCOPYSECURITYATTRIBS)))
-	    {
-                TRACE("%s level=%ld lpFileOp->fFlags=0x%x not implemented, Aborted=TRUE, stub\n",
-                      debug_shfileops_action(FuncSwitch), level, OFl);
-                retCode = 0x403; /* 1027, we need an extension to shlfileop */
-                goto shfileop_end;
-	    }
-	    else
-	    {
-                TRACE("%s level=%ld lpFileOp->fFlags=0x%x not fully implemented, stub\n", 
-                      debug_shfileops_action(FuncSwitch), level, OFl);
-	    } 
-        } 
-
-        if ((pNextFrom) && (!(b_MultiTo) || (pNextTo)))
-        {
-	    nFileOp.pFrom = pTempFrom = HeapAlloc(GetProcessHeap(), 0, ((1 + 2 * (b_MultiTo)) * MAX_PATH + 6) * sizeof(WCHAR));
-	    if (!pTempFrom)
-	    {
-                retCode = ERROR_OUTOFMEMORY;
-                SetLastError(retCode);
-                goto shfileop_end;
-	    }
-	    if (b_MultiTo)
-                pTempTo = &pTempFrom[MAX_PATH + 4];
-	    nFileOp.pTo = pTempTo;
-	    ask_overwrite = (!(nFileOp.fFlags & FOF_NOCONFIRMATION) && !(nFileOp.fFlags & FOF_RENAMEONCOLLISION));
-	    not_overwrite = (!(nFileOp.fFlags & FOF_NOCONFIRMATION) ||  (nFileOp.fFlags & FOF_RENAMEONCOLLISION));
-        }
-        else
-        {
-	    retCode = ERROR_SHELL_INTERNAL_FILE_NOT_FOUND;
-	    goto shfileop_end;
-        }
-        /* need break at error before change sourcepointer */
-        while(!nFileOp.fAnyOperationsAborted && (pNextFrom[0]))
-        {
-	    nFileOp.wFunc =  ((level + 1) << 4) + FuncSwitch;
-	    nFileOp.fFlags = lpFileOp->fFlags;
-
-	    if (b_MultiTo)
-	    {
-                pTo = pNextTo;
-                pNextTo = &pNextTo[lstrlenW(pTo)+1];
-                b_MultiTo = (b_Multi && pNextTo[0]);
-	    }
+	BOOL b_DELETE;
+	BOOL b_SameRoot_MOVE = FALSE;
+	BOOL b_ToMask = FALSE;
+	BOOL b_FromMask;
+	BOOL b_FileMask;
+
+	long FuncSwitch = 0, level = 0;
+	int retCode = SHFileOperationCheck(lpFileOp, &FuncSwitch, &level);
+
+	if (retCode)
+	  return retCode;
+
+	nFileOp = *lpFileOp;
+	pNextFrom = lpFileOp->pFrom;
+	pNextTo = lpFileOp->pTo;
+	pFrom = pNextFrom;
+	b_Multi = FlagMultiDestFiles(lpFileOp);
+	b_MultiTo = (FO_DELETE != FuncSwitch);
+	b_MultiPaired = (0 < level) /* FALSE only at Level 0 */;
+	b_DELETE = (FO_DELETE == FuncSwitch);
+
+	if (!(b_MultiTo) || (pNextTo))
+	{
+	  nFileOp.pFrom = pTempFrom = SHAlloc(((1 + 2 * (b_MultiTo)) * MAX_PATH + 6) * sizeof(WCHAR));
+	  if (!pTempFrom)
+	  {
+	    retCode = ERROR_OUTOFMEMORY;
+	    SetLastError(retCode);
+	  }
+	  if (b_MultiTo)
+	  {
+	    pTempTo = &pTempFrom[MAX_PATH + 4];
+	  }
+	  nFileOp.pTo = pTempTo;
+	  if (!level)
+	  {
+	    if (FlagNoConfirmation(&nFileOp) && FlagRenameOnCollision(&nFileOp))
+	      nFileOp.fAnyOperationsAborted |= FOI_NeverOverwrite;
+	  }
+	}
+	else
+	{
+	  retCode = ERROR_SHELL_INTERNAL_FILE_NOT_FOUND;
+	}
+
+	/* need break at error before change sourcepointer */
+	while (!retCode && (pNextFrom[0]))
+	{
+	  nFileOp.wFunc =  ((level + 1) << FO_LevelShift) + FuncSwitch;
+	  nFileOp.fFlags = lpFileOp->fFlags;
 
-	    pFrom = pNextFrom;
-	    pNextFrom = &pNextFrom[lstrlenW(pNextFrom)+1];
-	    if (!b_MultiFrom && !b_MultiTo)
-                b_MultiFrom = (pNextFrom[0]);
+	  if (b_MultiTo)
+	  {
+	    pTo = pNextTo;
+	    pNextTo = &pNextTo[lstrlenW(pTo)+1];
+	    b_MultiTo = (b_Multi && (0 != pNextTo[0]));
+	  }
+
+	  pFrom = pNextFrom;
+	  pNextFrom = &pNextFrom[lstrlenW(pNextFrom) + 1];
+	  if (!b_MultiFrom && !b_MultiTo)
+	  {
+	    b_MultiFrom = (0 != pNextFrom[0]);
+	  }
 
-	    pFromFile = SHFileStrCpyCatW(pTempFrom, pFrom, NULL);
+	  pFromFile = SHFileStrCpyCatW(pTempFrom, pFrom, NULL);
+	  b_FromMask = (pFromFile && (NULL != StrPBrkW(&pFromFile[1], wWildcardChars)));
 
-	    if (pTo)
+	  if (pTo)
+	  {
+	    pToFile = SHFileStrCpyCatW(pTempTo, pTo, NULL);
+	    b_ToMask = (NULL != StrPBrkW(pTempTo, wWildcardChars));
+	  }
+
+	  if (FO_RENAME == FuncSwitch)
+	  {
+	    if (b_MultiTo || b_MultiFrom || (b_FromMask && !b_ToMask))
 	    {
-                pToFile = SHFileStrCpyCatW(pTempTo, pTo, NULL);
+	      retCode = ERROR_GEN_FAILURE;  /* W2K ERROR_GEN_FAILURE, w95,W98,NT40 returns no error, wMe? */
+	      break;
 	    }
-	    if (!b_MultiPaired)
+	  }
+
+	  if (!b_MultiPaired)
+	  {
+	    b_MultiPaired = SHELL_FileNamesMatch(lpFileOp->pFrom, lpFileOp->pTo,
+	                                         (b_DELETE || !b_Multi || b_MultiFrom));
+	  } /* endif */
+	  if (!(b_MultiPaired) || !(pFromFile) || !(pFromFile[1]) || ((pTo) && !(pToFile)))
+	  {
+	    retCode = ERROR_SHELL_INTERNAL_FILE_NOT_FOUND; /* nt40 and earlier returns NO_ERROR */
+	    break;
+	  }
+
+	  b_FileMask = b_FromMask && (FOF_FILESONLY & nFileOp.fFlags);
+	  if (!b_DELETE)
+	  {
+	    f_SameDrive = SHFileICmpW(&nFileOp);
+	    b_SameRoot_MOVE = ((f_SameDrive) && (FO_MOVE == FuncSwitch));
+	    TRACE("SHFileICmpW b_SameRoot_MOVE=%d f_SameDrive=%ld", b_SameRoot_MOVE, f_SameDrive);
+	  }
+	  FromAttr = SHFindAttrW(pTempFrom, b_FileMask);
+	  if (INVALID_FILE_ATTRIBUTES == FromAttr) 
+	  {
+	    retCode = GetLastError();
+	    if (ERROR_FILE_NOT_FOUND == retCode || ERROR_NO_MORE_FILES == retCode) /* only tested in wXp,w2k,w98 */
 	    {
-                b_MultiPaired =
-                    SHELL_FileNamesMatch(lpFileOp->pFrom, lpFileOp->pTo, (!b_Multi || b_MultiFrom));
+	      if (b_FromMask)
+	      {
+	        retCode = NO_ERROR;
+	      }
+	      else
+	      {
+	        if (!b_SameRoot_MOVE && !b_DELETE)
+	        {
+	          retCode = ERROR_CANCELLED;
+	          nFileOp.fAnyOperationsAborted |= TRUE;
+	        }
+	      }
 	    }
-	    if (!(b_MultiPaired) || !(pFromFile) || !(pFromFile[1]) || ((pTo) && !(pToFile)))
+	    continue;
+	  } /* endif */
+
+	  if (b_DELETE)
+	  {
+	    hFind = FindFirstFileW(pFrom, &wfd);
+	    if (INVALID_HANDLE_VALUE == hFind)
 	    {
-                retCode = ERROR_SHELL_INTERNAL_FILE_NOT_FOUND;
-                goto shfileop_end;
+	      retCode = ERROR_CANCELLED;
+	      if (!b_SameRoot_MOVE)
+	      {
+	        nFileOp.fAnyOperationsAborted |= TRUE;
+	      }
+	      break;
 	    }
-	    if (pTo)
+	    do
 	    {
-                b_ToTailSlash = (!pToFile[1]);
-                if (b_ToTailSlash)
-                {
-                    pToFile[0] = '\0';
-                    if (StrChrW(pTempTo,'\\'))
-                    {
-                        pToFile = SHFileStrCpyCatW(pTempTo, NULL, NULL);
-                    }
-                }
-                b_ToInvalidTail = (NULL != StrPBrkW(&pToFile[1], wWildcardChars));
-	    }
-
-	    /* for all */
-	    b_Mask = (NULL != StrPBrkW(&pFromFile[1], wWildcardChars));
-	    if (FO_RENAME == FuncSwitch)
-	    {
-                /* temporary only for FO_RENAME */
-                if (b_MultiTo || b_MultiFrom || (b_Mask && !b_ToInvalidTail))
-                {
-#ifndef W98_FO_FUNCTION
-                    retCode = ERROR_GEN_FAILURE;  /* W2K ERROR_GEN_FAILURE, W98 returns no error */
-#endif
-                    goto shfileop_end;
-                }
-	    }
+	      if (wfd.cAlternateFileName[0])
+	        lpFileName = wfd.cAlternateFileName;
+	      else
+	        lpFileName = wfd.cFileName;
+	      if (IsDotDir(lpFileName) || (b_FileMask && IsAttribDir(wfd.dwFileAttributes)))
+	        continue;
+	      SHFileStrCpyCatW(&pFromFile[1], lpFileName, NULL);
+	      /* TODO: Check the SHELL_DeleteFileOrDirectoryW() function in shell32.dll */
+	      if (IsAttribFile(wfd.dwFileAttributes))
+	      {
+	        if (SHNotifyDeleteFileW(pTempFrom))
+	          retCode = 0x78; /* value unknown */
+	      }
+	      else
+	      {
+	        if (!SHELL_DeleteDirectoryW(pTempFrom, (!FlagNoConfirmation(&nFileOp))))
+	          retCode = 0x79; /* value unknown */
+	      }
+	    } while (!retCode && FindNextFileW(hFind, &wfd));
+	    FindClose(hFind);
+	    continue;
+	  } /* FO_DELETE ends, pTo must be always valid from here */
 
-	    hFind = FindFirstFileW(pFrom, &wfd);
-	    if (INVALID_HANDLE_VALUE == hFind)
+	  pToTailSlash = NULL;
+	  if ((pToFile) && !(pToFile[1]))
+	  {
+	    pToFile[0] = '\0';
+	    lpFileName = StrRChrW(pTempTo, NULL, '\\');
+	    if (lpFileName)
 	    {
-                if ((FO_DELETE == FuncSwitch) && (b_Mask))
-                {
-                    pFromFile[0] = '\0';
-                    FromPathAttr = GetFileAttributesW(pTempFrom);
-                    pFromFile[0] = '\\';
-                    if (IsAttribDir(FromPathAttr))
-                    {
-                        /* FO_DELETE with mask and without found is valid */
-                        goto shfileop_end;
-                    }
-                }
-                /* root (without mask) is also not allowed as source, tested in W98 */
-                retCode = ERROR_SHELL_INTERNAL_FILE_NOT_FOUND;
-                goto shfileop_end;
+	      pToTailSlash = pToFile; 
+	      pToFile = lpFileName;
 	    }
+	  }
+	  ToPathAttr = ToAttr = GetFileAttributesW(pTempTo);
+	  if (!IsAttribDir(ToAttr) && SetIfPointer(pToFile, '\0'))
+	  {
+	    ToPathAttr = GetFileAttributesW(pTempTo);
+	    *pToFile = '\\';
+	  }
+	  SetIfPointer(pToTailSlash,'\\');
 
-            /* for all */
+	  /* FO_RENAME is not only a Filter for FO_MOVE */
+	  if (FO_RENAME == FuncSwitch)
+	  {
+	    if (!(b_FromMask) && (((SFSC_IDENTIICAL | SFSC_SOURCE_CONTAINS_TARGET) & f_SameDrive)))
+	    {
+	      if (!FlagRenameOnCollision(&nFileOp))
+	      {
+	        /* target is the same as source ? W98 has 0x71, W2K also */
+	        retCode = 0x71;
+	      }
+	      break;
+	    } /* endif */
 
-            /* ??? b_Mask = (!SHFileStrICmpA(&pFromFile[1], &wfd.cFileName[0], HIGH_ADR, HIGH_ADR)); */
-	    if (!pTo) /* FO_DELETE */
+	    if ((b_FromMask && !b_ToMask) ||
+	       ((SFSC_DIFFERENT_DRIVE | SFSC_DIFFERENT_ROOT | SFSC_TARGET_CONTAINS_SOURCE) & f_SameDrive))
 	    {
-                do
-                {
-                    lpFileName = wfd.cAlternateFileName;
-                    if (!lpFileName[0])
-                        lpFileName = wfd.cFileName;
-                    if (IsDotDir(lpFileName) ||
-                        ((b_Mask) && IsAttribDir(wfd.dwFileAttributes) && (nFileOp.fFlags & FOF_FILESONLY)))
-                        continue;
-                    SHFileStrCpyCatW(&pFromFile[1], lpFileName, NULL);
-                    /* TODO: Check the SHELL_DeleteFileOrDirectoryW() function in shell32.dll */
-                    if (IsAttribFile(wfd.dwFileAttributes))
-                    {
-                        if(SHNotifyDeleteFileW(pTempFrom) != ERROR_SUCCESS)
-                        {
-                            nFileOp.fAnyOperationsAborted = TRUE;
-                            retCode = 0x78; /* value unknown */
-                        }
-                    }
-                    else
-                    {
-                        if(!SHELL_DeleteDirectoryW(pTempFrom, (!(nFileOp.fFlags & FOF_NOCONFIRMATION))))
-                        {
-                            nFileOp.fAnyOperationsAborted = TRUE;
-                            retCode = 0x79; /* value unknown */
-                        }
-                    }
-                } while (!nFileOp.fAnyOperationsAborted && FindNextFileW(hFind, &wfd));
-                FindClose(hFind);
-                hFind = INVALID_HANDLE_VALUE;
-                if (nFileOp.fAnyOperationsAborted)
-                    goto shfileop_end;
-                continue;
-	    } /* FO_DELETE ends, pTo must be always valid from here */
-
-	    b_SameRoot = (toupperW(pTempFrom[0]) == toupperW(pTempTo[0]));
-	    b_SameTailName = SHFileStrICmpW(pToFile, pFromFile, NULL, NULL);
-
-	    ToPathAttr = ToAttr = GetFileAttributesW(pTempTo);
-	    if (!b_Mask && (ToAttr == INVALID_FILE_ATTRIBUTES) && (pToFile))
-	    {
-                pToFile[0] = '\0';
-                ToPathAttr = GetFileAttributesW(pTempTo);
-                pToFile[0] = '\\';
+	      retCode = 0x73; /* must be here or before, not later */
+	      break;
 	    }
 
-	    if (FO_RENAME == FuncSwitch)
+	    if (!(b_ToMask) && !(pToTailSlash) && IsAttribDir(ToAttr))
 	    {
-                if (!b_SameRoot || b_Mask /* FO_RENAME works not with Mask */
-                    || !SHFileStrICmpW(pTempFrom, pTempTo, pFromFile, NULL)
-                    || (SHFileStrICmpW(pTempFrom, pTempTo, pFromFile, HIGH_ADR) && !b_ToTailSlash))
-                {
-                    retCode = 0x73;
-                    goto shfileop_end;
-                }
-                if (b_ToInvalidTail)
-                {
-                    retCode=0x2;
-                    goto shfileop_end;
-                }
-                if (INVALID_FILE_ATTRIBUTES == ToPathAttr)
-                {
-                    retCode = 0x75;
-                    goto shfileop_end;
-                }
-                if (IsAttribDir(wfd.dwFileAttributes) && IsAttribDir(ToAttr))
-                {
-                    retCode = (b_ToTailSlash) ? 0xb7 : 0x7b;
-                    goto shfileop_end;
-                }
-                /* we use SHNotifyMoveFile() instead MoveFileW */
-                if (SHNotifyMoveFileW(pTempFrom, pTempTo) != ERROR_SUCCESS)
-                {
-                    /* we need still the value for the returncode, we use the mostly assumed */
-                    retCode = 0xb7;
-                    goto shfileop_end;
-                }
-                goto shfileop_end;
-	    }
+	      if (IsAttribDir(FromAttr))
+	        ToAttr = SHRenameOnCollision(&nFileOp, pToFile + 1, pFromFile + 1);
+	    } /* endif */
 
-	    /* W98 Bug with FO_MOVE different from FO_COPY, better the same as FO_COPY */
-	    b_ToValid = ((b_SameTailName &&  b_SameRoot && (FO_COPY == FuncSwitch)) ||
-                         (b_SameTailName && !b_SameRoot) || (b_ToInvalidTail));
-
-	    /* handle mask in source */
-	    if (b_Mask)
-	    {
-                if (!IsAttribDir(ToAttr))
-                {
-                    retCode = (b_ToInvalidTail &&/* b_SameTailName &&*/ (FO_MOVE == FuncSwitch)) \
-                        ? 0x2 : 0x75;
-                    goto shfileop_end;
-                }
-                pToFile = SHFileStrCpyCatW(pTempTo, NULL, wBackslash);
-                nFileOp.fFlags = (nFileOp.fFlags | FOF_MULTIDESTFILES);
-                do
-                {
-                    lpFileName = wfd.cAlternateFileName;
-                    if (!lpFileName[0])
-                        lpFileName = wfd.cFileName;
-                    if (IsDotDir(lpFileName) ||
-                        (IsAttribDir(wfd.dwFileAttributes) && (nFileOp.fFlags & FOF_FILESONLY)))
-                        continue; /* next name in pTempFrom(dir) */
-                    SHFileStrCpyCatW(&pToFile[1], lpFileName, NULL);
-                    SHFileStrCpyCatW(&pFromFile[1], lpFileName, NULL);
-                    retCode = SHFileOperationW (&nFileOp);
-                } while(!nFileOp.fAnyOperationsAborted && FindNextFileW(hFind, &wfd));
-	    }
-	    FindClose(hFind);
-	    hFind = INVALID_HANDLE_VALUE;
-	    /* FO_COPY/FO_MOVE with mask, FO_DELETE and FO_RENAME are solved */
-	    if (b_Mask)
-                continue;
-
-	    /* only FO_COPY/FO_MOVE without mask, all others are (must be) solved */
-	    if (IsAttribDir(wfd.dwFileAttributes) && (ToAttr == INVALID_FILE_ATTRIBUTES))
-	    {
-                if (pToFile)
-                {
-                    pToFile[0] = '\0';
-                    ToPathAttr = GetFileAttributesW(pTempTo);
-                    if ((ToPathAttr == INVALID_FILE_ATTRIBUTES) && b_ToValid)
-                    {
-                        /* create dir must be here, sample target D:\y\ *.* create with RC=10003 */
-                        if (SHNotifyCreateDirectoryW(pTempTo, NULL))
-                        {
-                            retCode = 0x73;/* value unknown */
-                            goto shfileop_end;
-                        }
-                        ToPathAttr = GetFileAttributesW(pTempTo);
-                    }
-                    pToFile[0] = '\\';
-                    if (b_ToInvalidTail)
-                    {
-                        retCode = 0x10003;
-                        goto shfileop_end;
-                    }
-                }
+	    if (INVALID_FILE_ATTRIBUTES != ToPathAttr)
+	    {
+	      /* w2k only (INVALID_FILE_ATTRIBUTES != ToPathAttr) */
+	      retCode = SHNotifyMoveFileW(nFileOp.pFrom, nFileOp.pTo);
 	    }
-
-	    /* trailing BackSlash is ever removed and pToFile points to BackSlash before */
-	    if (!b_MultiTo && (b_MultiFrom || (!(b_Multi) && IsAttribDir(ToAttr))))
+	    else
 	    {
-                if ((FO_MOVE == FuncSwitch) && IsAttribDir(ToAttr) && IsAttribDir(wfd.dwFileAttributes))
-                {
-                    if (b_Multi)
-                    {
-                        retCode = 0x73; /* !b_Multi = 0x8 ?? */
-                        goto shfileop_end;
-                    }
-                }
-                pToFile = SHFileStrCpyCatW(pTempTo, NULL, wfd.cFileName);
-                ToAttr = GetFileAttributesW(pTempTo);
+	      /* W98 returns 0x75 */
+	      retCode = ERROR_CANCELLED;
 	    }
+	    break;
+	  } /* FO_RENAME ends here, only FO_MOVE or FO_COPY below this line */
 
-	    if (IsAttribDir(ToAttr))
+	  if (b_FromMask)
+	  {
+	    /* FO_COPY/FO_MOVE with mask, FO_DELETE are solved long before */
+	    hFind = FindFirstFileW(pFrom, &wfd);
+	    if (INVALID_HANDLE_VALUE != hFind)
 	    {
-                if (IsAttribFile(wfd.dwFileAttributes))
-                {
-                    retCode = (FO_COPY == FuncSwitch) ? 0x75 : 0xb7;
-                    goto shfileop_end;
-                }
+	      SetIfPointer(pToTailSlash, '\0');
+	      ToAttr = SHFindAttrW(pTempTo, FALSE);
+	      if (b_ToMask && !b_Multi)
+	      {
+	        nFileOp.fFlags &= ~FOF_RENAMEONCOLLISION;
+	      }
+	      nFileOp.fFlags &= ~FOF_MULTIDESTFILES;
+	      pToFile = SHFileStrCpyCatW(pTempTo, NULL, wBackslash);
+	      do
+	      {
+	        if (wfd.cAlternateFileName[0])
+	          lpFileName = wfd.cAlternateFileName;
+	        else
+	          lpFileName = wfd.cFileName;
+	        if (IsDotDir(lpFileName) || (b_FileMask && IsAttribDir(wfd.dwFileAttributes)))
+	          continue; /* next name in pTempFrom(dir) */
+	        if (INVALID_FILE_ATTRIBUTES == ToAttr)
+	        {
+	          if (b_MultiTo)
+	          {
+	            retCode = ERROR_CANCELLED;
+	            break; /* we need the FindClose */
+	          }
+	          if (b_ToMask)
+	          {
+	            if (IsAttribDir(wfd.dwFileAttributes)) /* w2k,wXp is tested */
+	            {
+	              /* no root for target exist, but w2k returns ERROR_ALREADY_EXISTS! */
+#if 1
+	              retCode = ERROR_ALREADY_EXISTS;
+#else
+	              retCode = ERROR_INVALID_NAME; /* wXp returns this, which is more useful */
+#endif
+	              nFileOp.fAnyOperationsAborted |= TRUE;
+	              break;
+	            }
+	          }
+	          else
+	          {
+	            nFileOp.fFlags |= FOF_MULTIDESTFILES;
+	            SHFileStrCpyCatW(&pToFile[1], lpFileName, NULL);
+	          }
+	        }
+	        SHFileStrCpyCatW(&pFromFile[1], lpFileName, NULL);
+	        retCode = SHFileOperationW (&nFileOp);
+	      } while(!retCode && FindNextFileW(hFind, &wfd));
+	      FindClose(hFind);
 	    }
 	    else
 	    {
-                pToFile[0] = '\0';
-                ToPathAttr = GetFileAttributesW(pTempTo);
-                pToFile[0] = '\\';
-                if (IsAttribFile(ToPathAttr))
-                {
-                    /* error, is this tested ? */
-                    retCode = 0x777402;
-                    goto shfileop_end;
-                }
+	      retCode = ERROR_CANCELLED;
 	    }
 
-	    /* singlesource + no mask */
-	    if (INVALID_FILE_ATTRIBUTES == (ToAttr & ToPathAttr))
+	    if (ERROR_CANCELLED == retCode)
 	    {
-                /* Target-dir does not exist, and cannot be created */
-                retCode=0x75;
-                goto shfileop_end;
+	      if (!b_SameRoot_MOVE)
+	      {
+	        nFileOp.fAnyOperationsAborted |= TRUE;
+	      } /* endif */
 	    }
+	    continue;
+	  }
 
-	    switch(FuncSwitch)
+	  if ((INVALID_FILE_ATTRIBUTES != (FromAttr | ToAttr)) &&
+	      FlagRenameOnCollision(&nFileOp) && !(b_ToMask) && !(pToTailSlash))
+	  {
+	    if (b_SameRoot_MOVE && (SFSC_IDENTIICAL & f_SameDrive))
 	    {
-	    case FO_MOVE:
-                pToFile = NULL;
-                if ((ToAttr == INVALID_FILE_ATTRIBUTES) && SHFileStrICmpW(pTempFrom, pTempTo, pFromFile, NULL))
-                {
-                    nFileOp.wFunc =  ((level+1)<<4) + FO_RENAME;
-                }
-                else
-                {
-                    if (b_SameRoot && IsAttribDir(ToAttr) && IsAttribDir(wfd.dwFileAttributes))
-                    {
-                        /* we need pToFile for FO_DELETE after FO_MOVE contence */
-                        pToFile = SHFileStrCpyCatW(pTempFrom, NULL, wWildcardFile);
-                    }
-                    else
-                    {
-                        nFileOp.wFunc =  ((level+1)<<4) + FO_COPY;
-                    }
-                }
-                retCode = SHFileOperationW(&nFileOp);
-                if (pToFile)
-                    ((DWORD*)pToFile)[0] = '\0';
-                if (!nFileOp.fAnyOperationsAborted && (FO_RENAME != (nFileOp.wFunc & 0xf)))
-                {
-                    nFileOp.wFunc =  ((level+1)<<4) + FO_DELETE;
-                    retCode = SHFileOperationW(&nFileOp);
-                }
-                continue;
-	    case FO_COPY:
-                if (SHFileStrICmpW(pTempFrom, pTempTo, NULL, NULL))
-                { /* target is the same as source ? */
-                    /* we still need the value for the returncode, we assume 0x71 */
-                    retCode = 0x71;
-                    goto shfileop_end;
-                }
-                if (IsAttribDir((ToAttr & wfd.dwFileAttributes)))
-                {
-                    if (IsAttribDir(ToAttr) || !SHNotifyCreateDirectoryW(pTempTo, NULL))
-                    {
-                        /* ??? nFileOp.fFlags = (nFileOp.fFlags | FOF_MULTIDESTFILES); */
-                        SHFileStrCpyCatW(pTempFrom, NULL, wWildcardFile);
-                        retCode = SHFileOperationW(&nFileOp);
-                    }
-                    else
-                    {
-                        retCode = 0x750;/* value unknown */
-                        goto shfileop_end;
-                    }
-                }
-                else
-                {
-                    if (!(ask_overwrite && SHELL_ConfirmDialogW(ASK_OVERWRITE_FILE, pTempTo))
-                        && (not_overwrite))
-                    {
-                        /* we still need the value for the returncode, we use the mostly assumed */
-                        retCode = 0x73;
-                        goto shfileop_end;
-                    }
-                    if (SHNotifyCopyFileW(pTempFrom, pTempTo, TRUE) != ERROR_SUCCESS)
-                    {
-                        retCode = 0x77; /* value unknown */
-                        goto shfileop_end;
-                    }
-                }
+	      if (b_Multi || IsAttribFile(FromAttr))
+	        continue;
+	      if (IsAttribDir(FromAttr))
+	      {
+	        /* in w98/w2k/wXp endless recursive move until it aborts due to filename getting to
+		   long, we don't do this stupidity but return the according error anyhow here */
+	        retCode = ERROR_FILENAME_EXCED_RANGE;
+	        break;
+	      }
 	    }
-        }
+	    if (( b_SameRoot_MOVE &&  (b_Multi) && !(b_MultiFrom) && IsAttribFile(FromAttr) && IsAttribDir(ToAttr))
+	     || (!(b_MultiFrom) && IsAttribFile(FromAttr) && IsAttribFile(ToAttr))
+	     || ( (b_Multi) && IsAttribDir(FromAttr)  && IsAttribDir(ToAttr))
+	     || (!b_SameRoot_MOVE && IsAttribDir(FromAttr) && IsAttribFile(ToAttr))
+	       )
+	    {
+	      ToAttr = SHRenameOnCollision(&nFileOp, pToFile + 1, pFromFile + 1);
+	    } /* endif */
+	  } /* endif */
+	  if (!(b_MultiFrom) && (!FlagRenameOnCollision(&nFileOp) || b_SameRoot_MOVE) &&
+	      ((SFSC_IDENTIICAL | SFSC_SOURCE_CONTAINS_TARGET) & f_SameDrive))
+	  {
+	    if (!FlagRenameOnCollision(&nFileOp) && (IsAttribFile(FromAttr)))
+	    {
+	      /* target is the same as source */
+	      retCode = 0x71;
+	    }
+	    break;
+	  } /* endif */
+
+	  /* Analyzing for moving SourceName to TargetName */
+	  if ((b_MultiFrom || !b_Multi) && ((IsAttribFile(FromAttr) && IsAttribDir(ToAttr)) ||
+	      (!b_MultiTo && IsAttribDir(ToAttr)) ||
+	      (!b_MultiTo && IsAttribDir(FromAttr) && b_MultiFrom && !b_SameRoot_MOVE)))
+	  {
+	    SHFileStrCpyCatW(pTempTo, NULL, pFromFile);
+	    /* without FOF_MULTIDESTFILES shlfileop would create dir's recursively */
+	    nFileOp.fFlags |= FOF_MULTIDESTFILES;
+	    retCode = SHFileOperationW(&nFileOp);
+	    continue;
+	  }
+	  /* What can we do with one pair and FO_MOVE/FO_COPY ? */
+	  /* w98se, nt40 does create recursive dirs, W2K not! wMe ist not tested */
+	  if (IsAttribDir(ToPathAttr) || !b_SameRoot_MOVE)
+	  {
+	    if (!b_MultiFrom)
+	    {
+	      if (IsAttribFile(FromAttr))
+	      {
+	        if (b_SameRoot_MOVE &&
+	          /* windows-bug, MOVE for File also with pToTailSlash, COPY not for this */
+	           (!(IsAttribFile(ToAttr)) || (!(FlagAskOverwrite(&nFileOp)) && FlagNotOverwrite(&nFileOp))) &&
+	           (IsAttribDir(ToPathAttr) || b_ToMask))
+	        {
+	          /* With the same drive, we can move for FO_MOVE, dir to dir is solved later */
+	          retCode = SHNotifyMoveFileW(nFileOp.pFrom, nFileOp.pTo);
+	          continue;
+	        } /* endif */
+	        if (IsAttribDir(ToPathAttr) && !pToTailSlash && !IsAttribDir(ToAttr))
+	        {
+	          if (!(FOI_NeverOverwrite & nFileOp.fAnyOperationsAborted))
+	          {
+	            if (!FlagNotOverwrite(&nFileOp))
+	            {
+	              ToAttr = INVALID_FILE_ATTRIBUTES;
+	            }
+	            if (IsAttribFile(ToAttr) && FlagNotOverwrite(&nFileOp))
+	            {
+	              if (FlagAskOverwrite(&nFileOp))
+	              {
+	                /* FIXME: we must change the dialog in the future to allow 3-4 cases,
+	                 * 'Yes','No','Yes for all','Never ?' */
+	                if ((INVALID_FILE_ATTRIBUTES != ToAttr) && !SHELL_ConfirmDialogW(ASK_OVERWRITE_FILE, pTempTo))
+	                {
+	                  retCode = 0x10008;
+	                  nFileOp.fAnyOperationsAborted |= TRUE;
+	                  break;
+	                }
+	                ToAttr = INVALID_FILE_ATTRIBUTES;
+	              } /* endif */
+	            } /* endif */
+	          } /* endif */
+	          if (INVALID_FILE_ATTRIBUTES == ToAttr)
+	          {
+	            if (SHNotifyCopyFileW(pTempFrom, pTempTo, FlagRenameOnCollision(&nFileOp)))
+	            {
+	              /* the retcode for this case is unknown */
+	              retCode = 0x10009;
+	            }
+	            if ((FO_COPY != FuncSwitch) && !retCode)
+	            {
+	              nFileOp.wFunc = ((level + 1) << FO_LevelShift) + FO_DELETE;
+	              retCode = SHFileOperationW(&nFileOp);
+	            }
+	            continue;
+	          }
+	        }
+	      }
+	      if (IsAttribDir(FromAttr))
+	      {
+	        if (INVALID_FILE_ATTRIBUTES == ToAttr)
+	        {
+	          SetIfPointer(pToTailSlash, '\0');
+	          ToAttr = SHRenameOnCollision(&nFileOp, pToFile + 1, pFromFile + 1);
+	          if (INVALID_FILE_ATTRIBUTES != ToAttr)
+	          {
+	            /* w2k returns ERROR_ALREADY_EXISTS, if it found the target with mask */
+#if 1
+	            retCode = ERROR_ALREADY_EXISTS;
+#else
+	            retCode = ERROR_INVALID_NAME;
+#endif
+	            nFileOp.fAnyOperationsAborted |= TRUE;
+	            break;
+	          }
+	          lpFileName = &pToFile[lstrlenW(pToFile)];
+	          if (pToTailSlash)
+	          {
+	            pToTailSlash = lpFileName;
+	            lpFileName++;
+	          }
+	          ((DWORD*)lpFileName)[0] = '\0';
+	          retCode = SHNotifyCreateDirectoryW(pTempTo, NULL);
+
+	          if (retCode)
+	          {
+	            retCode = (ERROR_PATH_NOT_FOUND | 0x10000); /* nt40,w98se,w2k,wxp */
+	            break;
+	          }
+	          ToAttr = GetFileAttributesW(pTempTo);
+	          SetIfPointer(pToTailSlash,'\\');
+	          nFileOp.fFlags &= ~FOF_RENAMEONCOLLISION;
+	        }
+	        if (IsAttribDir(ToAttr))
+	        {
+	          /* both are existing dirs, we (FO_)MOVE/COPY the content */
+	          pToFile = SHFileStrCpyCatW(pTempFrom, NULL, wWildcardFile);
+	          nFileOp.fFlags &= ~FOF_MULTIDESTFILES;
+	          retCode = SHFileOperationW(&nFileOp);
+	          if ((FO_COPY != FuncSwitch) && !retCode)
+	          {
+	            ((DWORD*)pToFile)[0] = '\0';
+	            nFileOp.wFunc =  ((level + 1) << FO_LevelShift) + FO_DELETE;
+	            retCode = SHFileOperationW(&nFileOp);
+	          }
+	          continue;
+	        }
+	      } /* endif IsAttribDir(from) */
+	    } /* endif !b_MultiFrom */
+	  }
+	  if (!b_SameRoot_MOVE)
+	  {
+	    nFileOp.fAnyOperationsAborted |= TRUE;
+	  } /* endif */
+	  retCode = ERROR_CANCELLED;
+	} /* end-while */
 
-shfileop_end:
-	if (hFind != INVALID_HANDLE_VALUE)
-	    FindClose(hFind);
-	hFind = INVALID_HANDLE_VALUE;
 	if (pTempFrom)
-	    HeapFree(GetProcessHeap(), 0, pTempFrom);
-	if (retCode)
-	    nFileOp.fAnyOperationsAborted = TRUE;
-	TRACE("%s level=%ld AnyOpsAborted=%s ret=0x%x, with %s %s%s\n",
-              debug_shfileops_action(FuncSwitch), level,
-	      nFileOp.fAnyOperationsAborted ? "TRUE":"FALSE",
+	  SHFree(pTempFrom);
+
+	TRACE("%s level=%ld AnyOpsAborted=%s ret=0x%x, with %s %s%s\n", cFO_String[FuncSwitch],
+	      level, (TRUE & nFileOp.fAnyOperationsAborted) ? "TRUE":"FALSE",
 	      retCode, debugstr_w(pFrom), pTo ? "-> ":"", debugstr_w(pTo));
 
+	if (0 == level)
+	{
+	  nFileOp.fAnyOperationsAborted &= TRUE;
+	  if (!(nFileOp.fFlags & FOF_WANTMAPPINGHANDLE))
+	  {
+	    SHFreeNameMappings((HANDLE)nFileOp.hNameMappings);
+	    nFileOp.hNameMappings = 0;
+	  }
+	}
+	lpFileOp->hNameMappings         = nFileOp.hNameMappings;
 	lpFileOp->fAnyOperationsAborted = nFileOp.fAnyOperationsAborted;
 	return retCode;
 }
 
 /*************************************************************************
+ *
+ * SHNameTranslate HelperFunction for SHFileOperationA
+ *
+ * Translates a list of 0 terminated ASCII strings into Unicode. If *wString
+ * is NULL, only the necessary size of the string is determined and returned,
+ * otherwise the ASCII strings are copied into it and the buffer is increased
+ * to point to the location after the final 0 termination char.
+ */
+DWORD SHNameTranslate(LPWSTR* wString, LPCWSTR* pWToFrom, BOOL more)
+{
+	DWORD size = 0, aSize = 0;
+	LPCSTR aString = (LPCSTR)*pWToFrom;
+
+	if (aString)
+	{
+	  do
+	  {
+	    size = lstrlenA(aString) + 1;
+	    aSize += size;
+	    aString += size;
+	  } while ((size != 1) && more);
+	  /* The two sizes might be different in the case of multibyte chars */
+	  size = MultiByteToWideChar(CP_ACP, 0, aString, aSize, *wString, 0);
+	  if (*wString) /* only in the second loop */
+	  {
+	    MultiByteToWideChar(CP_ACP, 0, (LPCSTR)*pWToFrom, aSize, *wString, size);
+	    *pWToFrom = *wString;
+	    *wString += size;
+	  }
+	}
+	return size;
+}
+
+/*************************************************************************
+ * SHFileOperationA          [SHELL32.@]
+ *
+  * See SHFileOperationW
+*/
+int WINAPI SHFileOperationA(LPSHFILEOPSTRUCTA lpFileOp)
+{
+	SHFILEOPSTRUCTW nFileOp = *((LPSHFILEOPSTRUCTW)lpFileOp);
+	int retCode = 0;
+	DWORD size;
+	LPWSTR ForFree = NULL, /* we change wString in SHNameTranslate and can't use it for freeing */
+	       wString = NULL; /* we change this in SHNameTranslate */
+
+#ifdef __WIN32OS2__
+	TRACE("SHFileOperationA\n");
+#else
+	TRACE("\n");
+#endif
+	for (;;) /* every loop calculate size, second translate also, if we have storage for this */
+	{
+	  size = SHNameTranslate(&wString, &nFileOp.pFrom, TRUE); /* internal loop */
+	  if (FO_DELETE != (nFileOp.wFunc & FO_MASK))
+	    size += SHNameTranslate(&wString, &nFileOp.pTo, TRUE); /* internal loop */
+	  if ((nFileOp.fFlags & FOF_SIMPLEPROGRESS))
+	    size += SHNameTranslate(&wString, &nFileOp.lpszProgressTitle, FALSE); /* no loop */
+
+	  if (ForFree)
+	  {
+	    retCode = SHFileOperationW(&nFileOp);
+	    SHFree(ForFree); /* we can not use wString, it was changed */
+	    lpFileOp->hNameMappings         = nFileOp.hNameMappings;
+	    lpFileOp->fAnyOperationsAborted = nFileOp.fAnyOperationsAborted;
+	  }
+	  else
+	  {
+	    wString = ForFree = SHAlloc(size * sizeof(WCHAR));
+	    if (ForFree) continue;
+	    retCode = ERROR_OUTOFMEMORY;
+	    SetLastError(retCode);
+	  }
+	  return retCode;
+	}
+}
+
+/*************************************************************************
  * SHFileOperation        [SHELL32.@]
  *
  */ 





More information about the wine-patches mailing list