2024 lines
50 KiB
C++
2024 lines
50 KiB
C++
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
|
|
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
|
|
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
|
|
// PARTICULAR PURPOSE.
|
|
//
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
|
|
/*
|
|
* scandir.cpp
|
|
*
|
|
* build lists of filenames given a pathname.
|
|
*
|
|
* dir_buildlist takes a pathname and returns a handle. Subsequent
|
|
* calls to dir_firstitem and dir_nextitem return handles to
|
|
* items within the list, from which you can get the name of the
|
|
* file (relative to the original pathname, or complete), and a checksum
|
|
* and filesize.
|
|
*
|
|
* The list can be either built entirely during the build call, or
|
|
* built one directory at a time as required by dir_nextitem calls. This
|
|
* option affects only relative performance, and is taken as a
|
|
* recommendation only (ie some of the time we will ignore the flag).
|
|
*
|
|
* the list is ordered alphabetically (case-insensitive using lstrcmpi).
|
|
* within any one directory, we list filenames before going on
|
|
* to subdirectory contents.
|
|
*
|
|
* All memory is allocated using HeapAlloc
|
|
*/
|
|
|
|
#include "precomp.h"
|
|
|
|
#include "list.h"
|
|
#include "scandir.h"
|
|
|
|
#include "sdkdiff.h"
|
|
#include "wdiffrc.h"
|
|
|
|
#ifdef trace
|
|
extern BOOL bTrace; /* in sdkdiff.cpp. Read only here */
|
|
#endif //trace
|
|
|
|
/*
|
|
* The caller gets handles to two things: a DIRLIST, representing the
|
|
* entire list of filenames, and a DIRITEM: one item within the list.
|
|
*
|
|
* from the DIRITEM he can get the filename relative to tree root
|
|
* passed to dir_build*) - and also he can get to the next
|
|
* DIRITEM (and back to the DIRLIST).
|
|
*
|
|
* We permit lazy building of the tree (usually so the caller can keep
|
|
* the user-interface uptodate as we go along). In this case,
|
|
* we need to store information about how far we have scanned and
|
|
* what is next to do. We need to scan an entire directory at a time and then
|
|
* sort it so we can return files in the correct order.
|
|
*
|
|
*
|
|
* We scan an entire directory and store it in a DIRECT struct. This contains
|
|
* a list of DIRITEMs for the files in the current directory, and a list of
|
|
* DIRECTs for the subdirectories (possible un-scanned).
|
|
*
|
|
* dir_nextitem will use the list functions to get the next DIRITEM on the list.
|
|
* When the end of the list is reached, it will use the backpointer back to the
|
|
* DIRECT struct to find the next directory to scan.
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* hold name and information about a given file (one ITEM in a DIRectory)
|
|
* caller's DIRITEM handle is a pointer to one of these structures
|
|
*/
|
|
struct diritem {
|
|
LPSTR name; /* ptr to filename (final element only) */
|
|
long size; /* filesize */
|
|
DWORD checksum; /* checksum of file */
|
|
DWORD attr; /* file attributes */
|
|
FILETIME ft_lastwrite; /* last write time, set whenever size is set */
|
|
BOOL sumvalid; /* TRUE if checksum calculated */
|
|
BOOL fileerror; // true if some file error occurred
|
|
struct direct * direct; /* containing directory */
|
|
LPSTR localname; /* name of temp copy of file */
|
|
BOOL bLocalIsTemp; /* true if localname is tempfile. not
|
|
* defined if localname is NULL
|
|
*/
|
|
int sequence; /* sequence number, for dir_compsequencenumber */
|
|
};
|
|
|
|
|
|
/* DIRECT: hold state about directory and current position in list of filenames.
|
|
*/
|
|
typedef struct direct {
|
|
LPSTR relname; /* name of dir relative to DIRLIST root */
|
|
DIRLIST head; /* back ptr (to get fullname and server) */
|
|
struct direct * parent; /* parent directory (NULL if above tree root)*/
|
|
|
|
BOOL bScanned; /* TRUE if scanned */
|
|
LIST diritems; /* list of DIRITEMs for files in cur. dir */
|
|
LIST directs; /* list of DIRECTs for child dirs */
|
|
|
|
int pos; /* where are we? begin, files, dirs */
|
|
struct direct * curdir; /* subdir being scanned (ptr to list element)*/
|
|
} * DIRECT;
|
|
|
|
/* values for direct.pos */
|
|
#define DL_FILES 1 /* reading files from the diritems */
|
|
#define DL_DIRS 2 /* in the dirs: List_Next on curdir */
|
|
|
|
|
|
/*
|
|
* the DIRLIST handle returned from a build function is in fact
|
|
* a pointer to one of these. Although this is not built from a LIST object,
|
|
* it behaves like a list to the caller.
|
|
*/
|
|
struct dirlist {
|
|
|
|
char rootname[MAX_PATH]; /* name of root of tree */
|
|
BOOL bFile; /* TRUE if root of tree is file, not dir */
|
|
BOOL bSum; /* TRUE if checksums required */
|
|
DIRECT dot; /* dir for '.' - for tree root dir */
|
|
|
|
LPSTR pPattern; /* wildcard pattern or NULL */
|
|
LPSTR pDescription; /* description */
|
|
|
|
DIRLIST pOtherDirList;
|
|
};
|
|
|
|
extern BOOL bAbort; /* from sdkdiff.cpp (read only here). */
|
|
|
|
/* file times are completely different under DOS and NT.
|
|
On NT they are FILETIMEs with a 2 DWORD structure.
|
|
Under DOS they are single long. We emulate the NT
|
|
thing under DOS by providing CompareFileTime and a
|
|
definition of FILETIME
|
|
*/
|
|
|
|
/*-- forward declaration of internal functions ---------------------------*/
|
|
|
|
BOOL iswildpath(LPCSTR pszPath);
|
|
LPSTR dir_finalelem(LPSTR path);
|
|
void dir_cleardirect(DIRECT dir);
|
|
void dir_adddirect(DIRECT dir, LPSTR path);
|
|
BOOL dir_addfile(DIRECT dir, LPSTR path, DWORD size, FILETIME ft, DWORD attr, int *psequence);
|
|
void dir_scan(DIRECT dir, BOOL bRecurse);
|
|
BOOL dir_isvalidfile(LPSTR path);
|
|
BOOL dir_fileinit(DIRITEM pfile, DIRECT dir, LPSTR path, long size, FILETIME ft, DWORD attr, int *psequence);
|
|
void dir_dirinit(DIRECT dir, DIRLIST head, DIRECT parent, LPSTR name);
|
|
long dir_getpathsizeetc(LPSTR path, FILETIME FAR*ft, DWORD FAR*attr);
|
|
DIRITEM dir_findnextfile(DIRLIST dl, DIRECT curdir);
|
|
|
|
/* --- external functions ------------------------------------------------*/
|
|
|
|
|
|
/* ----- list initialisation/cleanup --------------------------------------*/
|
|
|
|
|
|
/*
|
|
* build a list of filenames
|
|
*
|
|
* optionally build the list on demand, in which case we scan the
|
|
* entire directory but don't recurse into subdirs until needed
|
|
*
|
|
* if bSum is true, checksum each file as we build the list. checksums can
|
|
* be obtained from the DIRITEM (dir_getchecksum(DIRITEM)). If bSum is FALSE,
|
|
* checksums will be calculated on request (the first call to dir_getchecksum
|
|
* for a given DIRITEM).
|
|
*/
|
|
|
|
DIRLIST
|
|
dir_buildlist(
|
|
LPSTR path,
|
|
BOOL bSum,
|
|
BOOL bOnDemand
|
|
)
|
|
{
|
|
DIRLIST dlOut = 0;
|
|
DIRLIST dl = 0;
|
|
BOOL bFile;
|
|
char tmppath[MAX_PATH] = {0};
|
|
LPSTR pstr;
|
|
LPSTR pPat = NULL;
|
|
LPSTR pTag = NULL;
|
|
HRESULT hr;
|
|
|
|
/*
|
|
* copy the path so we can modify it
|
|
*/
|
|
hr = StringCchCatN(tmppath, MAX_PATH, path, sizeof(tmppath)-1);
|
|
if (FAILED(hr))
|
|
{
|
|
goto LError;
|
|
}
|
|
|
|
/* look for wildcards and separate out pattern if there */
|
|
if (My_mbschr(tmppath, '*') || My_mbschr(tmppath, '?'))
|
|
{
|
|
pstr = dir_finalelem(tmppath);
|
|
pPat = (char*) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, lstrlen(pstr) +1);
|
|
if (pPat == NULL)
|
|
goto LError;
|
|
hr = StringCchCopy(pPat, (lstrlen(pstr)+1), pstr);
|
|
if (FAILED(hr)) {
|
|
OutputError(hr, IDS_SAFE_COPY);
|
|
goto LError;
|
|
}
|
|
*pstr = '\0';
|
|
}
|
|
|
|
/* we may have reduced the path to nothing - replace with . if so */
|
|
if (lstrlen(tmppath) == 0)
|
|
{
|
|
hr = StringCchCopy(tmppath, MAX_PATH, ".");
|
|
if (FAILED(hr)) {
|
|
OutputError(hr, IDS_SAFE_COPY);
|
|
goto LError;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* remove the trailing slash if unnecessary (\, c:\ need it)
|
|
*/
|
|
pstr = &tmppath[lstrlen(tmppath) -1];
|
|
if ((*pstr == '\\') && (pstr > tmppath) && (pstr[-1] != ':')
|
|
&& !IsDBCSLeadByte((BYTE)*(pstr-1)))
|
|
{
|
|
*pstr = '\0';
|
|
}
|
|
}
|
|
|
|
|
|
/* check if the path is valid */
|
|
if ((pTag && !iswildpath(tmppath)) || (tmppath[0] == '/' && tmppath[1] == '/'))
|
|
{
|
|
bFile = TRUE;
|
|
}
|
|
else if (dir_isvaliddir(tmppath))
|
|
{
|
|
bFile = FALSE;
|
|
}
|
|
else if (dir_isvalidfile(tmppath))
|
|
{
|
|
bFile = TRUE;
|
|
}
|
|
else
|
|
{
|
|
/* not valid */
|
|
goto LError;
|
|
}
|
|
|
|
|
|
/* alloc and init the DIRLIST head */
|
|
|
|
dl = (DIRLIST) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(struct dirlist));
|
|
if (dl == NULL)
|
|
{
|
|
goto LError;
|
|
}
|
|
dl->pOtherDirList = NULL;
|
|
|
|
/* convert the pathname to an absolute path */
|
|
if (NULL == _fullpath(dl->rootname, tmppath, sizeof(dl->rootname)))
|
|
{
|
|
goto LError;
|
|
}
|
|
|
|
dl->bSum = bSum;
|
|
dl->bSum = FALSE; // to speed things up. even if we do want checksums,
|
|
// let's get them on demand, not right now.
|
|
dl->bFile = bFile;
|
|
|
|
if (pPat)
|
|
{
|
|
dl->pPattern = pPat;
|
|
pPat = 0;
|
|
}
|
|
|
|
|
|
/* make a '.' directory for the tree root directory -
|
|
* all files and subdirs will be listed from here
|
|
*/
|
|
{ /* Do NOT chain on anything with garbage pointers in it */
|
|
DIRECT temp;
|
|
temp = (DIRECT) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(struct direct));
|
|
if (temp == NULL)
|
|
return NULL;
|
|
dl->dot = temp;
|
|
}
|
|
|
|
dir_dirinit(dl->dot, dl, NULL, ".");
|
|
|
|
/* were we given a file or a directory ? */
|
|
if (bFile)
|
|
{
|
|
/* its a file. create a single file entry
|
|
* and set the state accordingly
|
|
*/
|
|
long fsize;
|
|
FILETIME ft;
|
|
DWORD attr;
|
|
fsize = dir_getpathsizeetc(tmppath, &ft, &attr);
|
|
|
|
dl->dot->bScanned = TRUE;
|
|
|
|
if (!dir_addfile(dl->dot, dir_finalelem(tmppath), fsize, ft, attr, 0))
|
|
goto LError;
|
|
}
|
|
else
|
|
{
|
|
/* scan the root directory and return. if we are asked
|
|
* to scan the whole thing, this will cause a recursive
|
|
* scan all the way down the tree
|
|
*/
|
|
dir_scan(dl->dot, (!bOnDemand) );
|
|
}
|
|
|
|
dlOut = dl;
|
|
dl = 0;
|
|
|
|
LError:
|
|
if (pTag)
|
|
HeapFree(GetProcessHeap(), NULL, pTag);
|
|
if (pPat)
|
|
HeapFree(GetProcessHeap(), NULL, pPat);
|
|
dir_delete(dl);
|
|
return dlOut;
|
|
} /* dir_buildlist */
|
|
|
|
/*
|
|
* build/append a list of filenames
|
|
*
|
|
* if bSum is true, checksum each file as we build the list. checksums can
|
|
* be obtained from the DIRITEM (dir_getchecksum(DIRITEM)). If bSum is FALSE,
|
|
* checksums will be calculated on request (the first call to dir_getchecksum
|
|
* for a given DIRITEM).
|
|
*/
|
|
|
|
BOOL
|
|
dir_appendlist(
|
|
DIRLIST *pdl,
|
|
LPCSTR path,
|
|
BOOL bSum,
|
|
int *psequence
|
|
)
|
|
{
|
|
DIRLIST dl;
|
|
BOOL bFile=FALSE;
|
|
char tmppath[MAX_PATH];
|
|
LPSTR pstr;
|
|
LPSTR pTag = NULL;
|
|
BOOL fSuccess = FALSE;
|
|
HRESULT hr;
|
|
|
|
if (path)
|
|
{
|
|
// copy the path so we can modify it
|
|
hr = StringCchCopy(tmppath, MAX_PATH, path);
|
|
if (FAILED(hr))
|
|
OutputError(hr, IDS_SAFE_COPY);
|
|
|
|
// remove the trailing slash if unnecessary (\, c:\ need it)
|
|
pstr = &tmppath[lstrlen(tmppath) -1];
|
|
if ((*pstr == '\\') && (pstr > tmppath) && (pstr[-1] != ':')
|
|
&& !IsDBCSLeadByte((BYTE)*(pstr-1)))
|
|
{
|
|
*pstr = '\0';
|
|
}
|
|
|
|
|
|
/* check if the path is valid */
|
|
if ((pTag && !iswildpath(tmppath))|| (tmppath[0] == '/' && tmppath[1] == '/'))
|
|
{
|
|
// assume valid
|
|
bFile = TRUE;
|
|
}
|
|
else if (dir_isvaliddir(tmppath))
|
|
{
|
|
bFile = FALSE;
|
|
}
|
|
else if (dir_isvalidfile(tmppath))
|
|
{
|
|
bFile = TRUE;
|
|
}
|
|
else
|
|
{
|
|
/* not valid */
|
|
goto LError;
|
|
}
|
|
}
|
|
|
|
if (!*pdl)
|
|
{
|
|
DIRECT temp;
|
|
|
|
/* alloc and init the DIRLIST head */
|
|
*pdl = (DIRLIST) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(struct dirlist));
|
|
if (*pdl == NULL)
|
|
goto LError;
|
|
(*pdl)->pOtherDirList = NULL;
|
|
|
|
(*pdl)->bSum = bSum;
|
|
(*pdl)->bSum = FALSE; // to speed things up. even if we do want
|
|
// checksums, let's get them on demand, not
|
|
// right now.
|
|
(*pdl)->bFile = FALSE;
|
|
|
|
/* make a null directory for the tree root directory -
|
|
* all files and subdirs will be listed from here
|
|
*/
|
|
/* Do NOT chain on anything with garbage pointers in it */
|
|
temp = (DIRECT) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(struct direct));
|
|
if (temp == NULL)
|
|
goto LError;
|
|
(*pdl)->dot = temp;
|
|
|
|
dir_dirinit((*pdl)->dot, (*pdl), NULL, "");
|
|
(*pdl)->dot->relname[0] = 0;
|
|
(*pdl)->dot->bScanned = TRUE;
|
|
}
|
|
dl = *pdl;
|
|
|
|
if (path)
|
|
{
|
|
/* were we given a file or a directory ? */
|
|
if (bFile)
|
|
{
|
|
/* its a file. create a single file entry
|
|
* and set the state accordingly
|
|
*/
|
|
long fsize;
|
|
FILETIME ft;
|
|
DWORD attr;
|
|
|
|
fsize = dir_getpathsizeetc(tmppath, &ft, &attr);
|
|
|
|
if (!dir_addfile(dl->dot, tmppath, fsize, ft, attr, psequence))
|
|
goto LError;
|
|
}
|
|
}
|
|
|
|
fSuccess = TRUE;
|
|
|
|
LError:
|
|
if (pTag)
|
|
HeapFree(GetProcessHeap(), NULL, pTag);
|
|
return fSuccess;
|
|
} /* dir_appendlist */
|
|
|
|
void
|
|
dir_setotherdirlist(
|
|
DIRLIST dl,
|
|
DIRLIST otherdl
|
|
)
|
|
{
|
|
dl->pOtherDirList = otherdl;
|
|
}
|
|
|
|
/* free up the DIRLIST and all associated memory */
|
|
void
|
|
dir_delete(
|
|
DIRLIST dl
|
|
)
|
|
{
|
|
if (dl == NULL) {
|
|
return;
|
|
}
|
|
|
|
dir_cleardirect(dl->dot);
|
|
HeapFree(GetProcessHeap(), NULL, dl->dot);
|
|
|
|
if (dl->pPattern) {
|
|
HeapFree(GetProcessHeap(), NULL, dl->pPattern);
|
|
}
|
|
|
|
HeapFree(GetProcessHeap(), NULL, dl);
|
|
}
|
|
|
|
/* ----- DIRLIST functions ------------------------------------------------*/
|
|
|
|
/* was the original build request a file or a directory ? */
|
|
BOOL
|
|
dir_isfile(
|
|
DIRLIST dl
|
|
)
|
|
{
|
|
if (dl == NULL) {
|
|
return(FALSE);
|
|
}
|
|
|
|
return(dl->bFile);
|
|
}
|
|
|
|
|
|
/* return the first file in the list, or NULL if no files found.
|
|
* returns a DIRITEM. This can be used to get filename, size and chcksum.
|
|
* if there are no files in the root, we recurse down until we find a file
|
|
*/
|
|
DIRITEM
|
|
dir_firstitem(
|
|
DIRLIST dl
|
|
)
|
|
{
|
|
if (dl == NULL) {
|
|
return(NULL);
|
|
}
|
|
|
|
/*
|
|
* reset the state to indicate that no files have been read yet
|
|
*/
|
|
dl->dot->pos = DL_FILES;
|
|
dl->dot->curdir = NULL;
|
|
|
|
/* now get the next filename */
|
|
return(dir_findnextfile(dl, dl->dot));
|
|
} /* dir_firstitem */
|
|
|
|
|
|
/*
|
|
* get the next filename after the one given.
|
|
*
|
|
* The List_Next function can give us the next element on the list of files.
|
|
* If this is null, we need to go back to the DIRECT and find the
|
|
* next list of files to traverse (in the next subdir).
|
|
*
|
|
* after scanning all the subdirs, return to the parent to scan further
|
|
* dirs that are peers of this, if there are any. If we have reached the end of
|
|
* the tree (no more dirs in dl->dot to scan), return NULL.
|
|
*
|
|
* Don't recurse to lower levels unless fDeep is TRUE
|
|
*/
|
|
DIRITEM
|
|
dir_nextitem(
|
|
DIRLIST dl,
|
|
DIRITEM cur,
|
|
BOOL fDeep
|
|
)
|
|
{
|
|
DIRITEM next;
|
|
|
|
if ((dl == NULL) || (cur == NULL)) {
|
|
TRACE_ERROR("DIR: null arguments to dir_nextitem", FALSE);
|
|
return(NULL);
|
|
}
|
|
|
|
if (bAbort) return NULL; /* user requested abort */
|
|
|
|
/* local list */
|
|
|
|
if ( (next = (DIRITEM)List_Next(cur)) != NULL) {
|
|
/* there was another file on this list */
|
|
return(next);
|
|
}
|
|
if (!fDeep) return NULL;
|
|
|
|
/* get the head of the next list of filenames from the directory */
|
|
cur->direct->pos = DL_DIRS;
|
|
cur->direct->curdir = NULL;
|
|
return(dir_findnextfile(dl, cur->direct));
|
|
} /* dir_nextitem */
|
|
|
|
DIRITEM
|
|
dir_findnextfile(
|
|
DIRLIST dl,
|
|
DIRECT curdir
|
|
)
|
|
{
|
|
DIRITEM curfile;
|
|
|
|
if (bAbort) return NULL; /* user requested abort */
|
|
|
|
if ((dl == NULL) || (curdir == NULL)) {
|
|
return(NULL);
|
|
}
|
|
|
|
/* scan the subdir if necessary */
|
|
if (!curdir->bScanned) {
|
|
dir_scan(curdir, FALSE);
|
|
}
|
|
|
|
/* have we already read the files in this directory ? */
|
|
if (curdir->pos == DL_FILES) {
|
|
/* no - return head of file list */
|
|
curfile = (DIRITEM) List_First(curdir->diritems);
|
|
if (curfile != NULL) {
|
|
return(curfile);
|
|
}
|
|
|
|
/* no more files - try the subdirs */
|
|
curdir->pos = DL_DIRS;
|
|
}
|
|
|
|
/* try the next subdir on the list, if any */
|
|
/* is this the first or the next */
|
|
if (curdir->curdir == NULL) {
|
|
curdir->curdir = (DIRECT) List_First(curdir->directs);
|
|
} else {
|
|
curdir->curdir = (DIRECT) List_Next(curdir->curdir);
|
|
}
|
|
/* did we find a subdir ? */
|
|
if (curdir->curdir == NULL) {
|
|
|
|
/* no more dirs - go back to parent if there is one */
|
|
if (curdir->parent == NULL) {
|
|
/* no parent - we have exhausted the tree */
|
|
return(NULL);
|
|
}
|
|
|
|
/* reset parent state to indicate this is the current
|
|
* directory - so that next gets the next after this.
|
|
* this ensures that multiple callers of dir_nextitem()
|
|
* to the same tree work.
|
|
*/
|
|
curdir->parent->pos = DL_DIRS;
|
|
curdir->parent->curdir = curdir;
|
|
|
|
return(dir_findnextfile(dl, curdir->parent));
|
|
}
|
|
|
|
/* there is a next directory - set it to the
|
|
* beginning and get the first file from it
|
|
*/
|
|
curdir->curdir->pos = DL_FILES;
|
|
curdir->curdir->curdir = NULL;
|
|
return(dir_findnextfile(dl, curdir->curdir));
|
|
|
|
} /* dir_findnextfile */
|
|
|
|
|
|
/*
|
|
* get a description of this DIRLIST - this is essentially the
|
|
* rootname with any wildcard specifier at the end.
|
|
*
|
|
* NOTE that this is not a valid path to the tree root - for that you
|
|
* need dir_getrootpath().
|
|
*/
|
|
LPSTR
|
|
dir_getrootdescription(
|
|
DIRLIST dl
|
|
)
|
|
{
|
|
LPSTR pname;
|
|
HRESULT hr;
|
|
|
|
// allow enough space for \\servername! + MAX_PATH
|
|
pname = (char*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, MAX_PATH + 15);
|
|
if (pname == NULL) {
|
|
return(NULL);
|
|
}
|
|
|
|
if (dl->pDescription) {
|
|
hr = StringCchCopy(pname, (MAX_PATH+15), dl->pDescription);
|
|
if (FAILED(hr)) {
|
|
OutputError(hr, IDS_SAFE_COPY);
|
|
return (NULL);
|
|
}
|
|
} else {
|
|
hr = StringCchCopy(pname, (MAX_PATH+15), dl->rootname);
|
|
if (FAILED(hr)) {
|
|
OutputError(hr, IDS_SAFE_COPY);
|
|
return (NULL);
|
|
}
|
|
|
|
if (dl->pPattern) {
|
|
hr = StringCchCat(pname,(MAX_PATH+15), "\\");
|
|
if (FAILED(hr)) {
|
|
OutputError(hr, IDS_SAFE_CAT);
|
|
return (NULL);
|
|
}
|
|
hr = StringCchCat(pname,(MAX_PATH+15),dl->pPattern);
|
|
if (FAILED(hr)) {
|
|
OutputError(hr, IDS_SAFE_CAT);
|
|
return (NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
return(pname);
|
|
}
|
|
|
|
/*
|
|
* free up a string returned from dir_getrootdescription
|
|
*/
|
|
VOID
|
|
dir_freerootdescription(
|
|
DIRLIST dl,
|
|
LPSTR string
|
|
)
|
|
{
|
|
HeapFree(GetProcessHeap(), NULL, string);
|
|
}
|
|
|
|
|
|
/*
|
|
* dir_getrootpath
|
|
*
|
|
* return the path to the DIRLIST root. This will be a valid path, not
|
|
* including the checksum server name or pPattern etc
|
|
*/
|
|
LPSTR
|
|
dir_getrootpath(
|
|
DIRLIST dl
|
|
)
|
|
{
|
|
return(dl->rootname);
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* free up a path created by dir_getrootpath
|
|
*/
|
|
void
|
|
dir_freerootpath(
|
|
DIRLIST dl,
|
|
LPSTR path
|
|
)
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
/*
|
|
* set custom description for dirlist
|
|
*/
|
|
void
|
|
dir_setdescription(DIRLIST dl, LPCSTR psz)
|
|
{
|
|
dl->pDescription = (char*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, lstrlen(psz) + 1);
|
|
if (dl->pDescription) {
|
|
HRESULT hr = StringCchCopy(dl->pDescription, (lstrlen(psz)+1), psz);
|
|
if (FAILED(hr))
|
|
OutputError(hr, IDS_SAFE_COPY);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
* returns TRUE if the DIRLIST parameter has a wildcard specified
|
|
*/
|
|
BOOL
|
|
dir_iswildcard(
|
|
DIRLIST dl
|
|
)
|
|
{
|
|
return (dl->pPattern != NULL);
|
|
}
|
|
|
|
|
|
|
|
|
|
/* --- DIRITEM functions ----------------------------------------------- */
|
|
|
|
|
|
/*
|
|
* Return a handle to the DIRLIST given a handle to the DIRITEM within it.
|
|
*
|
|
*/
|
|
DIRLIST
|
|
dir_getlist(
|
|
DIRITEM item
|
|
)
|
|
{
|
|
if (item == NULL) {
|
|
return(NULL);
|
|
} else {
|
|
return(item->direct->head);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* return the name of the current file relative to tree root
|
|
* This allocates storage. Call dir_freerelname to release it.
|
|
*/
|
|
LPSTR
|
|
dir_getrelname(
|
|
DIRITEM cur
|
|
)
|
|
{
|
|
LPSTR name;
|
|
HRESULT hr;
|
|
|
|
/* check this is a valid item */
|
|
if (cur == NULL) {
|
|
return(NULL);
|
|
}
|
|
|
|
name = (char*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, MAX_PATH);
|
|
if (name == NULL)
|
|
return NULL;
|
|
hr = StringCchCopy(name, MAX_PATH, cur->direct->relname);
|
|
if (FAILED(hr)) {
|
|
OutputError(hr, IDS_SAFE_COPY);
|
|
return(NULL);
|
|
}
|
|
hr = StringCchCat(name, MAX_PATH, cur->name);
|
|
if (FAILED(hr)) {
|
|
OutputError(hr, IDS_SAFE_CAT);
|
|
return(NULL);
|
|
}
|
|
|
|
return(name);
|
|
} /* dir_getrelname */
|
|
|
|
|
|
/* free up a relname that we allocated. This interface allows us
|
|
* some flexibility in how we store relative and complete names
|
|
*
|
|
*/
|
|
void
|
|
dir_freerelname(
|
|
DIRITEM cur,
|
|
LPSTR name
|
|
)
|
|
{
|
|
if ((cur != NULL)) {
|
|
if (name != NULL) {
|
|
HeapFree(GetProcessHeap(), NULL, name);
|
|
}
|
|
}
|
|
} /* dir_freerelname */
|
|
|
|
|
|
/*
|
|
* get an open-able name for the file. This is the complete pathname
|
|
* of the item (DIRLIST rootpath + DIRITEM relname)
|
|
*
|
|
*/
|
|
LPSTR
|
|
dir_getopenname(
|
|
DIRITEM item
|
|
)
|
|
{
|
|
LPSTR fname;
|
|
DIRLIST phead;
|
|
HRESULT hr;
|
|
|
|
if (item == NULL) {
|
|
return(NULL);
|
|
}
|
|
|
|
phead = item->direct->head;
|
|
|
|
if (item->localname != NULL) {
|
|
return(item->localname);
|
|
}
|
|
|
|
if (phead->bFile) {
|
|
return(phead->rootname);
|
|
}
|
|
|
|
// build up the file name from rootname+relname
|
|
// start with the root portion
|
|
fname = (char*) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, MAX_PATH);
|
|
if (!fname)
|
|
return NULL;
|
|
hr = StringCchCopy(fname, MAX_PATH, phead->rootname);
|
|
if (FAILED(hr)) {
|
|
OutputError(hr, IDS_SAFE_COPY);
|
|
return(NULL);
|
|
}
|
|
|
|
/*
|
|
* it's a simple local name - add both relname and name to make
|
|
* the complete filename
|
|
*/
|
|
/* avoid the . or .\ at the end of the relname */
|
|
if (*CharPrev(fname, fname+lstrlen(fname)) == '\\') {
|
|
hr = StringCchCat(fname, MAX_PATH,&item->direct->relname[2]);
|
|
if (FAILED(hr)) {
|
|
OutputError(hr, IDS_SAFE_COPY);
|
|
return(NULL);
|
|
}
|
|
} else {
|
|
hr = StringCchCat(fname, MAX_PATH,&item->direct->relname[1]);
|
|
if (FAILED(hr)) {
|
|
OutputError(hr, IDS_SAFE_CAT);
|
|
return(NULL);
|
|
}
|
|
}
|
|
hr = StringCchCat(fname, MAX_PATH, item->name);
|
|
if (FAILED(hr)) {
|
|
OutputError(hr, IDS_SAFE_CAT);
|
|
return(NULL);
|
|
}
|
|
|
|
return(fname);
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* free up memory created by a call to dir_getopenname(). This *may*
|
|
* cause the file to be deleted if it was a temporary copy.
|
|
*/
|
|
void
|
|
dir_freeopenname(
|
|
DIRITEM item,
|
|
LPSTR openname
|
|
)
|
|
{
|
|
if ((item == NULL) || (openname == NULL)) {
|
|
return;
|
|
}
|
|
|
|
if (item->localname != NULL) {
|
|
/* freed in dir_cleardirect */
|
|
return;
|
|
}
|
|
if (item->direct->head->bFile) {
|
|
/* we used the rootname */
|
|
return;
|
|
}
|
|
|
|
HeapFree(GetProcessHeap(), NULL, openname);
|
|
|
|
} /* dir_freeopenname */
|
|
|
|
|
|
/*
|
|
* return an open file handle to the file. if it is local,
|
|
* just open the file.
|
|
*/
|
|
HANDLE
|
|
dir_openfile(
|
|
DIRITEM item
|
|
)
|
|
{
|
|
LPSTR fname;
|
|
HANDLE fh;
|
|
|
|
|
|
fname = dir_getopenname(item);
|
|
if (fname == NULL) {
|
|
return INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
fh = CreateFile(fname, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE,
|
|
0, OPEN_EXISTING, 0, 0);
|
|
|
|
dir_freeopenname(item, fname);
|
|
|
|
return(fh);
|
|
} /* dir_openfile */
|
|
|
|
|
|
|
|
|
|
/*
|
|
* close a file opened with dir_openfile.
|
|
*/
|
|
void
|
|
dir_closefile(
|
|
DIRITEM item,
|
|
HANDLE fh
|
|
)
|
|
{
|
|
CloseHandle(fh);
|
|
|
|
} /* dir_closefile */
|
|
|
|
|
|
|
|
/* Recreate all the checksums and status for di as though
|
|
it had never been looked at before
|
|
*/
|
|
void
|
|
dir_rescanfile(
|
|
DIRITEM di
|
|
)
|
|
{
|
|
LPSTR fullname;
|
|
|
|
if (di==NULL) return;
|
|
|
|
/* start with it invalid, erroneous and zero */
|
|
di->sumvalid = FALSE;
|
|
di->fileerror = TRUE;
|
|
di->checksum = 0;
|
|
|
|
fullname = dir_getopenname(di);
|
|
di->size = dir_getpathsizeetc(fullname, &(di->ft_lastwrite), &(di->attr));
|
|
di->checksum = dir_getchecksum(di);
|
|
|
|
dir_freeopenname(di, fullname);
|
|
|
|
di->sumvalid = !(di->fileerror);
|
|
|
|
} /* dir_rescanfile */
|
|
|
|
|
|
/* return a TRUE iff item has a valid checksum */
|
|
BOOL
|
|
dir_validchecksum(
|
|
DIRITEM item
|
|
)
|
|
{
|
|
return (item!=NULL) && (item->sumvalid);
|
|
}
|
|
|
|
|
|
BOOL
|
|
dir_fileerror(
|
|
DIRITEM item
|
|
)
|
|
{
|
|
return (item == NULL) || (item->fileerror);
|
|
}
|
|
|
|
|
|
/* return the current file checksum. Open the file and
|
|
* calculate the checksum if it has not already been done.
|
|
*/
|
|
DWORD
|
|
dir_getchecksum(
|
|
DIRITEM cur
|
|
)
|
|
{
|
|
LPSTR fullname;
|
|
|
|
/* check this is a valid item */
|
|
if (cur == NULL) {
|
|
return(0);
|
|
}
|
|
|
|
if (!cur->sumvalid) {
|
|
/*
|
|
* need to calculate checksum
|
|
*/
|
|
LONG err;
|
|
|
|
fullname = dir_getopenname(cur);
|
|
cur->checksum = checksum_file(fullname, &err);
|
|
if (err==0) {
|
|
cur->sumvalid = TRUE;
|
|
cur->fileerror = FALSE;
|
|
} else {
|
|
cur->fileerror = TRUE;
|
|
return 0;
|
|
}
|
|
|
|
dir_freeopenname(cur, fullname);
|
|
}
|
|
|
|
return(cur->checksum);
|
|
} /* dir_getchecksum */
|
|
|
|
|
|
|
|
/* return the file size (set during scanning) - returns 0 if invalid */
|
|
long
|
|
dir_getfilesize(
|
|
DIRITEM cur
|
|
)
|
|
{
|
|
/* check this is a valid item */
|
|
if (cur == NULL) {
|
|
return(0);
|
|
}
|
|
|
|
|
|
return(cur->size);
|
|
} /* dir_getfilesize */
|
|
|
|
/* return the file attributes (set during scanning) - returns 0 if invalid */
|
|
DWORD
|
|
dir_getattr(
|
|
DIRITEM cur
|
|
)
|
|
{
|
|
/* check this is a valid item */
|
|
if (cur == NULL) {
|
|
return(0);
|
|
}
|
|
|
|
|
|
return(cur->attr);
|
|
} /* dir_getattr */
|
|
|
|
/* return the file time (last write time) (set during scanning), (0,0) if invalid */
|
|
FILETIME
|
|
dir_GetFileTime(
|
|
DIRITEM cur
|
|
)
|
|
{
|
|
/* return time of (0,0) if this is an invalid item */
|
|
if (cur == NULL) {
|
|
FILETIME ft;
|
|
ft.dwLowDateTime = 0;
|
|
ft.dwHighDateTime = 0;
|
|
return ft;
|
|
}
|
|
|
|
return(cur->ft_lastwrite);
|
|
|
|
} /* dir_GetFileTime */
|
|
|
|
/*
|
|
* extract the portions of a name that match wildcards - for now,
|
|
* we only support wildcards at start and end.
|
|
* if pTag is non-null, then the source will have a tag matching it that
|
|
* can also be ignored.
|
|
*/
|
|
void
|
|
dir_extractwildportions(
|
|
LPSTR pDest,
|
|
LPSTR pSource,
|
|
LPSTR pPattern,
|
|
LPSTR pTag
|
|
)
|
|
{
|
|
int size;
|
|
|
|
/*
|
|
* for now, just support the easy cases where there is a * at beginning or
|
|
* end
|
|
*/
|
|
|
|
if (pPattern[0] == '*') {
|
|
size = lstrlen(pSource) - (lstrlen(pPattern) -1);
|
|
|
|
} else if (pPattern[lstrlen(pPattern) -1] == '*') {
|
|
pSource += lstrlen(pPattern) -1;
|
|
size = lstrlen(pSource);
|
|
} else {
|
|
size = lstrlen(pSource);
|
|
}
|
|
|
|
if (pTag != NULL) {
|
|
size -= lstrlen(pTag);
|
|
}
|
|
|
|
My_mbsncpy(pDest, pSource, size);
|
|
pDest[size] = '\0';
|
|
}
|
|
|
|
/*
|
|
* compares two DIRITEM paths that are both based on wildcards. if the
|
|
* directories match, then the filenames are compared after removing
|
|
* the fixed portion of the name - thus comparing only the
|
|
* wildcard portion.
|
|
*/
|
|
int
|
|
dir_compwildcard(
|
|
DIRLIST dleft,
|
|
DIRLIST dright,
|
|
LPSTR lname,
|
|
LPSTR rname
|
|
)
|
|
{
|
|
LPSTR pfinal1, pfinal2;
|
|
char final1[MAX_PATH], final2[MAX_PATH];
|
|
int res;
|
|
|
|
/*
|
|
* relnames always have at least one backslash
|
|
*/
|
|
pfinal1 = My_mbsrchr(lname, '\\');
|
|
pfinal2 = My_mbsrchr(rname, '\\');
|
|
|
|
My_mbsncpy(final1, lname, (size_t)(pfinal1 - lname));
|
|
final1[pfinal1 - lname] = '\0';
|
|
My_mbsncpy(final2, rname, (size_t)(pfinal2 - rname));
|
|
final2[pfinal2 - rname] = '\0';
|
|
|
|
|
|
/*
|
|
* compare all but the final component - if not the same, then
|
|
* all done.
|
|
*/
|
|
res = utils_CompPath(final1,final2);
|
|
if (res != 0) {
|
|
return(res);
|
|
}
|
|
|
|
// extract just the wildcard-matching portions of the final elements
|
|
dir_extractwildportions(final1, &pfinal1[1], dleft->pPattern, 0);
|
|
dir_extractwildportions(final2, &pfinal2[1], dright->pPattern, 0);
|
|
|
|
return(utils_CompPath(final1, final2));
|
|
}
|
|
|
|
/*
|
|
* compares two DIRLIST items, based on a sequence number rather than filenames.
|
|
*/
|
|
BOOL dir_compsequencenumber(DIRITEM dleft, DIRITEM dright, int *pcmpvalue)
|
|
{
|
|
if (!dleft->sequence && !dright->sequence)
|
|
return FALSE;
|
|
|
|
if (!dleft->sequence)
|
|
*pcmpvalue = -1;
|
|
else if (!dright->sequence)
|
|
*pcmpvalue = 1;
|
|
else
|
|
*pcmpvalue = dleft->sequence - dright->sequence;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* --- file copying ---------------------------------------------------*/
|
|
|
|
static int nLocalCopies; /* cleared in startcopy, ++d in copy
|
|
** inspected in endcopy
|
|
*/
|
|
|
|
/* start a bulk copy */
|
|
BOOL
|
|
dir_startcopy(
|
|
DIRLIST dl
|
|
)
|
|
{
|
|
nLocalCopies = 0;
|
|
return(TRUE);
|
|
} /* dir_startcopy */
|
|
|
|
int
|
|
dir_endcopy(
|
|
DIRLIST dl
|
|
)
|
|
{
|
|
return(nLocalCopies);
|
|
} /* dir_endcopy */
|
|
|
|
/* Build the real path from item and newroot into newpath.
|
|
* Create directories as needed so that it is valid.
|
|
* If mkdir fails, return FALSE, but return the full path that we were
|
|
* trying to make anyway.
|
|
*/
|
|
BOOL
|
|
dir_MakeValidPath(
|
|
LPSTR newpath,
|
|
DIRITEM item,
|
|
LPSTR newroot
|
|
)
|
|
{
|
|
LPSTR relname;
|
|
LPSTR pstart, pdest, pel;
|
|
BOOL bOK = TRUE;
|
|
HRESULT hr;
|
|
|
|
/*
|
|
* name of file relative to the tree root
|
|
*/
|
|
relname = dir_getrelname(item);
|
|
|
|
/*
|
|
* build the new pathname by concatenating the new root and
|
|
* the old relative name. add one path element at a time and
|
|
* ensure that the directory exists, creating it if necessary.
|
|
*/
|
|
hr = StringCchCopy(newpath, MAX_PATH, newroot);
|
|
if (FAILED(hr))
|
|
OutputError(hr, IDS_SAFE_COPY);
|
|
|
|
/* add separating slash if not already there */
|
|
if (*CharPrev(newpath, newpath+lstrlen(newpath)) != '\\') {
|
|
hr = StringCchCat(newpath, MAX_PATH, "\\");
|
|
if (FAILED(hr))
|
|
OutputError(hr, IDS_SAFE_CAT);
|
|
}
|
|
|
|
pstart = relname;
|
|
while ( (pel = My_mbschr(pstart, '\\')) != NULL) {
|
|
|
|
/*
|
|
* ignore .
|
|
*/
|
|
if (My_mbsncmp(pstart, ".\\", 2) != 0) {
|
|
|
|
pdest = &newpath[lstrlen(newpath)];
|
|
|
|
// copy all but the backslash
|
|
// on NT you can create a dir 'fred\'
|
|
// on dos you have to pass 'fred' to _mkdir()
|
|
My_mbsncpy(pdest, pstart, (size_t)(pel - pstart));
|
|
pdest[pel - pstart] = '\0';
|
|
|
|
/* create subdir if necessary */
|
|
if (!dir_isvaliddir(newpath)) {
|
|
if (_mkdir(newpath) != 0) {
|
|
/* note error, but keep going */
|
|
bOK = FALSE;
|
|
}
|
|
}
|
|
|
|
// now insert the backslash
|
|
hr = StringCchCat(pdest, (MAX_PATH - lstrlen(newpath)),"\\");
|
|
if (FAILED(hr))
|
|
OutputError(hr, IDS_SAFE_CAT);
|
|
}
|
|
|
|
/* found another element ending in slash. incr past the \\ */
|
|
pel++;
|
|
|
|
pstart = pel;
|
|
}
|
|
|
|
/*
|
|
* there are no more slashes, so pstart points at the final
|
|
* element
|
|
*/
|
|
hr = StringCchCat(newpath, (MAX_PATH - lstrlen(newpath)), pstart);
|
|
if (FAILED(hr))
|
|
OutputError(hr, IDS_SAFE_CAT);
|
|
dir_freerelname(item, relname);
|
|
return bOK;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* create a copy of the file, in the new root directory.
|
|
* returns TRUE for success and FALSE for failure.
|
|
*/
|
|
BOOL
|
|
dir_copy(
|
|
DIRITEM item,
|
|
LPSTR newroot,
|
|
BOOL HitReadOnly,
|
|
BOOL CopyNoAttributes
|
|
)
|
|
{
|
|
/*
|
|
* newpath must be static for Win 3.1 so that it is in the
|
|
* data segment (near) and not on the stack (far).
|
|
*/
|
|
static char newpath[MAX_PATH];
|
|
BOOL bOK;
|
|
|
|
char msg[MAX_PATH+40];
|
|
BY_HANDLE_FILE_INFORMATION bhfi;
|
|
HANDLE hfile;
|
|
DWORD fa;
|
|
HRESULT hr;
|
|
|
|
/*
|
|
* check that the newroot directory itself exists
|
|
*/
|
|
if ((item == NULL) || !dir_isvaliddir(newroot)) {
|
|
return(FALSE);
|
|
}
|
|
|
|
if (!dir_MakeValidPath(newpath, item, newroot)) return FALSE;
|
|
|
|
/* local copy of file */
|
|
LPSTR pOpenName;
|
|
|
|
pOpenName = dir_getopenname(item);
|
|
|
|
/* if the target file already exists and is readonly,
|
|
* warn the user, and delete if ok (remembering to clear
|
|
* the read-only flag
|
|
*/
|
|
bOK = TRUE;
|
|
fa = GetFileAttributes(newpath);
|
|
if ( (fa != -1) && (fa & FILE_ATTRIBUTE_READONLY)) {
|
|
hr = StringCchPrintf(msg, (MAX_PATH+40), LoadRcString(IDS_IS_READONLY),
|
|
(LPSTR) newpath);
|
|
if (FAILED(hr))
|
|
OutputError(hr, IDS_SAFE_PRINTF);
|
|
|
|
sdkdiff_UI(TRUE);
|
|
if ((HitReadOnly)
|
|
|| (MessageBox(hwndClient, msg, LoadRcString(IDS_COPY_FILES),
|
|
MB_OKCANCEL|MB_ICONSTOP) == IDOK)) {
|
|
sdkdiff_UI(FALSE);
|
|
SetFileAttributes(newpath, fa & ~FILE_ATTRIBUTE_READONLY);
|
|
DeleteFile(newpath);
|
|
} else {
|
|
sdkdiff_UI(FALSE);
|
|
bOK = FALSE; /* don't overwrite */
|
|
// abort the copy... go and release resources
|
|
}
|
|
}
|
|
|
|
if (bOK) {
|
|
bOK = CopyFile(pOpenName, newpath, FALSE);
|
|
}
|
|
// The attributes are copied by CopyFile
|
|
if (bOK) {
|
|
|
|
/* having copied the file, now copy the times */
|
|
hfile = CreateFile(pOpenName, GENERIC_READ, 0, NULL,
|
|
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
|
|
|
GetFileTime(hfile, &bhfi.ftCreationTime,
|
|
&bhfi.ftLastAccessTime, &bhfi.ftLastWriteTime);
|
|
CloseHandle(hfile);
|
|
|
|
hfile = CreateFile(newpath, GENERIC_WRITE, 0, NULL,
|
|
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
|
SetFileTime(hfile, &bhfi.ftCreationTime,
|
|
&bhfi.ftLastAccessTime,
|
|
&bhfi.ftLastWriteTime);
|
|
CloseHandle(hfile);
|
|
|
|
if (CopyNoAttributes) {
|
|
// Prepare to kill the attributes...
|
|
SetFileAttributes(newpath, FILE_ATTRIBUTE_NORMAL);
|
|
}
|
|
}
|
|
if (bOK)
|
|
++nLocalCopies;
|
|
|
|
dir_freeopenname(item, pOpenName);
|
|
|
|
|
|
return(bOK);
|
|
} /* dir_copy */
|
|
|
|
|
|
|
|
/*--- internal functions ---------------------------------------- */
|
|
|
|
/* fill out a new DIRECT for a subdirectory (pre-allocated).
|
|
* init files and dirs lists to empty (List_Create). set the relname
|
|
* of the directory by pre-pending the parent relname if there
|
|
* is a parent, and appending a trailing slash (if there isn't one).
|
|
*/
|
|
void
|
|
dir_dirinit(
|
|
DIRECT dir,
|
|
DIRLIST head,
|
|
DIRECT parent,
|
|
LPSTR name
|
|
)
|
|
{
|
|
int size;
|
|
HRESULT hr;
|
|
|
|
dir->head = head;
|
|
dir->parent = parent;
|
|
|
|
/* add on one for the null and one for the trailing slash */
|
|
size = lstrlen(name) + 2;
|
|
if (parent != NULL) {
|
|
size += lstrlen(parent->relname);
|
|
}
|
|
|
|
/* build the relname from the parent and the current name
|
|
* with a terminating slash
|
|
*/
|
|
dir->relname = (char*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, size);
|
|
if (dir->relname == NULL)
|
|
return;
|
|
if (parent != NULL) {
|
|
hr = StringCchCopy(dir->relname, size,parent->relname);
|
|
if (FAILED(hr))
|
|
OutputError(hr, IDS_SAFE_COPY);
|
|
} else {
|
|
dir->relname[0] = '\0';
|
|
}
|
|
|
|
hr = StringCchCat(dir->relname, size, name);
|
|
if (FAILED(hr))
|
|
OutputError(hr, IDS_SAFE_CAT);
|
|
|
|
if (*CharPrev(dir->relname, dir->relname+lstrlen(dir->relname)) != '\\')
|
|
{
|
|
hr = StringCchCat(dir->relname, size, "\\");
|
|
if (FAILED(hr))
|
|
OutputError(hr, IDS_SAFE_CAT);
|
|
}
|
|
|
|
/* force name to lowercase */
|
|
AnsiLowerBuff(dir->relname, lstrlen(dir->relname));
|
|
|
|
dir->diritems = List_Create();
|
|
dir->directs = List_Create();
|
|
dir->bScanned = FALSE;
|
|
dir->pos = DL_FILES;
|
|
|
|
} /* dir_dirinit */
|
|
|
|
/* initialise the contents of an (allocated) DIRITEM struct. checksum
|
|
* the file if dir->head->bSum is true
|
|
*
|
|
*/
|
|
BOOL
|
|
dir_fileinit(
|
|
DIRITEM pfile,
|
|
DIRECT dir,
|
|
LPSTR path,
|
|
long size,
|
|
FILETIME ft,
|
|
DWORD attr,
|
|
int *psequence
|
|
)
|
|
{
|
|
BOOL bFileOk = TRUE;
|
|
HRESULT hr;
|
|
|
|
pfile->name = (char*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, lstrlen(path) + 1);
|
|
if (pfile->name == NULL)
|
|
return NULL;
|
|
|
|
hr = StringCchCopy(pfile->name, (lstrlen(path)+1), path);
|
|
if (FAILED(hr))
|
|
OutputError(hr, IDS_SAFE_COPY);
|
|
|
|
/*force name to lower case */
|
|
AnsiLowerBuff(pfile->name, lstrlen(path));
|
|
|
|
pfile->direct = dir;
|
|
pfile->size = size;
|
|
pfile->ft_lastwrite = ft;
|
|
pfile->attr = attr;
|
|
|
|
pfile->sequence = psequence ? *psequence : 0;
|
|
|
|
pfile->localname = NULL;
|
|
|
|
if (dir->head->bSum) {
|
|
LONG err;
|
|
LPSTR openname;
|
|
|
|
openname = dir_getopenname(pfile);
|
|
pfile->checksum = checksum_file(openname, &err);
|
|
|
|
if (err!=0) {
|
|
pfile->sumvalid = FALSE;
|
|
} else {
|
|
pfile->sumvalid = TRUE;
|
|
}
|
|
dir_freeopenname(pfile, openname);
|
|
|
|
} else {
|
|
pfile->sumvalid = FALSE;
|
|
}
|
|
|
|
return bFileOk;
|
|
} /* dir_fileinit */
|
|
|
|
|
|
|
|
/* is this a valid file or not */
|
|
BOOL
|
|
dir_isvalidfile(
|
|
LPSTR path
|
|
)
|
|
{
|
|
DWORD dwAttrib;
|
|
|
|
dwAttrib = GetFileAttributes(path);
|
|
if (dwAttrib == -1) {
|
|
return(FALSE);
|
|
}
|
|
if (dwAttrib & FILE_ATTRIBUTE_DIRECTORY) {
|
|
return(FALSE);
|
|
}
|
|
return(TRUE);
|
|
} /* dir_isvalidfile */
|
|
|
|
|
|
/* is this a valid directory ? */
|
|
BOOL
|
|
dir_isvaliddir(
|
|
LPCSTR path
|
|
)
|
|
{
|
|
DWORD dwAttrib;
|
|
|
|
dwAttrib = GetFileAttributes(path);
|
|
if (dwAttrib == -1) {
|
|
return(FALSE);
|
|
}
|
|
if (dwAttrib & FILE_ATTRIBUTE_DIRECTORY) {
|
|
return(TRUE);
|
|
}
|
|
return(FALSE);
|
|
} /* dir_isvaliddir */
|
|
|
|
|
|
|
|
/*
|
|
* scan the directory given. add all files to the list
|
|
* in alphabetic order, and add all directories in alphabetic
|
|
* order to the list of child DIRITEMs. If bRecurse is true, go on to
|
|
* recursive call dir_scan for each of the child DIRITEMs
|
|
*/
|
|
void
|
|
dir_scan(
|
|
DIRECT dir,
|
|
BOOL bRecurse
|
|
)
|
|
{
|
|
PSTR path=NULL, completepath = NULL;
|
|
int size;
|
|
DIRECT child;
|
|
BOOL bMore;
|
|
long filesize;
|
|
FILETIME ft;
|
|
BOOL bIsDir;
|
|
LPSTR name;
|
|
HANDLE hFind;
|
|
WIN32_FIND_DATA finddata;
|
|
HRESULT hr;
|
|
|
|
/* make the complete search string including *.* */
|
|
size = lstrlen(dir->head->rootname);
|
|
size += lstrlen(dir->relname);
|
|
|
|
/* add on one null and \*.* */
|
|
// in fact, we need space for pPattern instead of *.* but add an
|
|
// extra few in case pPattern is less than *.*
|
|
if (dir->head->pPattern != NULL) {
|
|
size += lstrlen(dir->head->pPattern);
|
|
}
|
|
size += 5;
|
|
|
|
path = (PSTR)HeapAlloc(GetProcessHeap(), NULL, size);
|
|
if (path == NULL)
|
|
goto LSkip;
|
|
completepath = (PSTR)HeapAlloc(GetProcessHeap(), NULL, size);
|
|
if (completepath == NULL)
|
|
goto LSkip;
|
|
|
|
if (!path || !completepath)
|
|
goto LSkip;
|
|
|
|
/*
|
|
* fill out path with all but the *.*
|
|
*/
|
|
hr = StringCchCopy(path, size, dir->head->rootname);
|
|
if (FAILED(hr))
|
|
OutputError(hr, IDS_SAFE_COPY);
|
|
|
|
/* omit the . at the beginning of the relname, and the
|
|
* .\ if there is a trailing \ on the rootname
|
|
*/
|
|
if (*CharPrev(path, path+lstrlen(path)) == '\\') {
|
|
hr = StringCchCat(path, size, &dir->relname[2]);
|
|
if (FAILED(hr))
|
|
OutputError(hr, IDS_SAFE_CAT);
|
|
} else {
|
|
hr = StringCchCat(path, size, &dir->relname[1]);
|
|
if (FAILED(hr))
|
|
OutputError(hr, IDS_SAFE_CAT);
|
|
}
|
|
|
|
|
|
/*
|
|
* do this scan twice, once for subdirectories
|
|
* (using *.* as the final element)
|
|
* and the other for files (using the pattern or *.* if none)
|
|
*/
|
|
|
|
hr = StringCchCopy(completepath, size, path);
|
|
if (FAILED(hr))
|
|
OutputError(hr, IDS_SAFE_COPY);
|
|
hr = StringCchCat(completepath, size, "*.*");
|
|
if (FAILED(hr))
|
|
OutputError(hr, IDS_SAFE_CAT);
|
|
|
|
|
|
/*
|
|
* scan for all subdirectories
|
|
*/
|
|
|
|
hFind = FindFirstFile(completepath, &finddata);
|
|
bMore = (hFind != INVALID_HANDLE_VALUE);
|
|
|
|
while (bMore) {
|
|
|
|
bIsDir = (finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
|
|
name = (LPSTR) &finddata.cFileName;
|
|
filesize = finddata.nFileSizeLow;
|
|
if (bIsDir) {
|
|
if ( (lstrcmp(name, ".") != 0) &&
|
|
(lstrcmp(name, "..") != 0)) {
|
|
|
|
if (dir->head->pOtherDirList == NULL) {
|
|
dir_adddirect(dir, name);
|
|
} else {
|
|
char otherName[MAX_PATH+1];
|
|
hr = StringCchCopy(otherName, (MAX_PATH+1), dir_getrootpath(dir->head->pOtherDirList));
|
|
if (FAILED(hr))
|
|
OutputError(hr, IDS_SAFE_COPY);
|
|
if (otherName[strlen(otherName)-1] == '\\') {
|
|
hr = StringCchCat(otherName, (MAX_PATH+1), &dir->relname[2]);
|
|
if (FAILED(hr))
|
|
OutputError(hr, IDS_SAFE_CAT);
|
|
} else {
|
|
hr = StringCchCat(otherName, (MAX_PATH+1), &dir->relname[1]);
|
|
if (FAILED(hr))
|
|
OutputError(hr, IDS_SAFE_CAT);
|
|
}
|
|
hr = StringCchCat(otherName, (MAX_PATH+1), name);
|
|
if (FAILED(hr))
|
|
OutputError(hr, IDS_SAFE_CAT);
|
|
|
|
if (dir_isvaliddir(otherName)) {
|
|
dir_adddirect(dir, name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (bAbort) break; /* User requested abort */
|
|
|
|
bMore = FindNextFile(hFind, &finddata);
|
|
}
|
|
|
|
FindClose(hFind);
|
|
|
|
/*
|
|
* now do it a second time looking for files
|
|
*/
|
|
hr = StringCchCopy(completepath, size, path);
|
|
if (FAILED(hr))
|
|
OutputError(hr, IDS_SAFE_COPY);
|
|
hr = StringCchCat(completepath, size,
|
|
dir->head->pPattern == NULL ? "*.*" : dir->head->pPattern);
|
|
if (FAILED(hr))
|
|
OutputError(hr, IDS_SAFE_CAT);
|
|
|
|
/* read all file entries in the directory */
|
|
hFind = FindFirstFile(completepath, &finddata);
|
|
bMore = (hFind != INVALID_HANDLE_VALUE);
|
|
|
|
while (bMore) {
|
|
if (bAbort) break; /* user requested abort */
|
|
|
|
bIsDir = (finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
|
|
name = (LPSTR) &finddata.cFileName;
|
|
filesize = finddata.nFileSizeLow;
|
|
ft = finddata.ftLastWriteTime;
|
|
if (!bIsDir) {
|
|
dir_addfile(dir, name, filesize, ft, finddata.dwFileAttributes, 0);
|
|
}
|
|
bMore = FindNextFile(hFind, &finddata);
|
|
}
|
|
|
|
FindClose(hFind);
|
|
|
|
LSkip:
|
|
HeapFree(GetProcessHeap(), NULL, path);
|
|
HeapFree(GetProcessHeap(), NULL, completepath);
|
|
|
|
dir->bScanned = TRUE;
|
|
dir->pos = DL_FILES;
|
|
|
|
if (bRecurse) {
|
|
for( child=(DIRECT)List_First(dir->directs); child!=NULL; child = (DIRECT)List_Next((LPVOID)child)) {
|
|
if (bAbort) break; /* user requested abort */
|
|
dir_scan(child, TRUE);
|
|
}
|
|
}
|
|
|
|
} /* dir_scan */
|
|
|
|
|
|
/*
|
|
* add the file 'path' to the list of files in dir, in order.
|
|
*
|
|
* checksum the file if dir->head->bSum is true
|
|
*
|
|
*/
|
|
BOOL
|
|
dir_addfile(
|
|
DIRECT dir,
|
|
LPSTR path,
|
|
DWORD size,
|
|
FILETIME ft,
|
|
DWORD attr,
|
|
int *psequence
|
|
)
|
|
{
|
|
DIRITEM pfile;
|
|
|
|
AnsiLowerBuff(path, lstrlen(path));
|
|
|
|
// when psequence is passed, do not sort the list
|
|
if (!psequence)
|
|
{
|
|
for( pfile=(DIRITEM)List_Last(dir->diritems); pfile!=NULL; pfile = (DIRITEM)List_Prev((LPVOID)pfile)) {
|
|
if (utils_CompPath(pfile->name, path) <= 0) {
|
|
break; /* goes after this one */
|
|
}
|
|
}
|
|
/* goes after pfile, NULL => goes at start */
|
|
pfile = (DIRITEM)List_NewAfter(dir->diritems, pfile, sizeof(struct diritem));
|
|
}
|
|
else
|
|
{
|
|
/* append to end -- psequence implies that we are being called in
|
|
* sequence order and do not need to do any further sorting.
|
|
*/
|
|
pfile = (DIRITEM)List_NewBefore(dir->diritems, NULL, sizeof(struct diritem));
|
|
}
|
|
|
|
if (!dir_fileinit(pfile, dir, path, size, ft, attr, psequence))
|
|
{
|
|
List_Delete(pfile);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
} /* dir_addfile */
|
|
|
|
|
|
/* add a new directory in alphabetic order on
|
|
* the list dir->directs
|
|
*
|
|
*/
|
|
void
|
|
dir_adddirect(
|
|
DIRECT dir,
|
|
LPSTR path
|
|
)
|
|
{
|
|
DIRECT child;
|
|
LPSTR finalel;
|
|
char achTempName[MAX_PATH];
|
|
|
|
AnsiLowerBuff(path, lstrlen(path));
|
|
for( child=(DIRECT)List_First(dir->directs); child!=NULL; child = (DIRECT)List_Next((LPVOID)child)) {
|
|
int cmpval;
|
|
|
|
/* we need to compare the child name with the new name.
|
|
* the child name is a relname with a trailing
|
|
* slash - so compare only the name up to but
|
|
* not including the final slash.
|
|
*/
|
|
finalel = dir_finalelem(child->relname);
|
|
|
|
/*
|
|
* we cannot use strnicmp since this uses a different
|
|
* collating sequence to lstrcmpi. So copy the portion
|
|
* we are interested in to a null-term. buffer.
|
|
*/
|
|
My_mbsncpy(achTempName, finalel, lstrlen(finalel)-1);
|
|
achTempName[lstrlen(finalel)-1] = '\0';
|
|
|
|
cmpval = utils_CompPath(achTempName, path);
|
|
|
|
#ifdef trace
|
|
{
|
|
HRESULT hr;
|
|
char msg[600];
|
|
hr = StringCchPrintf( msg, 600, "dir_adddirect: %s %s %s\n"
|
|
, achTempName
|
|
, ( cmpval<0 ? "<"
|
|
: (cmpval==0 ? "=" : ">")
|
|
)
|
|
, path
|
|
);
|
|
if (FAILED(hr))
|
|
OutputError(hr, IDS_SAFE_PRINTF);
|
|
if (bTrace) Trace_File(msg);
|
|
}
|
|
#endif
|
|
if (cmpval > 0) {
|
|
|
|
/* goes before this one */
|
|
child = (DIRECT)List_NewBefore(dir->directs, child, sizeof(struct direct));
|
|
dir_dirinit(child, dir->head, dir, path);
|
|
return;
|
|
}
|
|
}
|
|
/* goes at end */
|
|
child = (DIRECT)List_NewLast(dir->directs, sizeof(struct direct));
|
|
dir_dirinit(child, dir->head, dir, path);
|
|
} /* dir_adddirect */
|
|
|
|
|
|
/* free all memory associated with a DIRECT (including freeing
|
|
* child lists). Don't de-alloc the direct itself (allocated on a list)
|
|
*/
|
|
void
|
|
dir_cleardirect(
|
|
DIRECT dir
|
|
)
|
|
{
|
|
DIRITEM pfile;
|
|
DIRECT child;
|
|
|
|
/* clear contents of files list */
|
|
for( pfile=(DIRITEM)List_First(dir->diritems); pfile!=NULL; pfile = (DIRITEM)List_Next((LPVOID)pfile)) {
|
|
HeapFree(GetProcessHeap(), NULL, pfile->name);
|
|
|
|
if (pfile->localname) {
|
|
if (pfile->bLocalIsTemp) {
|
|
/*
|
|
* the copy will have copied the attributes,
|
|
* including read-only. We should unset this bit
|
|
* so we can delete the temp file.
|
|
*/
|
|
SetFileAttributes(pfile->localname,
|
|
GetFileAttributes(pfile->localname)
|
|
& ~FILE_ATTRIBUTE_READONLY);
|
|
DeleteFile(pfile->localname);
|
|
}
|
|
|
|
HeapFree(GetProcessHeap(), NULL, pfile->localname);
|
|
pfile->localname = NULL;
|
|
}
|
|
}
|
|
List_Destroy(&dir->diritems);
|
|
|
|
/* clear contents of dirs list (recursively) */
|
|
for( child=(DIRECT)List_First(dir->directs); child!=NULL; child = (DIRECT)List_Next((LPVOID)child)) {
|
|
dir_cleardirect(child);
|
|
}
|
|
List_Destroy(&dir->directs);
|
|
|
|
HeapFree(GetProcessHeap(), NULL, dir->relname);
|
|
|
|
} /* dir_cleardirect */
|
|
|
|
|
|
|
|
/*
|
|
* return a pointer to the final element in a path. note that
|
|
* we may be passed relnames with a trailing final slash - ignore this
|
|
* and return the element before that final slash.
|
|
*/
|
|
LPSTR
|
|
dir_finalelem(
|
|
LPSTR path
|
|
)
|
|
{
|
|
LPSTR chp;
|
|
int size;
|
|
|
|
/* is the final character a slash ? */
|
|
size = lstrlen(path) - 1;
|
|
if (*(chp = CharPrev(path, path+lstrlen(path))) == '\\') {
|
|
/* find the slash before this */
|
|
while (chp > path) {
|
|
if (*(chp = CharPrev(path, chp)) == '\\') {
|
|
/* skip the slash itself */
|
|
chp++;
|
|
break;
|
|
}
|
|
}
|
|
return(chp);
|
|
}
|
|
/* look for final slash */
|
|
chp = My_mbsrchr(path, '\\');
|
|
if (chp != NULL) {
|
|
return(chp+1);
|
|
}
|
|
|
|
/* no slash - is there a drive letter ? */
|
|
chp = My_mbsrchr(path, ':');
|
|
if (chp != NULL) {
|
|
return(chp+1);
|
|
}
|
|
|
|
/* this is a final-element anyway */
|
|
return(path);
|
|
|
|
} /* dir_finalelem */
|
|
|
|
|
|
|
|
/* find the size of a file given a pathname to it */
|
|
long
|
|
dir_getpathsizeetc(
|
|
LPSTR path,
|
|
FILETIME *pft,
|
|
DWORD *pattr
|
|
)
|
|
{
|
|
HANDLE fh;
|
|
long size;
|
|
|
|
fh = CreateFile(path, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
|
|
if (fh == INVALID_HANDLE_VALUE)
|
|
{
|
|
HANDLE hFind;
|
|
WIN32_FIND_DATA finddata;
|
|
|
|
hFind = FindFirstFile(path, &finddata);
|
|
if (hFind == INVALID_HANDLE_VALUE)
|
|
{
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
FindClose(hFind);
|
|
if (pft)
|
|
*pft = finddata.ftLastWriteTime;
|
|
if (pattr != NULL)
|
|
*pattr = finddata.dwFileAttributes;
|
|
return finddata.nFileSizeLow;
|
|
}
|
|
}
|
|
|
|
size = GetFileSize(fh, NULL);
|
|
if (pft)
|
|
GetFileTime(fh, NULL, NULL, pft);
|
|
if (pattr != NULL)
|
|
*pattr = GetFileAttributes(path);
|
|
CloseHandle(fh);
|
|
return(size);
|
|
} /* dir_getpathsize */
|
|
|
|
/* ---- helpers ----------------------------------------------------------- */
|
|
|
|
BOOL iswildpath(LPCSTR pszPath)
|
|
{
|
|
if (strchr(pszPath, '*') || strchr(pszPath, '?'))
|
|
return TRUE;
|
|
|
|
if (!(pszPath[0] && pszPath[0] == '/' && pszPath[1] && pszPath[1] == '/'))
|
|
{
|
|
DWORD dw;
|
|
|
|
dw = GetFileAttributes(pszPath);
|
|
if (dw != (DWORD)-1 && (dw & FILE_ATTRIBUTE_DIRECTORY))
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|