mirror of
git://sourceware.org/git/newlib-cygwin.git
synced 2025-03-06 15:06:25 +08:00
Cygwin: AF_UNIX: create an ack mechanism for SCM_RIGHTS
When a process sends a file descriptor via an SCM_RIGHTS control message, it creates a temporary copy of the fhandler associated with that descriptor and sends a serialization of that copy. The deserialization done by the receiver involves duplicating handles from the copy, so the latter must stay alive until the deserialization is done. But it must ultimately be closed in order to avoid a memory leak. We coordinate all this as follows: - Introduce a new struct scm_pending_fd that contains information about the temporary copy. For brevity, call such a struct a "pending fd" in what follows. - Maintain a list of pending fds in shared memory. - Add several methods for manipulating the list to the af_unix_shmem_t and fhandler_socket_unix classes. - Also add a lock, 'scm_fd_lock', to control access to the list. - When a serialized fhandler is received, the receiver sends an ack back to the sender in an administrative packet with a control message of a new (Cygwin-specific) type SCM_RIGHTS_ACK. - grab_admin_pkt is called in various places to process these packets. A complication here is that the process that calls grab_admin_pkt might not be the process that originally sent the serialized fhandler. (It could be a subprocess of the original process, for example.) This is why we need to maintain the list of pending fds in shared memory. - Each fhandler_socket_unix keeps a count of the pending fds that it has created but not yet processed; this count is in a new data member 'my_npending_fd'. - fhandler_socket_unix::close tries to process any remaining pending fds before closing, but it gives up after a short timeout and forcibly deletes them if necessary.
This commit is contained in:
parent
ee4f7ec67e
commit
81ceb96528
@ -928,6 +928,25 @@ class sun_name_t
|
||||
void set (const struct sockaddr_un *name, __socklen_t namelen);
|
||||
};
|
||||
|
||||
#define MAX_PENDING_FD 32
|
||||
|
||||
struct scm_pending_fd
|
||||
{
|
||||
fhandler_base *fh;
|
||||
int64_t id;
|
||||
pid_t pid; /* pid of creator */
|
||||
bool ack_recvd;
|
||||
|
||||
int close ()
|
||||
{
|
||||
/* FIXME: This may not be right for all types of fhandlers; all we
|
||||
really want to do is close all handles. */
|
||||
int ret = fh->close ();
|
||||
delete fh;
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
/* For each AF_UNIX socket, we need to maintain socket-wide data,
|
||||
regardless of the number of descriptors. The shmem region gets created
|
||||
in socket, socketpair or accept4 and reopened by dup, fork or exec. */
|
||||
@ -941,6 +960,7 @@ class af_unix_shmem_t
|
||||
af_unix_spinlock_t _conn_lock;
|
||||
af_unix_spinlock_t _state_lock;
|
||||
af_unix_spinlock_t _io_lock;
|
||||
af_unix_spinlock_t _scm_fd_lock;
|
||||
LONG _connection_state; /* conn_state */
|
||||
LONG _binding_state; /* bind_state */
|
||||
LONG _shutdown; /* shut_state */
|
||||
@ -954,6 +974,10 @@ class af_unix_shmem_t
|
||||
struct ucred _sock_cred; /* filled at listen time */
|
||||
struct ucred _peer_cred; /* filled at connect time */
|
||||
|
||||
/* SCM_RIGHTS fds sent and waiting for ack. */
|
||||
scm_pending_fd pending_fd[MAX_PENDING_FD];
|
||||
int npending;
|
||||
|
||||
public:
|
||||
void bind_lock () { _bind_lock.lock (); }
|
||||
void bind_unlock () { _bind_lock.unlock (); }
|
||||
@ -963,6 +987,8 @@ class af_unix_shmem_t
|
||||
void state_unlock () { _state_lock.unlock (); }
|
||||
void io_lock () { _io_lock.lock (); }
|
||||
void io_unlock () { _io_lock.unlock (); }
|
||||
void scm_fd_lock () { _scm_fd_lock.lock (); }
|
||||
void scm_fd_unlock () { _scm_fd_lock.unlock (); }
|
||||
|
||||
conn_state connect_state (conn_state val)
|
||||
{ return (conn_state) InterlockedExchange (&_connection_state, val); }
|
||||
@ -1004,6 +1030,57 @@ class af_unix_shmem_t
|
||||
struct ucred *sock_cred () { return &_sock_cred; }
|
||||
void peer_cred (struct ucred *uc) { _peer_cred = *uc; }
|
||||
struct ucred *peer_cred () { return &_peer_cred; }
|
||||
|
||||
bool add_pending_fd (scm_pending_fd pfd)
|
||||
{
|
||||
if (npending >= MAX_PENDING_FD)
|
||||
return false;
|
||||
pending_fd[npending++] = pfd;
|
||||
return true;
|
||||
}
|
||||
|
||||
void delete_pending_fd (int i)
|
||||
{
|
||||
pending_fd[i].close ();
|
||||
if (i < --npending)
|
||||
pending_fd[i] = pending_fd[npending];
|
||||
}
|
||||
|
||||
/* Return true if a pending fd is deleted. */
|
||||
bool recv_scm_fd_ack (int64_t id)
|
||||
{
|
||||
bool ret = false;
|
||||
int i;
|
||||
for (i = 0; i < npending; ++i)
|
||||
if (pending_fd[i].id == id)
|
||||
break;
|
||||
if (i < npending)
|
||||
{
|
||||
if (pending_fd[i].pid == myself->pid)
|
||||
{
|
||||
delete_pending_fd (i);
|
||||
ret = true;
|
||||
}
|
||||
else
|
||||
pending_fd[i].ack_recvd = true;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Return number of pending fds deleted. */
|
||||
int cleanup_pending_fd (pid_t pid, bool force = false)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
/* Work from the top down to try to avoid copying. */
|
||||
for (int i = npending - 1; i >= 0; --i)
|
||||
if (pending_fd[i].pid == pid && (force || pending_fd[i].ack_recvd))
|
||||
{
|
||||
delete_pending_fd (i);
|
||||
++ret;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
/* See the commentary in fhandler_socket_unix.cc. */
|
||||
@ -1044,6 +1121,8 @@ class fhandler_socket_unix : public fhandler_socket
|
||||
HANDLE connect_wait_thr;
|
||||
HANDLE cwt_termination_evt;
|
||||
PVOID cwt_param;
|
||||
int my_npending_fd; /* Number of SCM_RIGHTS fds sent by me
|
||||
and waiting for ack. */
|
||||
|
||||
void bind_lock () { shmem->bind_lock (); }
|
||||
void bind_unlock () { shmem->bind_unlock (); }
|
||||
@ -1053,6 +1132,8 @@ class fhandler_socket_unix : public fhandler_socket
|
||||
void state_unlock () { shmem->state_unlock (); }
|
||||
void io_lock () { shmem->io_lock (); }
|
||||
void io_unlock () { shmem->io_unlock (); }
|
||||
void scm_fd_lock () { shmem->scm_fd_lock (); }
|
||||
void scm_fd_unlock () { shmem->scm_fd_unlock (); }
|
||||
bind_state binding_state (bind_state val)
|
||||
{ return shmem->binding_state (val); }
|
||||
bind_state binding_state () const { return shmem->binding_state (); }
|
||||
@ -1115,6 +1196,21 @@ class fhandler_socket_unix : public fhandler_socket
|
||||
void set_close_on_exec (bool val);
|
||||
void fixup_helper ();
|
||||
|
||||
bool send_scm_fd_ack (int64_t id);
|
||||
void recv_scm_fd_ack (int64_t id)
|
||||
{
|
||||
if (shmem->recv_scm_fd_ack (id))
|
||||
--my_npending_fd;
|
||||
}
|
||||
bool add_pending_fd (scm_pending_fd pfd);
|
||||
bool check_pending_fd (DWORD timeout = 0);
|
||||
/* scm_fd_lock should be in place. */
|
||||
void cleanup_pending_fd (bool force = false)
|
||||
{
|
||||
grab_admin_pkt ();
|
||||
my_npending_fd -= shmem->cleanup_pending_fd (myself->pid, force);
|
||||
}
|
||||
|
||||
public:
|
||||
fhandler_socket_unix ();
|
||||
~fhandler_socket_unix ();
|
||||
|
@ -84,6 +84,9 @@ GUID __cygwin_socket_guid = {
|
||||
A bound DGRAM socket sends its sun_path with each sendmsg/sendto.
|
||||
*/
|
||||
|
||||
/* FIXME: Add commentary explaining how we handle SCM_RIGHTS ancillary
|
||||
data. */
|
||||
|
||||
static inline ptrdiff_t
|
||||
AF_UNIX_PKT_OFFSETOF_NAME (af_unix_pkt_hdr_t *phdr)
|
||||
{
|
||||
@ -716,9 +719,18 @@ fhandler_socket_unix::process_admin_pkt (af_unix_pkt_hdr_t *packet)
|
||||
struct cmsghdr *cmsg = (struct cmsghdr *)
|
||||
alloca (packet->cmsg_len);
|
||||
memcpy (cmsg, AF_UNIX_PKT_CMSG (packet), packet->cmsg_len);
|
||||
if (cmsg->cmsg_level == SOL_SOCKET
|
||||
&& cmsg->cmsg_type == SCM_CREDENTIALS)
|
||||
peer_cred ((struct ucred *) CMSG_DATA(cmsg));
|
||||
if (cmsg->cmsg_level == SOL_SOCKET)
|
||||
switch (cmsg->cmsg_type)
|
||||
{
|
||||
case SCM_CREDENTIALS:
|
||||
peer_cred ((struct ucred *) CMSG_DATA(cmsg));
|
||||
break;
|
||||
case SCM_RIGHTS_ACK:
|
||||
recv_scm_fd_ack (*((int64_t *) CMSG_DATA (cmsg)));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
state_unlock ();
|
||||
}
|
||||
@ -1272,7 +1284,8 @@ fhandler_socket_unix::set_close_on_exec (bool val)
|
||||
fhandler_socket_unix::fhandler_socket_unix () :
|
||||
fhandler_socket (),
|
||||
shmem_handle (NULL), shmem (NULL), backing_file_handle (NULL),
|
||||
connect_wait_thr (NULL), cwt_termination_evt (NULL), cwt_param (NULL)
|
||||
connect_wait_thr (NULL), cwt_termination_evt (NULL), cwt_param (NULL),
|
||||
my_npending_fd (0)
|
||||
{
|
||||
need_fork_fixup (true);
|
||||
}
|
||||
@ -1890,6 +1903,17 @@ fhandler_socket_unix::close ()
|
||||
PVOID param = InterlockedExchangePointer (&cwt_param, NULL);
|
||||
if (param)
|
||||
cfree (param);
|
||||
/* Try to clear pending fds. Give up if we can't make progress. */
|
||||
while (my_npending_fd > 0)
|
||||
if (!check_pending_fd (100))
|
||||
break;
|
||||
if (my_npending_fd > 0)
|
||||
{
|
||||
debug_printf ("can't cleanup pending fds; forcibly deleting");
|
||||
scm_fd_lock ();
|
||||
cleanup_pending_fd (true);
|
||||
scm_fd_unlock ();
|
||||
}
|
||||
HANDLE hdl = InterlockedExchangePointer (&get_handle (), NULL);
|
||||
if (hdl)
|
||||
NtClose (hdl);
|
||||
@ -1945,11 +1969,6 @@ struct fh_ser
|
||||
DWORD winpid; /* Windows pid of sender. */
|
||||
};
|
||||
|
||||
/* FIXME: For testing purposes I'm allowing a memory leak. save_fh is
|
||||
a reminder. It needs to be alive until the receiver runs
|
||||
deserialize and notifies us that it can be closed. */
|
||||
static fhandler_base *save_fh;
|
||||
|
||||
/* Return a pointer to an allocated buffer containing an fh_ser. The
|
||||
caller has to free it. */
|
||||
void *
|
||||
@ -1990,8 +2009,14 @@ fhandler_socket_unix::serialize (int fd)
|
||||
fhs->winpid = GetCurrentProcessId ();
|
||||
NtAllocateLocallyUniqueId ((PLUID) &id);
|
||||
fhs->uniq_id = id;
|
||||
/* FIXME: Save pending ack. */
|
||||
save_fh = newfh;
|
||||
scm_pending_fd pfd;
|
||||
pfd.fh = newfh;
|
||||
pfd.id = id;
|
||||
pfd.pid = myself->pid;
|
||||
pfd.ack_recvd = false;
|
||||
scm_fd_lock ();
|
||||
add_pending_fd (pfd);
|
||||
scm_fd_unlock ();
|
||||
out:
|
||||
return (void *) fhs;
|
||||
}
|
||||
@ -2024,7 +2049,8 @@ fhandler_socket_unix::deserialize (void *bufp)
|
||||
return -1;
|
||||
newfh = oldfh->clone ();
|
||||
int ret = oldfh->dup (newfh, 0, winpid);
|
||||
/* FIXME: Send ack to sender that it can close fhs->uniq_id. */
|
||||
if (!send_scm_fd_ack (fhs->uniq_id))
|
||||
debug_printf ("can't send ack");
|
||||
if (ret < 0)
|
||||
{
|
||||
debug_printf ("can't duplicate handles");
|
||||
@ -2050,6 +2076,81 @@ fhandler_socket_unix::deserialize (void *bufp)
|
||||
return cfd;
|
||||
}
|
||||
|
||||
/* FIXME: This only works if we have a pipe handle. */
|
||||
bool
|
||||
fhandler_socket_unix::send_scm_fd_ack (int64_t id)
|
||||
{
|
||||
if (!get_handle ())
|
||||
{
|
||||
debug_printf ("can't send ack; no handle");
|
||||
return false;
|
||||
}
|
||||
size_t clen, plen;
|
||||
af_unix_pkt_hdr_t *packet;
|
||||
NTSTATUS status;
|
||||
IO_STATUS_BLOCK io;
|
||||
|
||||
clen = CMSG_SPACE (sizeof (int64_t));
|
||||
plen = sizeof *packet + clen;
|
||||
packet = (af_unix_pkt_hdr_t *) alloca (plen);
|
||||
packet->init (true, _SHUT_NONE, 0, clen, 0);
|
||||
struct cmsghdr *cmsg = AF_UNIX_PKT_CMSG (packet);
|
||||
cmsg->cmsg_level = SOL_SOCKET;
|
||||
cmsg->cmsg_type = SCM_RIGHTS_ACK;
|
||||
cmsg->cmsg_len = CMSG_LEN (sizeof (int64_t));
|
||||
memcpy (CMSG_DATA(cmsg), &id, sizeof (int64_t));
|
||||
|
||||
/* The theory: Fire and forget. */
|
||||
io_lock ();
|
||||
set_pipe_non_blocking (true);
|
||||
status = NtWriteFile (get_handle (), NULL, NULL, NULL, &io, packet,
|
||||
packet->pckt_len, NULL, NULL);
|
||||
set_pipe_non_blocking (is_nonblocking ());
|
||||
io_unlock ();
|
||||
if (!NT_SUCCESS (status))
|
||||
{
|
||||
debug_printf ("can't send ack: NtWriteFile: %y", status);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* scm_fd_lock should be in place when this is called. */
|
||||
bool
|
||||
fhandler_socket_unix::add_pending_fd (scm_pending_fd pfd)
|
||||
{
|
||||
if (my_npending_fd > 0)
|
||||
cleanup_pending_fd ();
|
||||
if (shmem->add_pending_fd (pfd))
|
||||
{
|
||||
++my_npending_fd;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Return true if my_npending_fd is reduced. */
|
||||
bool
|
||||
fhandler_socket_unix::check_pending_fd (DWORD timeout)
|
||||
{
|
||||
DWORD sleep_time = 0, tot_sleep_time = 0;
|
||||
int save_npending = my_npending_fd;
|
||||
|
||||
while (tot_sleep_time <= timeout)
|
||||
{
|
||||
scm_fd_lock ();
|
||||
cleanup_pending_fd ();
|
||||
scm_fd_unlock ();
|
||||
if (my_npending_fd < save_npending)
|
||||
break;
|
||||
cygwait (sleep_time >> 3);
|
||||
tot_sleep_time += sleep_time >> 3;
|
||||
if (sleep_time < 80)
|
||||
++sleep_time;
|
||||
}
|
||||
return my_npending_fd < save_npending;
|
||||
}
|
||||
|
||||
/* FIXME: Check all errnos below. */
|
||||
bool
|
||||
fhandler_socket_unix::evaluate_cmsg_data (af_unix_pkt_hdr_t *packet,
|
||||
|
@ -107,6 +107,9 @@ struct cmsghdr
|
||||
|
||||
#ifdef __WITH_AF_UNIX
|
||||
#define SCM_MAX_FD 20
|
||||
#ifdef __INSIDE_CYGWIN__
|
||||
#define SCM_RIGHTS_ACK 0x10 /* descriptor acknowledgement; Cygwin specific */
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef __INSIDE_CYGWIN__
|
||||
|
Loading…
x
Reference in New Issue
Block a user