httpd-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
Subject Tip for 2.0 Win32 Service code.
Date Thu, 08 Jun 2000 01:58:49 GMT

Just upacked 2.04a a few minutes ago and noticed that 
the Win32 Service Startup is still jumping through hoops 
because of the way the Win32 Service Control Manager will 
'call' the 'main()' body with no command line parameters when 
it is trying to 'start' the Service.

This is a classic problem with Win32 console applications
like Apache that might want to retain some kind of 'normal'
'no parameters entered' behavior like showing a HELP screen if
main() fires off with no command line parameters.

Most people's instinct when they download a binary distribution
and see an executable is to simply type in the name of 
the executable and see if it has some 'help' or tells them
what to do first. In the case of Apache 2.0 on Win32
MANY people are going to try this and it might be handy
to head off a raft of 'Wuh happened?' PR reports by retaining
the ability to print some help or a 'You just typed in the name
of the program and that won't work' console error message.

This is NOT a patch because this is no time to confuse the
issues while you are simply trying to get it working the
way it is but I thought you should know there is a neat
trick which might help you work it out.

It is always possible for the 'main()' of the Win32 console
application to KNOW if it is the Service Control Manager
calling to start the Service OR whether the user simply
typed in the name of the executable on the command line.

Below is a little 'sample program' that shows what the
deal is. The STARTUPINFO structure for every Win32
process can tell you what you need to know.

Notice that the 'service_manager_is_calling' flag which
decides whether it is, in fact, the Win32 SCM calling or
not is a LOCAL STACK FLAG and this is what you want since
when the SCM calls it will be a different 'process' and
any kind of 'global' flag becomes a problem.

You could always use 'Read/WriteProcessMemory()' calls to
read a flag right out of the other process'es memory but
why bother when a local stack flag is all you need...

int main( int argc, char *argv[] )
 int service_control_manager_is_calling = 0; /* Goes TRUE if SCM calls */

 LPSTARTUPINFO lpsi = &si;

 /* Start... */


 Author: Kevin Kiley
 These are 2 captures from the debug log that show the differences
 in the STARTUPINFO structure when console job is started from
 the command line AND when started in the background by the
 Service Control Manager...

 STARTUPINFO for manual 'rctpdw -i' from command line...

 RCTPDW.C  @00227:main():lpsi->cb              = 68
 RCTPDW.C  @00228:main():lpsi->lpReserved      = []
 RCTPDW.C  @00229:main():lpsi->lpDesktop       = [WinSta0\Default]
 RCTPDW.C  @00230:main():lpsi->lpTitle         = [S:\RCTPDW\debug\rctpdw.exe]
 RCTPDW.C  @00231:main():lpsi->dwX             = 0
 RCTPDW.C  @00232:main():lpsi->dwY             = 0
 RCTPDW.C  @00233:main():lpsi->dwXSize         = 0
 RCTPDW.C  @00234:main():lpsi->dwYSize         = 0
 RCTPDW.C  @00235:main():lpsi->dwXCountChars   = 1310720
 RCTPDW.C  @00236:main():lpsi->dwYCountChars   = 1
 RCTPDW.C  @00237:main():lpsi->dwFillAttribute = 1322872
 RCTPDW.C  @00238:main():lpsi->dwFlags         = 0
 RCTPDW.C  @00239:main():lpsi->wShowWindow     = 1
 RCTPDW.C  @00240:main():lpsi->cbReserved2     = 0
 RCTPDW.C  @00241:main():lpsi->lpReserved2     = [(null)]
 RCTPDW.C  @00242:main():lpsi->hStdInput       = -858993460
 RCTPDW.C  @00243:main():lpsi->hStdOutput      = -858993460
 RCTPDW.C  @00244:main():lpsi->hStdError       = -858993460

 STARTUPINFO when Service Control Manager calls...

 RCTPDW.C  @00227:main():lpsi->cb              = 68
 RCTPDW.C  @00228:main():lpsi->lpReserved      = []
 RCTPDW.C  @00229:main():lpsi->lpDesktop       = [Default]
 RCTPDW.C  @00230:main():lpsi->lpTitle         = [S:\RCTPDW\debug\rctpdw.exe]
 RCTPDW.C  @00231:main():lpsi->dwX             = 0
 RCTPDW.C  @00232:main():lpsi->dwY             = 0
 RCTPDW.C  @00233:main():lpsi->dwXSize         = 0
 RCTPDW.C  @00234:main():lpsi->dwYSize         = 0
 RCTPDW.C  @00235:main():lpsi->dwXCountChars   = 0
 RCTPDW.C  @00236:main():lpsi->dwYCountChars   = 0
 RCTPDW.C  @00237:main():lpsi->dwFillAttribute = 0
 RCTPDW.C  @00238:main():lpsi->dwFlags         = 128
 RCTPDW.C  @00239:main():lpsi->wShowWindow     = 0
 RCTPDW.C  @00240:main():lpsi->cbReserved2     = 0
 RCTPDW.C  @00241:main():lpsi->lpReserved2     = [(null)]
 RCTPDW.C  @00242:main():lpsi->hStdInput       = -858993460
 RCTPDW.C  @00243:main():lpsi->hStdOutput      = -858993460
 RCTPDW.C  @00244:main():lpsi->hStdError       = -858993460

 The most obvious thing, of course, is that 'ShowWindow' flag will
 be TRUE (1) if program executes normally from command line and
 FALSE (0) if executed in the background by the Service Control
 Manager. Ditto for screen buffer related dw?CountChars variables.

 The other thing to notice is that when the Service Control Manager
 executes the program in the background the 'lpDesktop' string is
 always simply 'Default' instead of 'WinSta0\Default'.

 Why? Because when Win32 jobs are calling 'CreateProcess()' like the
 SCM does the Desktop name itself is 'assumed' since it's just one
 process starting another on the same Desktop. Command line
 execution(s) make no such assumptions about which Windows
 Desktop to use. ( Yes... there CAN be MULTIPLE Desktops active 
 just like UNIX X-Windows stuff. ).


 #endif /* CONSOLE_REFERENCE1 */

 /* Here is the trick to use for Windows console mode */
 /* SERVICE programs... */

 /* When the Service Control Manager calls this 'main()' as it */
 /* attempting to start the Service via NET START SERVICE_NAME or */
 /* via the Control Panel Service Applet or via a manual call */
 /* from StartServiceCtrlDispatcher() argc is always equal to 1 */
 /* and the only parameter is the name of the module */

 /* Since there might be times when we want the user to be able */
 /* to type in the name of the program on the command line and */
 /* still get some 'help' screen or something then the dilemma */
 /* becomes 'How do we know when it's the Service Control Manager */
 /* calling and trying to start the service or whether the user */
 /* simply typed in the program name on the command line? */

 /* A Win32 process can always call 'GetStartupInfo()' at any */
 /* time which returns a structure containing information about */
 /* how that process was 'started'. In that structure is a field */
 /* called 'lpDesktop' which is a pointer to a string containing */
 /* the NAME of the Desktop Object that launched this process as */
 /* well as a 'ShowWindow' flag which comes into play. */

 /* Under Windows NT... that 'name' will always be 'WinSta0/Default' */
 /* if the program was executed normally and simply 'Default' if it */
 /* is the Win32 Service Control Manager 'calling'... */

 /* Under Windows NT this is true for all of the following Service */
 /* startup methods... */

 /* 2. Control Panel Services Applet 'Start Service' button */
 /* 3. Manual in-process call to StartServiceCtrlDispatcher() */

 /* I have not tested for the strings using a 'remote' Service Start */
 /* from another machine across the Network but I assume this won't */
 /* make any difference since that simply ends up in an in-process call */
 /* to 'StartServiceCtrlDispatcher()' as in #3 above. */

 /* Example... */

 GetStartupInfo( (LPSTARTUPINFO) lpsi );

 x1 "lpsi->cb              = %ld", (long  ) lpsi->cb              x2;
 x1 "lpsi->lpReserved      = [%s]",(char *) lpsi->lpReserved      x2;
 x1 "lpsi->lpDesktop       = [%s]",(char *) lpsi->lpDesktop       x2;
 x1 "lpsi->lpTitle         = [%s]",(char *) lpsi->lpTitle         x2;
 x1 "lpsi->dwX             = %ld", (long  ) lpsi->dwX             x2;
 x1 "lpsi->dwY             = %ld", (long  ) lpsi->dwY             x2;
 x1 "lpsi->dwXSize         = %ld", (long  ) lpsi->dwXSize         x2;
 x1 "lpsi->dwYSize         = %ld", (long  ) lpsi->dwYSize         x2;
 x1 "lpsi->dwXCountChars   = %ld", (long  ) lpsi->dwXCountChars   x2;
 x1 "lpsi->dwYCountChars   = %ld", (long  ) lpsi->dwYCountChars   x2;
 x1 "lpsi->dwFillAttribute = %ld", (long  ) lpsi->dwFillAttribute x2;
 x1 "lpsi->dwFlags         = %ld", (long  ) lpsi->dwFlags         x2;
 x1 "lpsi->wShowWindow     = %ld", (long  ) lpsi->wShowWindow     x2;
 x1 "lpsi->cbReserved2     = %ld", (long  ) lpsi->cbReserved2     x2;
 x1 "lpsi->lpReserved2     = [%s]",(char *) lpsi->lpReserved2     x2;
 x1 "lpsi->hStdInput       = %ld", (long  ) lpsi->hStdInput       x2;
 x1 "lpsi->hStdOutput      = %ld", (long  ) lpsi->hStdOutput      x2;
 x1 "lpsi->hStdError       = %ld", (long  ) lpsi->hStdError       x2;

 /* If lpsi->lpTitle = "WinSta0/Default" then it's normal execution */
 /* If lpsi->lpTitle = "Default" then it's the Service Control Manager */


 if ( strnicmp( lpsi->lpDesktop, "Default", 7 ) == 0 )
    service_control_manager_is_calling = 1; /* Set flag TRUE */


 /* OR... just check the 'wShowWindow' flag and 'assume' that a */
 /* background execution means it WASN'T the user running the program */
 /* from the command line and expecting a help screen... */

 /* If it is TRUE then this is a normal command line execution. */
 /* If it is FALSE then the job is being run by SCM in the background. */


 if ( lpsi->wShowWindow == 0 ) /* This run is 'invisible'... */
    /* For console mode applications that can run as a Service */
    /* it is OK to assume that a 'background' execution is     */
    /* actually the Service Control Manager calling. If this   */
    /* was a background execution for a 'restart' after some   */
    /* 'watcher' program detects failure then there will be    */
    /* other command line parameters anyway and the flag       */
    /* itself will be ignored down below.                      */

    service_control_manager_is_calling = 1; /* Set flag TRUE */


 x1 "service_control_manager_is_calling = %d",
       service_control_manager_is_calling x2;

 /* Now take a look at the command line arguments... */

 /* If there is only one parameter ( execution pathname ) then    */
 /* either this is the Windows Service Control Manager attempting */
 /* to start the Service OR the user simply executed the program  */
 /* manually with no arguments expecting to see a 'help' screen.  */

 if ( argc == 1 )
    x1 "Only 1 command line parameter supplied." x2;

    /* If this is the Win32 Service Control Manager calling to */
    /* get the Service started then fall through and call the */
    /* logic that allows that to happen. */

    /* If this is NOT the Win32 Service Control Manager calling */
    /* then treat this as a 'normal' execution of the program */
    /* with no command line arguments entered. Show help screen. */

    x1 "service_control_manager_is_calling = %d",
          service_control_manager_is_calling x2;

    if ( service_control_manager_is_calling != 1 )
       /* This was simply the user typing in the name */
       /* of the console exectuable and expecting to  */
       /* see some 'help' appear on the screen...     */

       /* Print some help and exit... */

       printf( "\n");
       printf( "RCTPDW Help screen...\n");
       printf( "\n");
       printf( "Use %s -i to install   the Service\n", g_appname );
       printf( "Use %s -u to uninstall the Service\n", g_appname );
       printf( "\n");

       x1 "Help screen printed to console..." x2;
       x1 "Exit > return( 0 ) >" x2;

       return 0;

    x1 "Service Control Manager is calling..." x2;

    /* Call the ServiceStartup() routine with NO */
    /* command line parameters other than module name */
    /* and this will 'start' the Service. */

    /* This main thread can just EXIT on return from */
    /* the call if it was the SCM calling 'main()'... */

    brc = (BOOL) ServiceStartup( argc, argv );

    x1 "Exit > return( 0 ) >" x2;

    exit( 0 );

   }/* End 'if( argc == 1 )' */

 /* If we reach this point then there was simply more than */
 /* 1 command line parameter and it was NOT either the SCM */
 /* calling to start the Service or the user simply typing */
 /* the name of the executable console program on the      */
 /* command line. */

 /* Parse the valid arguments and do whatever... */

 /* Say Goodnight, Gracie... */

 x1 "Exit > return( 0 ) >" x2;

 return 0;

}/* End main() */

BTW: If you want to be REALLY sure it's the SCM calling then you can always 
make further calls to NtQueryInformationProcess() on NT or 
'GetThreadContext()' for
Win 9x and find out EXACTLY who is 'calling you'. You can even narrow it down 
HOW you are being called ( NET START, or Control Panel, or 
StartServiceCtrlDispather() )
though it doesn't really matter from an execution standpoint.

Kevin Kiley
CTO, RemoteCommunications, Inc. - Corporate page - Online HTTP Content Compression Server.

View raw message