507 lines
15 KiB
C++
507 lines
15 KiB
C++
|
/*++
|
||
|
|
||
|
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;
|
||
|
|
||
|
/*
|
||
|
* 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
|
||
|
|
||
|
eventlog.report_event(GROVMSG_SERVICE_STARTED, 0);
|
||
|
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, 0);
|
||
|
eventlog.report_event(GROVMSG_SERVICE_STOPPED, 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 READ parameters
|
||
|
//
|
||
|
|
||
|
ReadParameters read_parameters;
|
||
|
ASSERT(read_parameters.parameter_backup_interval >= 0);
|
||
|
|
||
|
//
|
||
|
// 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(read_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,
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
if (groveler_statuses[index] == Grovel_ok)
|
||
|
{
|
||
|
log_drive->partition_initialized(index);
|
||
|
eventlog.report_event(GROVMSG_GROVELER_STARTED, 1,
|
||
|
sis_drives.partition_mount_name(index));
|
||
|
}
|
||
|
else if (groveler_statuses[index] == Grovel_disable)
|
||
|
{
|
||
|
eventlog.report_event(GROVMSG_GROVELER_DISABLED, 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, 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, 0);
|
||
|
break;
|
||
|
|
||
|
case exception_create_event:
|
||
|
eventlog.report_event(GROVMSG_CREATE_EVENT_FAILURE, 0);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
eventlog.report_event(GROVMSG_UNKNOWN_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 index = 0; index < num_partitions; index++)
|
||
|
{
|
||
|
if (groveler_statuses[index] != Grovel_disable)
|
||
|
{
|
||
|
grovelers[index].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 index = 0; index < num_partitions; index++)
|
||
|
{
|
||
|
if (read_disk_info[index] != 0)
|
||
|
{
|
||
|
delete read_disk_info[index];
|
||
|
read_disk_info[index] = 0;
|
||
|
}
|
||
|
}
|
||
|
delete[] read_disk_info;
|
||
|
read_disk_info = 0;
|
||
|
}
|
||
|
|
||
|
if (write_disk_info != 0)
|
||
|
{
|
||
|
for (int index = 0; index < num_partitions; index++)
|
||
|
{
|
||
|
if (write_disk_info[index] != 0)
|
||
|
{
|
||
|
delete write_disk_info[index];
|
||
|
write_disk_info[index] = 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, 0);
|
||
|
return exit_code;
|
||
|
}
|