Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

431 lines
16 KiB

Given the state of the Win32_CurrentEvent class:
class Win32_CurrentTime
{
uint32 Year;
uint32 Month;
uint32 Day;
uint32 DayOfWeek;
uint32 WeekInMonth;
uint32 Quarter;
uint32 Hour;
uint32 Minute;
uint32 Second;
[key] sint32 UTCOffset;
};
Currently we can form queries for a specific point in time
in the future. The basic form of a query is:
select * from __InstanceModificationEvent
where TargetInstance isa "Win32_CurrentTime"
and TargetInstance.Year=1999
and TargetInstance.Month=10
and TargetInstance.Day=4
and TargetInstance.Hour=11
and TargetInstance.Minute=0
and TargetInstance.Second=0
This query specifies a single point in time, irrespective
of the time at which it is to be evaluated, at which an
event should be generated. If the time specified is in
the past at the time it is evaluated, then no event will
occur.
In order to be a valid, a query must have each of the following
properties:
1. A query must be specific enough to define the complete set
of all times for which an event is to be generated.
2. Each time the expression is evaluated, it must be possible
to specify unambiguously the closest point after the current
time when the next event will occur.
f(<WQL expression>, <current time>) -> <next event time>
3. The frequency of events must not be such that it may overwhelm
the system under a normal load.
The following, I believe, are the four most likely scenarios
for which a user will ask for time events:
1. They want timer events to be generated indefinitely at
a specified periodic interval.
2. They want a single event to be generated at some specified
time in the future. (alarm functionality)
3. They want a single event to be generated at some time
in the future relative to the time at which the query is
made. (timer functionality)
4. They want one or more events to be generated based on some
algebraic criteria.
A single query may embody one or more of the above scenarios.
An example of the first scenario could be the following:
select * from __InstanceModificationEvent
where TargetInstance isa "Win32_CurrentTime"
and TargetInstance.Year=1999
and TargetInstance.Month=10
and TargetInstance.Day=4
and TargetInstance.Hour=11
and TargetInstance.Second % 30 = 0
This specifies that we want an event every thirty seconds
on the minute and half minute boundaries for the eleventh
hour on the date 10/4/99. With the current approach we
cannot completely capture the functionality of the first
scenario. For example, we could not specify a timer to
generate an event exactly seven seconds apart. If, however,
we add a field to Win32_CurrentTime that is the equivalent
file time (64 bit number) rounded to seconds that represents
the same time as the system time fields, it then becomes
possible:
select * from __InstanceModificationEvent
where TargetInstance isa "Win32_CurrentTime"
and TargetInstance.Year=1999
and TargetInstance.Month=10
and TargetInstance.Day=4
and TargetInstance.Hour=11
and TargetInstance.TimeInSeconds % 30 = 0
This is equivalent to the above statement but uses the total
time in seconds instead of just seconds. By changing '30' to
'7' we get an event every seven seconds. Note that by omitting
a field we are signifying that it is a wildcard.
For this type of query we will introduce a reserved symbol that
evaluates to the current time when the expression is evaluated.
There is a reserved symbol for each field in Win32_CurrentTime.
Thus, to say "generate an event 45 seconds from now" we could
write:
select * from __InstanceModificationEvent
where TargetInstance isa "Win32_CurrentTime"
and TargetInstance.TimeInSeconds = %CurrentTimeInSeconds% + 45
Fundamentally, WQL statements are patterns, not program
expressions that can save state as part of their evaluation. It
is valid to have algebraic expressions within WQL as long as they
ultimately evaluate to a constant and there is no concept of
assignment. The symbol %CurrentTimeInSeconds% is actually a
macro that is replaced by the parser with the current time in
seconds when it is encountered. Thus, the above statement will
only generate one event and not an event every 45 seconds.
Each of the examples above are by definition examples of time
specification based on algebraic criteria. Here is an example
that does not fall into any of the first three categories:
select * from __InstanceModificationEvent
where TargetInstance isa "Win32_CurrentTime"
and TargetInstance.Year=1999
and TargetInstance.Month=10
and TargetInstance.Day=10
and TargetInstance.Hour <= 12
and TargetInstance.Minute = TargetInstance.Day
This says to generate an event once an hour for the first half
of the day when the minute in the current hour is equal to the
day of the month. This is only to occur on the date 10/10/99.
The following is an excerpt from the WQL1 grammar that has
been modified to allow support for queries containing simple
algebraic expressions:
<parse> ::= SELECT * FROM <class_name> WHERE <expr>;
<property_name> ::= IDENTIFIER <property_name_2>;
<property_name_2> ::= <>;
<property_name_2> ::= DOT IDENTIFIER <property_name_2>;
<class_name> ::= IDENTIFIER;
<expr> ::= <term> <expr2>;
<expr2> ::= OR <term> <expr2>;
<expr2> ::= <>;
<term> ::= <simple_expr> <term2>;
<term2> ::= AND <simple_expr> <term2>;
<term2> ::= <>;
<operand> ::= <property_name>
<operand> ::= <typed_constant>
<simple_expr> ::= <algebraic_expr> <rel_operator> <algebraic_expr>;
<algebraic_expr> ::= <operand>
<algebraic_expr> ::= <algebraic_expr> <arithmetic_operator> <algebraic_expr>
<algebraic_expr> ::= OPEN_PAREN <algebraic_expr> CLOSE_PAREN;
<typed_constant> ::= VARIANT; // VT_R8, VT_I4, VT_BSTR
<typed_constant> ::= TRUE;
<typed_constant> ::= FALSE;
<typed_constant> ::= <macro_constant>;
<macro_constant> ::= PERCENT IDENTIFIER PERCENT ;
<rel_operator> ::= <equiv_operator>;
<rel_operator> ::= <comp_operator>;
<equiv_operator> ::= EQUIVALENT_OPERATOR; // =, !=, <>
<comp_operator> ::= COMPARE_OPERATOR; // <=, >=, <, >, like, isa
<arithmetic_operator> ::= ARITHMETIC_OPERATOR; // +, -, *, /, %
**** INSERT SECTION ON HOW TO DECOMPOSE A WQL STATEMENT TO A
SERIES OF TIME PATTERNS TO MATCH AGAINST (DNF)
Solution to the Problem of Finding a Closest Date in the Future
to Current Time:
First, let's impose a few key constraints that do not detract from
the fundamental problem but allow us to more clearly illustrate the
solution.
1. Limit the problem to finding the next date within the current year.
This is reasonable because if a suitable date cannot be found
within the remainder of the current year, find the first matching
date from midnight January 1st of next year.
2. Limit ourselves to those fields of date that do not have overlaping
meaning and exactly contain each other. For our Win32_CurrentTime
structure those fields are: Year, Month, Day, Hour, Minute, Second.
Week and any representation dependent on week violates this
criteria because years and months do not, in general, start and
stop on week boundaries. This means that weeks are not countable
w.r.t. any larger unit of time.
Given these constraints, it becomes possible to perform addition with
carry on a given date with intervals expressed in any of the above
units quickly.
Now, our problem is one of finding the first date beyond the current
time that matches a specific pattern provided by a WQL query. Because
of the 2nd constraint, we can find a matching time incrementally by
working our way from seconds to years finding the next matching pattern
for each. When complete, we will have found the next closest date to
the current time matching the pattern.
From the parsing phase we derived a set of date "patterns" which can
be used to find matching future dates. Each such pattern is
represented by an object that contains matching criteria for each
date field. For each of second, minute, hour, day and month we
represent the set of valid times as members of a set.
The pattern object looks like:
DatePattern
{
VALIDMATCH Year; // integer
VALIDMATCH Month; // [1..12]
VALIDMATCH Day; // [1..31] *varies with month
VALIDMATCH DayOfWeek; // [1..7]
VALIDMATCH WeekInMonth; // [1..5]
VALIDMATCH Quarter; // [1..4]
VALIDMATCH Hour; // [1..24]
VALIDMATCH Minute; // [1..60]
VALIDMATCH Second; // [1..60]
}
The comment following each field indicates the valid
range of values.
The matching criteria VALIDMATCH looks like:
VALIDMATCH
{
unsigned UpperBound;
unsigned LowerBound;
unsigned Modulus;
unsigned CountNotMatching;
unsigned *NotMatching;
}
Because, for each field above except year, the set of valid values
is finite, we can collectively represent all of the limiting
criteria within a VALIDMATCH object as a set containing matching
members. The set described is a finite set on which there is a
total ordering of its members. This can be represented by a bit
field with each bit identifying one potential member. If a member
is present in a given set its bit is set to one otherwise it is
zero.
With this arrangement, given any value within the range of the
set we can find immediately whether or not that value is, in fact,
a member of the set or, if not, what is the next set value. The
following function outlines this task:
typedef Set __int64; // Set is a bitfield with 1 represented by
// leftmost bit
BOOL FindNextElement(int &iCurrentValue, Set ValidValues)
{
BOOL Carry = FALSE;
Set CurrentValue = 0x80000000000000000 >> iCurrentValue;
while(! (CurrentValue & ValidValues))
{
iCurrentValue >> 1;
CurrentValue++;
if(CurrentValue == 0x0)
{
CurrentValue = 0x8000000000000000;
iCurrentValue = 1;
Carry = TRUE;
}
}
return Carry;
}
Note: this function assumes ValidValues is not empty.
FindNextElement() takes a value as a member of a Set object
and finds the next greater member of the set ValidValues or
the first member encountered in case of roleover. Roleover is
indicated by the carry flag. The first member encountered is
returned in CurrentValue.
Now, applying this to a complete date pattern consisting of year,
month, day, hour, minute and second would involve applying this
function using the method described above with the following special
cases. First, if it was found that there was a carry on a month,
then we would need to go back and ensure that the new day is less
than or equal to the last day of the new month. If not, then keep
adding months until a month is found where this is true. Second,
if there is a carry on months, then we have crossed a year boundary.
In this case we need to obtain the information for a new calendar
year.
To address the additional fields WeekInMonth, DayOfWeek and Quarter,
we will use a technique based on the notion that these are just
alternative ways to specify the pattern for the day field. For each
for each of these fields create a bitfield representing the days of
the current month. In each such field set those bits to one that
represent those days in the month that match for each of WeekInMonth,
DayOfWeek and Quarter. And each of these together and to that of
days. The resultant bitfield is the total number of days for the
current month that match all criteria. This implies that the
problem reduces to that of addition with carry as described above.
Day = Day & WeekInMonth & DayOfWeek & Quarter
There is the additional problem of how to handle addition operations
that cross month boundaries. This will occur when imcrementing the
day field results in a carry over to the next month. When this
happens we need to generate the composite Day field for the next
valid month.
To generate the bitfields for each of Day, WeekInMonth, DayOfWeek
and Quarter w.r.t. some month we need to know how many days there
are in the month, what month it is in the year and what day of the
week is the first day of the month. The month information can be
generated using the system functions SystemTimeToFileTime() and
FileTimeToSystemTime(). Given a month and year we can build these
four bitfields as follows:
1. Populate a SYSTEMTIME structure with date of the start of the
month of interest at midnight of the first day of that month.
<month>/1/<year> 0:0:0
2. Convert the SYSTEMTIME to a FILETIME and back to a SYSTEMTIME.
Now SYSTEMTIME::wDayOfWeek will contain the day of the week
for the first day of the month. Note: we will save this
FILETIME for later use below.
3. Determine the FILETIME for the beginning of the month following
the one we are interested as in step 1 and 2 above.
4. Subtract the second FILETIME from the first and convert the
result to days. This is the number of days in the month.
5. Using the sets from each field of the DatePattern structure
we can now construct the bitfields for the current month
corresponding to each of the four fields above.
6. Combine the bitfields with the Day bitfield using logical and
to obtain the desired composite bitfield.
This functionality can be encapsulated into a single function that
takes a desired month and year plus a DatePattern structure and
returns the desired composite bitfield:
Set GetDaysInMonth(DatePattern *, int year, int month)
First, lets modify the DatePattern structure defined above to
fields for each member that represents it in terms of sets as
follows:
DatePattern
{
VALIDMATCH Year; // integer
VALIDMATCH Month; // [1..12]
Set MonthSet;
VALIDMATCH Day; // [1..31] *varies with month
Set DaySet;
VALIDMATCH DayOfWeek; // [1..7]
Set DayOfWeekSet;
VALIDMATCH WeekInMonth; // [1..5]
Set WeekInMonthSet;
VALIDMATCH Quarter; // [1..4]
VALIDMATCH Hour; // [1..24]
Set HourSet;
VALIDMATCH Minute; // [1..60]
Set MinuteSet;
VALIDMATCH Second; // [1..60]
Set SecondSet;
}
By providing a separate pattern for each of DayOfWeek and WeekInMonth,
we can optimize calculations of the composite field Day.
The current date is a collection of integers from SYSTEMTIME including
wYear, wMonth, wDay, wHour, wMinute and wSecond. Here, then, is the
algorithm for finding the closest next firing time to the current time:
Let Pattern = DatePattern object
Let Date = SYSTEMTIME object
Let Carry = BOOL object
GetSystemTime(&Date);
Carry = FindNextElement(&Date.wSecond, Pattern.SecondSet);
if(Carry) Date.wMinute++;
Carry = FindNextElement(&Date.wMinute, Pattern.MinuteSet);
if(Carry) Date.wHour++;
Carry = FindNextElement(&Date.wHour, Pattern.HourSet);
if(Carry) Date.wDay++;
do
{
Carry = FindNextElement(&Date.wDay, Pattern.DaySet);
if(Carry)
{
do
{
Date.wMonth++;
Carry2 = FindNextElement(&Date.wMonth, Pattern.MonthSet);
if(Carry2)
{
Date.wYear += 1;
<find next matching Date.wYear against Pattern.Year using
numerical methods>
}
Pattern.MonthSet = GetDaysInMonth(&Pattern, Date.wYear, Date.wMonth);
}
while(0x0 == Pattern.MonthSet);
}
}
while(Carry);