¿Cómo puedo detectar solo los archivos eliminados, modificados y creados en un volumen?

Necesito saber si hay una manera fácil de detectar solo los archivos que fueron eliminados, modificados o creados en un volumen NTFS.

He escrito un progtwig para copias de seguridad externas en C ++. Después de la primera copia de seguridad, compruebo el bit de archivo de cada archivo para ver si se realizó algún cambio y hago una copia de seguridad solo de los archivos que se cambiaron. Además, realiza una copia de seguridad de la instantánea de VSS para evitar lockings de archivos.

Esto parece funcionar bien en la mayoría de los sistemas de archivos, pero para algunos con muchos archivos y directorios, este proceso lleva demasiado tiempo y, a menudo, la copia de seguridad tarda más de un día en finalizar la copia de seguridad.

Traté de usar el diario de cambios para detectar fácilmente los cambios realizados en un volumen NTFS, pero el diario de cambios mostraría muchos registros, la mayoría de ellos relacionados con pequeños archivos temporales creados y destruidos. Además, pude obtener el nombre del archivo, el número de referencia del archivo y el número de referencia del archivo principal, pero no pude obtener la ruta completa del archivo. El número de referencia del archivo principal se supone que debe darle la ruta del directorio principal.

EDITAR: Esto debe ejecutarse todos los días, por lo que al comienzo de cada exploración, debe registrar solo los cambios que tuvieron lugar desde el último escaneo. O al menos, debería haber una forma de decir los cambios desde el momento y la fecha.

Puede enumerar todos los archivos en un volumen usando FSCTL_ENUM_USN_DATA. Este es un proceso rápido (mis pruebas arrojaron mejores resultados que 6000 registros por segundo incluso en una máquina muy antigua, y 20000+ es más típico) y solo incluye los archivos que existen actualmente.

Los datos devueltos incluyen los indicadores de archivos y los USN para que pueda verificar los cambios de la forma que prefiera.

Aún necesitará calcular la ruta completa de los archivos al hacer coincidir las ID principales con las ID de los archivos de los directorios. Un enfoque sería utilizar un búfer lo suficientemente grande como para contener todos los registros de archivos simultáneamente, y buscar entre los registros para encontrar el padre correspondiente para cada archivo que necesita respaldar. Para grandes volúmenes, probablemente necesite procesar los registros del directorio en una estructura de datos más eficiente, tal vez una tabla hash.

Alternativamente, puede leer / volver a leer los registros de los directorios principales según sea necesario. Esto sería menos eficiente, pero el rendimiento podría ser satisfactorio dependiendo de la cantidad de archivos que se están respaldando. Windows parece almacenar en caché los datos devueltos por FSCTL_ENUM_USN_DATA.

Este progtwig busca en el volumen C los archivos llamados test.txt y devuelve información sobre los archivos encontrados, así como sobre sus directorios principales.

#include  #include  #define BUFFER_SIZE (1024 * 1024) HANDLE drive; USN maxusn; void show_record (USN_RECORD * record) { void * buffer; MFT_ENUM_DATA mft_enum_data; DWORD bytecount = 1; USN_RECORD * parent_record; WCHAR * filename; WCHAR * filenameend; printf("=================================================================\n"); printf("RecordLength: %u\n", record->RecordLength); printf("MajorVersion: %u\n", (DWORD)record->MajorVersion); printf("MinorVersion: %u\n", (DWORD)record->MinorVersion); printf("FileReferenceNumber: %lu\n", record->FileReferenceNumber); printf("ParentFRN: %lu\n", record->ParentFileReferenceNumber); printf("USN: %lu\n", record->Usn); printf("Timestamp: %lu\n", record->TimeStamp); printf("Reason: %u\n", record->Reason); printf("SourceInfo: %u\n", record->SourceInfo); printf("SecurityId: %u\n", record->SecurityId); printf("FileAttributes: %x\n", record->FileAttributes); printf("FileNameLength: %u\n", (DWORD)record->FileNameLength); filename = (WCHAR *)(((BYTE *)record) + record->FileNameOffset); filenameend= (WCHAR *)(((BYTE *)record) + record->FileNameOffset + record->FileNameLength); printf("FileName: %.*ls\n", filenameend - filename, filename); buffer = VirtualAlloc(NULL, BUFFER_SIZE, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); if (buffer == NULL) { printf("VirtualAlloc: %u\n", GetLastError()); return; } mft_enum_data.StartFileReferenceNumber = record->ParentFileReferenceNumber; mft_enum_data.LowUsn = 0; mft_enum_data.HighUsn = maxusn; if (!DeviceIoControl(drive, FSCTL_ENUM_USN_DATA, &mft_enum_data, sizeof(mft_enum_data), buffer, BUFFER_SIZE, &bytecount, NULL)) { printf("FSCTL_ENUM_USN_DATA (show_record): %u\n", GetLastError()); return; } parent_record = (USN_RECORD *)((USN *)buffer + 1); if (parent_record->FileReferenceNumber != record->ParentFileReferenceNumber) { printf("=================================================================\n"); printf("Couldn't retrieve FileReferenceNumber %u\n", record->ParentFileReferenceNumber); return; } show_record(parent_record); } void check_record(USN_RECORD * record) { WCHAR * filename; WCHAR * filenameend; filename = (WCHAR *)(((BYTE *)record) + record->FileNameOffset); filenameend= (WCHAR *)(((BYTE *)record) + record->FileNameOffset + record->FileNameLength); if (filenameend - filename != 8) return; if (wcsncmp(filename, L"test.txt", 8) != 0) return; show_record(record); } int main(int argc, char ** argv) { MFT_ENUM_DATA mft_enum_data; DWORD bytecount = 1; void * buffer; USN_RECORD * record; USN_RECORD * recordend; USN_JOURNAL_DATA * journal; DWORDLONG nextid; DWORDLONG filecount = 0; DWORD starttick, endtick; starttick = GetTickCount(); printf("Allocating memory.\n"); buffer = VirtualAlloc(NULL, BUFFER_SIZE, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); if (buffer == NULL) { printf("VirtualAlloc: %u\n", GetLastError()); return 0; } printf("Opening volume.\n"); drive = CreateFile(L"\\\\?\\c:", GENERIC_READ, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, FILE_FLAG_NO_BUFFERING, NULL); if (drive == INVALID_HANDLE_VALUE) { printf("CreateFile: %u\n", GetLastError()); return 0; } printf("Calling FSCTL_QUERY_USN_JOURNAL\n"); if (!DeviceIoControl(drive, FSCTL_QUERY_USN_JOURNAL, NULL, 0, buffer, BUFFER_SIZE, &bytecount, NULL)) { printf("FSCTL_QUERY_USN_JOURNAL: %u\n", GetLastError()); return 0; } journal = (USN_JOURNAL_DATA *)buffer; printf("UsnJournalID: %lu\n", journal->UsnJournalID); printf("FirstUsn: %lu\n", journal->FirstUsn); printf("NextUsn: %lu\n", journal->NextUsn); printf("LowestValidUsn: %lu\n", journal->LowestValidUsn); printf("MaxUsn: %lu\n", journal->MaxUsn); printf("MaximumSize: %lu\n", journal->MaximumSize); printf("AllocationDelta: %lu\n", journal->AllocationDelta); maxusn = journal->MaxUsn; mft_enum_data.StartFileReferenceNumber = 0; mft_enum_data.LowUsn = 0; mft_enum_data.HighUsn = maxusn; for (;;) { // printf("=================================================================\n"); // printf("Calling FSCTL_ENUM_USN_DATA\n"); if (!DeviceIoControl(drive, FSCTL_ENUM_USN_DATA, &mft_enum_data, sizeof(mft_enum_data), buffer, BUFFER_SIZE, &bytecount, NULL)) { printf("=================================================================\n"); printf("FSCTL_ENUM_USN_DATA: %u\n", GetLastError()); printf("Final ID: %lu\n", nextid); printf("File count: %lu\n", filecount); endtick = GetTickCount(); printf("Ticks: %u\n", endtick - starttick); return 0; } // printf("Bytes returned: %u\n", bytecount); nextid = *((DWORDLONG *)buffer); // printf("Next ID: %lu\n", nextid); record = (USN_RECORD *)((USN *)buffer + 1); recordend = (USN_RECORD *)(((BYTE *)buffer) + bytecount); while (record < recordend) { filecount++; check_record(record); record = (USN_RECORD *)(((BYTE *)record) + record->RecordLength); } mft_enum_data.StartFileReferenceNumber = nextid; } } 

Notas adicionales

  • Como se discutió en los comentarios, es posible que deba reemplazar MFT_ENUM_DATA con MFT_ENUM_DATA_V0 en las versiones de Windows posteriores a Windows 7. (Esto también puede depender del comstackdor y del SDK que esté utilizando).

  • Estoy imprimiendo los números de referencia del archivo de 64 bits como si fueran de 32 bits. Eso fue solo un error de mi parte. Probablemente en el código de producción no los va a imprimir de todos modos, solo digo.

El diario de cambios es tu mejor apuesta. Puede usar los números de referencia del archivo para hacer coincidir los pares de creación / eliminación de archivos y así ignorar los archivos temporales, sin tener que procesarlos más.

Creo que tiene que escanear la tabla principal de archivos para dar sentido a ParentFileReferenceNumber. Por supuesto, solo necesita hacer un seguimiento de los directorios al hacer esto y utilizar una estructura de datos que le permitirá buscar rápidamente la información, por lo que solo necesita escanear el MFT una vez.

Puede usar ReadDirectoryChanges y la API de Windows circundante.

Sé cómo lograr esto en Java. Te ayudará si implementa código Java dentro de C ++.

En Java, puede lograr esto utilizando la Jnotify Jnotify Jnotify También busca cambios en el subdirectorio.