My minifilter doesn't see I/O generated by my CDO

I can’t help but think that this is a mind-bogglingly stupid question,
but I’ve been reading documentation for most of the afternoon and
haven’t found an answer. I’ve created a control device object using the
CDO example code as the model.

In my cdo, I open a file, do some i/o on it, untag it, and then close
it. All of this is invoked from userland using IOCTLs.

What’s bugging me is that when I create the file, I’m doing so with
IoCreateFileSpecifyDeviceObjectHint with a DeviceObject of 0, which
should send it to the top of the stack. Therefore, as I understand it,
my minifilter in the stack for the volume with the file on it should see
the i/o. However it’s not. This isn’t a problem per-se (if I were
seeing the i/o, I’d have become screwed up in the recursion). I don’t
want to see the i/o for the file in question, but I feel like I may be
doing something wrong.

Any thoughts? Code follows.

Thanks,

~Eric

// Called from DriverEntry, creates device:
NTSTATUS create_restorer_device_object (PDRIVER_OBJECT driver_object)
{
int i;
UNICODE_STRING device_name = RTL_CONSTANT_STRING
(RESTORER_DEVICE_NAME);
UNICODE_STRING link_name = RTL_CONSTANT_STRING
(RESTORER_USER_NAME);
NTSTATUS status = STATUS_SUCCESS;

DBG_PRINT ((“Creating restorer device object\n”));

if (globals.restorer_device) {
//wraps DbgPrint with some additional info (line #, etc)
DBG_PRINT ((
“Could not create restorer device
object, already exists\n”));
status = STATUS_UNSUCCESSFUL;
goto out;
}

status = IoCreateDevice (driver_object, 0, &device_name,
FILE_DEVICE_DISK_FILE_SYSTEM,
FILE_DEVICE_SECURE_OPEN,
FALSE, &globals.restorer_device);

// this is a macro that tests the status and DbgPrints an error
and jumps to out
// if it’s not a success code.
TEST_STATUS_AND_ABORT ((“Could not create restorer device\n”));

status = IoCreateSymbolicLink (&link_name, &device_name);

TEST_STATUS_AND_ABORT ((“Could not create symlink to restorer
device\n”));

//Default to invalid request for all IRPs
for (i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++) {
driver_object->MajorFunction[i] = ehr_invalid;
}

//Set up the few functions we’re going to support
driver_object->MajorFunction[IRP_MJ_CREATE] = ehr_create;
driver_object->MajorFunction[IRP_MJ_CLEANUP] = ehr_cleanup;
driver_object->MajorFunction[IRP_MJ_CLOSE] = ehr_close;
driver_object->MajorFunction[IRP_MJ_DEVICE_CONTROL] = ehr_ioctl;

DBG_PRINT ((“Done creating\n”));

out:
if (!NT_SUCCESS (status)) {
if (globals.restorer_device) {
IoDeleteDevice (globals.restorer_device);
}
}
return status;
}

// This is the function in the cdo that handles my file_open ioctl
static NTSTATUS ehr_file_open (PIRP irp, PIO_STACK_LOCATION irp_sp)
{
NTSTATUS status = STATUS_SUCCESS;
EHR_OPEN_ARGS *args = (EHR_OPEN_ARGS *)
irp->AssociatedIrp.SystemBuffer;
size_t input_size =
irp_sp->Parameters.DeviceIoControl.InputBufferLength;
UNICODE_STRING file_name;
UNICODE_STRING volume_name;
UNICODE_STRING om_root = RTL_CONSTANT_STRING (L"\??\");
OBJECT_ATTRIBUTES oa;
UNICODE_STRING oa_path;
IO_STATUS_BLOCK iosb;
USHORT total_size;
HANDLE t_handle = INVALID_HANDLE_VALUE;
eh_stream_context *sc = NULL;
BOOLEAN first_open = FALSE;
BOOLEAN do_restore = FALSE;

RtlInitUnicodeString (&file_name, args->wsFileName);
RtlInitUnicodeString (&volume_name, args->wsVolumeName);

//total size in bytes we need to allocate
total_size = file_name.Length + om_root.Length;

kuReserveUnicodeStringCb (&oa_path, total_size, PagedPool,
EHR_TAG);
TEST_STATUS_AND_ABORT ((“Failed to reserve oa_path\n”));

status = RtlUnicodeStringCat (&oa_path, &om_root);
TEST_STATUS_AND_ABORT ((“couldn’t add om_root to oa_path\n”));
status = RtlUnicodeStringCat (&oa_path, &file_name);
TEST_STATUS_AND_ABORT ((“couldn’t add file_name to oa_path\n”));

InitializeObjectAttributes (&oa, &oa_path, 0, 0, 0);

status = IoCreateFileSpecifyDeviceObjectHint (&t_handle,
GENERIC_WRITE, &oa, &iosb, 0,
FILE_ATTRIBUTE_NORMAL, 0,
FILE_OPEN, FILE_OPEN_REPARSE_POINT
| FILE_SYNCHRONOUS_IO_NONALERT, 0, 0,
CreateFileTypeNone,
0, IO_IGNORE_SHARE_ACCESS_CHECK, 0);

/* … more stuff happens */
}

Once I have this handle, I don’t see the i/o on it in my minifilter. It
seems like I should, and that’s what’s bugging me. The i/o is limited
to writes, FltUntagFile, and a close.

Why don’t you use ZwCreateFile instead of
IoCreateFileSpecifyDeviceObjectHint?

“Eric Diven” wrote in message news:xxxxx@ntfsd…
I can’t help but think that this is a mind-bogglingly stupid question,
but I’ve been reading documentation for most of the afternoon and
haven’t found an answer. I’ve created a control device object using the
CDO example code as the model.

In my cdo, I open a file, do some i/o on it, untag it, and then close
it. All of this is invoked from userland using IOCTLs.

What’s bugging me is that when I create the file, I’m doing so with
IoCreateFileSpecifyDeviceObjectHint with a DeviceObject of 0, which
should send it to the top of the stack. Therefore, as I understand it,
my minifilter in the stack for the volume with the file on it should see
the i/o. However it’s not. This isn’t a problem per-se (if I were
seeing the i/o, I’d have become screwed up in the recursion). I don’t
want to see the i/o for the file in question, but I feel like I may be
doing something wrong.

Any thoughts? Code follows.

Thanks,

~Eric

// Called from DriverEntry, creates device:
NTSTATUS create_restorer_device_object (PDRIVER_OBJECT driver_object)
{
int i;
UNICODE_STRING device_name = RTL_CONSTANT_STRING
(RESTORER_DEVICE_NAME);
UNICODE_STRING link_name = RTL_CONSTANT_STRING
(RESTORER_USER_NAME);
NTSTATUS status = STATUS_SUCCESS;

DBG_PRINT ((“Creating restorer device object\n”));

if (globals.restorer_device) {
//wraps DbgPrint with some additional info (line #, etc)
DBG_PRINT ((
“Could not create restorer device
object, already exists\n”));
status = STATUS_UNSUCCESSFUL;
goto out;
}

status = IoCreateDevice (driver_object, 0, &device_name,
FILE_DEVICE_DISK_FILE_SYSTEM,
FILE_DEVICE_SECURE_OPEN,
FALSE, &globals.restorer_device);

// this is a macro that tests the status and DbgPrints an error
and jumps to out
// if it’s not a success code.
TEST_STATUS_AND_ABORT ((“Could not create restorer device\n”));

status = IoCreateSymbolicLink (&link_name, &device_name);

TEST_STATUS_AND_ABORT ((“Could not create symlink to restorer
device\n”));

//Default to invalid request for all IRPs
for (i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++) {
driver_object->MajorFunction[i] = ehr_invalid;
}

//Set up the few functions we’re going to support
driver_object->MajorFunction[IRP_MJ_CREATE] = ehr_create;
driver_object->MajorFunction[IRP_MJ_CLEANUP] = ehr_cleanup;
driver_object->MajorFunction[IRP_MJ_CLOSE] = ehr_close;
driver_object->MajorFunction[IRP_MJ_DEVICE_CONTROL] = ehr_ioctl;

DBG_PRINT ((“Done creating\n”));

out:
if (!NT_SUCCESS (status)) {
if (globals.restorer_device) {
IoDeleteDevice (globals.restorer_device);
}
}
return status;
}

// This is the function in the cdo that handles my file_open ioctl
static NTSTATUS ehr_file_open (PIRP irp, PIO_STACK_LOCATION irp_sp)
{
NTSTATUS status = STATUS_SUCCESS;
EHR_OPEN_ARGS *args = (EHR_OPEN_ARGS *)
irp->AssociatedIrp.SystemBuffer;
size_t input_size =
irp_sp->Parameters.DeviceIoControl.InputBufferLength;
UNICODE_STRING file_name;
UNICODE_STRING volume_name;
UNICODE_STRING om_root = RTL_CONSTANT_STRING (L"\??\");
OBJECT_ATTRIBUTES oa;
UNICODE_STRING oa_path;
IO_STATUS_BLOCK iosb;
USHORT total_size;
HANDLE t_handle = INVALID_HANDLE_VALUE;
eh_stream_context sc = NULL;
BOOLEAN first_open = FALSE;
BOOLEAN do_restore = FALSE;

RtlInitUnicodeString (&file_name, args->wsFileName);
RtlInitUnicodeString (&volume_name, args->wsVolumeName);

//total size in bytes we need to allocate
total_size = file_name.Length + om_root.Length;

kuReserveUnicodeStringCb (&oa_path, total_size, PagedPool,
EHR_TAG);
TEST_STATUS_AND_ABORT ((“Failed to reserve oa_path\n”));

status = RtlUnicodeStringCat (&oa_path, &om_root);
TEST_STATUS_AND_ABORT ((“couldn’t add om_root to oa_path\n”));
status = RtlUnicodeStringCat (&oa_path, &file_name);
TEST_STATUS_AND_ABORT ((“couldn’t add file_name to oa_path\n”));

InitializeObjectAttributes (&oa, &oa_path, 0, 0, 0);

status = IoCreateFileSpecifyDeviceObjectHint (&t_handle,
GENERIC_WRITE, &oa, &iosb, 0,
FILE_ATTRIBUTE_NORMAL, 0,
FILE_OPEN, FILE_OPEN_REPARSE_POINT
| FILE_SYNCHRONOUS_IO_NONALERT, 0, 0,
CreateFileTypeNone,
0, IO_IGNORE_SHARE_ACCESS_CHECK, 0);

/
… more stuff happens */
}

Once I have this handle, I don’t see the i/o on it in my minifilter. It
seems like I should, and that’s what’s bugging me. The i/o is limited
to writes, FltUntagFile, and a close.

This will not make any difference, first ZwCreateFile is much more limited,
and second ZwCreateFile calls IoCreateFileSpecifyDeviceObjectHint to do its
work.


Don Burn (MVP, Windows DDK)
Windows 2k/XP/2k3 Filesystem and Driver Consulting
Website: http://www.windrvr.com
Blog: http://msmvps.com/blogs/WinDrvr
Remove StopSpam to reply

“Shangwu” wrote in message news:xxxxx@ntfsd…
> Why don’t you use ZwCreateFile instead of
> IoCreateFileSpecifyDeviceObjectHint?
>
> “Eric Diven” wrote in message
> news:xxxxx@ntfsd…
> I can’t help but think that this is a mind-bogglingly stupid question,
> but I’ve been reading documentation for most of the afternoon and
> haven’t found an answer. I’ve created a control device object using the
> CDO example code as the model.
>
> In my cdo, I open a file, do some i/o on it, untag it, and then close
> it. All of this is invoked from userland using IOCTLs.
>
> What’s bugging me is that when I create the file, I’m doing so with
> IoCreateFileSpecifyDeviceObjectHint with a DeviceObject of 0, which
> should send it to the top of the stack. Therefore, as I understand it,
> my minifilter in the stack for the volume with the file on it should see
> the i/o. However it’s not. This isn’t a problem per-se (if I were
> seeing the i/o, I’d have become screwed up in the recursion). I don’t
> want to see the i/o for the file in question, but I feel like I may be
> doing something wrong.
>
> Any thoughts? Code follows.
>
> Thanks,
>
> ~Eric
>
> // Called from DriverEntry, creates device:
> NTSTATUS create_restorer_device_object (PDRIVER_OBJECT driver_object)
> {
> int i;
> UNICODE_STRING device_name = RTL_CONSTANT_STRING
> (RESTORER_DEVICE_NAME);
> UNICODE_STRING link_name = RTL_CONSTANT_STRING
> (RESTORER_USER_NAME);
> NTSTATUS status = STATUS_SUCCESS;
>
> DBG_PRINT ((“Creating restorer device object\n”));
>
> if (globals.restorer_device) {
> //wraps DbgPrint with some additional info (line #, etc)
> DBG_PRINT ((
> “Could not create restorer device
> object, already exists\n”));
> status = STATUS_UNSUCCESSFUL;
> goto out;
> }
>
> status = IoCreateDevice (driver_object, 0, &device_name,
> FILE_DEVICE_DISK_FILE_SYSTEM,
> FILE_DEVICE_SECURE_OPEN,
> FALSE, &globals.restorer_device);
>
> // this is a macro that tests the status and DbgPrints an error
> and jumps to out
> // if it’s not a success code.
> TEST_STATUS_AND_ABORT ((“Could not create restorer device\n”));
>
> status = IoCreateSymbolicLink (&link_name, &device_name);
>
> TEST_STATUS_AND_ABORT ((“Could not create symlink to restorer
> device\n”));
>
> //Default to invalid request for all IRPs
> for (i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++) {
> driver_object->MajorFunction[i] = ehr_invalid;
> }
>
> //Set up the few functions we’re going to support
> driver_object->MajorFunction[IRP_MJ_CREATE] = ehr_create;
> driver_object->MajorFunction[IRP_MJ_CLEANUP] = ehr_cleanup;
> driver_object->MajorFunction[IRP_MJ_CLOSE] = ehr_close;
> driver_object->MajorFunction[IRP_MJ_DEVICE_CONTROL] = ehr_ioctl;
>
> DBG_PRINT ((“Done creating\n”));
>
> out:
> if (!NT_SUCCESS (status)) {
> if (globals.restorer_device) {
> IoDeleteDevice (globals.restorer_device);
> }
> }
> return status;
> }
>
> // This is the function in the cdo that handles my file_open ioctl
> static NTSTATUS ehr_file_open (PIRP irp, PIO_STACK_LOCATION irp_sp)
> {
> NTSTATUS status = STATUS_SUCCESS;
> EHR_OPEN_ARGS *args = (EHR_OPEN_ARGS *)
> irp->AssociatedIrp.SystemBuffer;
> size_t input_size =
> irp_sp->Parameters.DeviceIoControl.InputBufferLength;
> UNICODE_STRING file_name;
> UNICODE_STRING volume_name;
> UNICODE_STRING om_root = RTL_CONSTANT_STRING (L"\??\");
> OBJECT_ATTRIBUTES oa;
> UNICODE_STRING oa_path;
> IO_STATUS_BLOCK iosb;
> USHORT total_size;
> HANDLE t_handle = INVALID_HANDLE_VALUE;
> eh_stream_context sc = NULL;
> BOOLEAN first_open = FALSE;
> BOOLEAN do_restore = FALSE;
>
> RtlInitUnicodeString (&file_name, args->wsFileName);
> RtlInitUnicodeString (&volume_name, args->wsVolumeName);
>
> //total size in bytes we need to allocate
> total_size = file_name.Length + om_root.Length;
>
> kuReserveUnicodeStringCb (&oa_path, total_size, PagedPool,
> EHR_TAG);
> TEST_STATUS_AND_ABORT ((“Failed to reserve oa_path\n”));
>
> status = RtlUnicodeStringCat (&oa_path, &om_root);
> TEST_STATUS_AND_ABORT ((“couldn’t add om_root to oa_path\n”));
> status = RtlUnicodeStringCat (&oa_path, &file_name);
> TEST_STATUS_AND_ABORT ((“couldn’t add file_name to oa_path\n”));
>
> InitializeObjectAttributes (&oa, &oa_path, 0, 0, 0);
>
> status = IoCreateFileSpecifyDeviceObjectHint (&t_handle,
> GENERIC_WRITE, &oa, &iosb, 0,
> FILE_ATTRIBUTE_NORMAL, 0,
> FILE_OPEN, FILE_OPEN_REPARSE_POINT
> | FILE_SYNCHRONOUS_IO_NONALERT, 0, 0,
> CreateFileTypeNone,
> 0, IO_IGNORE_SHARE_ACCESS_CHECK, 0);
>
> /
… more stuff happens */
> }
>
> Once I have this handle, I don’t see the i/o on it in my minifilter. It
> seems like I should, and that’s what’s bugging me. The i/o is limited
> to writes, FltUntagFile, and a close.
>
>
>
>
>

Would you agree then, that based upon how I’m calling it, I should be
seeing the I/O in my minifilter? Or is there something about how I’m
doing it (or how I’m creating the DeviceObject for the CDO) that I’m
unaware of that’s preventing that from happening?

Based upon what I’ve read and understand so far I should be seeing the
i/o. The fact that I’m not means I’m doing something that I don’t
understand, or it’ll break sometime in the near future (like on a
different platform, etc).

Thanks,

Eric

-----Original Message-----
From: xxxxx@lists.osr.com
[mailto:xxxxx@lists.osr.com] On Behalf Of Don Burn
Sent: Tuesday, March 11, 2008 10:38 AM
To: Windows File Systems Devs Interest List
Subject: Re:[ntfsd] My minifilter doesn’t see I/O generated by my CDO

This will not make any difference, first ZwCreateFile is much more
limited, and second ZwCreateFile calls
IoCreateFileSpecifyDeviceObjectHint to do its work.


Don Burn (MVP, Windows DDK)
Windows 2k/XP/2k3 Filesystem and Driver Consulting
Website: http://www.windrvr.com
Blog: http://msmvps.com/blogs/WinDrvr
Remove StopSpam to reply

Eric,

I didn’t answer your question since I don’t see why it is not working,
unless your open is going to a different volume that you are not filtering.
From the quality of your questions I assumed that was not the case.

At this point I would do a direct appeal to one of the ultimate pro’s
such as Tony or Neal.


Don Burn (MVP, Windows DDK)
Windows 2k/XP/2k3 Filesystem and Driver Consulting
Website: http://www.windrvr.com
Blog: http://msmvps.com/blogs/WinDrvr
Remove StopSpam to reply

“Eric Diven” wrote in message news:xxxxx@ntfsd…
Would you agree then, that based upon how I’m calling it, I should be
seeing the I/O in my minifilter? Or is there something about how I’m
doing it (or how I’m creating the DeviceObject for the CDO) that I’m
unaware of that’s preventing that from happening?

Based upon what I’ve read and understand so far I should be seeing the
i/o. The fact that I’m not means I’m doing something that I don’t
understand, or it’ll break sometime in the near future (like on a
different platform, etc).

Thanks,

Eric

-----Original Message-----
From: xxxxx@lists.osr.com
[mailto:xxxxx@lists.osr.com] On Behalf Of Don Burn
Sent: Tuesday, March 11, 2008 10:38 AM
To: Windows File Systems Devs Interest List
Subject: Re:[ntfsd] My minifilter doesn’t see I/O generated by my CDO

This will not make any difference, first ZwCreateFile is much more
limited, and second ZwCreateFile calls
IoCreateFileSpecifyDeviceObjectHint to do its work.


Don Burn (MVP, Windows DDK)
Windows 2k/XP/2k3 Filesystem and Driver Consulting
Website: http://www.windrvr.com
Blog: http://msmvps.com/blogs/WinDrvr
Remove StopSpam to reply

Hmmm. Thanks for the suggestion. I wouldn’t rule out that I’ve done
something stupid or ignorant.

At this point, what I’m wondering if I should really be doing is using
FltCreateFile instead of IoCreate…Hint. Since I’m using
FltFsControlFile to get the RP data on the file, and FltUntagFile to
untag it, I’m thinking it’s okay to use FltCreateFile. As an added
bonus, other minifilters below mine would see the i/o (presumably)
rather than getting skipped (if I had to call IoCreate…Hint with
whatever legacy filter is below FltMgr to avoid re-entrancy).

Maybe the best way to test all of this and figure out what’s going on
would be with minispy. I’ll take a look at that when I get a chance.

~Eric

-----Original Message-----
From: xxxxx@lists.osr.com
[mailto:xxxxx@lists.osr.com] On Behalf Of Don Burn
Sent: Tuesday, March 11, 2008 1:02 PM
To: Windows File Systems Devs Interest List
Subject: Re:[ntfsd] My minifilter doesn’t see I/O generated by my CDO

Eric,

I didn’t answer your question since I don’t see why it is not
working, unless your open is going to a different volume that you are
not filtering.
From the quality of your questions I assumed that was not the case.

At this point I would do a direct appeal to one of the ultimate
pro’s such as Tony or Neal.


Don Burn (MVP, Windows DDK)
Windows 2k/XP/2k3 Filesystem and Driver Consulting
Website: http://www.windrvr.com
Blog: http://msmvps.com/blogs/WinDrvr
Remove StopSpam to reply

>This will not make any difference, first ZwCreateFile is much more limited,

and second ZwCreateFile calls IoCreateFileSpecifyDeviceObjectHint to do its
work.
IRQL also makes difference. With IoCreateXxx you are behind gate.

Therefore, as I understand it, my mini filter in the stack for the volume
. with the file on it should see the i/o. However it’s not.

This is a CDO right? CDO is usually a special device which is not attached to any FSD stack… and that’s how it should be. It is determined for communication purpose. Furthermore CDO is not registered as a file system so that’s reason why you cannot see requests sent to it in your minifilter. I hope I totally didn’t miss point of your question.

-bg

It looks like Web UI didn’t accept my “quick reply” so I trying to post it again.

This will not make any difference, first ZwCreateFile is much more limited,
and second ZwCreateFile calls IoCreateFileSpecifyDeviceObjectHint to do its
work.
IRQL also makes difference. With IoCreateXxx you are behind gate.

Therefore, as I understand it, my mini filter in the stack for the volume
. with the file on it should see the i/o. However it’s not.

This is a CDO right? My understanding from your code is that it is a special device which is not attached to any FSD stack… and that’s how it should be. It is determined for communication purpose. Furthermore CDO is not registered as a file system so that’s reason why you cannot see requests sent to it in your minifilter. I hope I totally didn’t miss point of your question.

-bg

The fact that it is a CDO has nothing to do with it. If the OP does a
IoCreateFileSpecifyDeviceObjectHint to a volume he is filtering then he
should see the call.


Don Burn (MVP, Windows DDK)
Windows 2k/XP/2k3 Filesystem and Driver Consulting
Website: http://www.windrvr.com
Blog: http://msmvps.com/blogs/WinDrvr
Remove StopSpam to reply

wrote in message news:xxxxx@ntfsd…
> It looks like Web UI didn’t accept my “quick reply” so I trying to post it
> again.
>
>>This will not make any difference, first ZwCreateFile is much more
>>limited,
>>and second ZwCreateFile calls IoCreateFileSpecifyDeviceObjectHint to do
>>its
>>work.
> IRQL also makes difference. With IoCreateXxx you are behind gate.
>
>>Therefore, as I understand it, my mini filter in the stack for the volume
>>. with the file on it should see the i/o. However it’s not.
>
> This is a CDO right? My understanding from your code is that it is a
> special device which is not attached to any FSD stack… and that’s how it
> should be. It is determined for communication purpose. Furthermore CDO is
> not registered as a file system so that’s reason why you cannot see
> requests sent to it in your minifilter. I hope I totally didn’t miss point
> of your question.
>
> -bg
>

I thought that he is opening handle to CDO. My mistake, I should read more carefully. But I still have two comments.

  1. I cannot see IOCTL code declaration, so I suppose that it is not METHOD_NEITHER.
  2. What about FILE_OPEN_REPARSE_POINT flag? Are you opening reparse data. I am not sure how it behaves if you provide longer path then reparse point, but I think ??\ is also reparse point, so I suspect you don’t get behind it.

-bg

Inline

  1. I cannot see IOCTL code declaration, so I suppose that it is not
    METHOD_NEITHER.

It’s METHOD_BUFFERED for the open operation.

  1. What about FILE_OPEN_REPARSE_POINT flag? Are you opening reparse
    data. I am not sure how it behaves if you provide longer path then
    reparse point, but I think ??\ is also reparse point, so I suspect you
    don’t get behind it.

Yeah, I’m opening a reparse point that I have set at some point in the
past. I’ll have to look into the ??\ thing, thanks.

-bg


NTFSD is sponsored by OSR

For our schedule debugging and file system seminars (including our new
fs mini-filter seminar) visit:
http://www.osr.com/seminars

You are currently subscribed to ntfsd as: xxxxx@edsiohio.com To
unsubscribe send a blank email to xxxxx@lists.osr.com