/*++ Copyright (c) 1998 Microsoft Corporation Module Name: grovel.cpp Abstract: SIS Groveler main function Authors: John Douceur, 1998 Environment: User Mode Revision History: --*/ #include "all.hxx" /* * The core of the groveler executable is an object of the EventTimer class. * All periodic operations are registered with the global event_timer object, * and they are called at appropriate times during the execution of the * event_timer.run() function. * * Errors are written to the system event log, which is accessed through * member functions of the EventLog class. The eventlog object is a global * so that any function or member function of any class can log an event if * necessary. * * The service control thread synchronizes with the main groveler thread via * a Windows event. This event is encapsulated in an object of the SyncEvent * class. * * The SISDrives class determines which drives have SIS installed. * * The SharedData class is used to write values that are read by the groveler * performance DLL, so that the groveler's operation can be monitored by * PerfMon. This object needs to be global so that any function or member * function of any class can record performance information. * * The CentralController class is instantiated into a global object, rather * than an object local to the main() function, so that the service controller * can invoke CentralController member functions in order to affect its * operation. * * Initially, the shared_data and controller pointers are set to null, so that * if an exception occurs, the code that deletes allocated objects can check * for a null to determine whether or not the object has been instantiated. * */ EventTimer event_timer; EventLog eventlog; SyncEvent sync_event(false, false); SISDrives sis_drives; LogDrive *log_drive = 0; SharedData *shared_data = 0; CentralController *controller = 0; // // It was determined that the groveler needed to be modified to by default // only grovel the files in the RIS tree. Since presently this can only // exist on one volume, these are globals used to define if SIS can do // all files on a volume or only the RIS tree. // int GrovelAllPaths = FALSE; PWCHAR RISVolumeName = NULL; PWCHAR RISVolumeGuidName = NULL; PWCHAR RISPath = NULL; void ConfigureRISOnlyState(); /* * Ordinarily, the groveler does not stop operation until it is told to by * a command from the service control manager. However, for testing, it can * sometimes be useful to specify a time limit for running. The groveler thus * accepts a first argument that indicates such a time limit. If an argument * is supplied, an invokation of the halt() function is scheduled in the * event_timer object for the specified time. * */ void halt( void *context) { event_timer.halt(); }; /* * The function groveler_new_handler() is installed as a new handler by the * _set_new_handler() function. Whenever a memory allocation failure occurs, * it throws an exception_memory_allocation, which is caught by the catch * clause in the main() function. * */ int __cdecl groveler_new_handler( size_t bytes) { throw exception_memory_allocation; return 0; } /* * This file contains the main() function and declarations for global objects * for the groveler.exe program, as well as a couple of simple ancillary * functions, halt() and groveler_new_handler(). * * The main() function reads configuration information, instantiates a set of * primary objects -- the most significant of which are instances of the * classes Groveler and CentralController -- and enters the run() member * function of the event_timer object, which periodically invokes member * functions of other objects, most notably those of the clasess * CentralController and PartitionController. * * Configuration information comes from objects of three classes: * ReadParameters, ReadDiskInformation, and PathList. The ReadParameters * and PathList classes provide configuration information that applies to * grovelers on all partitions. The ReadDiskInformation class provides * configuration information that applies to a single disk partition. One * object of the ReadDiskInformation class is instantiated for each drive * that has SIS installed, as explained above. * * For each SIS drive, the main() function instantiates an object of the * ReadDiskInformation class to determine the configuration options (which * ReadDiskInformation obtains from the registry) for the given disk * partition. If the drive is configured to enable groveling, then an object * of the Groveler class is instantiated for that drive. * * The main() function then instantiates an object of class CentralController, * which in turn instantiates an object of class PartitionController for each * SIS-enabled disk partition. Each partition controller is assigned to one * object of the Groveler class, and it controls the groveler by calling its * member functions at appropriate times. * * Nearly all of of the processing done by the groveler executable is * performed within a try clause, the purpose of which is to catch errors of * terminal severity. There are two such errors (defined in all.hxx) that are * expected to throw such exceptions: a memory allocation failure and a * failure to create an Windows event. If either of these conditions occurs, * the program terminates. * */ _main(int argc, _TCHAR **argv) { _set_new_handler(groveler_new_handler); SetErrorMode(SEM_FAILCRITICALERRORS); int exit_code = NO_ERROR; int num_partitions = 0; int index; // // Initially, these pointers are set to null, so that if an exception // occurs, the code that deletes allocated objects can check for a null // to determine whether or not the object has been instantiated. // Groveler *grovelers = 0; GrovelStatus *groveler_statuses = 0; ReadDiskInformation **read_disk_info = 0; WriteDiskInformation **write_disk_info = 0; // // If program tracing is being performed, and if the traces are being sent // to a file, the file is opened. This call is made through a macro // so that no code will be generated for released builds. Since this call // is made before the try clause, it is important that the function not // perform any operation that could throw an exception, such as a memory // allocation. // OPEN_TRACE_FILE(); try { // // If a first argument is provided, it is the run period. // if (argc > 1) { int run_period = _ttoi(argv[1]); if (run_period <= 0) { PRINT_DEBUG_MSG((_T("GROVELER: run period must be greater than zero\n"))); return ERROR_BAD_ARGUMENTS; } unsigned int start_time = GET_TICK_COUNT(); event_timer.schedule(start_time + run_period, 0, halt); } #if DEBUG_WAIT // When debugging the groveler as a service, if the process is attached // to a debugger after it has started, then the initialization code // will usually be executed before the debugger has a chance to break. // However, by defining DEBUG_WAIT to a non-zero value, the code will // get stuck in the following infinite loop before doing the bulk of // its initialization. (The event_timer, eventlog, and sync_event // objects will have been constructed, because they are declared as // globals.) The debugger can then be used to set debug_wait to false, // and debugging can commence with the subsequent code. bool debug_wait = true; while (debug_wait) { SLEEP(100); }; #endif // DEBUG_WAIT // // Report the service has started // eventlog.report_event(GROVMSG_SERVICE_STARTED, ERROR_SUCCESS, 0); // // Get READ parameters // ReadParameters read_parameters; ASSERT(read_parameters.parameter_backup_interval >= 0); // // If we are not Groveling all paths, setup RISonly state // if (!GrovelAllPaths) { ConfigureRISOnlyState(); } else { DPRINTF((L"Groveling ALL paths\n")); } // // Open the drives // sis_drives.open(); // // See if there were any partions to scan. If not, quit // num_partitions = sis_drives.partition_count(); if (num_partitions == 0) { PRINT_DEBUG_MSG((_T("GROVELER: No local partitions have SIS installed.\n"))); eventlog.report_event(GROVMSG_NO_PARTITIONS, ERROR_SUCCESS, 0); eventlog.report_event(GROVMSG_SERVICE_STOPPED, ERROR_SUCCESS, 0); return ERROR_SERVICE_NOT_ACTIVE; } // // Report the service is running // SERVICE_REPORT_START(); // // Setup shared data are between all the worker threads // num_partitions = sis_drives.partition_count(); _TCHAR **drive_names = new _TCHAR *[num_partitions]; for (index = 0; index < num_partitions; index++) { drive_names[index] = sis_drives.partition_mount_name(index); } shared_data = new SharedData(num_partitions, drive_names); delete [] drive_names; // // Get WRITE parameters // WriteParameters write_parameters(read_parameters.parameter_backup_interval); // // Get excluded path list // PathList excluded_paths; // // Setup LogDrive // log_drive = new LogDrive; Groveler::set_log_drive(sis_drives.partition_mount_name(log_drive->drive_index())); // // Setup Groveler objects // grovelers = new Groveler[num_partitions]; groveler_statuses = new GrovelStatus[num_partitions]; // // Initially, the status of each partition is set to Grovel_disable so // that the close() member function of each Groveler object will not // be called unless the open() function is called first. // for (index = 0; index < num_partitions; index++) { groveler_statuses[index] = Grovel_disable; } // // Initially, the read_disk_info[] and write_disk_info[] arrays are set // to null, so that if an exception occurs, the code that deletes // allocated objects can check for a null to determine whether or not // the object has been instantiated. // read_disk_info = new ReadDiskInformation *[num_partitions]; ZeroMemory(read_disk_info, sizeof(ReadDiskInformation *) * num_partitions); write_disk_info = new WriteDiskInformation *[num_partitions]; ZeroMemory(write_disk_info, sizeof(WriteDiskInformation *) * num_partitions); // // Now initilaize each partition // for (index = 0; index < num_partitions; index++) { read_disk_info[index] = new ReadDiskInformation(sis_drives.partition_guid_name(index)); write_disk_info[index] = new WriteDiskInformation(sis_drives.partition_guid_name(index),read_parameters.parameter_backup_interval); if (read_disk_info[index]->enable_groveling) { groveler_statuses[index] = grovelers[index].open( sis_drives.partition_guid_name(index), sis_drives.partition_mount_name(index), (index == log_drive->drive_index()), read_parameters.read_report_discard_threshold, read_disk_info[index]->min_file_size, read_disk_info[index]->min_file_age, read_disk_info[index]->allow_compressed_files, read_disk_info[index]->allow_encrypted_files, read_disk_info[index]->allow_hidden_files, read_disk_info[index]->allow_offline_files, read_disk_info[index]->allow_temporary_files, write_disk_info[index]->grovelAllPathsState, excluded_paths.num_paths[index], excluded_paths.paths[index], read_parameters.base_regrovel_interval, read_parameters.max_regrovel_interval); ASSERT(groveler_statuses[index] != Grovel_disable); } // // Set the new updated RSSonly state which will be saved on // the next interval. // write_disk_info[index]->grovelAllPathsState = GrovelAllPaths; write_disk_info[index]->flush(); if (groveler_statuses[index] == Grovel_ok) { log_drive->partition_initialized(index); eventlog.report_event(GROVMSG_GROVELER_STARTED, ERROR_SUCCESS, 1, sis_drives.partition_mount_name(index)); } else if (groveler_statuses[index] == Grovel_disable) { eventlog.report_event(GROVMSG_GROVELER_DISABLED, ERROR_SUCCESS, 1, sis_drives.partition_mount_name(index)); } else if (groveler_statuses[index] != Grovel_new) { ASSERT(groveler_statuses[index] == Grovel_error); eventlog.report_event(GROVMSG_GROVELER_NOSTART, ERROR_SUCCESS, 1, sis_drives.partition_mount_name(index)); } } // // We have to pass a lot of information to the central controller that // it shouldn't really need. However, if a groveler fails, this // information is needed to restart it. It would be better if the // Groveler open() member function had a form that did not require // arguments, but rather re-used the arguments that had been passed in // previously. But this is not how it currently works. // controller = new CentralController( num_partitions, grovelers, groveler_statuses, &read_parameters, &write_parameters, read_disk_info, write_disk_info, excluded_paths.num_paths, excluded_paths.paths); SERVICE_RECORD_PARTITION_INDICES(); ASSERT(read_parameters.grovel_duration > 0); SERVICE_SET_MAX_RESPONSE_TIME(read_parameters.grovel_duration); // // If any grovelers are alive, tell the service control manager that // we have concluded the initialization, then commence running. // if (controller->any_grovelers_alive()) { event_timer.run(); } // // If tracing is being performed in delayed mode, print the trace log // now that the run has completed. // PRINT_TRACE_LOG(); } catch (Exception exception) { switch (exception) { case exception_memory_allocation: eventlog.report_event(GROVMSG_MEMALLOC_FAILURE, ERROR_SUCCESS, 0); break; case exception_create_event: eventlog.report_event(GROVMSG_CREATE_EVENT_FAILURE, GetLastError(), 0); break; default: eventlog.report_event(GROVMSG_UNKNOWN_EXCEPTION, exception, 0); break; } exit_code = ERROR_EXCEPTION_IN_SERVICE; } // // If program tracing is being performed, and if the traces are being sent // to a file, the file is closed. This call is made through a macro // so that no code will be generated for released builds. Since the trace // file is being closed before the objects are deleted, it is important // not to write trace information in the destructor of an object. // CLOSE_TRACE_FILE(); // // Close each groveler object that was opened. // if (groveler_statuses != 0 && grovelers != 0) { for (int i = 0; i < num_partitions; i++) { if (groveler_statuses[i] != Grovel_disable) { grovelers[i].close(); } } } // // Delete all objects that were allocated. // if (groveler_statuses != 0) { delete[] groveler_statuses; groveler_statuses = 0; } if (grovelers != 0) { delete[] grovelers; grovelers = 0; } if (read_disk_info != 0) { for (int i = 0; i < num_partitions; i++) { if (read_disk_info[i] != 0) { delete read_disk_info[i]; read_disk_info[i] = 0; } } delete[] read_disk_info; read_disk_info = 0; } if (write_disk_info != 0) { for (int i = 0; i < num_partitions; i++) { if (write_disk_info[i] != 0) { delete write_disk_info[i]; write_disk_info[i] = 0; } } delete[] write_disk_info; write_disk_info = 0; } if (controller != 0) { delete controller; controller = 0; } if (shared_data != 0) { delete shared_data; shared_data = 0; } if (log_drive != 0) { delete log_drive; log_drive = 0; } eventlog.report_event(GROVMSG_SERVICE_STOPPED, ERROR_SUCCESS, 0); return exit_code; } // // This routine determines if we can grovel any volumes/files or only // RIS files // WCHAR RISPathNameKey[] = L"system\\CurrentControlSet\\Services\\tftpd\\parameters"; WCHAR RISPathNameValue[] = L"Directory"; VOID ConfigureRISOnlyState() { LONG status; HKEY keyHandle; DWORD valueType; DWORD nameSize; PWCHAR risName; DWORD bufsz; BOOL result; WCHAR volName[128]; WCHAR volGuidName[128]; __try { // // Open the TFTPD registry key and see if it exists. If not then RIS is // not installed. We will not grovel anything. // keyHandle = NULL; status = RegOpenKeyEx( HKEY_LOCAL_MACHINE, RISPathNameKey, 0, KEY_ALL_ACCESS, &keyHandle ); if (status != ERROR_SUCCESS) { DPRINTF((L"ConfigureRISOnlyState: Error opening registry key \"%s\", status=%d\n",RISPathNameKey,status)); __leave; } // // Query the data of the given value entry. Note that we are just // getting the size of the string to allocate. // nameSize = 0; status = RegQueryValueEx( keyHandle, RISPathNameValue, NULL, &valueType, NULL, &nameSize ); if (status != ERROR_SUCCESS) { DPRINTF((L"ConfigureRISOnlyState: Error querying the size of \"%s\" value, status=%d\n",RISPathNameValue,status)); __leave; } if (valueType != REG_SZ) { DPRINTF((L"ConfigureRISOnlyState: Invalud value type of %d for \'%s\" value\n",valueType,RISPathNameValue)); __leave; } // // Allocate name buffer // risName = new WCHAR[(nameSize/sizeof(WCHAR))]; // // Now get the string value // status = RegQueryValueEx( keyHandle, RISPathNameValue, NULL, &valueType, (LPBYTE)risName, &nameSize ); if (status != ERROR_SUCCESS) { DPRINTF((L"ConfigureRISOnlyState: Error querying the value of \"%s\", status=%d\n",RISPathNameValue,status)); __leave; } // // This will get the volume portion of the name. // result = GetVolumePathName( risName, volName, (sizeof(volName) / sizeof(WCHAR))); if (!result) { DPRINTF((L"ConfigureRISOnlyState: Error querying the volume name of \"%s\", status=%d\n",risName,status)); __leave; } if (_wcsnicmp(risName,volName,wcslen(volName)) != 0) { DPRINTF((L"ConfigureRISOnlyState: The queried volume name \"%s\" does not match the volume name portion of the original name \"%s\"\n",volName,risName)); __leave; } // // Get the volume GUID name // result = GetVolumeNameForVolumeMountPoint( volName, volGuidName, (sizeof(volGuidName) / sizeof(WCHAR))); if (!result) { DPRINTF((L"ConfigureRISOnlyState: Error querying the volume GUID name of \"%s\", status=%d\n",volName,status)); __leave; } // // Now allocate buffers for everything and setup the globals // bufsz = wcslen(volGuidName)+1; RISVolumeGuidName = new WCHAR[bufsz]; (void)StringCchCopy(RISVolumeGuidName, bufsz, volGuidName); bufsz = wcslen(volName)+1; RISVolumeName = new WCHAR[bufsz]; (void)StringCchCopy(RISVolumeName, bufsz, volName); // // Skip over the volume name at the front and point at the path // RISPath = risName + (wcslen(volName) - 1); //we want to keep the beginning slash } __finally { if (keyHandle) { RegCloseKey( keyHandle ); } } DPRINTF((L"GrovelAllPaths=%d\n" L"RisVolumeName=\"%s\"\n" L"RISVolumeGuidName=\"%s\"\n" L"RISPath=\"%s\"\n", GrovelAllPaths, RISVolumeName, RISVolumeGuidName, RISPath)); }