Mailing-List: contact cygwin-help AT cygwin DOT com; run by ezmlm List-Subscribe: List-Archive: List-Post: List-Help: , Sender: cygwin-owner AT cygwin DOT com Mail-Followup-To: cygwin AT cygwin DOT com Delivered-To: mailing list cygwin AT cygwin DOT com Message-ID: <428FE303.A897C7B3@dessent.net> Date: Sat, 21 May 2005 18:40:19 -0700 From: Brian Dessent MIME-Version: 1.0 To: cygwin AT cygwin DOT com Subject: Re: [patch] several new features for cygrunsrv References: <428CE837 DOT C00E288B AT dessent DOT net> <20050519203046 DOT GH2794 AT calimero DOT vinschen DOT de> Content-Type: multipart/mixed; boundary="------------922E1B4D8D71BFA52B6B8F16" X-IsSubscribed: yes Reply-To: cygwin AT cygwin DOT com --------------922E1B4D8D71BFA52B6B8F16 Content-Type: text/plain; charset=us-ascii Content-Transfer-Encoding: 7bit Corinna Vinschen wrote: > I really like this patch, cool stuff. However, I have two nits. > > First, your patch adds new options, so it should also add some wording to > cygrunsrv.README. Okay, here is a new patch. * --list outputs one service per line to avoid the awkward quoting issue. * checks basename only, so that it will match any service ending in cygrunsrv.exe (or whatever the binary might be renamed to.) * mention new features in .README * fixes goof with undersized static char buf[34] * ServiceType_desc has bitwise=true now, as I discovered that when you install with --interactive, both SERVICE_WIN32_OWN_PROCESS and SERVICE_INTERACTIVE_PROCESS are set, and it was showing (Unknown) before. * if both stdout_path and stderr_path are the same, they are combined into one line in the --verbose output, to save some space. I did not change the version number, but I imagine you would want to bump that. I'll leave that up to you. Still to-do: * patch cygcheck to call "cygrunsrv -LV" and include output, iff it can be found and iff running NT. * I started looking into adding logic for "cygrunsrv -I" that would print a big warning to stderr if it detects a mount problem, i.e. trying to install system service with user mode mounts. I had two ideas for how to accomplish this: 1. Just walk the mount entries and check to see that /usr/bin is not mounted user. 2. Impersonate the user specified (or .\SYSTEM) and try to stat() the --path value. #2 is more robust (i.e. it wouldn't falsely error if the user really did know what he's doing and is installing a service to run as his local user account with only user mode mounts.) It's also more complicated though. Do I presume correctly that cygwin will read the correct registry keys while impersonating, so that I can just seteuid() and then stat() the file trying to be installed as a service? Brian 2005-05-21 Brian Dessent * cygrunsrv.cc: Add include. (longopts): Add '--list' and '--verbose' options. (opts): Add '-L' and '-V' options; keep order consistent with above. (action_t): Add 'List'. (err_out_set_error): Define version of 'err_out' macro that allows for convenient setting the error code. (get_description): New function. (install_service): Use 'err_out_set_error' instead throughout. (start_service): Ditto. (stop_service): Ditto. (ServiceType_desc): Add. Use structs to map DWORD fields onto strings. (StartType_desc): Ditto. (CurrentState_desc): Ditto. (ControlsAccepted_desc): Ditto. (make_desc): Add new function that generalizes the task of creating a textual field from a binary DWORD. (serviceTypeToString): Remove. (serviceStateToString): Ditto. (controlsToString): Ditto. (parsedoublenull): Add new helper function for parsing lists of strings, which is used below when printing the 'lpDependencies' value. (print_service): Add new function that is responsible for generating the formatted output for --list and --query commands. (QSC_BUF_SIZE): Add. (query_service): Add verbosity parameter. Remove printf output from here, call 'print_service' instead. Call QueryServiceConfig to retrieve more detail on the service. (same_filename): New function. (list_services): Add new function that implements -L,--list command. Call EnumServicesStatus to get names of all services, and then determine which ones are cygrunsrv services. List their names, or call print_service() if verbosity was requested. (main): Declare new variable 'verbosity'. Support new command line switches. Pass on verbosity information to query_service and list_services. * utils.cc (reason_list): Update error text. (usage): Document new switches in the help text. * utils.h (reason_t): Add new symbolic name for error text. * cygrunsrv.README: Update documentation for new flags. --------------922E1B4D8D71BFA52B6B8F16 Content-Type: text/plain; charset=us-ascii; name="cygrunsrv-newfeatures3.patch" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="cygrunsrv-newfeatures3.patch" --- /tmp/cygrunsrv-1.02-1/cygrunsrv.cc 2005-05-16 07:55:41.000000000 -0700 +++ cygrunsrv.cc 2005-05-21 18:07:15.093750000 -0700 @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -51,6 +52,7 @@ struct option longopts[] = { { "start", required_argument, NULL, 'S' }, { "stop", required_argument, NULL, 'E' }, { "query", required_argument, NULL, 'Q' }, + { "list", no_argument, NULL, 'L' }, { "path", required_argument, NULL, 'p' }, { "args", required_argument, NULL, 'a' }, { "chdir", required_argument, NULL, 'c' }, @@ -69,6 +71,7 @@ struct option longopts[] = { { "shutdown", no_argument, NULL, 'o' }, { "interactive", no_argument, NULL, 'i' }, { "nohide", no_argument, NULL, 'j' }, + { "verbose", no_argument, NULL, 'V' }, { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, 'v' }, { 0, no_argument, NULL, 0 } @@ -77,8 +80,9 @@ struct option longopts[] = { char *opts = "I:" "R:" "S:" - "Q:" "E:" + "Q:" + "L" "p:" "a:" "c:" @@ -97,6 +101,7 @@ char *opts = "I:" "n" "i" "j" + "V" "h" "v"; @@ -118,7 +123,8 @@ enum action_t { Remove, Start, Stop, - Query + Query, + List }; enum type_t { @@ -156,6 +162,8 @@ eval_wait_time (register DWORD wait) } #define err_out(name) {err_func = #name; err = GetLastError (); goto out;} +#define err_out_set_error(name, error) \ + {err_func = #name; err = error; SetLastError (error); goto out;} /* Installs the subkeys of the service registry entry so that cygrunsrv can determine what application to start on service startup. */ @@ -463,6 +471,36 @@ out: return ret; } +/* Retrieves the description of the service. Note: it would be so much + cleaner to do this by a simple call to QueryServiceConfig2(), but alas this + does not exist in NT4. *sigh* */ +int +get_description (const char *name, char *&descr) +{ + HKEY desc_key = NULL; + char desc_key_path[MAX_PATH]; + int ret; + + strcat (strcpy (desc_key_path, SRV_KEY), name); + if ((ret = RegOpenKeyEx (HKEY_LOCAL_MACHINE, desc_key_path, 0, + KEY_READ, &desc_key)) != ERROR_SUCCESS) + goto out; + + if ((ret = get_opt_string_entry (desc_key, DESC, descr))) + goto out; + + ret = 0; + +out: + if (ret) + descr = NULL; + if (desc_key) + RegCloseKey (desc_key); + return ret; +} + + + int add_env_var (char *envstr, env_t *&env) { @@ -575,10 +613,7 @@ install_service (const char *name, const GetLastError() != ERROR_SERVICE_DOES_NOT_EXIST) err_out (OpenService); if (sh) - { - SetLastError (ERROR_SERVICE_EXISTS); - err_out (OpenService); - } + err_out_set_error (OpenService, ERROR_SERVICE_EXISTS); /* Set optional dependencies. */ if (deps) { @@ -587,10 +622,7 @@ install_service (const char *name, const concat_length += (strlen(deps[i]) + 1); concat_length++; if (! (dependencies = (char *) malloc(concat_length))) - { - SetLastError(ERROR_OUTOFMEMORY); - err_out(malloc); - } + err_out_set_error (malloc, ERROR_OUTOFMEMORY); char *p = dependencies; for (int i = 0; i < MAX_DEPS && deps[i]; i++) { @@ -626,10 +658,7 @@ install_service (const char *name, const } } if (!(username = (char *) malloc (strlen (user) + 3))) - { - SetLastError (ERROR_OUTOFMEMORY); - err_out (malloc); - } + err_out_set_error (malloc, ERROR_OUTOFMEMORY); /* If no "\" is part of the name, prepend ".\" */ if (!strchr (user, '\\')) strcat (strcpy (username, ".\\"), user); @@ -789,18 +818,12 @@ start_service (const char *name) last_tick = GetTickCount (); } else if (GetTickCount() - last_tick > ss.dwWaitHint) - { - SetLastError (ERROR_SERVICE_REQUEST_TIMEOUT); - err_out (QueryServiceStatus); - } + err_out_set_error (QueryServiceStatus, ERROR_SERVICE_REQUEST_TIMEOUT); } } if (ss.dwCurrentState != SERVICE_RUNNING) - { - SetLastError (ERROR_SERVICE_NOT_ACTIVE); - err_out (QueryServiceStatus); - } + err_out_set_error (QueryServiceStatus, ERROR_SERVICE_NOT_ACTIVE); out: if (sh) @@ -858,10 +881,7 @@ stop_service (const char *name) last_tick = GetTickCount (); } else if (GetTickCount() - last_tick > ss.dwWaitHint) - { - SetLastError (ERROR_SERVICE_REQUEST_TIMEOUT); - err_out (QueryServiceStatus); - } + err_out_set_error (QueryServiceStatus, ERROR_SERVICE_REQUEST_TIMEOUT); } } } @@ -875,104 +895,240 @@ out: return err == 0 ? 0 : error (StopErr, err_func, err); } -char * -serviceTypeToString(DWORD stype) -{ - switch (stype) { - case SERVICE_WIN32_OWN_PROCESS: - return "Own Process"; - break; - case SERVICE_WIN32_SHARE_PROCESS: - return "Share Process"; - break; - case SERVICE_KERNEL_DRIVER: - return "Kernel Driver"; - case SERVICE_FILE_SYSTEM_DRIVER: - return "File System Driver"; - case SERVICE_INTERACTIVE_PROCESS: - return "Interactive Process"; - default: - return "Undefined type"; - } +/* these are used to turn DWORDs into desciptive text */ +static struct desc_type { bool bitwise; DWORD flag; const char *meaning; } + +/* Note: service installed with --interactive will have + (SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS) so this + needs to have bitmask = true. */ +ServiceType_desc[] = +{ + {true, 0, "(Unknown)"}, + {true, SERVICE_WIN32_OWN_PROCESS, "Own Process, "}, + {true, SERVICE_WIN32_SHARE_PROCESS, "Shared Process, "}, + {true, SERVICE_KERNEL_DRIVER, "Kernel Driver, "}, + {true, SERVICE_FILE_SYSTEM_DRIVER, "Filesystem Driver, "}, + {true, SERVICE_INTERACTIVE_PROCESS, "Interactive, "}, + {true, 0, NULL} +}, + +StartType_desc[] = +{ + {false, 0, "(Unknown)"}, + {false, SERVICE_BOOT_START, "Boot"}, + {false, SERVICE_SYSTEM_START, "System"}, + {false, SERVICE_AUTO_START, "Automatic"}, + {false, SERVICE_DISABLED, "Disabled"}, + {false, SERVICE_DEMAND_START, "Manual"}, + {false, 0, NULL} +}, + +CurrentState_desc[] = +{ + {false, 0, "(Unknown)"}, + {false, SERVICE_STOPPED, "Stopped"}, + {false, SERVICE_START_PENDING, "Start Pending"}, + {false, SERVICE_STOP_PENDING, "Stop Pending"}, + {false, SERVICE_RUNNING, "Running"}, + {false, SERVICE_CONTINUE_PENDING, "Continue Pending"}, + {false, SERVICE_PAUSE_PENDING, "Pause Pending"}, + {false, SERVICE_PAUSED, "Paused"}, + {false, 0, NULL} +}, + +ControlsAccepted_desc[] = +{ + {true, 0, "(none)"}, + {true, SERVICE_ACCEPT_STOP, "Stop, "}, + {true, SERVICE_ACCEPT_SHUTDOWN, "Shutdown, "}, + {true, SERVICE_ACCEPT_PAUSE_CONTINUE, "Pause, Continue, "}, + {true, SERVICE_ACCEPT_PARAMCHANGE, "Change Parameters, "}, + {true, SERVICE_ACCEPT_NETBINDCHANGE, "Change Network Binding, "}, + {true, SERVICE_ACCEPT_HARDWAREPROFILECHANGE, "Hardware Profile Change Notify, "}, + {true, SERVICE_ACCEPT_POWEREVENT, "Power Status Change Notify, "}, + {true, SERVICE_ACCEPT_SESSIONCHANGE, "Session Status Change Notify, "}, + {true, 0, NULL } +}; + + +/* Passed one of the above static arrays and a DWORD, this returns a + pointer to a descriptive string: either a concatenation of matching + items (if bitwise is true) otherwise just the matching item. */ +const char * +make_desc(struct desc_type *desc, DWORD thing) +{ + static char buf[256]; + + if (desc[0].bitwise) + { + buf[0] = '\0'; + for (int i = 1; desc[i].meaning; i++) + if (thing & desc[i].flag) + strcat (buf, desc[i].meaning); + + char *ptr = strchr (buf, '\0'); + if (ptr - buf > 2 && ptr[-1] == ' ' && ptr[-2] == ',') + { + ptr[-2] = '\0'; /* remove tailing ", " */ + return (buf); + } + } + else + for (int i = 1; desc[i].meaning; i++) + if (thing == desc[i].flag) + return (desc[i].meaning); + + return desc[0].meaning; /* default value */ } +/* Passed a pointer to a double-NULL terminated list of strings, this + returns a formatted list of those items, each delimited by `delim'. */ char * -serviceStateToString(DWORD state) +parsedoublenull (const char *input, const char *delim) { - switch (state) { - case SERVICE_STOPPED: - return "Stopped"; - case SERVICE_START_PENDING: - return "Start Pending"; - case SERVICE_STOP_PENDING: - return "Stop Pending"; - case SERVICE_RUNNING: - return "Running"; - case SERVICE_CONTINUE_PENDING: - return "Continue Pending"; - case SERVICE_PAUSE_PENDING: - return "Pause Pending"; - case SERVICE_PAUSED: - return "Paused"; - default: - return "Undefined state"; - } -} - -#define ACCEPT_STOP_MSG "Accept Stop" -#define ACCEPT_PAUSE_CONTINUE_MSG "Accept Pause Continue" -#define ACCEPT_SHUTDOWN_MSG "Accept Shutdown" -char * -controlsToString(DWORD controls) + char *base, *end; + static char buf[256]; + int used = 0, dsiz = strlen (delim); + +#define parsedoublenull_done (*end == 0 && *base == 0) + + for (buf[0] = 0, base = end = (char *) input; !parsedoublenull_done; end++) + if (*end == 0) + { + if ((used += ((end - base) + dsiz)) >= sizeof(buf)) + break; /* don't overflow */ + strcat (buf, base); + base = end + 1; + if (!parsedoublenull_done) + strcat (buf, delim); + } + return buf; +} + +/* Passed the name, opened handle, and status information about a service, + this formats all the information and outputs it to stdout. */ +void +print_service (const char *name, SC_HANDLE &sh, SERVICE_STATUS &ss, + QUERY_SERVICE_CONFIG *qsc, bool verbose) { - static char buf[sizeof(ACCEPT_STOP_MSG) + sizeof(ACCEPT_PAUSE_CONTINUE_MSG) - + sizeof(ACCEPT_SHUTDOWN_MSG) + 5]; - buf[0] = '\0'; - - if ( controls & SERVICE_ACCEPT_STOP ) { - strcat(buf, ACCEPT_STOP_MSG); - } - if ( controls & SERVICE_ACCEPT_PAUSE_CONTINUE ) { - if ( buf[0] != '\0' ) - strcat(buf, ", "); - strcat(buf, ACCEPT_STOP_MSG); - } - if ( controls & SERVICE_ACCEPT_SHUTDOWN ) { - if ( buf[0] != '\0' ) - strcat(buf, ", "); - strcat(buf, ACCEPT_SHUTDOWN_MSG); - } - return(buf); + char *descrip = NULL, *path = NULL, *args = NULL, *dir = NULL, + *stdin_path = NULL, *stdout_path = NULL, *stderr_path = NULL; + DWORD termsignal, neverex, shutd, interact, showc; + env_t *env = NULL; + +#define P(x, y) printf ("%-20s: %s\n", x, y) + + P("Service", name); + if (strcmp (name, qsc->lpDisplayName)) + P("Display name", qsc->lpDisplayName); + if (!get_description (name, descrip) && descrip && strlen (descrip)) + P("Description", descrip); + + P("Current State", make_desc(CurrentState_desc, ss.dwCurrentState)); + if (ss.dwControlsAccepted) + P("Controls Accepted", make_desc(ControlsAccepted_desc, ss.dwControlsAccepted)); + + /* Get the cygrunsrv-specific things from the registry. */ + if (get_reg_entries (name, path, args, dir, env, &termsignal, + stdin_path, stdout_path, stderr_path, + &neverex, &shutd, &interact, &showc)) + return; /* bail on error */ + + printf ("%-20s: %s", "Command", path); + if (args) + printf (" %s\n", args); + else + fputc ('\n', stdout); + + if (verbose) + { + if (dir) + P("Working Dir", dir); + if (stdin_path) + P("stdin path", stdin_path); + if (stdout_path && stderr_path && !stricmp(stdout_path, stderr_path)) + P("stdout+stderr path", stdout_path); + else + { + if (stdout_path) + P("stdout path", stdout_path); + if (stderr_path) + P("stderr path", stderr_path); + } + + char tmp[128] = {0}; + if (neverex) + strcat (tmp, "--neverexits "); + if (shutd) + strcat (tmp, "--shutdown "); + if (interact) + strcat (tmp, "--interactive "); + if (interact) + strcat (tmp, "--nohide "); + if (strlen(tmp)) + P("Special flags", tmp); + + if (termsignal != SIGTERM) + P("Termination Signal", strsignal (termsignal)); + + if (env) + { + printf ("%-20s: ", "Environment"); + for (int i = 0; i <= MAX_ENV && env[i].name; ++i) + printf ("%s=\"%s\" ", env[i].name, env[i].val); + fputc ('\n', stdout); + } + + P("Process Type", make_desc(ServiceType_desc, ss.dwServiceType)); + P("Startup", make_desc(StartType_desc, qsc->dwStartType)); + if (qsc->lpDependencies && strlen (qsc->lpDependencies)) + P("Dependencies", parsedoublenull(qsc->lpDependencies, ", ")); + if (qsc->lpServiceStartName) + P("Account", qsc->lpServiceStartName); + } + +#undef P + fputc ('\n', stdout); } +/* According to the platform SDK, the maximum size that a QUERY_SERVICE_CONFIG + buffer need be is 8kb. */ +#define QSC_BUF_SIZE 8192 + /* Query service `name'. */ int -query_service (const char *name) +query_service (const char *name, bool verbose) { SC_HANDLE sm = (SC_HANDLE) 0; SC_HANDLE sh = (SC_HANDLE) 0; SERVICE_STATUS ss; + QUERY_SERVICE_CONFIG *qsc_buf = NULL; int cnt = 0; char *err_func; - DWORD err = 0; + DWORD err = 0, bytes_needed; + + /* Allocate qsc buffer. */ + if ((qsc_buf = (QUERY_SERVICE_CONFIG *) malloc (QSC_BUF_SIZE)) == NULL) + err_out_set_error (malloc, ERROR_OUTOFMEMORY); /* Open service manager database. */ if (!(sm = OpenSCManager (NULL, NULL, SC_MANAGER_CONNECT))) err_out (OpenSCManager); + /* Check whether service exists. */ if (!(sh = OpenService (sm, name, SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG))) err_out (OpenService); - printf("Service %s exists\n", name); - /* Get the status and print it out */ + /* Get the current status of the service. */ if (!QueryServiceStatus(sh, &ss)) err_out (QueryServiceStatus); - printf("%-20s: %s\n", "Type", serviceTypeToString(ss.dwServiceType)); - printf("%-20s: %s\n", "Current State", - serviceStateToString(ss.dwCurrentState)); - printf("%-20s: %s\n", "Controls Accepted", \ - controlsToString(ss.dwControlsAccepted)); + /* Get configuration info about the service. */ + if (!QueryServiceConfig (sh, qsc_buf, QSC_BUF_SIZE, &bytes_needed)) + err_out (QueryServiceConfig); + + print_service (name, sh, ss, qsc_buf, verbose); + err = 0; out: @@ -980,11 +1136,109 @@ out: CloseServiceHandle (sh); if (sm) CloseServiceHandle (sm); + if (qsc_buf) + free (qsc_buf); return err == 0 ? 0 : error (QueryErr, err_func, err); } +/* Returns true if the two file/path names end in the same filename */ +bool +same_filename (const char *a, const char *b) +{ + char a_buf[MAX_PATH + 1], b_buf[MAX_PATH + 1]; + + strcpy (a_buf, basename ((char *) a)); + strcpy (b_buf, basename ((char *) b)); + return !stricmp (a_buf, b_buf); +} + +/* Iterates through all services and reports on + those that are cygrunsrv-managed. */ +int +list_services (bool verbose) +{ + char mypath[MAX_PATH]; + SC_HANDLE sm = (SC_HANDLE) 0; + SC_HANDLE sh = (SC_HANDLE) 0; + ENUM_SERVICE_STATUS *srv_buf = NULL; + QUERY_SERVICE_CONFIG *qsc_buf = NULL; + SERVICE_STATUS ss; + DWORD bytes_needed, num_services, resume_handle = 0; + char *err_func; + DWORD err = 0; + + /* Get our own filename so that we can tell which services are ours. */ + if (!GetModuleFileName (NULL, mypath, MAX_PATH)) + err_out (GetModuleFileName); + + /* This buffer will be used for querying the details of a service. */ + if ((qsc_buf = (QUERY_SERVICE_CONFIG *) malloc (QSC_BUF_SIZE)) == NULL) + err_out_set_error (malloc, ERROR_OUTOFMEMORY); + + /* Open service manager database. */ + if (!(sm = OpenSCManager (NULL, NULL, SC_MANAGER_CONNECT | + SC_MANAGER_ENUMERATE_SERVICE))) + err_out (OpenSCManager); + + /* First call with lpServices to NULL to get length of needed buffer. */ + if (EnumServicesStatus (sm, SERVICE_WIN32, SERVICE_STATE_ALL, NULL, 0, + &bytes_needed, &num_services, &resume_handle) != 0) + err_out (EnumServiceStatus); + + if ((srv_buf = (ENUM_SERVICE_STATUS *) malloc (bytes_needed)) == NULL) + err_out_set_error (malloc, ERROR_OUTOFMEMORY); + + /* Call the function for real this time with the allocated buffer. + FIXME: In theory this should be a while loop that checks for ERROR_MORE_DATA + and continues fetching the remaining records. However, we'll just trust + that the value returned above in bytes_needed was sufficient to get all + records in a single pass. */ + if (!EnumServicesStatus (sm, SERVICE_WIN32, SERVICE_STATE_ALL, srv_buf, + bytes_needed, &bytes_needed, &num_services, &resume_handle)) + err_out (EnumServiceStatus); + + for (int i = 0; i < num_services; i++) + { + /* get details of this service and see if it's one of ours. */ + if (!(sh = OpenService (sm, srv_buf[i].lpServiceName, GENERIC_READ))) + err_out (OpenService); + + if (!QueryServiceConfig (sh, qsc_buf, QSC_BUF_SIZE, &bytes_needed)) + err_out (QueryServiceConfig); + + /* is this us? */ + if (same_filename (qsc_buf->lpBinaryPathName, mypath)) + { + if (!verbose) + printf ("%s\n", srv_buf[i].lpServiceName); + else + { + if (!QueryServiceStatus(sh, &ss)) + err_out (QueryServiceStatus); + + print_service (srv_buf[i].lpServiceName, sh, ss, qsc_buf, verbose); + } + } + CloseServiceHandle (sh); + } + + //fputc ('\n', stdout); + err = 0; + +out: + if (qsc_buf) + free (qsc_buf); + if (srv_buf) + free (srv_buf); + if (sh) + CloseServiceHandle (sh); + if (sm) + CloseServiceHandle (sm); + return err == 0 ? 0 : error (ListErr, err_func, err); +} #undef err_out +#undef err_out_set_error int server_pid; @@ -1338,6 +1592,7 @@ main (int argc, char **argv) int in_shutdown = 0; int in_interactive = 0; int in_showcons = 0; + bool verbose = false; appname = argv[0]; @@ -1391,6 +1646,11 @@ main (int argc, char **argv) action = Query; in_name = optarg; break; + case 'L': + if (action != Undefined) + return error (ReqAction); + action = List; + break; case 'p': if (action != Install) return error (PathNotAllowed); @@ -1530,6 +1790,9 @@ main (int argc, char **argv) return error (OnlyOneIO); in_stderr = optarg; break; + case 'V': + verbose = true; + break; case 'h': return usage (); case 'v': @@ -1573,7 +1836,10 @@ main (int argc, char **argv) return stop_service (in_name); break; case Query: - return query_service (in_name); + return query_service (in_name, verbose); + break; + case List: + return list_services (verbose); break; } return error (ReqAction); --- /tmp/cygrunsrv-1.02-1/utils.cc 2004-04-07 07:06:05.000000000 -0700 +++ utils.cc 2005-05-19 10:55:41.609375000 -0700 @@ -32,7 +32,7 @@ char *reason_list[] = { "", - "Exactly one of --install, --remove, --start or --stop is required", + "Exactly one of --install, --remove, --start, --stop, --query, or --list is required", "--path is required with --install", "Given path doesn't point to a valid executable", "--path is only allowed with --install", @@ -75,6 +75,7 @@ char *reason_list[] = { "Error starting a service", "Error stopping a service", "Error querying a service", + "Error enumerating services", NULL }; @@ -135,6 +136,8 @@ usage () uprint (" -S, --start Starts a service named ."); uprint (" -E, --stop Stops a service named ."); uprint (" -Q, --query Queries a service named ."); + uprint (" -L, --list Lists services that have been installed"); + uprint (" with cygrunsrv."); uprint ("\nRequired install options:"); uprint (" -p, --path Application path which is run as a service."); uprint ("\nMiscellaneous install options:"); @@ -180,6 +183,8 @@ usage () uprint (" -j, --nohide Don't hide console window when service interacts"); uprint (" with desktop."); uprint ("\nInformative output:"); + uprint (" -V, --verbose When used with --query or --list, causes extra"); + uprint (" information to be printed."); uprint (" -h, --help print this help, then exit."); uprint (" -v, --version print cygrunsrv program version number, then exit."); uprint ("\nReport bugs to ."); --- /tmp/cygrunsrv-1.02-1/utils.h 2004-04-07 07:06:05.000000000 -0700 +++ utils.h 2005-05-19 10:56:49.750000000 -0700 @@ -66,6 +66,7 @@ enum reason_t { StartErr, StopErr, QueryErr, + ListErr, MaxReason /* Always the last element */ }; --- /tmp/cygrunsrv-1.02-1/cygrunsrv.README 2004-04-07 07:06:05.000000000 -0700 +++ cygrunsrv.README 2005-05-21 18:17:40.562500000 -0700 @@ -62,7 +62,22 @@ Query a service: cygrunsrv -Q cygrunsrv --query - reports on the existence and status of the service. + reports on the existence and status of the service. Use the + --verbose or -V flag to receive extra information. + +********************************************** +List services installed with cygrunsrv: + +cygrunsrv -L +cygrunsrv --list + lists all services that have been installed with cygrunsrv, + one per line. You can use this for example to stop all + running cygwin services as follows: + + $ cygrunsrv -L | (while read S; do cygrunsrv -E $S; done) + + You can combine this with the -V / --verbose option to get + full details of each service instead of just names. ********************************************** Remove a service: --------------922E1B4D8D71BFA52B6B8F16 Content-Type: text/plain; charset=us-ascii -- Unsubscribe info: http://cygwin.com/ml/#unsubscribe-simple Problem reports: http://cygwin.com/problems.html Documentation: http://cygwin.com/docs.html FAQ: http://cygwin.com/faq/ --------------922E1B4D8D71BFA52B6B8F16--