525 lines
12 KiB
C++
525 lines
12 KiB
C++
|
/* client.cc
|
||
|
|
||
|
Written by Egor Duda <deo@logos-m.ru>
|
||
|
|
||
|
This file is part of Cygwin.
|
||
|
|
||
|
This software is a copyrighted work licensed under the terms of the
|
||
|
Cygwin license. Please consult the file "CYGWIN_LICENSE" for
|
||
|
details. */
|
||
|
|
||
|
/* to allow this to link into cygwin and the .dll, a little magic is needed. */
|
||
|
#ifdef __OUTSIDE_CYGWIN__
|
||
|
#include "woutsup.h"
|
||
|
#else
|
||
|
#include "winsup.h"
|
||
|
#endif
|
||
|
|
||
|
#include <assert.h>
|
||
|
#include <errno.h>
|
||
|
#include <stdio.h>
|
||
|
#include <unistd.h>
|
||
|
|
||
|
#include "cygserver_msg.h"
|
||
|
#include "cygserver_sem.h"
|
||
|
#include "cygserver_shm.h"
|
||
|
#include "cygserver_setpwd.h"
|
||
|
#include "cygserver_pwdgrp.h"
|
||
|
|
||
|
#include "cygserver.h"
|
||
|
#include "transport.h"
|
||
|
|
||
|
int cygserver_running = CYGSERVER_UNKNOWN; // Nb: inherited by children.
|
||
|
|
||
|
client_request_get_version::client_request_get_version ()
|
||
|
: client_request (CYGSERVER_REQUEST_GET_VERSION, &version, sizeof (version))
|
||
|
{
|
||
|
msglen (0); // No parameters for request.
|
||
|
|
||
|
// verbose: syscall_printf ("created");
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* client_request_get_version::check_version ()
|
||
|
*
|
||
|
* The major version and API version numbers must match exactly. An
|
||
|
* older than expected minor version number is accepted (as long as
|
||
|
* the first numbers match, that is).
|
||
|
*/
|
||
|
|
||
|
#ifdef __INSIDE_CYGWIN__
|
||
|
|
||
|
bool
|
||
|
client_request_get_version::check_version () const
|
||
|
{
|
||
|
const bool ok = (version.major == CYGWIN_SERVER_VERSION_MAJOR
|
||
|
&& version.api == CYGWIN_SERVER_VERSION_API
|
||
|
&& version.minor <= CYGWIN_SERVER_VERSION_MINOR);
|
||
|
|
||
|
if (!ok)
|
||
|
syscall_printf (("incompatible version of cygwin server: "
|
||
|
"client version %d.%d.%d.%d, "
|
||
|
"server version %d.%d.%d.%d"),
|
||
|
CYGWIN_SERVER_VERSION_MAJOR,
|
||
|
CYGWIN_SERVER_VERSION_API,
|
||
|
CYGWIN_SERVER_VERSION_MINOR,
|
||
|
CYGWIN_SERVER_VERSION_PATCH,
|
||
|
version.major,
|
||
|
version.api,
|
||
|
version.minor,
|
||
|
version.patch);
|
||
|
|
||
|
return ok;
|
||
|
}
|
||
|
|
||
|
client_request_attach_tty::client_request_attach_tty (DWORD nmaster_pid,
|
||
|
HANDLE nfrom_master,
|
||
|
HANDLE nto_master)
|
||
|
: client_request (CYGSERVER_REQUEST_ATTACH_TTY, &req, sizeof (req))
|
||
|
{
|
||
|
req.pid = GetCurrentProcessId ();
|
||
|
req.master_pid = nmaster_pid;
|
||
|
req.from_master = nfrom_master;
|
||
|
req.to_master = nto_master;
|
||
|
|
||
|
syscall_printf (("created: pid = %u, master_pid = %u, "
|
||
|
"from_master = %p, to_master = %p"),
|
||
|
req.pid, req.master_pid, req.from_master, req.to_master);
|
||
|
}
|
||
|
#endif /* __INSIDE_CYGWIN__ */
|
||
|
|
||
|
/*
|
||
|
* client_request_attach_tty::send ()
|
||
|
*
|
||
|
* Wraps the base method to provide error handling support. If the
|
||
|
* reply contains a body but is flagged as an error, close any handles
|
||
|
* that have been returned by cygserver and then discard the message
|
||
|
* body, i.e. the client either sees a successful result with handles
|
||
|
* or an unsuccessful result with no handles.
|
||
|
*/
|
||
|
|
||
|
void
|
||
|
client_request_attach_tty::send (transport_layer_base * const conn)
|
||
|
{
|
||
|
client_request::send (conn);
|
||
|
|
||
|
if (msglen () && error_code ())
|
||
|
{
|
||
|
if (from_master ())
|
||
|
CloseHandle (from_master ());
|
||
|
if (to_master ())
|
||
|
CloseHandle (to_master ());
|
||
|
msglen (0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
client_request::header_t::header_t (const request_code_t request_code,
|
||
|
const size_t len)
|
||
|
: request_code (request_code)
|
||
|
{
|
||
|
assert (request_code >= 0 && request_code < CYGSERVER_REQUEST_LAST);
|
||
|
msglen = len;
|
||
|
}
|
||
|
|
||
|
// FIXME: also check write and read result for -1.
|
||
|
|
||
|
void
|
||
|
client_request::send (transport_layer_base * const conn)
|
||
|
{
|
||
|
assert (conn);
|
||
|
assert (!(msglen () && !_buf)); // i.e., msglen () implies _buf
|
||
|
assert (msglen () <= _buflen);
|
||
|
|
||
|
{
|
||
|
const ssize_t count = conn->write (&_header, sizeof (_header));
|
||
|
|
||
|
if (count != sizeof (_header))
|
||
|
{
|
||
|
assert (errno);
|
||
|
error_code (errno);
|
||
|
syscall_printf (("request header write failure: "
|
||
|
"only %ld bytes sent of %lu, "
|
||
|
"error = %d(%u)"),
|
||
|
count, sizeof (_header),
|
||
|
errno, GetLastError ());
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (msglen ())
|
||
|
{
|
||
|
const ssize_t count = conn->write (_buf, msglen ());
|
||
|
|
||
|
if (count == -1 || (size_t) count != msglen ())
|
||
|
{
|
||
|
assert (errno);
|
||
|
error_code (errno);
|
||
|
syscall_printf (("request body write failure: "
|
||
|
"only %ld bytes sent of %lu, "
|
||
|
"error = %d(%u)"),
|
||
|
count, msglen (),
|
||
|
errno, GetLastError ());
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
{
|
||
|
const ssize_t count = conn->read (&_header, sizeof (_header));
|
||
|
|
||
|
if (count != sizeof (_header))
|
||
|
{
|
||
|
assert (errno);
|
||
|
error_code (errno);
|
||
|
syscall_printf (("reply header read failure: "
|
||
|
"only %ld bytes received of %lu, "
|
||
|
"error = %d(%u)"),
|
||
|
count, sizeof (_header),
|
||
|
errno, GetLastError ());
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (msglen () && !_buf)
|
||
|
{
|
||
|
system_printf ("no client buffer for reply body: %lu bytes needed",
|
||
|
msglen ());
|
||
|
error_code (EINVAL);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (msglen () > _buflen)
|
||
|
{
|
||
|
system_printf (("client buffer too small for reply body: "
|
||
|
"have %lu bytes and need %lu"),
|
||
|
_buflen, msglen ());
|
||
|
error_code (EINVAL);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (msglen ())
|
||
|
{
|
||
|
const ssize_t count = conn->read (_buf, msglen ());
|
||
|
|
||
|
if (count == -1 || (size_t) count != msglen ())
|
||
|
{
|
||
|
assert (errno);
|
||
|
error_code (errno);
|
||
|
syscall_printf (("reply body read failure: "
|
||
|
"only %ld bytes received of %lu, "
|
||
|
"error = %d(%u)"),
|
||
|
count, msglen (),
|
||
|
errno, GetLastError ());
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#ifdef __OUTSIDE_CYGWIN__
|
||
|
|
||
|
client_request_attach_tty::client_request_attach_tty ()
|
||
|
: client_request (CYGSERVER_REQUEST_ATTACH_TTY, &req, sizeof (req))
|
||
|
{
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* client_request::handle_request ()
|
||
|
*
|
||
|
* A server-side method.
|
||
|
*
|
||
|
* This is a factory method for the client_request subclasses. It
|
||
|
* reads the incoming request header and, based on its request code,
|
||
|
* creates an instance of the appropriate class.
|
||
|
*
|
||
|
* FIXME: If the incoming packet is malformed, the server drops it on
|
||
|
* the floor. Should it try and generate some sort of reply for the
|
||
|
* client? As it is, the client will simply get a broken connection.
|
||
|
*
|
||
|
* FIXME: also check write and read result for -1.
|
||
|
*/
|
||
|
|
||
|
/* static */ void
|
||
|
client_request::handle_request (transport_layer_base *const conn,
|
||
|
process_cache *const cache)
|
||
|
{
|
||
|
// verbose: debug_printf ("about to read");
|
||
|
|
||
|
header_t header;
|
||
|
|
||
|
{
|
||
|
const ssize_t count = conn->read (&header, sizeof (header));
|
||
|
|
||
|
if (count != sizeof (header))
|
||
|
{
|
||
|
syscall_printf (("request header read failure: "
|
||
|
"only %ld bytes received of %lu, "
|
||
|
"error = %d(%u)"),
|
||
|
count, sizeof (header),
|
||
|
errno, GetLastError ());
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
client_request *req = NULL;
|
||
|
|
||
|
switch (header.request_code)
|
||
|
{
|
||
|
case CYGSERVER_REQUEST_GET_VERSION:
|
||
|
req = new client_request_get_version;
|
||
|
break;
|
||
|
case CYGSERVER_REQUEST_SHUTDOWN:
|
||
|
req = new client_request_shutdown;
|
||
|
break;
|
||
|
case CYGSERVER_REQUEST_ATTACH_TTY:
|
||
|
req = new client_request_attach_tty;
|
||
|
break;
|
||
|
case CYGSERVER_REQUEST_MSG:
|
||
|
req = new client_request_msg;
|
||
|
break;
|
||
|
case CYGSERVER_REQUEST_SEM:
|
||
|
req = new client_request_sem;
|
||
|
break;
|
||
|
case CYGSERVER_REQUEST_SHM:
|
||
|
req = new client_request_shm;
|
||
|
break;
|
||
|
case CYGSERVER_REQUEST_SETPWD:
|
||
|
req = new client_request_setpwd;
|
||
|
break;
|
||
|
case CYGSERVER_REQUEST_PWDGRP:
|
||
|
req = new client_request_pwdgrp;
|
||
|
break;
|
||
|
default:
|
||
|
syscall_printf ("unknown request code %d received: request ignored",
|
||
|
header.request_code);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
assert (req);
|
||
|
|
||
|
req->msglen (header.msglen);
|
||
|
req->handle (conn, cache);
|
||
|
|
||
|
delete req;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* client_request::handle ()
|
||
|
*
|
||
|
* A server-side method.
|
||
|
*
|
||
|
* At this point, the header of an incoming request has been read and
|
||
|
* an appropriate client_request object constructed. This method has
|
||
|
* to read the request body into its buffer, if there is such a body,
|
||
|
* then perform the request and send back the results to the client.
|
||
|
*
|
||
|
* FIXME: If the incoming packet is malformed, the server drops it on
|
||
|
* the floor. Should it try and generate some sort of reply for the
|
||
|
* client? As it is, the client will simply get a broken connection.
|
||
|
*
|
||
|
* FIXME: also check write and read result for -1.
|
||
|
*/
|
||
|
|
||
|
void
|
||
|
client_request::handle (transport_layer_base *const conn,
|
||
|
process_cache *const cache)
|
||
|
{
|
||
|
if (msglen () && !_buf)
|
||
|
{
|
||
|
system_printf ("no buffer for request body: %lu bytes needed",
|
||
|
msglen ());
|
||
|
error_code (EINVAL);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (msglen () > _buflen)
|
||
|
{
|
||
|
system_printf (("buffer too small for request body: "
|
||
|
"have %lu bytes and need %lu"),
|
||
|
_buflen, msglen ());
|
||
|
error_code (EINVAL);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (msglen ())
|
||
|
{
|
||
|
const ssize_t count = conn->read (_buf, msglen ());
|
||
|
|
||
|
if (count == -1 || (size_t) count != msglen ())
|
||
|
{
|
||
|
assert (errno);
|
||
|
error_code (errno);
|
||
|
syscall_printf (("request body read failure: "
|
||
|
"only %ld bytes received of %lu, "
|
||
|
"error = %d(%u)"),
|
||
|
count, msglen (),
|
||
|
errno, GetLastError ());
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
error_code (0); // Overwrites the _header.request_code field.
|
||
|
|
||
|
/*
|
||
|
* This is not allowed to fail. We must return ENOSYS at a minimum
|
||
|
* to the client.
|
||
|
*/
|
||
|
serve (conn, cache);
|
||
|
|
||
|
{
|
||
|
const ssize_t count = conn->write (&_header, sizeof (_header));
|
||
|
|
||
|
if (count != sizeof (_header))
|
||
|
{
|
||
|
assert (errno);
|
||
|
error_code (errno);
|
||
|
syscall_printf (("reply header write failure: "
|
||
|
"only %ld bytes sent of %lu, "
|
||
|
"error = %d(%u)"),
|
||
|
count, sizeof (_header),
|
||
|
errno, GetLastError ());
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (msglen ())
|
||
|
{
|
||
|
const ssize_t count = conn->write (_buf, msglen ());
|
||
|
|
||
|
if (count == -1 || (size_t) count != msglen ())
|
||
|
{
|
||
|
assert (errno);
|
||
|
error_code (errno);
|
||
|
syscall_printf (("reply body write failure: "
|
||
|
"only %ld bytes sent of %lu, "
|
||
|
"error = %d(%u)"),
|
||
|
count, msglen (),
|
||
|
errno, GetLastError ());
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* The server side implementation of make_request. Very simple. */
|
||
|
int
|
||
|
client_request::make_request ()
|
||
|
{
|
||
|
transport_layer_base *const transport = create_server_transport ();
|
||
|
assert (transport);
|
||
|
if (transport->connect () == -1)
|
||
|
{
|
||
|
if (errno)
|
||
|
error_code (errno);
|
||
|
else
|
||
|
error_code (ENOSYS);
|
||
|
delete transport;
|
||
|
return -1;
|
||
|
}
|
||
|
send (transport);
|
||
|
delete transport;
|
||
|
return 0;
|
||
|
}
|
||
|
#endif /* __OUTSIDE_CYGWIN__ */
|
||
|
|
||
|
client_request::client_request (request_code_t const id,
|
||
|
void * const buf,
|
||
|
size_t const buflen)
|
||
|
: _header (id, buflen),
|
||
|
_buf (buf),
|
||
|
_buflen (buflen)
|
||
|
{
|
||
|
assert ((!_buf && !_buflen) || (_buf && _buflen));
|
||
|
}
|
||
|
|
||
|
client_request::~client_request ()
|
||
|
{}
|
||
|
|
||
|
#ifdef __INSIDE_CYGWIN__
|
||
|
int
|
||
|
client_request::make_request ()
|
||
|
{
|
||
|
assert (cygserver_running == CYGSERVER_UNKNOWN \
|
||
|
|| cygserver_running == CYGSERVER_OK \
|
||
|
|| cygserver_running == CYGSERVER_UNAVAIL);
|
||
|
|
||
|
if (cygserver_running == CYGSERVER_UNKNOWN)
|
||
|
cygserver_init ();
|
||
|
|
||
|
assert (cygserver_running == CYGSERVER_OK \
|
||
|
|| cygserver_running == CYGSERVER_UNAVAIL);
|
||
|
|
||
|
/* Don't retry every request if the server's not there */
|
||
|
if (cygserver_running == CYGSERVER_UNAVAIL)
|
||
|
{
|
||
|
syscall_printf ("cygserver un-available");
|
||
|
error_code (ENOSYS);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
transport_layer_base *const transport = create_server_transport ();
|
||
|
|
||
|
assert (transport);
|
||
|
|
||
|
if (transport->connect () == -1)
|
||
|
{
|
||
|
if (errno)
|
||
|
error_code (errno);
|
||
|
else
|
||
|
error_code (ENOSYS);
|
||
|
delete transport;
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
// verbose: debug_printf ("connected to server %p", transport);
|
||
|
|
||
|
send (transport);
|
||
|
|
||
|
delete transport;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
check_cygserver_available ()
|
||
|
{
|
||
|
assert (cygserver_running == CYGSERVER_UNKNOWN \
|
||
|
|| cygserver_running == CYGSERVER_UNAVAIL);
|
||
|
|
||
|
cygserver_running = CYGSERVER_OK; // For make_request ().
|
||
|
|
||
|
client_request_get_version req;
|
||
|
|
||
|
/* This indicates that we failed to connect to cygserver at all but
|
||
|
* that's fine as cygwin doesn't need it to be running.
|
||
|
*/
|
||
|
if (req.make_request () == -1)
|
||
|
return false;
|
||
|
|
||
|
/* We connected to the server but something went wrong after that
|
||
|
* (in sending the message, in cygserver itself, or in receiving the
|
||
|
* reply).
|
||
|
*/
|
||
|
if (req.error_code ())
|
||
|
{
|
||
|
syscall_printf ("failure in cygserver version request: %d",
|
||
|
req.error_code ());
|
||
|
syscall_printf ("process will continue without cygserver support");
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return req.check_version ();
|
||
|
}
|
||
|
|
||
|
void
|
||
|
cygserver_init ()
|
||
|
{
|
||
|
assert (cygserver_running == CYGSERVER_UNKNOWN \
|
||
|
|| cygserver_running == CYGSERVER_OK \
|
||
|
|| cygserver_running == CYGSERVER_UNAVAIL);
|
||
|
|
||
|
if (cygserver_running == CYGSERVER_OK)
|
||
|
return;
|
||
|
|
||
|
if (!check_cygserver_available ())
|
||
|
cygserver_running = CYGSERVER_UNAVAIL;
|
||
|
}
|
||
|
#endif /* __INSIDE_CYGWIN__ */
|