IO_METHOD(IoDirectory, createSubdirectory) { /*doc Directory createSubdirectory(name) Create a subdirectory with the specified name. */ IoState *state = IOSTATE; IoSymbol *subfolderName = IoMessage_locals_symbolArgAt_(m, locals, 0); IoObject *currentItem = IoDirectory_justAt(self, subfolderName); if (ISDIRECTORY(currentItem)) { return currentItem; } if (ISFILE(currentItem)) { IoState_error_(IOSTATE, m, "Attempt to create directory %s on top of existing file", CSTRING(subfolderName)); } else { IoSymbol *fullPath = IoDirectory_justFullPath(self, subfolderName); MKDIR(CSTRING(fullPath), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); return IoDirectory_newWithPath_(state, fullPath); } return IONIL(self); }
IO_METHOD(IoFile, reopen) { /*doc File reopen(otherFile, mode) Reopens otherFile and redirects its stream to this file's path using mode. If mode is omitted, it is copied from otherFile. Returns self or raises a File exception on error. */ IoFile *otherFile; IoSeq *mode; DATA(self)->flags = IOFILE_FLAGS_NONE; IoMessage_assertArgCount_receiver_(m, 1, self); otherFile = IoMessage_locals_valueArgAt_(m, locals, 0); IOASSERT(ISFILE(otherFile), "arg must be a File"); mode = IoMessage_locals_valueArgAt_(m, locals, 1); if(ISSEQ(mode)) { DATA(self)->mode = IOREF(mode); } else { DATA(self)->mode = IOREF(IoSeq_newWithUArray_copy_(IOSTATE, (UArray *)DATA(DATA(otherFile)->mode), 1)); } if (!DATA(self)->stream) { FILE *fp = freopen(UTF8CSTRING(DATA(self)->path), CSTRING(DATA(self)->mode), DATA(otherFile)->stream); if (fp) { DATA(self)->stream = fp; } else { printf("%i:%s\n", errno, strerror(errno)); IoState_error_(IOSTATE, m, "unable to reopen to file '%s' with mode %s.", UTF8CSTRING(DATA(self)->path), CSTRING(DATA(self)->mode)); fclose(fp); } } return self; }
// ---------------------------------------------------------------------- // Get file snap shot // ---------------------------------------------------------------------- VOID GetFilesSnap(LPSNAPSHOT lpShot, LPTSTR lpszFullName, LPFILECONTENT lpFatherFC, LPFILECONTENT *lplpCaller) { LPFILECONTENT lpFC; HANDLE hFile; BOOL calculateHash = TRUE; BOOL found = FALSE; // Full dir/file name is already given // Extra local block to reduce stack usage due to recursive calls { LPTSTR lpszFindFileName; lpszFindFileName = NULL; // Get father file data if not already provided (=called from FileShot) // lpFatherFC only equals "C:\" and is called once at start if (NULL == lpFatherFC) { // Check if file is to be GENERIC excluded if ((NULL == lpszFullName) || (((TCHAR)'.' == lpszFullName[0]) // fast exclusion for 99% of the cases && ((0 == _tcscmp(lpszFullName, TEXT("."))) || (0 == _tcscmp(lpszFullName, TEXT("..")))))) { return; } // Create new file content lpFatherFC = MYALLOC0(sizeof(FILECONTENT)); // Set file name length lpFatherFC->cchFileName = _tcslen(lpszFullName); // Copy file name to new buffer for directory search and more lpszFindFileName = MYALLOC((lpFatherFC->cchFileName + 4 + 1) * sizeof(TCHAR)); // +4 for "\*.*" search when directory (later in routine) _tcscpy(lpszFindFileName, lpszFullName); // Special case if root dir of a drive was specified, needs trailing backslash otherwise current dir of that drive is used if ((TCHAR)':' == lpszFindFileName[lpFatherFC->cchFileName - 1]) { lpszFindFileName[lpFatherFC->cchFileName] = (TCHAR)'\\'; lpszFindFileName[lpFatherFC->cchFileName + 1] = (TCHAR)'\0'; } //printf("lpszFullName: %ws\n", lpszFullName); //printf("lpszFindFileName: %ws\n", lpszFindFileName); hFile = FindFirstFile(lpszFullName, &FindData); if (INVALID_HANDLE_VALUE != hFile) { FindClose(hFile); } else { // Workaround for some cases in Windows Vista and later ZeroMemory(&FindData, sizeof(FindData)); hFile = CreateFile(lpszFullName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); if (INVALID_HANDLE_VALUE != hFile) { BY_HANDLE_FILE_INFORMATION FileInformation; BOOL bResult; bResult = GetFileInformationByHandle(hFile, &FileInformation); if (bResult) { FindData.dwFileAttributes = FileInformation.dwFileAttributes; FindData.ftCreationTime = FileInformation.ftCreationTime; FindData.ftLastAccessTime = FileInformation.ftLastAccessTime; FindData.ftLastWriteTime = FileInformation.ftLastWriteTime; FindData.nFileSizeHigh = FileInformation.nFileSizeHigh; FindData.nFileSizeLow = FileInformation.nFileSizeLow; } else { FindData.dwFileAttributes = GetFileAttributes(lpszFullName); if (INVALID_FILE_ATTRIBUTES == FindData.dwFileAttributes) { FindData.dwFileAttributes = 0; } bResult = GetFileTime(hFile, &FindData.ftCreationTime, &FindData.ftLastAccessTime, &FindData.ftLastWriteTime); if (!bResult) { FindData.ftCreationTime.dwLowDateTime = 0; FindData.ftCreationTime.dwHighDateTime = 0; FindData.ftLastAccessTime.dwLowDateTime = 0; FindData.ftLastAccessTime.dwHighDateTime = 0; FindData.ftLastWriteTime.dwLowDateTime = 0; FindData.ftLastWriteTime.dwHighDateTime = 0; } FindData.nFileSizeLow = GetFileSize(hFile, &FindData.nFileSizeHigh); if (INVALID_FILE_SIZE == FindData.nFileSizeLow) { FindData.nFileSizeHigh = 0; FindData.nFileSizeLow = 0; } } CloseHandle(hFile); } } // Remove previously added backslash (if any) lpszFindFileName[lpFatherFC->cchFileName] = (TCHAR)'\0'; // Copy pointer to current file into caller's pointer if (NULL != lplpCaller) { *lplpCaller = lpFatherFC; } // Increase dir/file count if (ISFILE(FindData.dwFileAttributes)) { lpShot->stCounts.cFiles++; } else { lpShot->stCounts.cDirs++; } // Copy file name lpFatherFC->lpszFileName = MYALLOC((lpFatherFC->cchFileName + 1) * sizeof(TCHAR)); _tcscpy(lpFatherFC->lpszFileName, lpszFullName); // Copy file data lpFatherFC->nWriteDateTimeLow = FindData.ftLastWriteTime.dwLowDateTime; lpFatherFC->nWriteDateTimeHigh = FindData.ftLastWriteTime.dwHighDateTime; lpFatherFC->nAccessDateTimeLow = FindData.ftLastWriteTime.dwLowDateTime; lpFatherFC->nAccessDateTimeHigh = FindData.ftLastWriteTime.dwHighDateTime; lpFatherFC->nFileSizeLow = FindData.nFileSizeLow; lpFatherFC->nFileSizeHigh = FindData.nFileSizeHigh; lpFatherFC->nFileAttributes = FindData.dwFileAttributes; // Set "lpFirstSubFC" pointer for storing the first child's pointer lplpCaller = &lpFatherFC->lpFirstSubFC; } // If father is a file, then leave (=special case when called from FileShot) if (ISFILE(lpFatherFC->nFileAttributes)) { if (NULL != lpszFindFileName) { MYFREE(lpszFindFileName); } return; } // Process all entries of directory // a) Create search pattern and start search if (NULL == lpszFindFileName) { lpszFindFileName = lpszFullName; } _tcscat(lpszFindFileName, TEXT("\\*.*")); hFile = FindFirstFile(lpszFindFileName, &FindData); if (lpszFindFileName != lpszFullName) { MYFREE(lpszFindFileName); } } // End of extra local block if (INVALID_HANDLE_VALUE == hFile) { // error: nothing in dir, no access, etc. //printf(">>> ERROR: fileshot.c: getFileSnap: INVALID_HANDLE_VALUE: %ws\n", lpszFullName); return; } // b) process entry then find next do { lpszFullName = NULL; //BOOL calculateHash = TRUE; //BOOL found = FALSE; // Check if file is to be GENERIC excluded (dot dirs) if ((NULL == FindData.cFileName) || (((TCHAR)'.' == FindData.cFileName[0]) // fast exclusion for 99% of the cases && ((0 == _tcscmp(FindData.cFileName, TEXT("."))) || (0 == _tcscmp(FindData.cFileName, TEXT("..")))))) { continue; // ignore this entry and continue with next file } // Create new file content lpFC = MYALLOC0(sizeof(FILECONTENT)); // Set father of current key lpFC->lpFatherFC = lpFatherFC; // Set file name length lpFC->cchFileName = _tcslen(FindData.cFileName); // Allocate memory copy file name to FILECONTENT lpFC->lpszFileName = MYALLOC0((lpFC->cchFileName + 1) * sizeof(TCHAR)); _tcscpy(lpFC->lpszFileName, FindData.cFileName); // Static blacklist for directories if (ISDIR(FindData.dwFileAttributes)) { if (performStaticBlacklisting) { LPTSTR lpszFullPath; lpszFullPath = GetWholeFileName(lpFC, 4); found = TrieSearchPath(blacklistDIRS->children, lpszFullPath); if (found) { lpShot->stCounts.cFilesBlacklist++; MYFREE(lpszFullPath); FreeAllFileContents(lpFC); // Increase value count for display purposes lpShot->stCounts.cDirsBlacklist++; // Ignore this entry and continue with next brother value continue; } else { MYFREE(lpszFullPath); } } } // Check if the file system entry is a symbolic link // If so, skip as it actually resides somewhere else on the file system! if (ISSYM(FindData.dwFileAttributes)) { if (ISFILE(FindData.dwFileAttributes)) { lpShot->stCounts.cFilesBlacklist++; } else { lpShot->stCounts.cDirsBlacklist++; } continue; } // Blacklisting implementation for files if (ISFILE(FindData.dwFileAttributes)) { if (dwBlacklist == 1) { // First snapshot, therefore populate the Trie (Prefix Tree) // Get the full file path LPTSTR lpszFullPath; lpszFullPath = GetWholeFileName(lpFC, 4); // Add full path to file blacklist prefix tree, then free path TrieAdd(&blacklistFILES, lpszFullPath); MYFREE(lpszFullPath); // Increase value count for display purposes lpShot->stCounts.cFiles++; // Ignore this entry and continue with next brother value //continue; } else if (dwBlacklist == 2) { // Not the first snapshot, so either: // 1) If blacklisting enabled: Ignore file if its in the blacklist // 2) If hashing enabled (and not blacklisting): Mark file as not to be hashed LPTSTR lpszFullPath; lpszFullPath = GetWholeFileName(lpFC, 4); found = TrieSearchPath(blacklistFILES->children, lpszFullPath); if (found) { if (performDynamicBlacklisting) { MYFREE(lpszFullPath); FreeAllFileContents(lpFC); // Increase value count for display purposes lpShot->stCounts.cFilesBlacklist++; // Ignore this entry and continue with next brother value continue; } if (performSHA1Hashing || performMD5Hashing) { lpShot->stCounts.cFiles++; MYFREE(lpszFullPath); calculateHash = FALSE; } } else { MYFREE(lpszFullPath); } } } // Copy pointer to current file into caller's pointer if (NULL != lplpCaller) { *lplpCaller = lpFC; } // Increase dir/file count if (ISFILE(FindData.dwFileAttributes)) { lpShot->stCounts.cFiles++; } else { lpShot->stCounts.cDirs++; } // Copy file data lpFC->nWriteDateTimeLow = FindData.ftLastWriteTime.dwLowDateTime; lpFC->nWriteDateTimeHigh = FindData.ftLastWriteTime.dwHighDateTime; lpFC->nAccessDateTimeLow = FindData.ftLastWriteTime.dwLowDateTime; lpFC->nAccessDateTimeHigh = FindData.ftLastWriteTime.dwHighDateTime; lpFC->nFileSizeLow = FindData.nFileSizeLow; lpFC->nFileSizeHigh = FindData.nFileSizeHigh; lpFC->nFileAttributes = FindData.dwFileAttributes; // Calculate file hash (computationally intensive!) // This should only be executed in the following scenarios: // 1) If the file system entry is a data file // 2) If the data file is not blacklisted (previously known) if (ISFILE(FindData.dwFileAttributes)) { if (dwBlacklist == 2 && calculateHash) { if (performSHA1Hashing) { lpFC->cchSHA1 = 40; lpFC->lpszSHA1 = MYALLOC((lpFC->cchSHA1 + 1) * sizeof(TCHAR)); _tcscpy(lpFC->lpszSHA1, CalculateSHA1(GetWholeFileName(lpFC, 4))); } if (performMD5Hashing) { lpFC->cchMD5 = 32; lpFC->lpszMD5 = MYALLOC((lpFC->cchMD5 + 1) * sizeof(TCHAR)); _tcscpy(lpFC->lpszMD5, CalculateMD5(GetWholeFileName(lpFC, 4))); } if (performMD5BlockHashing) { LPMD5BLOCK theMD5Block = CalculateMD5Blocks(GetWholeFileName(lpFC, 4), dwHashBlockSize); lpFC->lpMD5Block = MYALLOC0(sizeof(MD5BLOCK)); lpFC->lpMD5Block = theMD5Block; } } } // Print file system shot status if ((dwBlacklist == 2 && performDynamicBlacklisting) || (performStaticBlacklisting)) { printf(" > Dirs: %d Files: %d Blacklisted Dirs: %d Blacklisted Files: %d\r", lpShot->stCounts.cDirs, lpShot->stCounts.cFiles, lpShot->stCounts.cDirsBlacklist, lpShot->stCounts.cFilesBlacklist); } else { printf(" > Dirs: %d Files: %d\r", lpShot->stCounts.cDirs, lpShot->stCounts.cFiles); } // ATTENTION!!! FindData will be INVALID from this point on, due to recursive calls // If the entry is a directory, then do a recursive call for it // Pass this entry as father and "lpFirstSubFC" pointer for storing the first child's pointer if (ISDIR(lpFC->nFileAttributes)) { if (NULL == lpszFullName) { lpszFullName = GetWholeFileName(lpFC, 4); // +4 for "\*.*" search (in recursive call) } GetFilesSnap(lpShot, lpszFullName, lpFC, &lpFC->lpFirstSubFC); } if (NULL != lpszFullName) { MYFREE(lpszFullName); } // Set "lpBrotherFC" pointer for storing the next brother's pointer lplpCaller = &lpFC->lpBrotherFC; } while (FindNextFile(hFile, &FindData)); FindClose(hFile); }
//------------------------------------------------------------- // File comparison engine //------------------------------------------------------------- VOID CompareFiles(LPFILECONTENT lpStartFC1, LPFILECONTENT lpStartFC2) { LPFILECONTENT lpFC1; LPFILECONTENT lpFC2; // Compare dirs/files for (lpFC1 = lpStartFC1; NULL != lpFC1; lpFC1 = lpFC1->lpBrotherFC) { if (ISFILE(lpFC1->nFileAttributes)) { CompareResult.stcCompared.cFiles++; } else { CompareResult.stcCompared.cDirs++; } // Find a matching dir/file for FC1 for (lpFC2 = lpStartFC2; NULL != lpFC2; lpFC2 = lpFC2->lpBrotherFC) { // skip FC2 if already matched if (NOMATCH != lpFC2->fFileMatch) { continue; } // skip FC2 if types do *not* match (even if same name then interpret as deleted+added) if (ISFILE(lpFC1->nFileAttributes) != ISFILE(lpFC2->nFileAttributes)) { continue; } // skip FC2 if names do *not* match if ((NULL == lpFC1->lpszFileName) || (NULL == lpFC2->lpszFileName) || (0 != _tcsicmp(lpFC1->lpszFileName, lpFC2->lpszFileName))) { // 1.8.2 from lstrcmp to strcmp // 1.9.0 to _tcsicmp continue; } // Same file type and (case-insensitive) name of FC1 found in FC2, so compare their attributes and if applicable their dates and sizes if (ISFILE(lpFC1->nFileAttributes)) { // Both are files if ((lpFC1->nWriteDateTimeLow == lpFC2->nWriteDateTimeLow) && (lpFC1->nWriteDateTimeHigh == lpFC2->nWriteDateTimeHigh) && (lpFC1->nFileSizeLow == lpFC2->nFileSizeLow) && (lpFC1->nFileSizeHigh == lpFC2->nFileSizeHigh) && (lpFC1->nFileAttributes == lpFC2->nFileAttributes)) { // Same file of FC1 found in FC2 lpFC2->fFileMatch = ISMATCH; } else { // File data differ, so file is modified lpFC2->fFileMatch = ISMODI; CompareResult.stcChanged.cFiles++; CompareResult.stcModified.cFiles++; CreateNewResult(FILEMODI, lpFC1, lpFC2); } } else { // Both are dirs if (lpFC1->nFileAttributes == lpFC2->nFileAttributes) { // Same dir of FC1 found in FC2 lpFC2->fFileMatch = ISMATCH; } else { // Dir data differ, so dir is modified lpFC2->fFileMatch = ISMODI; CompareResult.stcChanged.cDirs++; CompareResult.stcModified.cDirs++; CreateNewResult(DIRMODI, lpFC1, lpFC2); } // Compare sub files if any if ((NULL != lpFC1->lpFirstSubFC) || (NULL != lpFC2->lpFirstSubFC)) { CompareFiles(lpFC1->lpFirstSubFC, lpFC2->lpFirstSubFC); } } break; } if (NULL == lpFC2) { // FC1 has no matching FC2, so FC1 is a deleted dir/file if (ISFILE(lpFC1->nFileAttributes)) { CompareResult.stcChanged.cFiles++; CompareResult.stcDeleted.cFiles++; CreateNewResult(FILEDEL, lpFC1, NULL); } else { CompareResult.stcChanged.cDirs++; CompareResult.stcDeleted.cDirs++; CreateNewResult(DIRDEL, lpFC1, NULL); // "Compare"/Log sub files if any if (NULL != lpFC1->lpFirstSubFC) { CompareFiles(lpFC1->lpFirstSubFC, NULL); } } } } // After looping all FC1 files, do an extra loop over all FC2 files and check previously set match flags to determine added dirs/files for (lpFC2 = lpStartFC2; NULL != lpFC2; lpFC2 = lpFC2->lpBrotherFC) { // skip FC2 if already matched if (NOMATCH != lpFC2->fFileMatch) { continue; } // FC2 has no matching FC1, so FC2 is an added dir/file if (ISFILE(lpFC2->nFileAttributes)) { CompareResult.stcCompared.cFiles++; CompareResult.stcChanged.cFiles++; CompareResult.stcAdded.cFiles++; CreateNewResult(FILEADD, NULL, lpFC2); } else { CompareResult.stcCompared.cDirs++; CompareResult.stcChanged.cDirs++; CompareResult.stcAdded.cDirs++; CreateNewResult(DIRADD, NULL, lpFC2); // "Compare"/Log sub files if any if (NULL != lpFC2->lpFirstSubFC) { CompareFiles(NULL, lpFC2->lpFirstSubFC); } } } }